@appforge-ci/cli

中文文档
0.3.29发布于 2026/10/24MITES Module近 30 天下载 2206

appforge — the customer-facing CLI: sign up/log in, manage apps, trigger/watch builds, buy build-token top-ups, start an in-browser device-test session. No fleet commands — that's a separate tool, appforge-admin (packages/admin-cli), for the platfor

原始使用文档
文档目录4 节
  1. Install
  2. Usage
  3. Logging out
  4. Site-to-site VPN

@appforge-ci/cli

appforge — the customer-facing CLI: sign up/log in, manage apps, trigger/watch builds, buy build-token top-ups, start an in-browser device-test session. No fleet commands — that's a separate tool, appforge-admin (packages/admin-cli), for the platform operator only.

Install

On a machine that already has this repo cloned:

pnpm install                        # once, from the repo root
pnpm --filter @appforge-ci/cli build
cd packages/cli && npm link         # symlinks `appforge` onto PATH

From npm (no clone needed):

npm install -g @appforge-ci/cli          # or run once: npx @appforge-ci/cli login --web --api-url https://api.appforge.skoor.ee
appforge login --web --api-url https://api.appforge.skoor.ee

Published by .github/workflows/publish.yml on every version tag, together with @appforge-ci/core. The tarball contains the compiled dist/ only: no tests, no test helpers, no sources.

Usage

appforge signup --email [email protected] --org "My Org"   # or: appforge login --email [email protected]
appforge logout                             # remove the login from THIS machine (the file and leftover copies) AND revoke its token on the server — see "Logging out"
appforge login --email [email protected] --totp-code 123456   # if 2FA is enabled (--recovery-code also works)
appforge login --email [email protected] --email-code         # no password: emails you a 6-digit code, then asks for it
appforge login --web                        # sign in (or sign up) in your browser, like `aws sso login`: no password typed here; prints the claude mcp add command at the end
appforge apps create --name demo --platform flutter --repo-url https://github.com/you/app.git
appforge apps list
appforge apps update <appId> [--name] [--repo-url] [--default-branch]   # fix a stale field without recreating the app
appforge apps webhook-secret <appId>        # the secret GitHub's webhook "Secret" field needs
appforge apps delete <appId>                # permanently delete an app and its builds/OTA history
appforge apps secrets set <appId> --name KEYSTORE_PASSWORD   # --value omitted -> prompted, echo off (never in shell history); injected as $KEYSTORE_PASSWORD on every build for this app
appforge apps secrets list <appId>          # names only, never values
appforge apps secrets unset <appId> --name KEYSTORE_PASSWORD
appforge fleet-secrets set DEVICE_STREAM_PUBLIC_KEY "$(base64 < device-stream-public.der | tr -d '\n')"   # a PUBLIC key; control-plane secrets are refused
appforge fleet-secrets list                  # names only, never values
appforge fleet-secrets unset DEVICE_STREAM_PUBLIC_KEY_PREVIOUS
appforge orgs rename --name "New Org Name"  # fix a typo'd/stale org name (e.g. a placeholder from signup)
appforge orgs set-build-webhook --url https://example.com/hook  # POST build id/status/appId/costTokens/artifacts on every build's completion
appforge orgs set-build-webhook --clear     # turn build-status notifications back off
appforge build trigger --app-id <appId>
appforge build trigger --app-id <appId> --remote-access   # run in a VM you can log in to (docs/remote-access-user.md)
appforge builds ssh <buildId>          # shell in that VM while the build runs and for 10 minutes after; ssh options go after --
appforge builds vnc <buildId>          # screen sharing into it (opens macOS Screen Sharing on a local port)
appforge builds remote-access <buildId>  # is the VM ready, and who opened sessions
appforge builds simulator <buildId>    # a succeeded build's iOS Simulator app (*.app.zip) on a Mac VM, shown by macOS Screen Sharing (100 tokens; docs/remote-access-user.md)
appforge apps test-in-simulator <appId>   # the same for the app's newest succeeded build that has a *.app.zip
appforge build list --app-id <appId>
appforge build logs <buildId> --follow
appforge build artifact <buildId>
appforge build cancel <buildId>
appforge build explain <buildId>       # AI explanation for a failed build (needs ANTHROPIC_API_KEY on the deployment)
appforge build analyze-smoke-test <buildId>  # re-run AI analysis of the build's smoke-test screenshots
appforge build test <buildId>          # where to test a succeeded build in the browser (the dashboard page: Android device test, or iOS Simulator)
appforge init                          # draft an appforge.yaml from what's actually in the current repo
appforge billing balance
appforge billing packs
appforge billing topup --pack starter          # via LHV Paytech, see docs/payments-lhv.md
appforge billing ledger                        # every balance change (top-ups, charges, refunds), newest first
appforge billing usage                         # billing mode + this period's accrued usage, on postpaid
appforge billing setup-card                    # save a card — required before switching to postpaid
appforge billing mode <prepaid|postpaid-monthly>
appforge billing invoices                      # monthly invoices (postpaid billing only)
appforge ota channels --app <appId>         # list channels and each one's active release
appforge ota releases --app <appId> [--channel production]
appforge ota promote <buildId> --app <appId> [--channel production] [--rollout 25] [--mandatory]
appforge ota set-rollout <releaseId> --app <appId> --rollout 50   # progress an existing release's rollout in place
appforge ota app-key --app <appId>          # generate/rotate the manifest key a shipped app embeds
appforge ota activate <releaseId> --app <appId> --channel production  # jump directly to a specific release
appforge ota activate --app <appId> --channel production --clear      # deactivate the channel instead
appforge ota rollback --app <appId> --channel production               # step back exactly one release
appforge tokens list [--limit n] [--offset n]  # the org's API tokens, newest first, 50 a page — id, label, created/last-used (never the token value)
appforge tokens revoke <tokenId>            # permanently revoke one — 'appforge login' again mints a fresh one
appforge vpn status [--json]                # the org's site-to-site VPN tunnel (any member)
appforge vpn create --name hq --endpoint vpn.example.com:51820 --gateway-key <base64> --lan 10.20.0.0/16 [--dns 10.20.0.2 --domain corp.example.com] [--tunnel-cidr 169.254.200.0/30]
appforge vpn peer-config                    # re-show the [Peer] block for your gateway (prints a secret; admin, audited)
appforge vpn rotate-keys [--yes]            # new AppForge keys; tunnel returns to pending (a disabled tunnel stays disabled) until you update your gateway
appforge vpn disable [--yes] | enable       # disable cancels + refunds queued VPN builds
appforge vpn delete [--yes]
appforge vpn events [--json]                # newest first
appforge vpn test [--json]                 # start a build-free handshake test of the gateway (owner/admin; nothing charged; once per 5 minutes)
appforge vpn test --status [--json]        # the latest test's verdict (any member); exits 0 only on handshake_ok
appforge apps update <appId> --vpn          # opt an app in (--no-vpn to opt out; org owner/admin)

