@appforge-ci/admin-cli 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +70 -0
  3. package/dist/commands/agents.d.ts +55 -0
  4. package/dist/commands/agents.d.ts.map +1 -0
  5. package/dist/commands/agents.js +436 -0
  6. package/dist/commands/agents.js.map +1 -0
  7. package/dist/commands/agents.test.d.ts +2 -0
  8. package/dist/commands/agents.test.d.ts.map +1 -0
  9. package/dist/commands/agents.test.js +127 -0
  10. package/dist/commands/agents.test.js.map +1 -0
  11. package/dist/commands/login.d.ts +38 -0
  12. package/dist/commands/login.d.ts.map +1 -0
  13. package/dist/commands/login.js +128 -0
  14. package/dist/commands/login.js.map +1 -0
  15. package/dist/commands/login.test.d.ts +2 -0
  16. package/dist/commands/login.test.d.ts.map +1 -0
  17. package/dist/commands/login.test.js +69 -0
  18. package/dist/commands/login.test.js.map +1 -0
  19. package/dist/index.d.ts +3 -0
  20. package/dist/index.d.ts.map +1 -0
  21. package/dist/index.js +34 -0
  22. package/dist/index.js.map +1 -0
  23. package/dist/lib/bootstrap.d.ts +21 -0
  24. package/dist/lib/bootstrap.d.ts.map +1 -0
  25. package/dist/lib/bootstrap.js +242 -0
  26. package/dist/lib/bootstrap.js.map +1 -0
  27. package/dist/lib/bootstrap.test.d.ts +2 -0
  28. package/dist/lib/bootstrap.test.d.ts.map +1 -0
  29. package/dist/lib/bootstrap.test.js +59 -0
  30. package/dist/lib/bootstrap.test.js.map +1 -0
  31. package/dist/lib/config.d.ts +27 -0
  32. package/dist/lib/config.d.ts.map +1 -0
  33. package/dist/lib/config.js +61 -0
  34. package/dist/lib/config.js.map +1 -0
  35. package/dist/lib/config.test.d.ts +2 -0
  36. package/dist/lib/config.test.d.ts.map +1 -0
  37. package/dist/lib/config.test.js +120 -0
  38. package/dist/lib/config.test.js.map +1 -0
  39. package/dist/lib/prompt.d.ts +11 -0
  40. package/dist/lib/prompt.d.ts.map +1 -0
  41. package/dist/lib/prompt.js +54 -0
  42. package/dist/lib/prompt.js.map +1 -0
  43. package/dist/lib/prompt.test.d.ts +2 -0
  44. package/dist/lib/prompt.test.d.ts.map +1 -0
  45. package/dist/lib/prompt.test.js +22 -0
  46. package/dist/lib/prompt.test.js.map +1 -0
  47. package/dist/lib/two-factor.d.ts +21 -0
  48. package/dist/lib/two-factor.d.ts.map +1 -0
  49. package/dist/lib/two-factor.js +31 -0
  50. package/dist/lib/two-factor.js.map +1 -0
  51. package/dist/lib/two-factor.test.d.ts +2 -0
  52. package/dist/lib/two-factor.test.d.ts.map +1 -0
  53. package/dist/lib/two-factor.test.js +51 -0
  54. package/dist/lib/two-factor.test.js.map +1 -0
  55. package/dist/test-support/home-sandbox.d.ts +3 -0
  56. package/dist/test-support/home-sandbox.d.ts.map +1 -0
  57. package/dist/test-support/home-sandbox.js +47 -0
  58. package/dist/test-support/home-sandbox.js.map +1 -0
  59. package/package.json +33 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 AppForge CI contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,70 @@
