@byterm/agc-cli
v0.2.0
Published
Command-line interface for the AppGallery Connect API
Downloads
546
Maintainers
Readme
agc — AppGallery Connect CLI
A command-line interface for the AppGallery Connect (AGC) API,
built on @byterm/agc-sdk. Authenticates with a Service Account and
prints either human-readable tables or clean, single-document JSON for scripts
and agents.
- Install
- Authentication
- Global options
- Output modes
- Exit codes
- The
--dataconvention - Command reference
- Scripting & agents
Install
From the workspace root:
pnpm install
pnpm build # builds @byterm/agc-sdk then @byterm/agc-cliRun it via node:
node packages/cli/dist/index.js --helpOr expose the agc binary on your PATH:
pnpm --filter @byterm/agc-cli exec npm link # now `agc` works anywhere
# or during development, without building:
pnpm --filter @byterm/agc-cli dev -- app info 123 # runs via ts-nodeAuthentication
The CLI uses Service Account auth only. Create a developer-level Service
Account in AGC (Users and permissions → API key → Connect API → Service
Account), download its *.private.json credential file, and make it findable.
Credentials are resolved in this order (first hit wins):
--credentials <path>$AGC_CREDENTIALS./agc-credentials.json(current directory)~/.agc/credentials.json
The CLI signs a short-lived PS256 JWT from the credential file and sends it
as Authorization: Bearer …. Nothing is sent anywhere except the AGC API host.
Keep the credential file secret. The repo
.gitignoreexcludes*.private.json,agc-credentials.json, and.agc/.
Verify your setup:
agc auth whoami # prints the sub-account the credentials authenticate asGlobal options
These apply to every command and may appear before the subcommand:
| Flag | Description | Default |
| --- | --- | --- |
| -c, --credentials <path> | Path to the Service Account *.private.json file | (see resolution order) |
| -s, --site <site> | Region: CN | DE | SG | RU, or an explicit base URL | CN |
| --json | Emit JSON instead of formatted tables | off |
| --no-mask | Do not redact sensitive values (see Masking) | masking on |
| -v, --verbose | Log each HTTP request to stderr | off |
| --timeout <seconds> | Per-request timeout | 60 |
| -V, --version | Print the CLI version | |
| -h, --help | Show help for the program or a command | |
The --site domain must match the project's configured data-processing
location:
| Site | Domain |
| --- | --- |
| CN | connect-api.cloud.huawei.com |
| DE | connect-api-dre.cloud.huawei.com |
| SG | connect-api-dra.cloud.huawei.com |
| RU | connect-api-drru.cloud.huawei.com |
Output modes
Human mode (default) renders tables and short status lines, e.g.:
$ agc app appid com.example.app
┌─────────┬─────────────────────┐
│ appName │ appId │
├─────────┼─────────────────────┤
│ Term │ 1234567890 │
└─────────┴─────────────────────┘JSON mode (--json) prints the AGC API response as exactly one JSON
document — nothing else on stdout. This is the mode to use from scripts and
agents:
$ agc app appid com.example.app --json
{
"ret": { "code": 0, "msg": "success" },
"appids": [ { "key": "Term", "value": "1234567890" } ]
}Human mode may show a focused subset of columns for readability; --json
carries every field of the response. Both modes redact sensitive values by
default — see Masking below. Add --no-mask for the
full, unredacted payload.
Masking sensitive data
To avoid leaking secrets and PII into terminals, logs, screenshots or an agent's
context, the CLI redacts sensitive values by default in both human and JSON
output. Pass --no-mask to disable it when you genuinely need the raw values.
What gets redacted:
| Category | Examples of fields / values | Result |
| --- | --- | --- |
| Secrets | token, access_token, client_secret, private_key, signature, apiKey; any JWT-shaped string | fully hidden → •••••• |
| Signed URLs | signature-bearing query params (HW-CC-Sign, …Signature, …token, …key) | that param's value hidden, rest of the URL kept |
| PII | email, phone/mobile, qqNum, idNumber, developerName*, appReviewName, entityName, account | partially hidden, keeping a short hint → te••••et, 11••••51 |
Non-sensitive identifiers you need for scripting are never masked — e.g.
appId, versionId, packageId, projectId, ret.code, status/state fields,
timestamps.
agc app info 1234567890 --json # developer name/email redacted
agc app info 1234567890 --json --no-mask # raw valuesThe auth token command is exempt (its purpose is to emit a usable token), so
it always prints the real JWT regardless of masking.
Exit codes
| Code | Meaning |
| --- | --- |
| 0 | Success |
| 1 | Any error — bad credentials, network/HTTP failure, or an AGC business error (ret.code != 0) |
On error:
- Human mode prints
✗ <label>: <message>to stderr. - JSON mode prints one structured error document to stdout:
{ "error": "AgcApiError", "code": 204144641, "message": "…", "response": { } }Error error is one of AgcApiError (2xx with non-zero ret.code, includes
code), AgcHttpError (network / non-2xx, includes status), or Error.
The --data convention
Endpoints with a non-trivial request body take -d, --data, which accepts:
- inline JSON —
-d '{"versionId":"123"}' @file—-d @body.json(reads JSON from a file)-—-d -(reads JSON from stdin)
The value must be a JSON object. It is sent as the request body (or, for some read endpoints, as the query parameters — noted per command).
Command reference
Workflows
High-level commands that chain the individual API calls for the two common
tasks. Both prepare by default and only submit with --submit — submitting
sends the build to Huawei review. For the full step-by-step reference (step→API
mapping, output shape, SDK equivalents, troubleshooting) see
WORKFLOWS.md.
Each workflow runs two ways: from a typed config file (recommended) or from imperative flags.
# Config-driven: define once in .agc/release.ts, then:
agc release run # loads .agc/release.{ts,mjs,js,json}
agc release run --submit # override the config
agc test run --submit
# Imperative (one-offs / CI):
agc release create <appId> <file> [flags]
agc test create <appId> <file> [flags]// .agc/release.ts — typed config (autocomplete via defineRelease)
import { defineRelease } from '@byterm/agc-cli';
export default defineRelease({
packageName: 'com.example.app',
file: './build/entry.app',
languages: {
'zh-CN': { newFeatures: '修复若干问题' },
'en-US': { newFeatures: 'Bug fixes', appDesc: '…' }, // appName/briefInfo also supported
},
review: { remark: 'Test guide for the review team…' }, // + testUserName/testUserPassword
materials: { screenshots: { 'zh-CN': { 4: ['./store/1.png', './store/2.png'] } } },
submit: false,
});agc release run accepts --config <path>, --submit / --skip-submit,
--release-time, and the compile-wait flags (--no-wait, --poll-interval,
--wait-timeout, --initial-delay); agc test run accepts --config,
--submit / --skip-submit.
See WORKFLOWS.md → Config files
for the full schema.
release create <appId> <file> — production release
Runs: upload the package → bind it to the app → (config) update
language/review/material info → wait for AGC to compile/parse it →
(with --submit) submit for review.
| Option | Description |
| --- | --- |
| --submit | Submit for review (otherwise stop after compile) |
| --notes <text> / --notes-file <path> | Set update notes (newFeatures) before submit |
| --notes-lang <lang> | Language for the notes (default: the app's default language) |
| --release-time <t> | Scheduled release time (UTC yyyy-MM-ddTHH:mm:ssZZ) |
| --name <fileName> | Override the uploaded file name |
| --cn-mainland <0\|1> | chineseMainlandFlag |
| --no-wait | Skip the compile wait |
| --initial-delay <s> | Wait before the first compile poll (AGC advises ~120) |
| --poll-interval <s> | Seconds between polls (default 15) |
| --wait-timeout <s> | Max seconds to wait (default 600) |
| -d, --data <json> | Extra fields merged into the submit body |
# Prepare only (upload + bind + wait for compile) — safe, no review submission:
agc release create 1234567890 ./app.app --initial-delay 120
# Full release, scheduled:
agc release create 1234567890 ./app.app --submit \
--release-time 2026-08-01T00:00:00+0800Progress streams to stderr; the final summary (objectId, packageId,
compileState, submitted) prints to stdout — one JSON object under --json.
On a compile failure or timeout the command exits non-zero.
test create <appId> <file> — test version
Runs: create a test version → upload the package → attach it →
(with --submit) submit the test version.
| Option | Description |
| --- | --- |
| --submit | Submit the test version (otherwise stop after attaching) |
| --release-type <n> | Release type (default 6 = HarmonyOS test) |
| --test-type <n> | Test type (e.g. 3) |
| --desc <text> | Version description |
| --distribute-mode <n> | Package distribution mode (default 1) |
| --name <fileName> / --cn-mainland <0\|1> | Upload overrides |
agc test create 1234567890 ./app.app --desc "beta build"
agc test create 1234567890 ./app.app --desc "beta build" --submitInviting testers is a separate step — see test below.
Permissions: the Testing API requires the Service Account's role to grant testing access. If calls return
205524993 "client token auth failed", the account has API access but not for that API group — grant the role in AGC.
auth
Authentication helpers. No network calls except as noted.
| Command | Description |
| --- | --- |
| auth whoami | Print the sub-account the current credentials authenticate as |
| auth token | Generate and print a Service Account JWT bearer token (human mode prints just the token; --json adds the sub-account) |
agc auth whoami
agc auth token # e.g. for use with curl
export TOKEN=$(agc auth token)app (Publishing)
Manage app info, versions, packages and the release lifecycle.
| Command | Args / Options | Description |
| --- | --- | --- |
| app info <appId> | -l, --lang <lang>, --release-type <n>, --version-id <id> | Query app information (basic info + languages) |
| app appid <packageName> | --types <types> | Resolve package name(s) → appId (comma-separated, max 50) |
| app versions | -a, --app-id <id>, -p, --package-name <name>, -s, --state <state> | List versions across all types (appId and/or packageName) |
| app package-info <appId> <packageId> | | Query package information |
| app compile-status <appId> <pkgIds> | | Poll compile status for package id(s) (comma-separated) |
| app submit <appId> | --release-time <t>, -d, --data <json>, -y, --yes | Submit the app for review / publishing |
| app cancel-review <appId> <versionId> | -y, --yes | Withdraw a version from review |
| app off-shelf <appId> | -r, --reason <r>, --release-type <n>, -y, --yes | Take a published app off the shelf |
| app update-info <appId> | -d, --data <json> (required) | Update basic app info |
| app update-package <appId> | -d, --data <json> (required) | Bind an uploaded package file to the app version |
| app update-language <appId> | -l, --lang, --notes, --notes-file, --name, --desc, --brief, -d, --data | Update localized info / update notes (newFeatures) for one language |
| app update-files <appId> | -d, --data <json> (required) | Bind uploaded images/videos (icon, screenshots, …) to the listing |
# Read-only
agc app info 1234567890 --lang zh-CN
agc app appid com.example.app,com.example.other
agc app versions --app-id 1234567890 --state 1
agc app compile-status 1234567890 10004151
# Release lifecycle
agc app submit 1234567890 --release-time 2026-01-01T00:00:00+0800
agc app cancel-review 1234567890 155236000
agc app off-shelf 1234567890 --reason "temporary maintenance"
# Updates (body from --data)
agc app update-info 1234567890 -d '{"defaultLang":"zh-CN"}'
agc app update-package 1234567890 -d '{"fileName":"app.app","objectId":"CN/…/x.app"}'release-type values: 1 = full network, 6 = HarmonyOS test (requires
--version-id).
upload
Upload a file to the AGC file server and print its objectId (which you then
pass to app update-package). The bytes are streamed to a pre-signed URL AGC
returns; the SDK computes the SHA-256 and sends the correct length.
agc upload <appId> <filePath> [--name <fileName>] [--cn-mainland <0|1>]
agc upload 1234567890 ./app.app
# ✓ File uploaded.
# objectId CN/2026…/xxxx.app--cn-mainland sets chineseMainlandFlag (required when the developer is
registered outside mainland China).
Provisioning
Devices, signing certificates, provisioning profiles, and app-id creation.
| Command | Args / Options | Description |
| --- | --- | --- |
| device list | -n, --name, --from <n>, --max <n>, --order <n> | List registered test devices |
| device add | -d, --data <json> (required) | Batch add devices |
| cert list | -d, --data <json> | Query certificates |
| cert apply | -d, --data <json> (required) | Apply for a signing certificate |
| profile list | -d, --data <json> | List provisioning profiles |
| profile apply | -d, --data <json> (required) | Apply for a provisioning profile |
| create-appid | --project-id, --name, --package-name, --parent-type, --installation-free | Create an APP ID under a project |
agc device list --name test --max 50 --order 1
agc create-appid --project-id 10059 --name "My App" \
--package-name com.example.app --parent-type 13 --installation-free 0device list sort --order: 1 = create-time desc, 2 = name asc.
create-appid: --parent-type 2=game / 13=app; --installation-free
0=HarmonyOS app / 1=atomic service.
test (Testing)
Test versions, groups and invitation codes. All take <appId>. For the
end-to-end version flow use the test create
workflow above; the commands here are the individual steps.
| Command | Args / Options | Description |
| --- | --- | --- |
| test create <appId> <file> | (workflow — see above) | Create version + upload + attach [+ submit] |
| test new-version <appId> | -d, --data <json> (required) | Create a test version (returns versionId) |
| test add-package <appId> <file> | --distribute-mode <n>, --name, --cn-mainland | Upload a package and attach it to the draft version |
| test submit <appId> <versionId> | -y, --yes | Submit a test version for review |
| test stop <appId> | -d, --data <json> (required), -y, --yes | Stop a test version |
| test groups <appId> | -d, --data <json> | List test groups |
| test create-group <appId> | -d, --data <json> (required) | Create a test group |
| test invite <appId> | -d, --data <json> (required) | Generate an invitation code |
# Manual, step by step:
vid=$(agc test new-version 1234567890 -d '{"releaseType":6,"testType":3,"testDesc":"beta"}' --json | jq -r .versionId)
agc test add-package 1234567890 ./app.app --distribute-mode 1
agc test submit 1234567890 "$vid" --yes
# …or all at once with the workflow:
agc test create 1234567890 ./app.app --desc beta --submitreport (Reports)
Quality metrics and analytics export files.
| Command | Args / Options | Description |
| --- | --- | --- |
| report crash <appId> | --start <t>, --end <t>, --start-version, --end-version, -d, --data | Crash metrics |
| report freeze <appId> | (same as crash) | AppFreeze (ANR) metrics |
| report analytics | -d, --data <json> (required) | Analytics report metric data |
| report export <kind> | -d, --data <json> (required) | Export file URL; <kind> = user | download | install-failed | distribute | voc |
--start / --end accept epoch milliseconds or an ISO date (e.g.
2026-01-01 or 2026-01-01T00:00:00Z), which the CLI converts to epoch ms.
Add filters via --data, e.g. {"filters":{"appVersions":["26.0.4"]}}.
agc report crash 1234567890 --start 2026-01-01 --end 2026-01-08 --json
agc report crash 1234567890 --start 2026-01-01 --end 2026-01-08 \
-d '{"filters":{"appVersions":["26.0.4"],"osVersions":["5.0.0"]}}'
agc report export download -d '{"appId":"1234567890","startDate":"20260101","endDate":"20260108"}'domain (Domain Management)
Business-domain configuration for atomic services (元服务).
| Command | Options | Description |
| --- | --- | --- |
| domain get | -d, --data <json> (required) | Query domain configuration (params via --data) |
| domain set | -d, --data <json> (required) | Create/update domain configuration |
| domain pre-check | -d, --data <json> (required) | Pre-check a business-domain configuration |
| domain limits | -d, --data <json> (required) | Query modification count / config limits |
agc domain get -d '{"appId":"1234567890"}'
agc domain set -d @domain-config.jsonScripting & agents
--json guarantees a single parseable document per invocation, and the exit
code reflects success. A robust pattern:
# Resolve a package name to a bare appId
appId=$(agc app appid com.example.app --json | jq -r '.appids[0].value')
# Fetch app info, failing the script on any error
if ! info=$(agc app info "$appId" --json); then
echo "lookup failed" >&2; exit 1
fi
echo "$info" | jq -r '.appInfo.versionNumber'Notes for agents:
- Always pass
--json; parse stdout as one JSON object. - Check the process exit code (
0= ok). On failure, stdout holds{ "error", "message", … }and (for API errors) a numericcode. - Even on success, inspect
ret.codewhen present — the CLI already treats a non-zeroret.codeas an error, but downstream data lives alongsideret. - Use
--verboseto trace the exact HTTP requests on stderr (does not pollute the JSON on stdout). - Destructive commands (
app submit,app cancel-review,app off-shelf,test submit,test stop) ask for confirmation on a TTY; in--jsonor other non-interactive use they refuse unless you pass-y, --yes. - Output is masked by default. Identifiers used for scripting (
appId,versionId,packageId,projectId, states, timestamps) are not masked, so most pipelines work unchanged. Add--no-maskonly when you specifically need a redacted value (a developer email, a signed download URL, etc.).
See also
@byterm/agc-sdk— the programmatic client the CLI is built on.- Workspace README — overview and build instructions.