Logging out

login/signup store a long-lived afk_ API token in ~/.appforge/config.json. On a Mac that builds for tenants that file is readable by every standard-tier build, which runs as the same user, so keep the login on your own machine and run appforge logout as soon as you are done with a shared or build Mac:

  • It deletes config.json and any config.json.* copy next to it (the temp file of a killed save, a .bak), prints what it removed, leaves everything else in ~/.appforge alone (the VPN state lives there too), and is safe to run when nothing is there. A config.json that is a symlink is unlinked, not followed: the output names the file it pointed to, which stays.
  • It revokes the token on the server too (DELETE /auth/token, issue #952): the token stops working at once, so a copy someone already read is dead. The files are read first and removed before any network call, so the Mac is clean even if the API is unreachable; every saved copy's token is revoked, each against the API URL saved beside it. If the revoke fails (offline, an API that predates the route) logout still removes the login, exits 0, says it could not revoke, and prints the manual follow-up: from a trusted machine, appforge tokens list (the token is labelled cli:<email>; match it by its created time), then appforge tokens revoke <tokenId>, and appforge login again only there. Deleting the file by hand revokes nothing. The token is never printed. If logout runs where a long-lived CI token is the saved login, that token is the one revoked: mint a new one for the pipeline.
  • login and signup print a warning when builds run as the account you logged in with (the agent's config or LaunchAgent plist is in your home, or the boot-time agent runs as you); a laptop with no agent gets nothing, and neither does another account on a Mac whose agent runs as someone else. appforge-agent doctor fails (cli-credentials-off-build-mac) while a login is stored on a Mac with an agent. Nothing is refused.
  • appforge tokens list shows the newest 50 tokens first. The token an old login left on a Mac is among the oldest, so if it is not in the list, go on with --offset 50, --offset 100, ... (or --limit 200) before tokens revoke.

See docs/security.md, "A CLI login never stays on a build Mac". The token self-revoke (issue #952) is covered by unit and route tests; not yet run against the deployed control plane (UNVERIFIED until a dated run is recorded there).

Site-to-site VPN

You describe your own WireGuard gateway; AppForge's Mac dials out to it for builds of apps that opted in. vpn create prints a [Peer] block to paste into your gateway; update the gateway again after rotating keys or changing LAN/tunnel ranges. It contains a secret (the preshared key) — treat it like a password, and only create, peer-config and rotate-keys print it. All changes need an org owner/admin; any member can read status/events. vpn test checks that your gateway accepts the handshake without building anything: it releases the tunnel's keys to one build machine for the test (see docs/vpn.md, "Test tunnel"), and vpn test --status prints the verdict and when it ran. Limits: tools that ignore proxy settings (raw sockets, UDP, ICMP, xcodebuild, simulators) don't use the tunnel; SSH git remotes (ssh://user@host/path or user@host:path) go through it when you pin your server's host keys (vpn ssh-hosts set) and install the app's deploy key (apps deploy-key show), while a bare ssh in a build script is not routed; your gateway must accept inbound UDP on its endpoint port; status becomes ready after the first successful VPN build (vpn test checks the handshake alone; a build is the end-to-end check); builds appear from the tunnel's AppForge-side /30 address, so allow-list it or masquerade.

--api-url (default http://localhost:8787, or set APPFORGE_API_URL) only needs passing on login/signup — after that it's saved to ~/.appforge/config.json and reused. See the root README.md's Quick start for the full local-dev flow and docs/self-hosting.md for pointing this at a real deployment.

login prompts interactively for a 2FA code if the account has it enabled and neither --totp-code nor --recovery-code was passed — see docs/security.md's "2FA (TOTP)" note.

login --email-code signs in with a one-time code emailed to the account instead of a password (verified email addresses and platform admins; the code is typed at a prompt, never passed as a flag, and expires in 10 minutes). Two-factor works the same way as for a password login. See docs/security.md's "Email one-time-code sign-in".

版本与发布记录

@appforge-ci/cli 共发布 36 个版本,首次发布于 2026/09/23;过去 12 个月发布了 36 个版本。

版本发布日期距今
0.3.29latest2026/10/2414 天前
0.3.282026/10/2315 天前
0.3.272026/10/2315 天前
0.3.262026/10/2315 天前
0.3.252026/10/1721 天前
0.3.242026/10/1722 天前

关于 @appforge-ci/cli 的常见问题

@appforge-ci/cli 是什么?

appforge — the customer-facing CLI: sign up/log in, manage apps, trigger/watch builds, buy build-token top-ups, start an in-browser device-test session. No fleet commands — that's a separate tool, appforge-admin (packages/admin-cli), for the platfor

如何安装 @appforge-ci/cli?

在项目目录运行 npm install @appforge-ci/cli,或使用 pnpm add / yarn add / bun add。它还提供命令行工具 appforge,可以用 npx appforge 直接运行。

@appforge-ci/cli 支持 TypeScript 吗?

该包的 package.json 没有声明类型入口。需要类型提示时,可以在 npm 上查找社区维护的 @types/appforge-ci__cli 是否存在。

@appforge-ci/cli 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@appforge-ci/cli 可以商用吗?

@appforge-ci/cli 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@appforge-ci/cli 的下载量有多少?

根据 npm 官方下载统计,@appforge-ci/cli 在 2026/10/07 – 2026/11/05 这 30 天内被下载 2,206 次,日均约 74 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@appforge-ci/cli 还在维护吗?

最新版本 0.3.29 发布于 2026/10/24(14 天前);至今共发布 36 个版本,其中过去 12 个月发布了 36 个;首次发布于 2026/09/23。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。