1
+ # @appforge-ci/admin-cli
2
+
3
+ `appforge-admin` — the platform-operator CLI for fleet management (enroll,
4
+ list, drain, resume, reboot, run maintenance scripts on Mac minis). This
5
+ is a **separate** tool from `appforge` (`packages/cli`), which is for
6
+ AppForge's customers and has no fleet commands at all — see
7
+ `docs/self-hosting.md`'s "Fleet management" section for why they're kept
8
+ apart. Config lives in `~/.appforge-admin/config.json`, independent of
9
+ `~/.appforge/config.json`.
10
+
11
+ For the full day-to-day runbook (reading fleet status, troubleshooting a
12
+ `NotReady`/`offline` mini, rotating tokens, decommissioning) once this is
13
+ installed, see **`docs/fleet-management.md`**.
14
+
15
+ ## Install
16
+
17
+ **On a machine that already has this repo cloned** (the common case —
18
+ your own laptop, or a Mac mini you `git clone`d onto): build it and put
19
+ it on `PATH` with `npm link`:
20
+
21
+ ```bash
22
+ pnpm install # once, from the repo root
23
+ pnpm --filter @appforge-ci/admin-cli build
24
+ cd packages/admin-cli && npm link # symlinks `appforge-admin` onto PATH
25
+ ```
26
+
27
+ `npm link` needs to be re-run only if the symlink itself ever gets
28
+ removed (e.g. `npm uninstall -g @appforge-ci/admin-cli`) — a plain
29
+ rebuild (`pnpm --filter @appforge-ci/admin-cli build`) is enough to pick
30
+ up code changes afterwards, since the link points at `dist/` in place.
31
+
32
+ **From npm, once published:** `.github/workflows/publish.yml` publishes
33
+ this package (tag-triggered, `git tag vX.Y.Z && git push origin vX.Y.Z`),
34
+ but that workflow needs an `NPM_TOKEN` repo secret that **isn't
35
+ configured yet** — until it is and a version has actually been published,
36
+ `npm install -g @appforge-ci/admin-cli` will 404. The `npm link` path
37
+ above is what actually works today.
38
+
39
+ ## Usage
40
+
41
+ ```bash
42
+ appforge-admin login --api-url <api-url> --admin-api-url <admin-api-url> # once; add --web to approve in a browser instead of typing your password here
43
+ appforge-admin agents list
44
+ appforge-admin agents add-mac --name "my-mac-mini" # run ON the mini you're enrolling — see docs/self-hosting.md
45
+ appforge-admin agents add-mac --force # re-enroll THIS Mac (rotate token / rename) — same fleet row, never a duplicate
46
+ appforge-admin agents decommission <agentId> # permanently remove a mini (retired machine, or a stale duplicate row)
47
+ appforge-admin agents drain <agentId>
48
+ appforge-admin agents resume <agentId>
49
+ appforge-admin agents reboot <agentId>
50
+ appforge-admin agents restart <agentId> # just the agent process (e.g. after a manual upgrade) — no reboot needed
51
+ appforge-admin agents cancel-build <agentId> <buildId> # kill one wedged build, other concurrent builds on the same mini untouched
52
+ appforge-admin agents allow-remote-control <agentId> --on|--off # authorize (or revoke) the VNC "Connect" button's mouse/keyboard control — off by default for every agent
53
+ appforge-admin agents run-script <agentId> <scriptName>
54
+ appforge-admin agents commands <agentId> [--limit 20] # remote-command history + who issued each one — see docs/fleet-management.md
55
+ appforge-admin agents update <agentId> [--to <version>] # roll one mini onto a new agent build (OTA) — see docs/fleet-management.md
56
+ appforge-admin agents update --all # every enrolled mini, one at a time
57
+ appforge-admin agents bootstrap [--full] # install missing toolchains on THIS Mac
58
+ appforge-admin agents enroll # advanced: just a token, no local enrollment — see add-mac instead
59
+ ```
60
+
61
+ `--api-url`/`--admin-api-url` only need passing on that first `login` —
62
+ they're saved to `~/.appforge-admin/config.json` and reused after that.
63
+ Only accounts listed in the admin-api Worker's `PLATFORM_ADMIN_EMAILS`
64
+ secret can actually log in here; see `docs/self-hosting.md`.
65
+
66
+ The password-based `login` (not `--web`) prompts interactively for a 2FA
67
+ code if the account has it enabled and neither `--totp-code` nor
68
+ `--recovery-code` was passed — see `docs/security.md`'s "2FA (TOTP)"
69
+ note. `login --web`'s device-authorization flow has no password step at
70
+ all, so it's unaffected either way.
@@ -0,0 +1,55 @@
1
+ import { Command } from "commander";
2
+ import { ApiClient, type AgentCommand } from "@appforge-ci/core";
3
+ /**
4
+ * Polls `GET /agents/:agentId/commands` for one specific command (an
5
+ * UPDATE_AGENT the CLI just sent) until it leaves `pending`/`acked` for a
6
+ * terminal `done`/`failed`, or `timeoutMs` runs out. The agent only picks
7
+ * a command up on its next ~15s heartbeat and a real self-update (npm
8
+ * install / git checkout + pnpm build) can take real minutes, so this is
9
+ * a genuine wait, not a quick round-trip — see `--timeout`.
10
+ *
11
+ * Exported (and `pollIntervalMs` made overridable) purely for direct
12
+ * testing — every real call site still gets the production
13
+ * `UPDATE_POLL_INTERVAL_MS` default; a test injects a near-zero interval
14
+ * so exercising "it actually loops a few times before resolving" doesn't
15
+ * mean a real multi-second wait.
16
+ */
17
+ /**
18
+ * Parses `agents update`'s `--timeout <seconds>` option — pulled out so
19
+ * it's directly testable, same "test seam via extraction" shape this
20
+ * monorepo already uses for CLI flag parsing (`packages/cli/src/lib/
21
+ * parse-rollout.ts`, `packages/agent/src/lib/parse-args.ts`).
22
+ * `Number(opts.timeout)` alone isn't enough: a non-numeric value (a
23
+ * plausible typo like `"5m"`, meant as "5 minutes" — the option's own
24
+ * description just says "seconds") parses to `NaN`, and
25
+ * `waitForCommandResolution`'s deadline math (`Date.now() + timeoutMs`)
26
+ * then becomes `NaN` too — `Date.now() >= NaN` is always `false`, so the
27
+ * command polls forever instead of ever timing out (with `--all`, that
28
+ * hangs the whole fleet rollout). Returns `null` for anything that isn't
29
+ * a finite, positive number of seconds, so the caller can fail fast with
30
+ * a clear error instead of a silent infinite poll.
31
+ */
32
+ export declare function parseTimeoutSeconds(raw: string): number | null;
33
+ /**
34
+ * `agents commands <agentId>`'s per-row line, pulled out so it's
35
+ * directly testable — same "extract the one real piece of logic" shape
36
+ * `parseTimeoutSeconds` above already established. Every layer below
37
+ * this command already existed and was already tested
38
+ * (`ApiClient.listAgentCommands`, `GET /agents/:agentId/commands`,
39
+ * `packages/db/src/agents.ts`'s `listAgentCommands` LEFT JOIN on
40
+ * `User`) — only a CLI command to call it and print it was missing.
41
+ * `docs/fleet-management.md`'s own "Auditing who ran what" section used
42
+ * to tell a reader there was "still no CLI/dashboard view" for the full
43
+ * history or the issuing admin's email, and to fall back to a raw
44
+ * `curl` — this command is that view. `issuedByEmail` prints as
45
+ * "system/auto" rather than a blank/undefined for a command with none
46
+ * (issued before this was tracked, or a since-deleted account), so the
47
+ * output never looks like a formatting bug. `[AUTO-ROLLBACK]` prefixes
48
+ * an UPDATE_AGENT OTA/self-update Phase 4 auto-issued after detecting a
49
+ * stuck update (`AgentCommand.isAutoRollback`) — otherwise this and an
50
+ * ordinary UPDATE_AGENT look identical in the audit trail.
51
+ */
52
+ export declare function formatAgentCommandLine(cmd: Pick<AgentCommand, "type" | "status" | "result" | "createdAt" | "issuedByEmail" | "isAutoRollback">): string;
53
+ export declare function waitForCommandResolution(api: ApiClient, agentId: string, commandId: string, timeoutMs: number, pollIntervalMs?: number): Promise<AgentCommand | null>;
54
+ export declare function registerAgentsCommands(program: Command): void;
55
+ //# sourceMappingURL=agents.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agents.d.ts","sourceRoot":"","sources":["../../src/commands/agents.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAGpC,OAAO,EACL,SAAS,EAMT,KAAK,YAAY,EAClB,MAAM,mBAAmB,CAAC;AAqB3B;;;;;;;;;;;;;GAaG;AACH;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAG9D;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,sBAAsB,CACpC,GAAG,EAAE,IAAI,CAAC,YAAY,EAAE,MAAM,GAAG,QAAQ,GAAG,QAAQ,GAAG,WAAW,GAAG,eAAe,GAAG,gBAAgB,CAAC,GACvG,MAAM,CAIR;AAED,wBAAsB,wBAAwB,CAC5C,GAAG,EAAE,SAAS,EACd,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,cAAc,GAAE,MAAgC,GAC/C,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAS9B;AAED,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,QAyZtD"}
@@ -0,0 +1,436 @@
1
+ import { createRequire } from "node:module";
2
+ import { existsSync, readFileSync } from "node:fs";
3
+ import { ApiClient, computeAgentReadiness, effectiveAgentStatus, isAgentVersionCurrent, parseActiveBuildIds, CURRENT_AGENT_VERSION, } from "@appforge-ci/core";
4
+ import { collectSystemInfo } from "@appforge-ci/agent/dist/lib/system-info.js";
5
+ import { saveConfig as saveAgentConfig, configPath as agentConfigPath } from "@appforge-ci/agent/dist/lib/config.js";
6
+ import { loadAdminCliConfig } from "../lib/config.js";
7
+ import { installAgentLaunchAgent, stableNodePath } from "@appforge-ci/agent/dist/lib/launchd.js";
8
+ import { AGENT_VERSION } from "@appforge-ci/agent/dist/lib/version.js";
9
+ import { AgentApi } from "@appforge-ci/agent/dist/lib/api.js";
10
+ import { installDeviceStreamIfPresent, syncDeviceStreamFleetSecrets } from "@appforge-ci/agent/dist/lib/device-stream-install.js";
11
+ import { bootstrapSafeToolchains, bootstrapPrivileged } from "../lib/bootstrap.js";
12
+ const require = createRequire(import.meta.url);
13
+ const UPDATE_POLL_INTERVAL_MS = 5000;
14
+ function client() {
15
+ const cfg = loadAdminCliConfig();
16
+ return {
17
+ cfg,
18
+ api: new ApiClient({ baseUrl: cfg.apiUrl, token: cfg.token, adminApiUrl: cfg.adminApiUrl }),
19
+ };
20
+ }
21
+ /**
22
+ * Polls `GET /agents/:agentId/commands` for one specific command (an
23
+ * UPDATE_AGENT the CLI just sent) until it leaves `pending`/`acked` for a
24
+ * terminal `done`/`failed`, or `timeoutMs` runs out. The agent only picks
25
+ * a command up on its next ~15s heartbeat and a real self-update (npm
26
+ * install / git checkout + pnpm build) can take real minutes, so this is
27
+ * a genuine wait, not a quick round-trip — see `--timeout`.
28
+ *
29
+ * Exported (and `pollIntervalMs` made overridable) purely for direct
30
+ * testing — every real call site still gets the production
31
+ * `UPDATE_POLL_INTERVAL_MS` default; a test injects a near-zero interval
32
+ * so exercising "it actually loops a few times before resolving" doesn't
33
+ * mean a real multi-second wait.
34
+ */
35
+ /**
36
+ * Parses `agents update`'s `--timeout <seconds>` option — pulled out so
37
+ * it's directly testable, same "test seam via extraction" shape this
38
+ * monorepo already uses for CLI flag parsing (`packages/cli/src/lib/
39
+ * parse-rollout.ts`, `packages/agent/src/lib/parse-args.ts`).
40
+ * `Number(opts.timeout)` alone isn't enough: a non-numeric value (a
41
+ * plausible typo like `"5m"`, meant as "5 minutes" — the option's own
42
+ * description just says "seconds") parses to `NaN`, and
43
+ * `waitForCommandResolution`'s deadline math (`Date.now() + timeoutMs`)
44
+ * then becomes `NaN` too — `Date.now() >= NaN` is always `false`, so the
45
+ * command polls forever instead of ever timing out (with `--all`, that
46
+ * hangs the whole fleet rollout). Returns `null` for anything that isn't
47
+ * a finite, positive number of seconds, so the caller can fail fast with
48
+ * a clear error instead of a silent infinite poll.
49
+ */
50
+ export function parseTimeoutSeconds(raw) {
51
+ const seconds = Number(raw);
52
+ return Number.isFinite(seconds) && seconds > 0 ? seconds : null;
53
+ }
54
+ /**
55
+ * `agents commands <agentId>`'s per-row line, pulled out so it's
56
+ * directly testable — same "extract the one real piece of logic" shape
57
+ * `parseTimeoutSeconds` above already established. Every layer below
58
+ * this command already existed and was already tested
59
+ * (`ApiClient.listAgentCommands`, `GET /agents/:agentId/commands`,
60
+ * `packages/db/src/agents.ts`'s `listAgentCommands` LEFT JOIN on
61
+ * `User`) — only a CLI command to call it and print it was missing.
62
+ * `docs/fleet-management.md`'s own "Auditing who ran what" section used
63
+ * to tell a reader there was "still no CLI/dashboard view" for the full
64
+ * history or the issuing admin's email, and to fall back to a raw
65
+ * `curl` — this command is that view. `issuedByEmail` prints as
66
+ * "system/auto" rather than a blank/undefined for a command with none
67
+ * (issued before this was tracked, or a since-deleted account), so the
68
+ * output never looks like a formatting bug. `[AUTO-ROLLBACK]` prefixes
69
+ * an UPDATE_AGENT OTA/self-update Phase 4 auto-issued after detecting a
70
+ * stuck update (`AgentCommand.isAutoRollback`) — otherwise this and an
71
+ * ordinary UPDATE_AGENT look identical in the audit trail.
72
+ */
73
+ export function formatAgentCommandLine(cmd) {
74
+ const resultTag = cmd.result ? ` result:${cmd.result}` : "";
75
+ const rollbackTag = cmd.isAutoRollback === 1 ? "[AUTO-ROLLBACK] " : "";
76
+ return `${cmd.createdAt} ${rollbackTag}${cmd.type.padEnd(22)} ${cmd.status.padEnd(8)} by:${cmd.issuedByEmail ?? "system/auto"}${resultTag}`;
77
+ }
78
+ export async function waitForCommandResolution(api, agentId, commandId, timeoutMs, pollIntervalMs = UPDATE_POLL_INTERVAL_MS) {
79
+ const deadline = Date.now() + timeoutMs;
80
+ for (;;) {
81
+ const { commands } = await api.listAgentCommands(agentId, 20);
82
+ const command = commands.find((c) => c.id === commandId);
83
+ if (command && (command.status === "done" || command.status === "failed"))
84
+ return command;
85
+ if (Date.now() >= deadline)
86
+ return null;
87
+ await new Promise((r) => setTimeout(r, pollIntervalMs));
88
+ }
89
+ }
90
+ export function registerAgentsCommands(program) {
91
+ const agents = program.command("agents").description("Manage the Mac mini build fleet");
92
+ agents
93
+ .command("list")
94
+ .action(async () => {
95
+ const { cfg, api } = client();
96
+ const { agents } = await api.listAgents(cfg.orgId);
97
+ for (const a of agents) {
98
+ const { ready } = computeAgentReadiness(a);
99
+ // Not a.status directly — see effectiveAgentStatus's docblock in
100
+ // @appforge-ci/core: the raw column freezes at whatever the mini last
101
+ // heartbeated and never becomes "offline" on its own.
102
+ const status = effectiveAgentStatus(a);
103
+ // isAgentVersionCurrent is null for a row with no reported version
104
+ // yet (pre-migrations/0011, or hasn't (re-)enrolled/heartbeated
105
+ // since) — print nothing for that case rather than guessing.
106
+ const versionSuffix = a.agentVersion === null
107
+ ? ""
108
+ : isAgentVersionCurrent(a.agentVersion)
109
+ ? ` agent v${a.agentVersion}`
110
+ : ` agent v${a.agentVersion} (outdated — latest is v${CURRENT_AGENT_VERSION})`;
111
+ // See docs/fleet-management.md's "Concurrent builds" section —
112
+ // one Mac mini can run more than one build at once now.
113
+ const slots = `${parseActiveBuildIds(a.activeBuildIds).length}/${a.maxConcurrentBuilds}`;
114
+ console.log(`${a.id} ${a.name.padEnd(20)} ${status.padEnd(10)} slots ${slots.padEnd(5)} ${(ready ? "Ready" : "NotReady").padEnd(9)} cpu ${a.cpuUsagePercent}% mem ${a.memUsagePercent}% disk free ${a.diskFreeGb}GB last seen ${a.lastSeenAt}${versionSuffix}`);
115
+ }
116
+ if (agents.length === 0)
117
+ console.log("No Mac minis enrolled yet. Run 'appforge-admin agents add-mac' on the Mac mini you want to add.");
118
+ });
119
+ agents
120
+ .command("enroll")
121
+ .description("Generate a one-time enrollment token for a new Mac mini (advanced — see 'add-mac' for the one-command version)")
122
+ .action(async () => {
123
+ const { cfg, api } = client();
124
+ const { enrollmentToken, expiresAt } = await api.createEnrollmentToken(cfg.orgId);
125
+ console.log(`Enrollment token (expires ${expiresAt}):\n`);
126
+ console.log(` ${enrollmentToken}\n`);
127
+ console.log("On the Mac mini, run:");
128
+ console.log(` appforge-agent enroll --api-url ${cfg.adminApiUrl} --token ${enrollmentToken} --name <mac-mini-name>`);
129
+ });
130
+ agents
131
+ .command("bootstrap")
132
+ .description("Install missing build toolchains on THIS Mac (Homebrew + PATH wiring only, no sudo) — run before 'add-mac' if any toolchain shows 'not found'")
133
+ .option("--full", "Also apply infra/mac-mini-setup/bootstrap.sh's privileged, dedicated-mini-only setup: a sudoers rule for remote reboot, disables sleep, full Xcode reinstall. Appropriate for a dedicated always-on Mac mini, NOT a machine you use day to day.")
134
+ .action(async (opts) => {
135
+ await bootstrapSafeToolchains();
136
+ if (opts.full) {
137
+ console.log("\n==> --full requested: applying privileged/system changes (sudoers, sleep, Xcode)...");
138
+ bootstrapPrivileged();
139
+ }
140
+ });
141
+ agents
142
+ .command("add-mac")
143
+ .description("Enroll THIS Mac as a build agent and start it running in the background, in one step — run this ON the Mac mini you want to add (must already be 'appforge-admin login'd)")
144
+ .option("--name <name>", "Name for this Mac in the fleet (default: its hostname)")
145
+ .option("--force", "Re-enroll even though this Mac already has an agent config. The control plane dedupes on hardware UUID, so this updates the existing fleet record rather than adding a second one — use it to rotate the agent token or change the name.")
146
+ .action(async (opts) => {
147
+ const { cfg, api } = client();
148
+ // Guard against the classic double-enrollment: someone runs add-mac
149
+ // again on a Mac that's already in the fleet. Server-side dedup (on
150
+ // hardware UUID) means a re-enroll now updates the existing row
151
+ // rather than creating a duplicate, but it still mints a new agent
152
+ // token and restarts the daemon, which is rarely what you want by
153
+ // accident — so require --force to say you meant it.
154
+ if (existsSync(agentConfigPath()) && !opts.force) {
155
+ let existing = {};
156
+ try {
157
+ existing = JSON.parse(readFileSync(agentConfigPath(), "utf8"));
158
+ }
159
+ catch {
160
+ /* unreadable/corrupt — fall through with blanks */
161
+ }
162
+ console.error(`This Mac is already enrolled as "${existing.name ?? "?"}" (${existing.agentId ?? "?"}).\n` +
163
+ `Config: ${agentConfigPath()}\n\n` +
164
+ ` • Just (re)install the background service: appforge-agent install-service\n` +
165
+ ` • Repair a broken/rotated token or rename: appforge-admin agents add-mac --force\n` +
166
+ ` • Retire this machine from the fleet: appforge-admin agents decommission ${existing.agentId ?? "<agentId>"}\n`);
167
+ process.exit(1);
168
+ }
169
+ console.log("Reading this Mac's info and build toolchains (this can take a moment — flutter --version isn't instant)...");
170
+ const info = await collectSystemInfo();
171
+ console.log(` Xcode: ${info.xcodeVersion ?? "not found"}`);
172
+ console.log(` Flutter: ${info.flutterVersion ?? "not found"}`);
173
+ console.log(` Java: ${info.javaVersion ?? "not found"}`);
174
+ console.log(` Android SDK: ${info.androidSdkVersion ?? "not found"}`);
175
+ console.log(` CocoaPods: ${info.cocoapodsVersion ?? "not found"}`);
176
+ // Same computeAgentReadiness the fleet dashboard uses — see its
177
+ // docblock in @appforge-ci/core. Printed here too so add-mac's own
178
+ // output doesn't silently disagree with what the dashboard says
179
+ // once this Mac shows up in it. Assumes "online" with a fresh
180
+ // lastSeenAt since that's the state this enrollment is about to set.
181
+ const { androidReady, iosReady, flutterReady, ready } = computeAgentReadiness({
182
+ ...info,
183
+ status: "online",
184
+ lastSeenAt: new Date().toISOString(),
185
+ });
186
+ console.log(`\n Fleet readiness: ${ready ? "Ready" : "NotReady"}` +
187
+ ` (Android build ${androidReady ? "✓" : "✗"}, iOS build ${iosReady ? "✓" : "✗"}, Flutter build ${flutterReady ? "✓" : info.flutterVersion === null ? "✗ Flutter SDK missing" : "✗ needs Android or iOS toolchain too"})`);
188
+ if (!ready) {
189
+ console.log(" Neither platform toolchain is complete — run 'appforge-admin agents bootstrap' first (or, for a dedicated always-on mini, ./infra/mac-mini-setup/bootstrap.sh).");
190
+ }
191
+ console.log("\nRequesting a one-time enrollment token...");
192
+ const { enrollmentToken } = await api.createEnrollmentToken(cfg.orgId);
193
+ const name = opts.name ?? info.hostname;
194
+ console.log(`Enrolling as "${name}"...`);
195
+ const enrollRes = await fetch(`${cfg.adminApiUrl}/agents/enroll`, {
196
+ method: "POST",
197
+ headers: { "content-type": "application/json" },
198
+ body: JSON.stringify({
199
+ enrollmentToken,
200
+ name,
201
+ machineId: info.machineId,
202
+ hostname: info.hostname,
203
+ macOsVersion: info.macOsVersion,
204
+ xcodeVersion: info.xcodeVersion,
205
+ flutterVersion: info.flutterVersion,
206
+ javaVersion: info.javaVersion,
207
+ androidSdkVersion: info.androidSdkVersion,
208
+ cocoapodsVersion: info.cocoapodsVersion,
209
+ cpuModel: info.cpuModel,
210
+ agentVersion: AGENT_VERSION,
211
+ }),
212
+ });
213
+ if (!enrollRes.ok) {
214
+ console.error(`Enrollment failed: ${enrollRes.status} ${await enrollRes.text()}`);
215
+ process.exit(1);
216
+ }
217
+ const { agentId, agentToken } = (await enrollRes.json());
218
+ saveAgentConfig({ apiUrl: cfg.adminApiUrl, agentId, agentToken, name });
219
+ console.log(`Enrolled as agent ${agentId}. Config saved to ${agentConfigPath()}.`);
220
+ console.log("\nInstalling as a background service (survives reboot/logout, no sudo needed)...");
221
+ const agentDistIndexPath = require.resolve("@appforge-ci/agent/dist/index.js");
222
+ // Same version-pinned-Cellar-node footgun packages/agent's own
223
+ // install-service already dodges (see stableNodePath's docblock) —
224
+ // process.execPath here is whatever ran `appforge-admin` itself, and
225
+ // baking that straight into the plist means the next `brew upgrade
226
+ // node` deletes it out from under launchd. add-mac is the documented
227
+ // one-command onboarding path, so it needs the same fix, not just
228
+ // packages/agent/src/index.ts's installService().
229
+ const nodePath = stableNodePath(process.execPath);
230
+ const { plistPath, logPath } = installAgentLaunchAgent(nodePath, agentDistIndexPath);
231
+ console.log(` Service: ${plistPath}`);
232
+ console.log(` Logs: tail -f ${logPath}`);
233
+ // Folds apps/device-stream's own persistent-service setup into this
234
+ // one-command onboarding path too — same shape
235
+ // packages/agent/src/index.ts's installService() already
236
+ // establishes for the plain `appforge-agent install-service` path
237
+ // (see that file's own docblock for exactly when this does/doesn't
238
+ // fire). A no-op (or an explained skip) never fails add-mac itself:
239
+ // the build agent's own service above is what this command must
240
+ // always succeed at installing.
241
+ const deviceStreamApi = new AgentApi({ apiUrl: cfg.adminApiUrl, agentToken });
242
+ let deviceStream;
243
+ try {
244
+ ({ outcome: deviceStream } = await syncDeviceStreamFleetSecrets({
245
+ api: deviceStreamApi,
246
+ agentDistIndexPath,
247
+ nodePath,
248
+ apiUrl: cfg.adminApiUrl,
249
+ env: process.env,
250
+ agentId,
251
+ }));
252
+ }
253
+ catch (err) {
254
+ console.warn(`Couldn't fetch this org's fleet secrets (${err.message}) — ` +
255
+ `falling back to whatever is set in this shell's environment.`);
256
+ deviceStream = await installDeviceStreamIfPresent(agentDistIndexPath, nodePath, cfg.adminApiUrl, process.env, {}, undefined, undefined, agentId);
257
+ }
258
+ if (deviceStream.message)
259
+ console.log(deviceStream.message);
260
+ console.log(`\nDone — "${name}" is enrolled and already polling for builds.`);
261
+ console.log(`Manage it with: launchctl {start|stop|unload} com.appforge.agent`);
262
+ console.log(`Un-enroll later with the same fleet commands you'd use for any Mac mini (see 'appforge-admin agents --help').`);
263
+ });
264
+ agents
265
+ .command("drain <agentId>")
266
+ .description("Stop an agent from leasing new builds once its current build finishes")
267
+ .action(async (agentId) => {
268
+ const { api } = client();
269
+ await api.sendAgentCommand(agentId, "DRAIN");
270
+ console.log("Drain command sent.");
271
+ });
272
+ agents
273
+ .command("resume <agentId>")
274
+ .action(async (agentId) => {
275
+ const { api } = client();
276
+ await api.sendAgentCommand(agentId, "RESUME");
277
+ console.log("Resume command sent.");
278
+ });
279
+ agents
280
+ .command("reboot <agentId>")
281
+ .description("Reboot the Mac mini (agent must have the sudoers entry from infra/mac-mini-setup)")
282
+ .action(async (agentId) => {
283
+ const { api } = client();
284
+ await api.sendAgentCommand(agentId, "REBOOT_HOST");
285
+ console.log("Reboot command sent.");
286
+ });
287
+ agents
288
+ .command("restart <agentId>")
289
+ .description("Restart just the agent process on a Mac mini (e.g. after upgrading the agent version by hand) " +
290
+ "— it acks done, then re-execs; launchd brings it back up immediately, no reboot needed")
291
+ .action(async (agentId) => {
292
+ const { api } = client();
293
+ await api.sendAgentCommand(agentId, "RESTART_AGENT");
294
+ console.log("Restart command sent.");
295
+ });
296
+ agents
297
+ .command("cancel-build <agentId> <buildId>")
298
+ .description("Kill one specific build running on this Mac mini, leaving any other concurrent build on it untouched " +
299
+ "(unlike restart/reboot, which abandon everything on the machine) — see docs/fleet-management.md")
300
+ .action(async (agentId, buildId) => {
301
+ const { api } = client();
302
+ await api.sendAgentCommand(agentId, "CANCEL_BUILD", undefined, undefined, buildId);
303
+ console.log(`Cancel command sent for build ${buildId}.`);
304
+ });
305
+ agents
306
+ .command("run-script <agentId> <scriptName>")
307
+ .description("Run a named maintenance script from the agent's local allowlist")
308
+ .action(async (agentId, scriptName) => {
309
+ const { api } = client();
310
+ await api.sendAgentCommand(agentId, "RUN_MAINTENANCE_SCRIPT", scriptName);
311
+ console.log(`Maintenance script "${scriptName}" queued.`);
312
+ });
313
+ agents
314
+ .command("commands <agentId>")
315
+ .description("Show recent remote-management commands sent to a Mac mini (drain/resume/reboot/update/...), their status, and who issued them")
316
+ .option("--limit <n>", "How many to show (server caps at 50)", "20")
317
+ .action(async (agentId, opts) => {
318
+ const { api } = client();
319
+ const { commands } = await api.listAgentCommands(agentId, Number(opts.limit));
320
+ if (commands.length === 0) {
321
+ console.log("No commands sent to this agent yet.");
322
+ return;
323
+ }
324
+ for (const cmd of commands) {
325
+ console.log(formatAgentCommandLine(cmd));
326
+ }
327
+ });
328
+ agents
329
+ .command("update [agentId]")
330
+ .description("Roll a Mac mini onto a new @appforge-ci/agent build via the UPDATE_AGENT remote command (drains, waits out any in-flight build, self-updates, then re-execs)")
331
+ .option("--to <version>", "npm version (or, for a repo-checkout install, a git ref) to update to — omit for latest")
332
+ .option("--all", "Update every enrolled Mac mini, one at a time — a bad agent build only ever takes down the one mini currently updating, never the whole fleet at once")
333
+ .option("--timeout <seconds>", "How long to wait for each mini to ack done/failed before giving up", "600")
334
+ .action(async (agentId, opts) => {
335
+ const { cfg, api } = client();
336
+ if (!opts.all && !agentId) {
337
+ console.error("Specify an agentId, or pass --all to update every enrolled Mac mini one at a time.");
338
+ process.exit(1);
339
+ }
340
+ const { agents: fleet } = await api.listAgents(cfg.orgId);
341
+ const targets = opts.all ? fleet : fleet.filter((a) => a.id === agentId);
342
+ if (targets.length === 0) {
343
+ console.error(opts.all
344
+ ? "No Mac minis enrolled yet."
345
+ : `No agent ${agentId} in this org. 'appforge-admin agents list' shows the current fleet.`);
346
+ process.exit(1);
347
+ }
348
+ const timeoutSeconds = parseTimeoutSeconds(opts.timeout);
349
+ if (timeoutSeconds === null) {
350
+ console.error(`--timeout must be a positive number of seconds, got "${opts.timeout}".`);
351
+ process.exit(1);
352
+ }
353
+ const timeoutMs = timeoutSeconds * 1000;
354
+ let failures = 0;
355
+ for (const target of targets) {
356
+ console.log(`Updating "${target.name}" (${target.id})${opts.to ? ` to ${opts.to}` : ""}...`);
357
+ const { command } = await api.sendAgentCommand(target.id, "UPDATE_AGENT", undefined, opts.to);
358
+ const resolved = await waitForCommandResolution(api, target.id, command.id, timeoutMs);
359
+ if (!resolved) {
360
+ console.error(` timed out after ${opts.timeout}s waiting for "${target.name}" to ack — check it manually.`);
361
+ failures++;
362
+ continue;
363
+ }
364
+ if (resolved.status === "done") {
365
+ console.log(` done: ${resolved.result ?? "updated"}`);
366
+ }
367
+ else {
368
+ console.error(` failed: ${resolved.result ?? "no reason given"}`);
369
+ failures++;
370
+ }
371
+ }
372
+ // --all is a sequential, drain-aware rollout by design (issue #87):
373
+ // stop nagging with an overall summary only when something actually
374
+ // went wrong, so a clean run just shows the per-mini lines above.
375
+ if (failures > 0) {
376
+ console.error(`\n${failures} of ${targets.length} update(s) did not finish cleanly.`);
377
+ process.exit(1);
378
+ }
379
+ });
380
+ agents
381
+ .command("allow-remote-control <agentId>")
382
+ .description("Grant or revoke authorization for the VNC \"Connect\" button to forward mouse/keyboard input to this Mac, not just watch its screen — off by default for every agent (see docs/fleet-management.md)")
383
+ .option("--on", "Grant remote control")
384
+ .option("--off", "Revoke remote control")
385
+ .action(async (agentId, opts) => {
386
+ if (opts.on === opts.off) {
387
+ // Both flags given, or neither — either way there's no single
388
+ // unambiguous intent to act on. Same "require exactly one" shape
389
+ // `agents update`'s own `--all`/`agentId` check above uses.
390
+ console.error("Specify exactly one of --on or --off.");
391
+ process.exit(1);
392
+ }
393
+ const { cfg, api } = client();
394
+ const { agents: fleet } = await api.listAgents(cfg.orgId);
395
+ const target = fleet.find((a) => a.id === agentId);
396
+ if (!target) {
397
+ console.error(`No agent ${agentId} in this org. 'appforge-admin agents list' shows the current fleet.`);
398
+ process.exit(1);
399
+ }
400
+ const enabled = !!opts.on;
401
+ await api.setAgentRemoteControl(cfg.orgId, agentId, enabled);
402
+ console.log(enabled
403
+ ? `Remote control is now ENABLED for "${target.name}" (${agentId}) — the VNC "Connect" button can now request a control session for it.`
404
+ : `Remote control is now disabled for "${target.name}" (${agentId}).`);
405
+ });
406
+ agents
407
+ .command("decommission <agentId>")
408
+ .description("Permanently remove a Mac mini from the fleet (for a retired machine or a stale duplicate row) — does NOT stop the daemon on the machine itself")
409
+ .action(async (agentId) => {
410
+ const { cfg, api } = client();
411
+ const { agents: fleet } = await api.listAgents(cfg.orgId);
412
+ const target = fleet.find((a) => a.id === agentId);
413
+ if (!target) {
414
+ console.error(`No agent ${agentId} in this org. 'appforge-admin agents list' shows the current fleet.`);
415
+ process.exit(1);
416
+ }
417
+ await api.deleteAgent(cfg.orgId, agentId);
418
+ console.log(`Removed "${target.name}" (${agentId}) from the fleet.`);
419
+ // A still-running daemon does NOT re-enroll itself once its row is
420
+ // gone — its next heartbeat 401s (the agent token no longer
421
+ // matches any row), which it treats as a fatal auth failure: it
422
+ // stops leasing builds and idles, logging that it needs
423
+ // `add-mac --force` to come back. It won't silently reappear, but
424
+ // it also won't cleanly stop on its own — only nag about this
425
+ // when the agent looks alive, since that's the case where leaving
426
+ // the daemon running just means a Mac mini quietly erroring
427
+ // forever instead of actually retiring.
428
+ if (effectiveAgentStatus(target) !== "offline") {
429
+ console.log(`\nThis agent was last seen ${target.lastSeenAt} — if its daemon is still running, it will\n` +
430
+ `start failing heartbeats (401) and go idle rather than actually stopping. Stop it ON\n` +
431
+ `that machine to retire it cleanly:\n` +
432
+ ` launchctl unload ~/Library/LaunchAgents/com.appforge.agent.plist && rm ~/.appforge-agent/config.json`);
433
+ }
434
+ });
435
+ }
436
+ //# sourceMappingURL=agents.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agents.js","sourceRoot":"","sources":["../../src/commands/agents.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import "../test-support/home-sandbox.js";
2
+ //# sourceMappingURL=agents.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agents.test.d.ts","sourceRoot":"","sources":["../../src/commands/agents.test.ts"],"names":[],"mappings":"AACA,OAAO,iCAAiC,CAAC"}