@tokenfactory/acc-runner 0.41.7 → 0.42.0

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 (50) hide show
  1. package/README.md +87 -4
  2. package/dist/acc.d.ts +3 -0
  3. package/dist/acc.d.ts.map +1 -0
  4. package/dist/acc.js +21 -0
  5. package/dist/acc.js.map +1 -0
  6. package/dist/cli-name.d.ts +29 -0
  7. package/dist/cli-name.d.ts.map +1 -0
  8. package/dist/cli-name.js +17 -0
  9. package/dist/cli-name.js.map +1 -0
  10. package/dist/cli.d.ts +20 -2
  11. package/dist/cli.d.ts.map +1 -1
  12. package/dist/cli.js +45 -289
  13. package/dist/cli.js.map +1 -1
  14. package/dist/keychain.d.ts +17 -0
  15. package/dist/keychain.d.ts.map +1 -1
  16. package/dist/keychain.js +36 -0
  17. package/dist/keychain.js.map +1 -1
  18. package/dist/login.d.ts +45 -1
  19. package/dist/login.d.ts.map +1 -1
  20. package/dist/login.js +91 -4
  21. package/dist/login.js.map +1 -1
  22. package/dist/program.d.ts +16 -0
  23. package/dist/program.d.ts.map +1 -0
  24. package/dist/program.js +342 -0
  25. package/dist/program.js.map +1 -0
  26. package/dist/user-auth/device-login.d.ts +47 -0
  27. package/dist/user-auth/device-login.d.ts.map +1 -0
  28. package/dist/user-auth/device-login.js +189 -0
  29. package/dist/user-auth/device-login.js.map +1 -0
  30. package/dist/user-auth/liveness.d.ts +27 -0
  31. package/dist/user-auth/liveness.d.ts.map +1 -0
  32. package/dist/user-auth/liveness.js +128 -0
  33. package/dist/user-auth/liveness.js.map +1 -0
  34. package/dist/user-auth/session.d.ts +82 -0
  35. package/dist/user-auth/session.d.ts.map +1 -0
  36. package/dist/user-auth/session.js +172 -0
  37. package/dist/user-auth/session.js.map +1 -0
  38. package/dist/user-cli/chat.d.ts +44 -0
  39. package/dist/user-cli/chat.d.ts.map +1 -0
  40. package/dist/user-cli/chat.js +199 -0
  41. package/dist/user-cli/chat.js.map +1 -0
  42. package/dist/user-cli/client.d.ts +20 -0
  43. package/dist/user-cli/client.d.ts.map +1 -0
  44. package/dist/user-cli/client.js +60 -0
  45. package/dist/user-cli/client.js.map +1 -0
  46. package/dist/user-cli/tasks.d.ts +37 -0
  47. package/dist/user-cli/tasks.d.ts.map +1 -0
  48. package/dist/user-cli/tasks.js +131 -0
  49. package/dist/user-cli/tasks.js.map +1 -0
  50. package/package.json +3 -2
package/dist/keychain.js CHANGED
@@ -38,6 +38,42 @@ export async function clearSession() {
38
38
  const k = await getKeytar();
39
39
  return k.deletePassword(RUNNER_KEYCHAIN_SERVICE, ACCOUNT);
40
40
  }
41
+ /**
42
+ * AX-G1: a SECOND entry in the same keychain service, under a different account.
43
+ *
44
+ * The runner session lives at (service "acc-runner", account "default") and its
45
+ * JSON shape is a frozen contract. A person's CLI session is a different
46
+ * credential with a different shape and a different lifecycle — it is the
47
+ * human's own Supabase session, not a runner impersonation token — so it gets
48
+ * its own account slot rather than being squeezed into the frozen one. A machine
49
+ * that is both a runner and a signed-in workstation holds both, independently,
50
+ * and `acc-runner logout` still only clears the runner's.
51
+ *
52
+ * Same rule as the runner slot: the keychain is the ONLY writable surface for a
53
+ * credential. Nothing here ever touches the filesystem.
54
+ */
55
+ export async function saveSlot(account, value) {
56
+ const k = await getKeytar();
57
+ await k.setPassword(RUNNER_KEYCHAIN_SERVICE, account, JSON.stringify(value));
58
+ }
59
+ export async function loadSlot(account) {
60
+ const k = await getKeytar();
61
+ const raw = await k.getPassword(RUNNER_KEYCHAIN_SERVICE, account);
62
+ if (!raw)
63
+ return null;
64
+ try {
65
+ return JSON.parse(raw);
66
+ }
67
+ catch {
68
+ // Corrupted entry — treat as "not present" rather than crashing, matching
69
+ // loadSession above.
70
+ return null;
71
+ }
72
+ }
73
+ export async function clearSlot(account) {
74
+ const k = await getKeytar();
75
+ return k.deletePassword(RUNNER_KEYCHAIN_SERVICE, account);
76
+ }
41
77
  /** Test-only seam used by the keychain test (overrides the lazy import). */
