Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ User-visible changes are listed here. Published binaries and versions are availa

## Unreleased

### Added

- `agc skills add` installs the bundled, self-contained agc-cli skill for Copilot, Claude Code, or Codex.

## v0.2.0 — 2026-10-07

### Added
Expand Down
2 changes: 1 addition & 1 deletion README.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ Endpoint definitions include `affordances` command templates and `_links` REST r
}
```

JSON is the default; add `--output table` or `--output markdown` for people. `agc web-server` exposes a local REST API. Templates require parameters and currently do not check remote business state to determine submission readiness. More in the [agent and REST guide](docs/CLI_USAGE.en.md#json-and-agents).
JSON is the default; add `--output table` or `--output markdown` for people. Install the bundled skill with `agc skills add --agent copilot` (or `--agent all` for Copilot, Claude Code, and Codex). `agc web-server` exposes a local REST API. Templates require parameters and currently do not check remote business state to determine submission readiness. More in the [agent and REST guide](docs/CLI_USAGE.en.md#json-and-agents).

## What It Covers

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ agc publishing app-info-query --invoke --query appId=YOUR_APP_ID --query lang=zh
}
```

默认 JSON;人工浏览可加 `--output table` 或 `--output markdown`。`agc web-server` 提供本地 REST API。命令模板需要补齐参数,当前不会根据远程业务状态判断能否提交。详见 [Agent 与 REST 使用说明](docs/CLI_USAGE.md#json-与-agent-支持)。
默认 JSON;人工浏览可加 `--output table` 或 `--output markdown`。运行 `agc skills add --agent copilot` 安装内置 skill,也可用 `--agent all` 安装到 Copilot、Claude Code 和 Codex。`agc web-server` 提供本地 REST API。命令模板需要补齐参数,当前不会根据远程业务状态判断能否提交。详见 [Agent 与 REST 使用说明](docs/CLI_USAGE.md#json-与-agent-支持)。

## 功能覆盖

Expand Down
139 changes: 139 additions & 0 deletions cmd/agc/command/assets/agc-cli/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
---
name: agc-cli
description: Use the agc CLI to manage AppGallery Connect workflows, discover API endpoints, configure profiles, and safely preview or invoke requests.
---

# agc-cli agent skill

This skill is the complete operational guide for using `agc-cli`; it does not depend on other repository files being present. The executable is named `agc`. It is for Huawei AppGallery Connect (AGC), not Google Cloud or Huawei Cloud.

## Safety rules

- Never ask a user to paste credentials, private keys, or access tokens into chat. Do not print or expose secrets in command output, logs, files, issues, or reports. Treat raw authentication responses as sensitive: API Client output may contain `clientKey`.
- Do not perform a remote check or send an API request unless the user authorized that specific remote action. Requests are dry-run by default; preserve that default until the user explicitly approves execution.
- Before any update, submission, reply, or other mutation, inspect the endpoint, required parameters, request body, target app/project, and dry-run preview. Do not infer business intent or fill missing data with guesses.
- Do not repeat a mutation automatically after a timeout or ambiguous response. First determine whether the remote action may have succeeded.
- Endpoint registration and a successful HTTP response do not prove business prerequisites, approval, or publication.

## Check the CLI and discover commands

The CLI can be installed from a release archive or package manager, or with Go 1.22+:

```bash
go install github.com/Createitv/agc-cli/cmd/agc@latest
agc version
```

Discover commands and endpoint contracts without credentials or network access:

```bash
agc --help
agc capabilities --output table
agc endpoints --pretty
agc publishing endpoints --output table
agc publishing app-info-query --pretty
agc publishing app-info-query --help
agc openapi --pretty
```

The API families include `publishing`, `upload`, `provisioning`, `domains`, `testing`, `reports`, `projects`, `comments`, `pms`, `gameplay`, `game-items`, `resources`, and `cicd`. Discover the installed CLI's current registry rather than relying on a fixed endpoint count.

An endpoint definition shows its method, path, parameter contract, official Huawei `sourceUrl`, REST links, and command affordances. Read the definition before constructing a request. Use its official reference for permissions, field semantics, and business prerequisites. Affordance strings are templates, not verified plans.

## Global flags and output

Global flags can be placed before the command:

| Flag | Purpose |
| --- | --- |
| `--output json\|table\|markdown` | Choose output format; JSON is the default and preferred for agents/scripts. |
| `--pretty` | Pretty-print JSON. |
| `--timeout 60s` | Set request timeout; default is 60 seconds. |
| `--profile NAME` | Select a saved credential profile. |
| `--project DIR` | Select project context directory; default is `.`. |

Parse JSON structurally and check command exit status. Do not assume every response has the same `data` shape. Preserve warnings and errors, and avoid echoing returned credentials, tokens, user data, or other sensitive values. Use table/markdown output only when it helps a person inspect listings.

## Credentials and project context

Credentials are stored locally, by default at `~/.agc/credentials.json`. `AGC_CREDENTIALS_PATH` overrides that path; the `auth` commands also accept `--credentials-path`. Profiles are selected in this order: explicit `--profile`, the project profile, globally active profile, or the only profile.

For credential setup, have the user configure the secret locally or make it available through their approved secret manager. Do not request the secret value in chat. Examples (the values must already exist in the user's protected environment):

```bash
agc auth login --service-account-file PATH --name production
agc auth login --client-id "$AGC_CLIENT_ID" --client-key "$AGC_CLIENT_KEY" --name staging
```

The CLI does not automatically read `AGC_CLIENT_ID` or `AGC_CLIENT_KEY`; shell expansion in the example supplies the values. Service Account JSON needs `key_id`, `private_key`, and `sub_account`; login stores its file path, so that local file must remain available. Avoid putting literal secrets in shell history or command transcripts.

Useful local credential commands:

```bash
agc auth list
agc --profile staging auth check
agc auth token
```

`auth check` inspects local credential configuration; it does not prove Huawei accepts it. `auth check --remote` contacts Huawei and may perform an app/project read to check permissions. `auth token` creates a real token. Use either only with explicit authorization, and never display or log their raw output.

Bind a project to an app and optionally a default profile:

```bash
agc init --app-id APP_ID --default-profile production
agc init --app-id APP_ID --project-id PROJECT_ID --package-name PACKAGE_NAME
```

This writes `.agc/project.json` under the selected `--project` directory. It selects project context/profile; it does not automatically supply every endpoint parameter. Pass `appId`, `projectId`, package name, and other required values explicitly when the endpoint contract requires them. `AGC_ACCESS_TOKEN` can provide an access token for endpoint requests; never print or expose it.

## Inspect, preview, and invoke an endpoint

Without `--invoke`, an endpoint command displays its definition. With `--invoke`, the CLI constructs a dry-run request by default. Only `--dry-run=false` sends it.

| Flag | Usage |
| --- | --- |
| `--param key=value` | Repeat for path parameters. |
| `--query key=value` | Repeat for query parameters. |
| `--header key=value` | Repeat for required HTTP headers, such as `client_id`. |
| `--field key=value` | Repeat for JSON fields; values are encoded as strings. |
| `--body FILE` | Read a JSON object request body; takes precedence over `--field`. Use for arrays, objects, numbers, or booleans. |
| `--token TOKEN` | Explicit request token; handle as a secret. |
| `--out FILE` | Save raw response bytes to a file. |
| `--base-url URL` | Override the API base URL, primarily for an explicitly selected compatible service/test environment. |
| `--dry-run=false` | Send the request; use only after explicit authorization and review. |

Example: inspect, then preview a read-only app information query:

```bash
agc publishing app-info-query --pretty
agc publishing app-info-query --invoke \
--query appId=APP_ID --query lang=en-US --pretty
```

The preview identifies the method, URL, and `dryRun: true`; it does not contact Huawei and does not validate all business fields. After reviewing the preview and confirming authorization, append `--dry-run=false` to send. Supply `--header client_id=CLIENT_ID` when the endpoint contract requires it; this header is not automatically populated.

For JSON request bodies, prepare the file locally, review its contents, and keep secrets out of it:

```bash
agc publishing app-info-update --invoke --body request.json --pretty
```

If the endpoint requires a parameter, the CLI reports a missing `--param`, `--query`, `--header`, or `--field` value. Consult the endpoint's displayed parameter contract instead of guessing.

## Common workflows and limitations

- **App release:** Discover and invoke the separate query, upload handoff, metadata, and submission endpoints. The CLI does not orchestrate a full release. Binary/multipart file transfer is not a complete built-in workflow; use a separate tool that implements Huawei's documented upload protocol. `--body` does not upload binaries.
- **Reports:** Some report endpoints return raw responses or download URLs. `--out` preserves response bytes; it does not convert them into CSV/Excel or necessarily fetch a second-stage download URL.
- **Local REST:** `agc web-server` starts a local REST API (default listen address `127.0.0.1:8421`). REST invocation defaults to dry-run and does not load saved CLI credential profiles. Real REST invocation requires an explicit token and required headers in the request. Non-loopback listening requires `AGC_SERVER_TOKEN`; do not expose the server or token.
- **OpenAPI:** `agc openapi --pretty` exports the local REST and endpoint invocation contract.
- **Skills:** `agc skills add --agent copilot|claude|codex|all` installs this skill into the selected project's `.github/skills`, `.claude/skills`, or `.agents/skills` directory. Existing files are not overwritten unless `--force` is supplied.

## Troubleshooting

- `agc: command not found`: install the CLI, ensure its binary directory is on `PATH`, and run `agc version`.
- `credential profile ... not found`: inspect local profile names and select the intended profile or project.
- `auth check` passes but an API call fails: local check is not remote verification; review credentials locally, account permissions, app/project ownership, required headers, and the endpoint contract.
- A command shows only an endpoint definition or preview: actual invocation requires `--invoke`; sending also requires `--dry-run=false`.
- `appId` is still required after `agc init`: project setup does not necessarily populate endpoint parameters; pass the required value explicitly.

When reporting an error, include CLI version, OS/architecture, a redacted command, and the error. Never include credentials, tokens, private keys, or unredacted auth output.
88 changes: 88 additions & 0 deletions cmd/agc/command/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ package command

import (
"context"
_ "embed"
"encoding/json"
"fmt"
"net"
"net/http"
"os"
"path/filepath"
"strings"
"time"

Expand Down Expand Up @@ -53,6 +55,7 @@ func NewRootCommand() *cobra.Command {
cmd.AddCommand(initCommand(opts))
cmd.AddCommand(webServerCommand())
cmd.AddCommand(docsCommand(opts))
cmd.AddCommand(skillsCommand(opts))
for _, capability := range domain.DecoratedCapabilities() {
cmd.AddCommand(moduleCommand(opts, capability))
}
Expand Down Expand Up @@ -356,6 +359,91 @@ func webServerCommand() *cobra.Command {
return cmd
}

//go:embed assets/agc-cli/SKILL.md
var agcCLISkill []byte

func skillsCommand(opts *options) *cobra.Command {
cmd := &cobra.Command{
Use: "skills",
Short: "Install agent skills",
}
var agent string
var force bool
add := &cobra.Command{
Use: "add",
Short: "Add the agc-cli skill to an agent",
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
agentDirs := map[string]string{
"copilot": ".github/skills",
"claude": ".claude/skills",
"codex": ".agents/skills",
}
agents := []string{agent}
if agent == "all" {
agents = []string{"copilot", "claude", "codex"}
}
for _, name := range agents {
if _, ok := agentDirs[name]; !ok {
return fmt.Errorf("unsupported agent %q; choose copilot, claude, codex, or all", name)
}
}

type skillInstall struct {
Agent string `json:"agent"`
Path string `json:"path"`
}
paths := make([]string, 0, len(agents))
for _, name := range agents {
path := filepath.Join(agentDirs[name], "agc-cli", "SKILL.md")
info, err := os.Lstat(filepath.Join(opts.project, path))
if err == nil && !force {
return fmt.Errorf("skill already exists at %s; use --force to replace it", path)
} else if err == nil && info.IsDir() {
return fmt.Errorf("skill destination %s is a directory", path)
} else if err != nil && !os.IsNotExist(err) {
return fmt.Errorf("check skill destination %s: %w", path, err)
}
paths = append(paths, path)
}

installed := make([]skillInstall, 0, len(agents))
for i, path := range paths {
destination := filepath.Join(opts.project, path)
if err := os.MkdirAll(filepath.Dir(destination), 0755); err != nil {
return fmt.Errorf("create skill directory %s: %w", filepath.Dir(destination), err)
}
if force {
if err := os.Remove(destination); err != nil && !os.IsNotExist(err) {
return fmt.Errorf("replace skill at %s: %w", path, err)
}
}
file, err := os.OpenFile(destination, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0644)
if err != nil {
return fmt.Errorf("install skill at %s: %w", path, err)
}
_, writeErr := file.Write(agcCLISkill)
closeErr := file.Close()
if writeErr != nil {
_ = os.Remove(destination)
return fmt.Errorf("write skill at %s: %w", path, writeErr)
}
if closeErr != nil {
_ = os.Remove(destination)
return fmt.Errorf("close skill at %s: %w", path, closeErr)
}
installed = append(installed, skillInstall{Agent: agents[i], Path: filepath.ToSlash(path)})
}
return output.Write(cmd.OutOrStdout(), domain.Envelope[[]skillInstall]{Data: installed}, output.Format(opts.output), opts.pretty)
},
}
add.Flags().StringVar(&agent, "agent", "", "Target agent: copilot, claude, codex, or all")
add.Flags().BoolVar(&force, "force", false, "Replace an existing agc-cli skill")
_ = add.MarkFlagRequired("agent")
cmd.AddCommand(add)
return cmd
}

func docsCommand(opts *options) *cobra.Command {
return &cobra.Command{
Use: "docs [module]",
Expand Down
91 changes: 91 additions & 0 deletions cmd/agc/command/root_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,97 @@ func TestVersionCommand(t *testing.T) {
}
}

func TestSkillsAddInstallsForAllAgents(t *testing.T) {
projectDir := t.TempDir()
out, err := execute("--project", projectDir, "skills", "add", "--agent", "all")
if err != nil {
t.Fatal(err)
}
var body domain.Envelope[[]struct {
Agent string `json:"agent"`
Path string `json:"path"`
}]
if err := json.Unmarshal([]byte(out), &body); err != nil {
t.Fatal(err)
}
want := map[string]string{
"copilot": ".github/skills/agc-cli/SKILL.md",
"claude": ".claude/skills/agc-cli/SKILL.md",
"codex": ".agents/skills/agc-cli/SKILL.md",
}
if len(body.Data) != len(want) {
t.Fatalf("installed %d skills, want %d", len(body.Data), len(want))
}
for _, skill := range body.Data {
if want[skill.Agent] != skill.Path {
t.Errorf("installed agent/path = %q/%q", skill.Agent, skill.Path)
}
content, err := os.ReadFile(filepath.Join(projectDir, filepath.FromSlash(skill.Path)))
if err != nil {
t.Fatal(err)
}
if string(content) != string(agcCLISkill) {
t.Errorf("%s skill differs from embedded skill", skill.Agent)
}
}
}

func TestSkillsAddDoesNotReplaceUnlessForced(t *testing.T) {
projectDir := t.TempDir()
destination := filepath.Join(projectDir, ".claude", "skills", "agc-cli", "SKILL.md")
if err := os.MkdirAll(filepath.Dir(destination), 0755); err != nil {
t.Fatal(err)
}
const existing = "existing skill"
if err := os.WriteFile(destination, []byte(existing), 0644); err != nil {
t.Fatal(err)
}
if _, err := execute("--project", projectDir, "skills", "add", "--agent", "claude"); err == nil {
t.Fatal("expected existing skill error")
}
content, err := os.ReadFile(destination)
if err != nil {
t.Fatal(err)
}
if string(content) != existing {
t.Fatalf("existing skill changed to %q", content)
}
if _, err := execute("--project", projectDir, "skills", "add", "--agent", "claude", "--force"); err != nil {
t.Fatal(err)
}
content, err = os.ReadFile(destination)
if err != nil {
t.Fatal(err)
}
if string(content) != string(agcCLISkill) {
t.Fatal("forced skill does not match embedded content")
}
}

func TestSkillsAddRejectsUnsupportedAgent(t *testing.T) {
if _, err := execute("skills", "add", "--agent", "unknown"); err == nil {
t.Fatal("expected unsupported agent error")
}
}

func TestSkillsAddDoesNotReplaceDirectory(t *testing.T) {
projectDir := t.TempDir()
destination := filepath.Join(projectDir, ".agents", "skills", "agc-cli", "SKILL.md")
if err := os.MkdirAll(destination, 0755); err != nil {
t.Fatal(err)
}
if _, err := execute("--project", projectDir, "skills", "add", "--agent", "codex", "--force"); err == nil {
t.Fatal("expected directory destination error")
}
info, err := os.Stat(destination)
if err != nil {
t.Fatal(err)
}
if !info.IsDir() {
t.Fatal("destination directory was replaced")
}
}

func TestEveryRegisteredCommandHasHelp(t *testing.T) {
root := NewRootCommand()
for _, cmd := range root.Commands() {
Expand Down
10 changes: 10 additions & 0 deletions docs/CLI_USAGE.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,16 @@ agc publishing app-info-query --pretty

Endpoint definitions include `_links` to local REST routes and `affordances` with next-command templates. Agents can inspect the definition and `sourceUrl`, fill in parameters, and preview requests. Templates do not confirm that business prerequisites are satisfied.

Install the bundled `agc-cli` skill into an agent's skill directory in the selected project:

```bash
agc --project . skills add --agent copilot
# Or install for all supported agents: copilot, claude, and codex
agc --project . skills add --agent all
```

The command creates `.github/skills/agc-cli`, `.claude/skills/agc-cli`, or `.agents/skills/agc-cli` as appropriate. Existing skills are left unchanged unless `--force` is specified. Run `agc skills add --help` for details.

## Web, REST, and OpenAPI

Browse static endpoint references at [agccli.app](https://agccli.app/). For local REST data, start the API and Web development server from the source checkout:
Expand Down
Loading