42
78
  export function __setKeytar(impl) {
43
79
  cached = impl;
@@ -1 +1 @@
1
- {"version":3,"file":"keychain.js","sourceRoot":"","sources":["../src/keychain.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,uBAAuB,EAAsB,MAAM,YAAY,CAAC;AAEzE,MAAM,OAAO,GAAG,SAAkB,CAAC;AAQnC,IAAI,MAAM,GAAsB,IAAI,CAAC;AAErC,KAAK,UAAU,SAAS;IACtB,IAAI,MAAM;QAAE,OAAO,MAAM,CAAC;IAC1B,qEAAqE;IACrE,MAAM,GAAG,GAAG,CAAC,MAAM,MAAM,CAAC,QAAQ,CAAC,CAAqD,CAAC;IACzF,MAAM,GAAG,CAAC,GAAG,CAAC,OAAO,IAAI,GAAG,CAAe,CAAC;IAC5C,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,OAAsB;IACtD,MAAM,CAAC,GAAG,MAAM,SAAS,EAAE,CAAC;IAC5B,MAAM,CAAC,CAAC,WAAW,CAAC,uBAAuB,EAAE,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC;AACjF,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW;IAC/B,MAAM,CAAC,GAAG,MAAM,SAAS,EAAE,CAAC;IAC5B,MAAM,GAAG,GAAG,MAAM,CAAC,CAAC,WAAW,CAAC,uBAAuB,EAAE,OAAO,CAAC,CAAC;IAClE,IAAI,CAAC,GAAG;QAAE,OAAO,IAAI,CAAC;IACtB,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAkB,CAAC;IAC1C,CAAC;IAAC,MAAM,CAAC;QACP,mEAAmE;QACnE,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY;IAChC,MAAM,CAAC,GAAG,MAAM,SAAS,EAAE,CAAC;IAC5B,OAAO,CAAC,CAAC,cAAc,CAAC,uBAAuB,EAAE,OAAO,CAAC,CAAC;AAC5D,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,WAAW,CAAC,IAAuB;IACjD,MAAM,GAAG,IAAI,CAAC;AAChB,CAAC"}
1
+ {"version":3,"file":"keychain.js","sourceRoot":"","sources":["../src/keychain.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,uBAAuB,EAAsB,MAAM,YAAY,CAAC;AAEzE,MAAM,OAAO,GAAG,SAAkB,CAAC;AAQnC,IAAI,MAAM,GAAsB,IAAI,CAAC;AAErC,KAAK,UAAU,SAAS;IACtB,IAAI,MAAM;QAAE,OAAO,MAAM,CAAC;IAC1B,qEAAqE;IACrE,MAAM,GAAG,GAAG,CAAC,MAAM,MAAM,CAAC,QAAQ,CAAC,CAAqD,CAAC;IACzF,MAAM,GAAG,CAAC,GAAG,CAAC,OAAO,IAAI,GAAG,CAAe,CAAC;IAC5C,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,OAAsB;IACtD,MAAM,CAAC,GAAG,MAAM,SAAS,EAAE,CAAC;IAC5B,MAAM,CAAC,CAAC,WAAW,CAAC,uBAAuB,EAAE,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC;AACjF,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW;IAC/B,MAAM,CAAC,GAAG,MAAM,SAAS,EAAE,CAAC;IAC5B,MAAM,GAAG,GAAG,MAAM,CAAC,CAAC,WAAW,CAAC,uBAAuB,EAAE,OAAO,CAAC,CAAC;IAClE,IAAI,CAAC,GAAG;QAAE,OAAO,IAAI,CAAC;IACtB,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAkB,CAAC;IAC1C,CAAC;IAAC,MAAM,CAAC;QACP,mEAAmE;QACnE,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY;IAChC,MAAM,CAAC,GAAG,MAAM,SAAS,EAAE,CAAC;IAC5B,OAAO,CAAC,CAAC,cAAc,CAAC,uBAAuB,EAAE,OAAO,CAAC,CAAC;AAC5D,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,OAAe,EAAE,KAAc;IAC5D,MAAM,CAAC,GAAG,MAAM,SAAS,EAAE,CAAC;IAC5B,MAAM,CAAC,CAAC,WAAW,CAAC,uBAAuB,EAAE,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC;AAC/E,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAI,OAAe;IAC/C,MAAM,CAAC,GAAG,MAAM,SAAS,EAAE,CAAC;IAC5B,MAAM,GAAG,GAAG,MAAM,CAAC,CAAC,WAAW,CAAC,uBAAuB,EAAE,OAAO,CAAC,CAAC;IAClE,IAAI,CAAC,GAAG;QAAE,OAAO,IAAI,CAAC;IACtB,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAM,CAAC;IAC9B,CAAC;IAAC,MAAM,CAAC;QACP,0EAA0E;QAC1E,qBAAqB;QACrB,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,OAAe;IAC7C,MAAM,CAAC,GAAG,MAAM,SAAS,EAAE,CAAC;IAC5B,OAAO,CAAC,CAAC,cAAc,CAAC,uBAAuB,EAAE,OAAO,CAAC,CAAC;AAC5D,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,WAAW,CAAC,IAAuB;IACjD,MAAM,GAAG,IAAI,CAAC;AAChB,CAAC"}
package/dist/login.d.ts CHANGED
@@ -42,11 +42,55 @@ export declare function runnerCapsWithRepos(repos: readonly string[] | undefined
42
42
  };
43
43
  export declare function machineIdentity(): string;
44
44
  export declare function detectInstalledModels(): Promise<string[]>;
45
- export interface LoginOptions {
45
+ /**
46
+ * RUNNER-NO-OPEN — the browser-open gate.
47
+ *
48
+ * `open(verification_url)` used to run unconditionally, which meant an agent or
49
+ * a test that merely EXERCISED the login flow launched the browser on whatever
50
+ * machine the process happened to be running on. That is exactly what happened:
51
+ * fixture logins against `https://acc.test` with the placeholder code
52
+ * `ABCD-1234` opened dead tabs on an operator's Mac, over and over, while
53
+ * agents worked the CLI login surfaces. A side-effect that reaches out of the
54
+ * process and onto the host has to be opt-out-able, and the default has to be
55
+ * "only when a human is plausibly watching".
56
+ *
57
+ * "A human is plausibly watching" is approximated by: stdout is a TTY, no
58
+ * explicit suppression, and no test/CI marker in the environment. Everything
59
+ * else falls back to the printed URL + code, which is the primary instruction
60
+ * anyway — the browser-open has always been a convenience layered on top of it.
61
+ */
62
+ export declare const NO_OPEN_ENV = "ACC_RUNNER_NO_OPEN";
63
+ export interface BrowserOpenContext {
64
+ /** Environment to read the suppression markers from. Defaults to `process.env`. */
65
+ env?: NodeJS.ProcessEnv;
66
+ /** Whether stdout is a terminal. Defaults to `process.stdout.isTTY`. */
67
+ isTty?: boolean;
68
+ }
69
+ /**
70
+ * The reason the browser-open is suppressed, or `null` when it may proceed.
71
+ * Returning the reason rather than a bare boolean keeps the gate debuggable —
72
+ * "why did/didn't it open?" is answerable without re-deriving the conditions.
73
+ */
74
+ export declare function browserOpenBlockedBy(ctx?: BrowserOpenContext): string | null;
75
+ export declare function shouldOpenBrowser(ctx?: BrowserOpenContext): boolean;
76
+ export interface MaybeOpenBrowserOptions extends BrowserOpenContext {
77
+ /** Injectable `open` (tests). Defaults to the real `open` package. */
78
+ openBrowser?: (url: string) => Promise<unknown>;
79
+ }
80
+ /**
81
+ * Open `url` in the host's browser IF the gate allows it. Resolves `true` when
82
+ * the browser was actually launched, `false` when it was suppressed or the
83
+ * launch failed (headless box, no default handler) — either way the caller has
84
+ * already printed the URL, so a `false` is never an error.
85
+ */
86
+ export declare function maybeOpenBrowser(url: string, options?: MaybeOpenBrowserOptions): Promise<boolean>;
87
+ export interface LoginOptions extends BrowserOpenContext {
46
88
  /** Override the polling interval seconds (mostly for tests). */
47
89
  intervalSeconds?: number;
48
90
  /** Override the timeout (seconds) to abort polling. */
49
91
  timeoutSeconds?: number;
92
+ /** Injectable `open` (tests). Defaults to the real `open` package. */
93
+ openBrowser?: (url: string) => Promise<unknown>;
50
94
  }
51
95
  export declare function loginCommand(options?: LoginOptions): Promise<void>;
52
96
  //# sourceMappingURL=login.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"login.d.ts","sourceRoot":"","sources":["../src/login.ts"],"names":[],"mappings":"AAaA,OAAO,EAKL,KAAK,iBAAiB,EACtB,KAAK,iBAAiB,EACvB,MAAM,aAAa,CAAC;AAIrB,OAAO,KAAK,EAGV,UAAU,EACX,MAAM,YAAY,CAAC;AAOpB,eAAO,MAAM,WAAW,EAAE,UAczB,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,EACpC,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,EACvC,IAAI,GAAE,iBAA8C,EAKpD,MAAM,GAAE,iBAAgE,GACvE,UAAU,GAAG;IACd,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,qBAAqB,EAAE,MAAM,CAAC;IAC9B,kBAAkB,EAAE,iBAAiB,CAAC;CACvC,CASA;AAED,wBAAgB,eAAe,IAAI,MAAM,CAExC;AAGD,wBAAsB,qBAAqB,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC,CAe/D;AAiBD,MAAM,WAAW,YAAY;IAC3B,gEAAgE;IAChE,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,uDAAuD;IACvD,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,wBAAsB,YAAY,CAAC,OAAO,GAAE,YAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAwG5E"}
1
+ {"version":3,"file":"login.d.ts","sourceRoot":"","sources":["../src/login.ts"],"names":[],"mappings":"AAaA,OAAO,EAKL,KAAK,iBAAiB,EACtB,KAAK,iBAAiB,EACvB,MAAM,aAAa,CAAC;AAIrB,OAAO,KAAK,EAGV,UAAU,EACX,MAAM,YAAY,CAAC;AAOpB,eAAO,MAAM,WAAW,EAAE,UAczB,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,EACpC,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,EACvC,IAAI,GAAE,iBAA8C,EAKpD,MAAM,GAAE,iBAAgE,GACvE,UAAU,GAAG;IACd,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,qBAAqB,EAAE,MAAM,CAAC;IAC9B,kBAAkB,EAAE,iBAAiB,CAAC;CACvC,CASA;AAED,wBAAgB,eAAe,IAAI,MAAM,CAExC;AAGD,wBAAsB,qBAAqB,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC,CAe/D;AAoBD;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,WAAW,uBAAuB,CAAC;AAEhD,MAAM,WAAW,kBAAkB;IACjC,mFAAmF;IACnF,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,wEAAwE;IACxE,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AASD;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,GAAE,kBAAuB,GAAG,MAAM,GAAG,IAAI,CAWhF;AAED,wBAAgB,iBAAiB,CAAC,GAAG,GAAE,kBAAuB,GAAG,OAAO,CAEvE;AAED,MAAM,WAAW,uBAAwB,SAAQ,kBAAkB;IACjE,sEAAsE;IACtE,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CACjD;AAED;;;;;GAKG;AACH,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,MAAM,EACX,OAAO,GAAE,uBAA4B,GACpC,OAAO,CAAC,OAAO,CAAC,CASlB;AAED,MAAM,WAAW,YAAa,SAAQ,kBAAkB;IACtD,gEAAgE;IAChE,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,uDAAuD;IACvD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,sEAAsE;IACtE,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CACjD;AAED,wBAAsB,YAAY,CAAC,OAAO,GAAE,YAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CA0H5E"}
package/dist/login.js CHANGED
@@ -118,6 +118,76 @@ async function postJson(url, body) {
118
118
  }
119
119
  return (await res.json());
120
120
  }
121
+ /** Consecutive transient exchange failures tolerated before we give up. */
122
+ const MAX_POLL_FAILURES = 5;
123
+ /**
124
+ * RUNNER-NO-OPEN — the browser-open gate.
125
+ *
126
+ * `open(verification_url)` used to run unconditionally, which meant an agent or
127
+ * a test that merely EXERCISED the login flow launched the browser on whatever
128
+ * machine the process happened to be running on. That is exactly what happened:
129
+ * fixture logins against `https://acc.test` with the placeholder code
130
+ * `ABCD-1234` opened dead tabs on an operator's Mac, over and over, while
131
+ * agents worked the CLI login surfaces. A side-effect that reaches out of the
132
+ * process and onto the host has to be opt-out-able, and the default has to be
133
+ * "only when a human is plausibly watching".
134
+ *
135
+ * "A human is plausibly watching" is approximated by: stdout is a TTY, no
136
+ * explicit suppression, and no test/CI marker in the environment. Everything
137
+ * else falls back to the printed URL + code, which is the primary instruction
138
+ * anyway — the browser-open has always been a convenience layered on top of it.
139
+ */
140
+ export const NO_OPEN_ENV = "ACC_RUNNER_NO_OPEN";
141
+ /** `1` / `true` / any other non-empty value except `0` / `false` counts as set. */
142
+ function envFlag(value) {
143
+ if (value === undefined)
144
+ return false;
145
+ const v = value.trim().toLowerCase();
146
+ return v !== "" && v !== "0" && v !== "false";
147
+ }
148
+ /**
149
+ * The reason the browser-open is suppressed, or `null` when it may proceed.
150
+ * Returning the reason rather than a bare boolean keeps the gate debuggable —
151
+ * "why did/didn't it open?" is answerable without re-deriving the conditions.
152
+ */
153
+ export function browserOpenBlockedBy(ctx = {}) {
154
+ const env = ctx.env ?? process.env;
155
+ if (envFlag(env[NO_OPEN_ENV]))
156
+ return NO_OPEN_ENV;
157
+ // Vitest sets both; VITEST_WORKER_ID is present even when a suite has
158
+ // deliberately overwritten NODE_ENV, so check it independently.
159
+ if (envFlag(env.VITEST) || env.VITEST_WORKER_ID !== undefined)
160
+ return "VITEST";
161
+ if ((env.NODE_ENV ?? "").trim().toLowerCase() === "test")
162
+ return "NODE_ENV=test";
163
+ if (envFlag(env.CI))
164
+ return "CI";
165
+ const isTty = ctx.isTty ?? Boolean(process.stdout.isTTY);
166
+ if (!isTty)
167
+ return "non-TTY stdout";
168
+ return null;
169
+ }
170
+ export function shouldOpenBrowser(ctx = {}) {
171
+ return browserOpenBlockedBy(ctx) === null;
172
+ }
173
+ /**
174
+ * Open `url` in the host's browser IF the gate allows it. Resolves `true` when
175
+ * the browser was actually launched, `false` when it was suppressed or the
176
+ * launch failed (headless box, no default handler) — either way the caller has
177
+ * already printed the URL, so a `false` is never an error.
178
+ */
179
+ export async function maybeOpenBrowser(url, options = {}) {
180
+ if (!shouldOpenBrowser(options))
181
+ return false;
182
+ try {
183
+ await (options.openBrowser ?? open)(url);
184
+ return true;
185
+ }
186
+ catch {
187
+ /* no browser available; the user falls back to the printed URL */
188
+ return false;
189
+ }
190
+ }
121
191
  export async function loginCommand(options = {}) {
122
192
  const cfg = loadConfig();
123
193
  // 1. Request a device code.
@@ -129,18 +199,35 @@ export async function loginCommand(options = {}) {
129
199
  console.log(` ${chalk.cyan("2.")} Enter code: ${chalk.bold.green(start.user_code)}`);
130
200
  console.log("");
131
201
  console.log(chalk.gray("Waiting for confirmation…"));
132
- open(start.verification_url).catch(() => {
133
- /* no browser available; user falls back to the printed URL */
134
- });
202
+ // Fire-and-forget, as before: the poll below must start immediately and must
203
+ // never be blocked by a browser that is slow to spawn (or by the gate itself).
204
+ void maybeOpenBrowser(start.verification_url, options);
135
205
  const machine = machineIdentity();
136
206
  const hostname = os.hostname();
137
207
  // 2. Poll the exchange endpoint.
138
208
  const interval = (options.intervalSeconds ?? start.interval) * 1000;
139
209
  const deadline = Date.now() + (options.timeoutSeconds ?? start.expires_in) * 1000;
140
210
  let exchanged = null;
211
+ // A transient exchange failure (pooler hiccup, cold function, 5xx) must not
212
+ // abort a login whose code is still valid — poll through it and only give up
213
+ // when the failures are persistent or the real TTL runs out.
214
+ let consecutiveFailures = 0;
215
+ let lastFailure = null;
141
216
  while (Date.now() < deadline) {
142
217
  await sleep(interval);
143
- const exchange = await postJson(`${cfg.publicUrl.replace(/\/+$/, "")}/api/runner/device-code/exchange`, { device_code: start.device_code, machine, hostname });
218
+ let exchange;
219
+ try {
220
+ exchange = await postJson(`${cfg.publicUrl.replace(/\/+$/, "")}/api/runner/device-code/exchange`, { device_code: start.device_code, machine, hostname });
221
+ }
222
+ catch (err) {
223
+ consecutiveFailures += 1;
224
+ lastFailure = err.message;
225
+ if (consecutiveFailures >= MAX_POLL_FAILURES) {
226
+ throw new Error(`Device-code exchange failed ${consecutiveFailures} times in a row: ${lastFailure}`);
227
+ }
228
+ continue;
229
+ }
230
+ consecutiveFailures = 0;
144
231
  if (exchange.status === "expired") {
145
232
  throw new Error("Device code expired before sign-in completed. Run `acc-runner login` again.");
146
233
  }
package/dist/login.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"login.js","sourceRoot":"","sources":["../src/login.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"login.js","sourceRoot":"","sources":["../src/login.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import { Command } from "commander";
2
+ import type { CliName } from "./cli-name.js";
3
+ export interface BuildProgramOptions {
4
+ /** Which bin the operator typed. Drives the `login` mapping and the help text. */
5
+ invokedAs?: CliName;
6
+ }
7
+ export declare function buildProgram(options?: BuildProgramOptions): Command;
8
+ /**
9
+ * True when `moduleUrl` is the process entry point — NOT when it is imported by
10
+ * a test. realpath resolves the npm bin symlink to the built file so the compare
11
+ * holds for a globally-installed CLI.
12
+ */
13
+ export declare function isMainModule(moduleUrl: string): boolean;
14
+ /** Parse argv and turn a thrown error into a red line + exit 1. */
15
+ export declare function runProgram(program: Command): void;
16
+ //# sourceMappingURL=program.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"program.d.ts","sourceRoot":"","sources":["../src/program.ts"],"names":[],"mappings":"AAmCA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA4BpC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AA8B7C,MAAM,WAAW,mBAAmB;IAClC,kFAAkF;IAClF,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,wBAAgB,YAAY,CAAC,OAAO,GAAE,mBAAwB,GAAG,OAAO,CA6SvE;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAQvD;AAED,mEAAmE;AACnE,wBAAgB,UAAU,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAKjD"}
@@ -0,0 +1,342 @@
1
+ /**
2
+ * The ONE command tree, built for whichever binary name invoked it.
3
+ *
4
+ * The package ships two bins — `acc` (unified) and `acc-runner` (the original
5
+ * name, kept as a deprecated alias) — over this single tree. Almost every
6
+ * command is registered identically for both; the exception is `login` /
7
+ * `logout`, and the reason is compatibility rather than taste.
8
+ *
9
+ * WHY `login` IS NAME-DEPENDENT
10
+ * ----------------------------------------------------------------------------
11
+ * There are two lanes, and the published meaning of the old name is already
12
+ * spoken for:
13
+ *
14
+ * * the MACHINE lane — enrol this host into the fleet, mint an ACC-signed JWT
15
+ * that impersonates its owner, create a runner row. That is what
16
+ * `acc-runner login` has always meant, and it is baked into docker
17
+ * entrypoints, launchd plists and the enrollment guidance the UI prints.
18
+ * * the PERSON lane — sign the human at the keyboard in as themselves, via the
19
+ * generalized device flow, with no runner row. That is what a command called
20
+ * `acc login` must mean; a unified CLI whose `login` enrolled a server would
21
+ * be actively misleading.
22
+ *
23
+ * Silently redefining `acc-runner login` to the person lane would be a breaking
24
+ * change wearing an alias costume — every existing enrollment script would stop
25
+ * producing a runner and start producing a session. So the mapping is:
26
+ *
27
+ * acc login → person acc runner login → machine
28
+ * acc-runner login → machine acc-runner auth login → person
29
+ *
30
+ * Both spellings of each lane exist under both names, so nothing an operator or
31
+ * a script types today changes meaning, and `acc runner login` is the forward
32
+ * name for what the alias called `login`.
33
+ */
34
+ import { realpathSync } from "node:fs";
35
+ import { fileURLToPath } from "node:url";
36
+ import { Command } from "commander";
37
+ import chalk from "chalk";
38
+ import { loadConfig } from "./config.js";
39
+ import { fetchPricing } from "./cost-pricing.js";
40
+ import { loginCommand } from "./login.js";
41
+ import { logoutCommand } from "./logout.js";
42
+ import { watchCommand } from "./watch.js";
43
+ import { companionCommand, companionInstallCommand, companionStatusCommand, companionUninstallCommand, } from "./companion-run.js";
44
+ import { authLoginCommand, authLogoutCommand, authStatusCommand, } from "./user-auth/device-login.js";
45
+ import { ensureUserSessionLive } from "./user-auth/liveness.js";
46
+ import { chatCommand } from "./user-cli/chat.js";
47
+ import { taskListCommand, taskShowCommand } from "./user-cli/tasks.js";
48
+ import { doctorCommand } from "./doctor.js";
49
+ import { clarifyCommand } from "./clarification.js";
50
+ import { clearQuarantine, getQuarantine } from "./runtime/quarantine.js";
51
+ import { clearChatCircuit, getChatCircuit } from "./runtime/chat-circuit-breaker.js";
52
+ import { runCommitGuardHook } from "./commit-guard.js";
53
+ import { PROTOCOL_VERSION } from "./types.js";
54
+ import { PACKAGE_VERSION } from "./pkg-version.js";
55
+ /** Every action shares this shape: report the message, set a failing exit code. */
56
+ function guard(fn) {
57
+ return async () => {
58
+ try {
59
+ await fn();
60
+ }
61
+ catch (err) {
62
+ console.error(chalk.red(`✗ ${err.message}`));
63
+ process.exitCode = 1;
64
+ }
65
+ };
66
+ }
67
+ /**
68
+ * A person-lane command that ACTS on the operator's credential, rather than one
69
+ * that manages it. These check first that the credential still exists server-side,
70
+ * because a revoke on Settings → Sessions cannot reach into this machine's
71
+ * keychain: without the check, a revoked terminal keeps reading the board until
72
+ * its access token expires (see user-auth/liveness.ts). Login / logout / whoami
73
+ * are deliberately NOT wrapped — logging in has nothing to check yet, and logging
74
+ * out of a session that is already gone must still clear the keychain.
75
+ */
76
+ function personGuard(fn) {
77
+ return guard(async () => {
78
+ await ensureUserSessionLive();
79
+ await fn();
80
+ });
81
+ }
82
+ export function buildProgram(options = {}) {
83
+ const invokedAs = options.invokedAs ?? "acc-runner";
84
+ const unified = invokedAs === "acc";
85
+ const program = new Command();
86
+ program
87
+ .name(invokedAs)
88
+ .description(unified
89
+ ? "Agent Control Center. Sign in, read the board, ask ACC, and run agents on this machine."
90
+ : "Agent Control Center local runner. Subscribes to ACC, spawns Claude Code on assignment, and opens a PR when done.")
91
+ .version(`${PACKAGE_VERSION} (protocol v${PROTOCOL_VERSION})`);
92
+ // ── The person lane ───────────────────────────────────────────────────────
93
+ // Registered under `auth <verb>` for BOTH names (so `acc-runner auth login`
94
+ // is untouched), and additionally promoted to the top level under `acc`,
95
+ // where `login` is free to mean what a person expects it to mean.
96
+ const registerPersonLane = (parent, verbs) => {
97
+ parent
98
+ .command("login")
99
+ .description("Sign in via device-code and store a user session in the OS keychain.")
100
+ .action(guard(() => authLoginCommand()));
101
+ parent
102
+ .command(verbs.status)
103
+ .description("Show who this terminal is signed in as.")
104
+ .action(guard(() => authStatusCommand()));
105
+ parent
106
+ .command("logout")
107
+ .description("Revoke this terminal's user session and clear the keychain entry.")
108
+ .action(guard(() => authLogoutCommand()));
109
+ };
110
+ const authCmd = program
111
+ .command("auth")
112
+ .description("Sign in to ACC as yourself (no runner registration).");
113
+ registerPersonLane(authCmd, { status: "status" });
114
+ // ── The machine lane ──────────────────────────────────────────────────────
115
+ const registerMachineLane = (parent) => {
116
+ parent
117
+ .command("login")
118
+ .description("Sign in via device-code OAuth and register this machine.")
119
+ .action(guard(() => loginCommand()));
120
+ parent
121
+ .command("logout")
122
+ .description("Clear the local session and mark this runner offline.")
123
+ .action(guard(() => logoutCommand()));
124
+ };
125
+ registerMachineLane(program
126
+ .command("runner")
127
+ .description("Enrol or retire THIS machine as a runner in the fleet."));
128
+ if (unified) {
129
+ registerPersonLane(program, { status: "whoami" });
130
+ }
131
+ else {
132
+ registerMachineLane(program);
133
+ }
134
+ // ── Reading the board ─────────────────────────────────────────────────────
135
+ const taskCmd = program
136
+ .command("task")
137
+ .description("Read the task board as yourself (read-only).");
138
+ taskCmd
139
+ .command("list")
140
+ .description("List tasks visible to you, newest first.")
141
+ .option("--status <status>", "Only tasks in this status (e.g. in_progress).")
142
+ .option("--repo <slug>", "Only tasks for this repo (owner/name).")
143
+ .option("--limit <n>", "How many rows (1-100, default 25).")
144
+ .option("--json", "Emit raw JSON instead of a table.")
145
+ .action((opts) => personGuard(() => taskListCommand(opts))());
146
+ taskCmd
147
+ .command("show")
148
+ .description("Show one task in full.")
149
+ .argument("<id>", "Task id, e.g. T-1234.")
150
+ .option("--json", "Emit raw JSON instead of fields.")
151
+ .action((id, opts) => personGuard(() => taskShowCommand(id, opts))());
152
+ program
153
+ .command("chat")
154
+ .description("Ask ACC. Pass a question, pipe one in, or run bare for a session.")
155
+ .argument("[message...]", "The question. Omit for an interactive session.")
156
+ .option("--conversation <id>", "Continue an existing conversation.")
157
+ .option("--json", "Emit the turn as JSON instead of streaming it.")
158
+ .action((words, opts) => personGuard(() => chatCommand(words ?? [], opts))());
159
+ // ── Running agents on this machine ────────────────────────────────────────
160
+ program
161
+ .command("watch")
162
+ .description("Long-running. Receive task assignments and execute them.")
163
+ .action(guard(async () => {
164
+ // Warm the cost-pricing cache before the realtime loop opens.
165
+ // fetchPricing handles its own logging; embedded fallback governs
166
+ // if the endpoint is unreachable.
167
+ await fetchPricing(loadConfig().publicUrl);
168
+ await watchCommand();
169
+ // Keep the process alive — stop() runs in the SIGINT handler.
170
+ await new Promise(() => { });
171
+ }));
172
+ // COMPANION: companion mode — an owner-affinity chat runner. Serves ONLY the
173
+ // logged-in owner's interactive chat turns (no code tasks), mirrors the turn
174
+ // stream to the same-machine browser over a loopback socket, and on exit
175
+ // requeues in-flight turns so an Auto turn fails over to the fleet.
176
+ const companionCmd = program
177
+ .command("companion")
178
+ .description("Long-running. Serve the owner's interactive chat turns (companion mode).")
179
+ .action(guard(async () => {
180
+ const handle = await companionCommand();
181
+ const shutdown = () => {
182
+ void handle.stop().finally(() => process.exit(0));
183
+ };
184
+ process.on("SIGINT", shutdown);
185
+ process.on("SIGTERM", shutdown);
186
+ // Keep the process alive; the loop runs on timers until a signal.
187
+ await new Promise(() => { });
188
+ }));
189
+ companionCmd
190
+ .command("status")
191
+ .description("Print the one-line companion status (menu-bar friendly).")
192
+ .action(guard(() => companionStatusCommand()));
193
+ companionCmd
194
+ .command("install")
195
+ .description("Install the companion login agent (autostart at login).")
196
+ .action(guard(() => companionInstallCommand()));
197
+ companionCmd
198
+ .command("uninstall")
199
+ .description("Remove the companion login agent.")
200
+ .action(guard(() => companionUninstallCommand()));
201
+ program
202
+ .command("doctor")
203
+ .description("Run environment checks. Exit 0 if all green, 1 otherwise.")
204
+ .option("--strict", "Treat advisory warnings (missing ANTHROPIC_API_KEY, version drift, etc.) as failures.")
205
+ .action(async (opts) => {
206
+ const code = await doctorCommand({ strict: opts.strict === true });
207
+ process.exitCode = code;
208
+ });
209
+ // NEEDS-INFO-HALT: a spawned agent that needs clarification / approval /
210
+ // missing information raises a needs_clarification halt for its task and hands
211
+ // the question off to JOJO, instead of guessing or dead-lettering. The operator
212
+ // answers by resolving the halt, which resumes the SAME task blocked -> queued.
213
+ program
214
+ .command("clarify")
215
+ .description("Raise a needs_clarification halt for a task and hand the question off to JOJO.")
216
+ .requiredOption("--task <id>", "Task id the agent is blocked on (e.g. T-1234).")
217
+ .requiredOption("--question <text>", "The question / missing info the operator must answer.")
218
+ .option("--context <json>", "Optional JSON object merged into the halt payload.")
219
+ .action((opts) => guard(() => clarifyCommand(opts))());
220
+ // Quarantine management. The runner enters quarantine automatically when the
221
+ // failure classifier detects a machine-level failure. An operator resolves the
222
+ // underlying issue then clears the quarantine to resume work.
223
+ const quarantineCmd = program
224
+ .command("quarantine")
225
+ .description("Inspect or clear the runner quarantine state.");
226
+ quarantineCmd
227
+ .command("status")
228
+ .description("Show quarantine state.")
229
+ .action(guard(async () => {
230
+ const q = await getQuarantine();
231
+ if (!q) {
232
+ console.log("Runner is not quarantined.");
233
+ return;
234
+ }
235
+ console.log(chalk.red.bold(`QUARANTINED (${q.cause})`));
236
+ console.log(` Detail: ${q.detail}`);
237
+ console.log(` Task: ${q.taskId}`);
238
+ console.log(` Since: ${q.classifiedAt}`);
239
+ // Consecutive instant-empty count (1 = definitive, 2 = heuristic
240
+ // confirmed by retry). Omitted for states written before it existed.
241
+ if (q.consecutive !== undefined) {
242
+ console.log(` Streak: ${q.consecutive} consecutive instant-empty exit(s)`);
243
+ }
244
+ console.log(chalk.gray(`\nResolve the underlying ${q.cause} issue, then run \`${invokedAs} quarantine clear\`.`));
245
+ process.exitCode = 1;
246
+ }));
247
+ quarantineCmd
248
+ .command("clear")
249
+ .description("Clear quarantine and allow the runner to resume claiming tasks.")
250
+ .action(guard(async () => {
251
+ await clearQuarantine();
252
+ console.log(chalk.green("✓ Quarantine cleared. Runner will resume on next task assignment."));
253
+ }));
254
+ // TURN-LANE: chat-circuit management. The runner opens the chat circuit
255
+ // automatically when a `claude_exit_1`-class fault surfaces while serving a
256
+ // chat turn — benching ONLY the chat lane (code/review keep running). An
257
+ // operator resolves the credential/host issue then clears it to resume chat.
258
+ const chatCircuitCmd = program
259
+ .command("chat-circuit")
260
+ .description("Inspect or clear the chat-capability circuit breaker (chat lane only).");
261
+ chatCircuitCmd
262
+ .command("status")
263
+ .description("Show chat circuit state.")
264
+ .action(guard(async () => {
265
+ const c = await getChatCircuit();
266
+ if (!c) {
267
+ console.log("Chat circuit is closed (chat lane serving).");
268
+ return;
269
+ }
270
+ console.log(chalk.red.bold(`CHAT CIRCUIT OPEN (${c.cause})`));
271
+ console.log(` Detail: ${c.detail}`);
272
+ console.log(` Turn: ${c.turnId}`);
273
+ console.log(` Conversation: ${c.conversationId}`);
274
+ console.log(` Since: ${c.trippedAt}`);
275
+ console.log(chalk.gray(`\nResolve the underlying ${c.cause} issue, then run \`${invokedAs} chat-circuit clear\`. ` +
276
+ `Code/review lanes are unaffected.`));
277
+ process.exitCode = 1;
278
+ }));
279
+ chatCircuitCmd
280
+ .command("clear")
281
+ .description("Close the chat circuit and allow the runner to resume claiming chat turns.")
282
+ .action(guard(async () => {
283
+ await clearChatCircuit();
284
+ console.log(chalk.green("✓ Chat circuit closed. Runner will resume claiming chat turns."));
285
+ }));
286
+ // The git pre-commit hook the runner installs into a task worktree (see
287
+ // commit-guard.ts) re-invokes this hidden subcommand. It scans the STAGED text
288
+ // files and exits non-zero (with an actionable message on stderr) when a NUL
289
+ // byte / invalid UTF-8 / binary-blob-where-text is found, so git aborts the
290
+ // commit and the agent fixes it in-session. Hidden — it is a hook entry point,
291
+ // not an operator command.
292
+ program
293
+ .command("_precommit-guard", { hidden: true })
294
+ .description("Internal: commit-time mechanical guard (pre-commit hook entry).")
295
+ .action(async () => {
296
+ try {
297
+ const result = await runCommitGuardHook();
298
+ if (!result.ok) {
299
+ console.error(result.message);
300
+ process.exitCode = 1;
301
+ }
302
+ }
303
+ catch (err) {
304
+ // Fail OPEN on an internal error: a guard bug must never wedge the agent's
305
+ // ability to commit. The pre-PR gate (prepr-gate.ts) is the backstop.
306
+ console.error(chalk.yellow(`commit-guard skipped (internal error): ${err.message}`));
307
+ }
308
+ });
309
+ program
310
+ .command("version")
311
+ .description("Print CLI + protocol versions.")
312
+ .action(() => {
313
+ const label = Math.max(invokedAs.length, "protocol".length);
314
+ console.log(`${invokedAs.padEnd(label)} ${PACKAGE_VERSION}`);
315
+ console.log(`${"protocol".padEnd(label)} ${PROTOCOL_VERSION}`);
316
+ });
317
+ return program;
318
+ }
319
+ /**
320
+ * True when `moduleUrl` is the process entry point — NOT when it is imported by
321
+ * a test. realpath resolves the npm bin symlink to the built file so the compare
322
+ * holds for a globally-installed CLI.
323
+ */
324
+ export function isMainModule(moduleUrl) {
325
+ const entry = process.argv[1];
326
+ if (!entry)
327
+ return false;
328
+ try {
329
+ return realpathSync(entry) === realpathSync(fileURLToPath(moduleUrl));
330
+ }
331
+ catch {
332
+ return false;
333
+ }
334
+ }
335
+ /** Parse argv and turn a thrown error into a red line + exit 1. */
336
+ export function runProgram(program) {
337
+ program.parseAsync(process.argv).catch((err) => {
338
+ console.error(chalk.red(`✗ ${err.message}`));
339
+ process.exit(1);
340
+ });
341
+ }
342
+ //# sourceMappingURL=program.js.map