@oh-hai/cli 0.2.0 → 0.2.2

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 (74) hide show
  1. package/README.md +51 -8
  2. package/dist/auth/resolve-token.d.ts +2 -4
  3. package/dist/auth/resolve-token.js +4 -11
  4. package/dist/auth/resolve-token.js.map +1 -1
  5. package/dist/bindings.d.ts +65 -0
  6. package/dist/bindings.js +242 -0
  7. package/dist/bindings.js.map +1 -0
  8. package/dist/cli.js +171 -2
  9. package/dist/cli.js.map +1 -1
  10. package/dist/commands/ask.js +21 -17
  11. package/dist/commands/ask.js.map +1 -1
  12. package/dist/commands/binary-update.d.ts +61 -0
  13. package/dist/commands/binary-update.js +160 -0
  14. package/dist/commands/binary-update.js.map +1 -0
  15. package/dist/commands/context.d.ts +123 -0
  16. package/dist/commands/doctor.js +30 -3
  17. package/dist/commands/doctor.js.map +1 -1
  18. package/dist/commands/flags.d.ts +14 -2
  19. package/dist/commands/flags.js +34 -6
  20. package/dist/commands/flags.js.map +1 -1
  21. package/dist/commands/handlers.js +6 -0
  22. package/dist/commands/handlers.js.map +1 -1
  23. package/dist/commands/inbox.js +129 -33
  24. package/dist/commands/inbox.js.map +1 -1
  25. package/dist/commands/messaging/identity.d.ts +9 -0
  26. package/dist/commands/messaging/identity.js +21 -0
  27. package/dist/commands/messaging/identity.js.map +1 -1
  28. package/dist/commands/messaging/validate.d.ts +6 -5
  29. package/dist/commands/messaging/validate.js +6 -5
  30. package/dist/commands/messaging/validate.js.map +1 -1
  31. package/dist/commands/registry.js +71 -8
  32. package/dist/commands/registry.js.map +1 -1
  33. package/dist/commands/setup.d.ts +17 -0
  34. package/dist/commands/setup.js +103 -3
  35. package/dist/commands/setup.js.map +1 -1
  36. package/dist/commands/task.js +21 -24
  37. package/dist/commands/task.js.map +1 -1
  38. package/dist/commands/teach.d.ts +13 -0
  39. package/dist/commands/teach.js +317 -0
  40. package/dist/commands/teach.js.map +1 -0
  41. package/dist/commands/update-check.d.ts +49 -0
  42. package/dist/commands/update-check.js +203 -0
  43. package/dist/commands/update-check.js.map +1 -0
  44. package/dist/commands/upgrade.d.ts +2 -0
  45. package/dist/commands/upgrade.js +178 -0
  46. package/dist/commands/upgrade.js.map +1 -0
  47. package/dist/commands/use.d.ts +2 -0
  48. package/dist/commands/use.js +99 -0
  49. package/dist/commands/use.js.map +1 -0
  50. package/dist/commands/whoami.js +113 -27
  51. package/dist/commands/whoami.js.map +1 -1
  52. package/dist/config-file.js +4 -20
  53. package/dist/config-file.js.map +1 -1
  54. package/dist/config.d.ts +14 -0
  55. package/dist/config.js +39 -3
  56. package/dist/config.js.map +1 -1
  57. package/dist/exit-codes.d.ts +6 -0
  58. package/dist/exit-codes.js +6 -0
  59. package/dist/exit-codes.js.map +1 -1
  60. package/dist/help.js +4 -1
  61. package/dist/help.js.map +1 -1
  62. package/dist/os.d.ts +16 -0
  63. package/dist/os.js +32 -0
  64. package/dist/os.js.map +1 -0
  65. package/dist/url.d.ts +7 -0
  66. package/dist/url.js +14 -0
  67. package/dist/url.js.map +1 -1
  68. package/dist/version.d.ts +11 -3
  69. package/dist/version.js +20 -3
  70. package/dist/version.js.map +1 -1
  71. package/dist/watch-lock.d.ts +85 -0
  72. package/dist/watch-lock.js +358 -0
  73. package/dist/watch-lock.js.map +1 -0
  74. package/package.json +2 -2
@@ -0,0 +1 @@
1
+ {"version":3,"file":"binary-update.js","sourceRoot":"","sources":["../../src/commands/binary-update.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,8 @@
1
1
  import type { ResolvedConfig } from "../config.js";
2
2
  import type { LoadedConfigFiles } from "../config-file.js";
3
+ import type { Binding, BindingsIo } from "../bindings.js";
3
4
  import type { CredentialStore, OpenStoreOptions } from "../auth/token-store.js";
5
+ import type { WatchLockIo } from "../watch-lock.js";
4
6
  /** Output sink — the same shape `cli.ts` uses, so tests capture stdout/stderr in-process. */
5
7
  export interface Io {
6
8
  log(line: string): void;
@@ -19,6 +21,13 @@ export interface HttpResponse {
19
21
  headers?: {
20
22
  get(name: string): string | null;
21
23
  };
24
+ /** The raw response body as bytes — read only by the binary self-update (#358), to download the
25
+ * release binary. Optional so a status-only / JSON fake still satisfies the type; the DOM
26
+ * `Response.arrayBuffer` is structurally assignable, so production `fetch` provides it. */
27
+ arrayBuffer?(): Promise<ArrayBuffer>;
28
+ /** The response body as text — read only by the binary self-update (#358), to download the
29
+ * `SHA256SUMS` checksum file. Optional for the same reason as `arrayBuffer`. */
30
+ text?(): Promise<string>;
22
31
  }
23
32
  /** The subset of `fetch` the CLI uses. `body` carries a JSON request payload (the device-code
24
33
  * POSTs); it's optional so a bodyless GET (`whoami --check`) is unaffected. Real `fetch`'s return
@@ -29,6 +38,84 @@ export type FetchLike = (url: string, init: {
29
38
  body?: string;
30
39
  signal?: AbortSignal;
31
40
  }) => Promise<HttpResponse>;
41
+ /** How the running CLI was installed (drives `oh-hai upgrade`, §4 / #343). `dev` is a source or
42
+ * local-dist checkout; `binary` is a compiled standalone; `npm-global` is a global npm install;
43
+ * `unknown` is the safe fallback (generic guidance, no self-mutation). */
44
+ export type InstallMethod = "npm-global" | "binary" | "dev" | "unknown";
45
+ /** The facts `classifyInstall` (update-check.ts) reduces to an `InstallMethod`. Injected via
46
+ * `RuntimeDeps.installFacts` so the classification stays a pure, unit-tested function and the CLI
47
+ * never touches the real process/module layout in a test. */
48
+ export interface InstallFacts {
49
+ /** `process.execPath` — the executable running the CLI: `node`/`bun` when an interpreter runs a
50
+ * built script, or the compiled standalone binary itself (which is what `binary` self-update
51
+ * replaces). Its basename is the signal that distinguishes the two (see `classifyInstall`). */
52
+ execPath: string;
53
+ /** The real (symlink-resolved) on-disk path of the CLI module — npm installs the bin as a symlink. */
54
+ modulePath: string;
55
+ }
56
+ /** Host + filesystem seam for `oh-hai upgrade`'s in-place binary self-update (§4 / #358). The
57
+ * command downloads the release asset via the existing `fetchImpl`; THIS seam owns the host
58
+ * identity (`platform`/`arch` → asset name) and the stage→verify-run→atomic-swap that
59
+ * `packaging/install.sh` performs in shell. Injected so the binary case is unit-testable with zero
60
+ * disk, network, or subprocess — a test passes a fake that records `stage`/`finalize` and returns a
61
+ * canned `verifyRuns`. Production (cli.ts) wires it over `node:fs`/`node:child_process` against
62
+ * `realpath(process.execPath)` (the running standalone). The current binary path is INTERNAL to the
63
+ * production impl — the orchestrator never learns where the binary lives, only that it can stage,
64
+ * verify, finalize, and clean up. */
65
+ export interface BinaryUpdateIo {
66
+ /** `process.platform` — mapped to the asset OS token (`win32`→`windows`, else as-is). */
67
+ platform: NodeJS.Platform;
68
+ /** `process.arch` — mapped to the asset arch token (`x64`/`arm64`; anything else is unsupported). */
69
+ arch: string;
70
+ /** Write `bytes` as an executable file (mode 0o755) BESIDE the running binary — same filesystem, so
71
+ * the later `finalize` rename is atomic — and return the staged path. Throws on a write failure. */
72
+ stage(bytes: Uint8Array): string;
73
+ /** Run `<stagedPath> --version`; true iff it exits 0. Guards against an authentic-but-unrunnable
74
+ * binary replacing a working install (e.g. a glibc build on musl) — the checksum proves the bytes
75
+ * are genuine, not that they execute here. Only ever called AFTER checksum verification. */
76
+ verifyRuns(stagedPath: string): boolean;
77
+ /** Atomically replace the running binary with the staged file (a same-dir rename on POSIX; on
78
+ * Windows the live `.exe` is locked, so rename the current binary aside then move the staged one
79
+ * into place). Throws on failure, leaving the current install untouched. */
80
+ finalize(stagedPath: string): void;
81
+ /** Best-effort delete of the staged file on an aborted update (checksum mismatch, unrunnable
82
+ * binary, or a finalize failure). Never throws. */
83
+ cleanup(stagedPath: string): void;
84
+ }
85
+ /** The captured result of a subprocess run (mirrors auth/keychain.ts's ProcResult). `spawnError` is
86
+ * set (e.g. `"ENOENT"`) when the binary is missing; `code` is null on a spawn failure. */
87
+ export interface CommandResult {
88
+ code: number | null;
89
+ stdout: string;
90
+ stderr: string;
91
+ spawnError?: string;
92
+ }
93
+ /** The result of inspecting a teach target. `absent` = no such file (safe to create); `file` = a regular
94
+ * file we read (safe to update); `unsafe` = it exists but must NOT be read or written through — a symlink
95
+ * (writing would follow it OUTSIDE the repo), a non-regular file (FIFO/device/dir — reading could hang),
96
+ * or an existing file that failed to read for a non-ENOENT reason (treating it as absent would let a later
97
+ * write TRUNCATE it). The three states are distinguished so teach never conflates "unreadable" with
98
+ * "missing" — the difference is the line between a safe create and silent data loss. */
99
+ export type TeachRead = {
100
+ kind: "absent";
101
+ } | {
102
+ kind: "file";
103
+ content: string;
104
+ } | {
105
+ kind: "unsafe";
106
+ reason: string;
107
+ };
108
+ /** Filesystem seam for `oh-hai teach` (§4.9) — the only command that writes arbitrary files (the
109
+ * agent-instruction files it manages). Injected so the teach tests run against an in-memory tree with
110
+ * zero disk access, mirroring `ConfigFileIo` (config-file.ts). `read` classifies a target (see
111
+ * `TeachRead`); `writeFile` creates any missing parent directories. `cwd`/`homedir` are the fixed anchors
112
+ * targets are joined onto — the project root (cwd) and the user's home (`--global`). */
113
+ export interface TeachFs {
114
+ read(path: string): TeachRead;
115
+ writeFile(path: string, content: string): void;
116
+ cwd: string;
117
+ homedir: string;
118
+ }
32
119
  /** Side-effecting dependencies, injected so command tests stay hermetic. */
33
120
  export interface RuntimeDeps {
34
121
  /** Open the credential store (selects keychain vs `0600` file fallback). */
@@ -43,6 +130,15 @@ export interface RuntimeDeps {
43
130
  fetchImpl: FetchLike;
44
131
  /** Load the user-global + per-project config files (§6). Injected so tests never read real disk. */
45
132
  loadConfigFiles: () => LoadedConfigFiles;
133
+ /** Load the user-side directory→agent bindings (bindings.ts, §6). The entrypoint feeds the result
134
+ * into `resolveConfig` for account resolution. Optional so an existing hermetic caller that never
135
+ * touches bindings (and doesn't set it) resolves as if there were none — the entrypoint treats an
136
+ * absent loader as an empty binding set. */
137
+ loadBindings?: () => Binding[];
138
+ /** Filesystem seam for `oh-hai use` — the command that READS/WRITES bindings and needs the process
139
+ * cwd (the directory it binds). Optional so no existing hermetic caller needs the field; production
140
+ * wires `nodeBindingsIo()`, and the `use` handler errors clearly if it's absent when `use` runs. */
141
+ bindingsIo?: BindingsIo;
46
142
  /** Sleep `ms` milliseconds between device-code polls (§4.3). Injected so tests don't actually
47
143
  * wait — a no-op keeps the happy path instant, and a recording sleep lets a test assert the
48
144
  * `slow_down` backoff grew the interval. */
@@ -64,6 +160,33 @@ export interface RuntimeDeps {
64
160
  * ever blocks on a prompt by accident. Distinct from `readLine`, which merely READS a line — its
65
161
  * presence says nothing about whether a human is there to answer. */
66
162
  isInteractive?: () => boolean;
163
+ /** Filesystem seam for `oh-hai teach` (§4.9). Optional so no existing hermetic caller needs the field;
164
+ * production wires `nodeTeachFs()` (teach.ts) and the handler throws a clear internal error if it's
165
+ * absent when teach actually runs. */
166
+ teachFs?: TeachFs;
167
+ /** Lock seam for the `inbox watch` concurrent-drain guard (#323). Optional so existing hermetic
168
+ * callers compile unchanged — when absent, the guard is skipped entirely (today's behavior).
169
+ * Production wires `nodeWatchLockIo()` (watch-lock.ts) so the guard is active in the shipped CLI. */
170
+ watchLockIo?: WatchLockIo;
171
+ /** How the CLI was installed, for `oh-hai upgrade` (#343). Optional so hermetic callers compile
172
+ * unchanged — when absent, `upgrade` classifies the install as `unknown` and gives generic
173
+ * guidance. Production wires the real process/module facts (cli.ts). */
174
+ installFacts?: InstallFacts;
175
+ /** Run a subprocess and capture its result, for `oh-hai upgrade` → `npm i -g @oh-hai/cli@latest`
176
+ * (#343). Optional so hermetic callers compile unchanged — when absent (or under a pipe / --json)
177
+ * `upgrade` PRINTS the command instead of spawning. Production wires a `spawn`-based runner (cli.ts). */
178
+ runCommand?: (file: string, args: string[]) => Promise<CommandResult>;
179
+ /** Whether stdout is a TTY, for `oh-hai upgrade`'s auto-run gate (#343/#349). `isInteractive` (stdin
180
+ * TTY) alone isn't enough: `oh-hai upgrade | tee log` has a TTY stdin but a piped stdout, and the
181
+ * spec promises a pipe PRINTS the command rather than mutating a global install. Optional — an
182
+ * omitting caller is treated as a TTY stdout (hermetic tests that only set isInteractive assume a
183
+ * terminal); production wires `process.stdout.isTTY`. */
184
+ isStdoutTty?: () => boolean;
185
+ /** Host + fs seam for `oh-hai upgrade`'s in-place binary self-update (#358). Optional so hermetic
186
+ * callers compile unchanged — when absent (or under `--json`), the `binary` case emits installer
187
+ * guidance instead of mutating (machine mode / an unwired caller must not swap the binary).
188
+ * Production wires the real download-and-replace over node fs/child_process (cli.ts). */
189
+ binaryUpdateIo?: BinaryUpdateIo;
67
190
  }
68
191
  export interface CommandContext {
69
192
  io: Io;
@@ -17,11 +17,13 @@ import { resolveIdentity } from "../auth/resolve-token.js";
17
17
  import { buildOk, serializeEnvelope } from "../envelope.js";
18
18
  import { ExitCode } from "../exit-codes.js";
19
19
  import { sanitizeForTerminal } from "../terminal.js";
20
+ import { cliVersion } from "../version.js";
20
21
  import { flagOn, parseCommandArgs } from "./flags.js";
21
22
  import { buildNotify } from "./messaging/build.js";
22
23
  import { submitEnvelope } from "./messaging/http.js";
23
24
  import { resolveSubmitIdentity } from "./messaging/identity.js";
24
25
  import { resolveRunId } from "./messaging/shared.js";
26
+ import { checkForUpdate } from "./update-check.js";
25
27
  /** Default per-request bound for the network probes when no --timeout/MA2H_TIMEOUT_MS is set —
26
28
  * §9 promises offline is an explicit state, never a silent hang. Mirrors whoami's checkToken. */
27
29
  const DEFAULT_PROBE_TIMEOUT_MS = 10_000;
@@ -96,6 +98,24 @@ async function testNotifyCheck(ctx) {
96
98
  return { name: "test-notify", status: "warn", detail: `could not send test notify — ${message}` };
97
99
  }
98
100
  }
101
+ /** Advisory "update available" check (§4.6, #343). Best-effort and network-based: a `warn` when a
102
+ * newer @oh-hai/cli is published, a `pass` when current. Returns `undefined` on ANY registry
103
+ * failure so doctor omits the line entirely — connectivity already reports offline, so a second
104
+ * network warn would be noise — and the exit code is never touched (advisory carries no exitCode). */
105
+ async function updateCheck(ctx) {
106
+ const current = cliVersion();
107
+ const result = await checkForUpdate(current, ctx.runtime.fetchImpl, probeTimeoutMs(ctx));
108
+ if (result.error !== undefined)
109
+ return undefined;
110
+ if (result.updateAvailable) {
111
+ return {
112
+ name: "update",
113
+ status: "warn",
114
+ detail: `update available: v${current} → v${result.latest ?? "?"} — run \`oh-hai upgrade\``,
115
+ };
116
+ }
117
+ return { name: "update", status: "pass", detail: `up to date (v${current})` };
118
+ }
99
119
  /** Emit the collected checks: the uniform --json envelope (data.checks, exactly name/status/detail),
100
120
  * or a human table with a per-status marker. Details are sanitized on BOTH paths (§5.4): some carry
101
121
  * Hub-supplied strings (a device-code-login discovered agent id, a --test-notify ack id), and
@@ -222,9 +242,16 @@ export async function doctorCommand(ctx) {
222
242
  : "OS keychain unavailable — the 0600 file fallback would be used (§5.2)";
223
243
  checks.push({ name: "keychain", status: "warn", detail });
224
244
  }
225
- // 4. Hub connectivity (ADVISORY).
226
- checks.push(await probeConnectivity(ctx, token));
227
- // 5. test-notify (ADVISORY, opt-in).
245
+ // 4-5. Hub connectivity + the update check (both ADVISORY, and hitting independent hosts — the Hub
246
+ // vs registry.npmjs.org) run concurrently, so a slow registry never serializes behind a slow
247
+ // Hub on every doctor run. Order is preserved by pushing connectivity first; the update line is
248
+ // omitted entirely on any registry failure (#343).
249
+ const [connectivity, update] = await Promise.all([probeConnectivity(ctx, token), updateCheck(ctx)]);
250
+ checks.push(connectivity);
251
+ if (update !== undefined) {
252
+ checks.push(update);
253
+ }
254
+ // 6. test-notify (ADVISORY, opt-in).
228
255
  if (testNotify) {
229
256
  checks.push(await testNotifyCheck(ctx));
230
257
  }
@@ -1 +1 @@
1
- {"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"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"}
@@ -10,8 +10,20 @@ export declare const GLOBAL_OPTIONS: OptionsConfig;
10
10
  * are on; `--json=false` / `0` / `no` are off. A repeated flag becomes an array — last wins.
11
11
  */
12
12
  export declare function flagOn(value: unknown): boolean;
13
- /** Parse argv for a command with no subcommand operand (the auth commands). */
14
- export declare function parseCommandArgs(argv: string[], extra?: OptionsConfig): ReturnType<typeof parseArgs>;
13
+ /**
14
+ * Like `flagOn`, but REJECTS an unrecognized value (usage error) instead of silently treating it as
15
+ * off. For a SAFETY flag where a typo'd value must not fall through to the unsafe path — e.g.
16
+ * `oh-hai upgrade --check=treu` must fail loudly, not run the live `npm i -g` (#349 review). Bare
17
+ * presence and clean truthy values are on; explicit `false`/`0`/`no`/`off` are off; anything else
18
+ * throws. The raw value is not echoed (it's user argv, and the message stays control-char-free).
19
+ */
20
+ export declare function flagOnStrict(value: unknown, flag: string): boolean;
21
+ /** Parse argv for a command with no subcommand operand (the auth commands). `maxOperands` (default 0)
22
+ * allows a command to accept that many positional operands BEYOND the command token — `oh-hai use
23
+ * <agent>` passes 1 so a single free-form operand is accepted while a second stray one still errors. */
24
+ export declare function parseCommandArgs(argv: string[], extra?: OptionsConfig, opts?: {
25
+ maxOperands?: number;
26
+ }): ReturnType<typeof parseArgs>;
15
27
  /** Parse argv for a command that takes a SUBCOMMAND operand (`ask`/`task` → submit | await).
16
28
  * Like `parseCommandArgs` (global + `extra` options, `strict:false` so the `--flag=value`
17
29
  * boolean form isn't rejected, then reject any unknown flag), but `positionals[0]` is the
@@ -36,6 +36,30 @@ export function flagOn(value) {
36
36
  }
37
37
  return false;
38
38
  }
39
+ /**
40
+ * Like `flagOn`, but REJECTS an unrecognized value (usage error) instead of silently treating it as
41
+ * off. For a SAFETY flag where a typo'd value must not fall through to the unsafe path — e.g.
42
+ * `oh-hai upgrade --check=treu` must fail loudly, not run the live `npm i -g` (#349 review). Bare
43
+ * presence and clean truthy values are on; explicit `false`/`0`/`no`/`off` are off; anything else
44
+ * throws. The raw value is not echoed (it's user argv, and the message stays control-char-free).
45
+ */
46
+ export function flagOnStrict(value, flag) {
47
+ if (value === true) {
48
+ return true;
49
+ }
50
+ if (typeof value === "string") {
51
+ const v = value.trim().toLowerCase();
52
+ if (v === "" || v === "true" || v === "1" || v === "yes" || v === "on")
53
+ return true;
54
+ if (v === "false" || v === "0" || v === "no" || v === "off")
55
+ return false;
56
+ throw new CliError("usage", `--${flag} is a boolean flag: pass it bare or as --${flag}=false (unrecognized value).`);
57
+ }
58
+ if (Array.isArray(value)) {
59
+ return value.length > 0 && flagOnStrict(value[value.length - 1], flag);
60
+ }
61
+ return false;
62
+ }
39
63
  /** Parse argv against the global flags plus the command's own `extra` options with `strict: false`
40
64
  * (so the `--flag=value` boolean form the CLI supports isn't rejected), then reject any flag NOT
41
65
  * in the known set — so a typo like `oh-hai whoami --chek` is a usage error (exit 2), not silently
@@ -67,13 +91,17 @@ function parseKnownFlags(argv, extra) {
67
91
  }
68
92
  return parsed;
69
93
  }
70
- /** Parse argv for a command with no subcommand operand (the auth commands). */
71
- export function parseCommandArgs(argv, extra = {}) {
94
+ /** Parse argv for a command with no subcommand operand (the auth commands). `maxOperands` (default 0)
95
+ * allows a command to accept that many positional operands BEYOND the command token — `oh-hai use
96
+ * <agent>` passes 1 so a single free-form operand is accepted while a second stray one still errors. */
97
+ export function parseCommandArgs(argv, extra = {}, opts = {}) {
72
98
  const parsed = parseKnownFlags(argv, extra);
73
- // positionals[0] is the command token; these auth commands take no positional operands, so a
74
- // stray one (`oh-hai logout all`, `oh-hai whoami check`) is a typo, not a silently-ignored arg.
75
- if (parsed.positionals.length > 1) {
76
- throw new CliError("usage", `unexpected argument: ${parsed.positionals[1]}. Run 'oh-hai <command> --help'.`);
99
+ // positionals[0] is the command token; most auth commands take no operands (maxOperands 0), so a
100
+ // stray one (`oh-hai logout all`, `oh-hai whoami check`) is a typo, not a silently-ignored arg. A
101
+ // command that DOES take an operand (`use <agent>`) raises the ceiling.
102
+ const ceiling = 1 + (opts.maxOperands ?? 0);
103
+ if (parsed.positionals.length > ceiling) {
104
+ throw new CliError("usage", `unexpected argument: ${parsed.positionals[ceiling]}. Run 'oh-hai <command> --help'.`);
77
105
  }
78
106
  return parsed;
79
107
  }
@@ -1 +1 @@
1
- {"version":3,"file":"flags.js","sourceRoot":"","sources":["../../src/commands/flags.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"flags.js","sourceRoot":"","sources":["../../src/commands/flags.ts"],"names":[],"mappings":"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"}
@@ -12,17 +12,23 @@ import { logoutCommand } from "./logout.js";
12
12
  import { notifyCommand } from "./notify.js";
13
13
  import { setupCommand } from "./setup.js";
14
14
  import { taskCommand } from "./task.js";
15
+ import { teachCommand } from "./teach.js";
16
+ import { upgradeCommand } from "./upgrade.js";
17
+ import { useCommand } from "./use.js";
15
18
  import { whoamiCommand } from "./whoami.js";
16
19
  export const COMMAND_HANDLERS = {
17
20
  setup: setupCommand,
18
21
  login: loginCommand,
19
22
  logout: logoutCommand,
20
23
  whoami: whoamiCommand,
24
+ use: useCommand,
21
25
  notify: notifyCommand,
22
26
  ask: askCommand,
23
27
  task: taskCommand,
24
28
  agents: agentsCommand,
25
29
  inbox: inboxCommand,
26
30
  doctor: doctorCommand,
31
+ teach: teachCommand,
32
+ upgrade: upgradeCommand,
27
33
  };
28
34
  //# sourceMappingURL=handlers.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"handlers.js","sourceRoot":"","sources":["../../src/commands/handlers.ts"],"names":[],"mappings":"AAAA,gGAAgG;AAChG,4FAA4F;AAC5F,2FAA2F;AAC3F,uFAAuF;AACvF,2FAA2F;AAE3F,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAEtC,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,MAAM,CAAC,MAAM,gBAAgB,GAAmC;IAC9D,KAAK,EAAE,YAAY;IACnB,KAAK,EAAE,YAAY;IACnB,MAAM,EAAE,aAAa;IACrB,MAAM,EAAE,aAAa;IACrB,MAAM,EAAE,aAAa;IACrB,GAAG,EAAE,UAAU;IACf,IAAI,EAAE,WAAW;IACjB,MAAM,EAAE,aAAa;IACrB,KAAK,EAAE,YAAY;IACnB,MAAM,EAAE,aAAa;CACtB,CAAC"}
1
+ {"version":3,"file":"handlers.js","sourceRoot":"","sources":["../../src/commands/handlers.ts"],"names":[],"mappings":"AAAA,gGAAgG;AAChG,4FAA4F;AAC5F,2FAA2F;AAC3F,uFAAuF;AACvF,2FAA2F;AAE3F,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAEtC,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AACtC,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,MAAM,CAAC,MAAM,gBAAgB,GAAmC;IAC9D,KAAK,EAAE,YAAY;IACnB,KAAK,EAAE,YAAY;IACnB,MAAM,EAAE,aAAa;IACrB,MAAM,EAAE,aAAa;IACrB,GAAG,EAAE,UAAU;IACf,MAAM,EAAE,aAAa;IACrB,GAAG,EAAE,UAAU;IACf,IAAI,EAAE,WAAW;IACjB,MAAM,EAAE,aAAa;IACrB,KAAK,EAAE,YAAY;IACnB,MAAM,EAAE,aAAa;IACrB,KAAK,EAAE,YAAY;IACnB,OAAO,EAAE,cAAc;CACxB,CAAC"}
@@ -12,20 +12,27 @@
12
12
  // loop's. The signed delivery `{ directive, signature }` is forwarded VERBATIM so a runtime that
13
13
  // wants defense-in-depth can verify it against the Hub key. In-session `id` dedup (§13.4) IS handled
14
14
  // here (see the loop). Sibling subcommands (`inbox list` / `inbox ack`) and long-poll are out of scope.
15
- import { buildOk, serializeEnvelope } from "../envelope.js";
15
+ import { originOf } from "../auth/resolve-token.js";
16
+ import { CliError, buildOk, serializeEnvelope } from "../envelope.js";
17
+ import { acquireWatchLock } from "../watch-lock.js";
16
18
  import { parseSubcommandArgs } from "./flags.js";
17
19
  import { ackInbox, drainInbox, redactToken } from "./messaging/http.js";
18
- import { requireToken } from "./messaging/identity.js";
20
+ import { requireWatchIdentity } from "./messaging/identity.js";
19
21
  import { positiveInt, sanitizeForTerminal, strictBoolFlag, stringFlag } from "./messaging/shared.js";
20
22
  const SUBCOMMANDS = ["watch"];
21
23
  const INBOX_WATCH_OPTIONS = {
22
24
  interval: { type: "string" },
23
25
  max: { type: "string" },
24
26
  once: { type: "boolean" },
27
+ force: { type: "boolean" },
25
28
  };
26
29
  /** Default poll cadence (ms). Well under the Hub's 90s presence-freshness window so the agent stays
27
30
  * `online` between polls, and responsive to arriving directives without hammering the Hub. */
28
31
  const DEFAULT_INTERVAL_MS = 5000;
32
+ /** Consecutive failed heartbeats before the guard warns (#323). Past a few misses the lock's heartbeat
33
+ * is frozen and a peer may reclaim it while this watcher keeps draining — a silent double-drain we
34
+ * surface once. Above 1 so a single transient write blip doesn't cry wolf. */
35
+ const HEARTBEAT_FAILURE_WARN_THRESHOLD = 3;
29
36
  /** A sane upper bound for `--max` (a drain batch size, not a timer): generous enough to never reject a
30
37
  * realistic mailbox drain, small enough to reject an absurd `?max=` — the Hub clamps to its own cap
31
38
  * regardless. Distinct from the timer ceiling `--interval` uses. */
@@ -42,9 +49,17 @@ async function inboxWatch(ctx, flags) {
42
49
  // Strict boolean: a mistyped `--once=treu` is a usage error, not a silent fall-through to the
43
50
  // infinite watch loop (it would keep draining + acking every interval instead of a one-shot).
44
51
  const once = strictBoolFlag(flags.once, "--once");
45
- // The mailbox is selected by the bearer alone (the Hub derives the agent from the token), so this
46
- // needs only a token — like `await`, and unlike a submit, it does not require a resolvable account.
47
- const token = await requireToken(ctx);
52
+ // `--force` takes over a lock held by another watcher for this identity (see the guard below).
53
+ const force = strictBoolFlag(flags.force, "--force");
54
+ // The mailbox is selected by the bearer (the Hub derives the agent from the token), so the drain
55
+ // needs only a token — like `await`. But the concurrent-drain guard keys on the AGENT ID (the mailbox
56
+ // is per-agent, not per-token: one agent can hold several bearers), so resolve both here.
57
+ const { token, agentId } = await requireWatchIdentity(ctx);
58
+ // Concurrent-drain guard (#323): `inbox watch` is a DESTRUCTIVE drain+ack, so two watchers sharing
59
+ // one identity race to consume the same directive. Acquire an agent-scoped lock before the loop,
60
+ // refuse a live second watcher (unless --force), and release on exit. Skipped when no lock seam is
61
+ // wired (hermetic callers) — production always wires one, so the guard is active in the shipped CLI.
62
+ const lock = acquireWatchGuard(ctx, agentId, intervalMs, force);
48
63
  // Cross-poll dedup (§13.4): once a directive id has been emitted AND its batch acked, a later
49
64
  // redelivery of that same id (fresh t/jti, after a crash or the server's visibility timeout) is
50
65
  // suppressed from re-emission — but still acked so the Hub consumes it. `seen` commits only AFTER a
@@ -54,36 +69,117 @@ async function inboxWatch(ctx, flags) {
54
69
  // set (dp-007), the runtime's concern, not this loop's. A long-lived watch grows `seen` with
55
70
  // directive volume; acceptable for v1.
56
71
  const seen = new Set();
57
- for (;;) {
58
- const { directives } = await drainInbox(ctx, token, max);
59
- // The unique ids in THIS drain — both the ack list and the within-batch emit guard (a Hub could
60
- // repeat an id inside one batch; emit it once and ack it once, not twice).
61
- const batchIds = new Set();
62
- for (const delivery of directives) {
63
- const id = delivery.directive.id;
64
- if (!seen.has(id) && !batchIds.has(id))
65
- emitDirective(ctx, delivery, token);
66
- batchIds.add(id);
72
+ // Track sustained heartbeat failure so a frozen-heartbeat watcher warns instead of silently letting a
73
+ // peer reclaim its identity mid-drain (#323). Reset on any successful heartbeat.
74
+ let heartbeatFailures = 0;
75
+ let heartbeatWarned = false;
76
+ try {
77
+ for (;;) {
78
+ // Refresh the heartbeat each poll so a live watcher isn't seen as stale, while a crashed one
79
+ // ages out and becomes reclaimable. Best-effort (a failed write never crashes the loop) but a
80
+ // SUSTAINED failure means our heartbeat is frozen; warn once so the operator knows the guard may
81
+ // have silently degraded and a second watcher could now be draining too.
82
+ if (lock !== undefined && !lock.heartbeat()) {
83
+ heartbeatFailures += 1;
84
+ if (!heartbeatWarned && heartbeatFailures >= HEARTBEAT_FAILURE_WARN_THRESHOLD) {
85
+ heartbeatWarned = true;
86
+ ctx.io.err(`⚠️ oh-hai: the watch-lock heartbeat has failed ${heartbeatFailures}× in a row (state dir ` +
87
+ `unwritable?) — another watcher may reclaim this identity while this one keeps draining.`);
88
+ }
89
+ }
90
+ else {
91
+ heartbeatFailures = 0;
92
+ heartbeatWarned = false;
93
+ }
94
+ const { directives } = await drainInbox(ctx, token, max);
95
+ // The unique ids in THIS drain — both the ack list and the within-batch emit guard (a Hub could
96
+ // repeat an id inside one batch; emit it once and ack it once, not twice).
97
+ const batchIds = new Set();
98
+ for (const delivery of directives) {
99
+ const id = delivery.directive.id;
100
+ if (!seen.has(id) && !batchIds.has(id))
101
+ emitDirective(ctx, delivery, token);
102
+ batchIds.add(id);
103
+ }
104
+ if (batchIds.size > 0) {
105
+ // Flush stdout BEFORE acking so the emit-then-ack handoff is genuinely at-least-once: `io.log`
106
+ // only queues to stdout, so when `watch` is piped to a runtime under backpressure the bytes may
107
+ // still be buffered in this process. Acking first would let the Hub consume a directive that a
108
+ // crash/EPIPE could still lose before it reaches the consumer. `flushOutput` waits for the
109
+ // queued writes to complete (a write-callback barrier, not just the drain flag); THEN ack.
110
+ await ctx.runtime.flushOutput?.();
111
+ // The single batched consume-ack (§14) — one call per drained batch, deduped ids.
112
+ await ackInbox(ctx, token, [...batchIds]);
113
+ // Commit to the cross-poll dedup set only now that the batch is durably consumed.
114
+ for (const id of batchIds)
115
+ seen.add(id);
116
+ }
117
+ // --once: a single drain→emit→ack pass (cron / scripting / deterministic tests). Otherwise loop;
118
+ // the poll itself IS the presence heartbeat, so keep polling on the interval even on an empty drain.
119
+ if (once)
120
+ return;
121
+ await ctx.runtime.sleep(intervalMs);
67
122
  }
68
- if (batchIds.size > 0) {
69
- // Flush stdout BEFORE acking so the emit-then-ack handoff is genuinely at-least-once: `io.log`
70
- // only queues to stdout, so when `watch` is piped to a runtime under backpressure the bytes may
71
- // still be buffered in this process. Acking first would let the Hub consume a directive that a
72
- // crash/EPIPE could still lose before it reaches the consumer. `flushOutput` waits for the
73
- // queued writes to complete (a write-callback barrier, not just the drain flag); THEN ack.
74
- await ctx.runtime.flushOutput?.();
75
- // The single batched consume-ack (§14) — one call per drained batch, deduped ids.
76
- await ackInbox(ctx, token, [...batchIds]);
77
- // Commit to the cross-poll dedup set only now that the batch is durably consumed.
78
- for (const id of batchIds)
79
- seen.add(id);
80
- }
81
- // --once: a single drain→emit→ack pass (cron / scripting / deterministic tests). Otherwise loop;
82
- // the poll itself IS the presence heartbeat, so keep polling on the interval even on an empty drain.
83
- if (once)
84
- return;
85
- await ctx.runtime.sleep(intervalMs);
86
123
  }
124
+ finally {
125
+ // Release on every exit path (--once return, a thrown drain/ack error, the loop's stop). Deletes
126
+ // the lockfile only if it is still ours, so a reclaimer's lock is never removed. An abrupt SIGKILL
127
+ // skips this, but the dead pid then makes the lock reclaimable — the stale mechanism is the net.
128
+ lock?.release();
129
+ }
130
+ }
131
+ /**
132
+ * Acquire the per-agent watch lock (#323) and translate the outcome into user-facing behavior:
133
+ * refuse a live second watcher (`already_running`, exit 10), a `--force` steal warning, a
134
+ * stale-reclaim note, or an "unavailable" warning when no lock dir resolves — or when the agent id
135
+ * can't be resolved (the guard keys on the agent id, never the bearer). Returns the live handle to
136
+ * heartbeat/release, or undefined when there is no handle to manage (guard unavailable, or no lock
137
+ * seam wired — the hermetic-test path). All human notes go to stderr so the `--json` NDJSON stdout
138
+ * stream stays clean.
139
+ */
140
+ function acquireWatchGuard(ctx, agentId, intervalMs, force) {
141
+ const io = ctx.runtime.watchLockIo;
142
+ if (io === undefined)
143
+ return undefined; // no seam wired → guard skipped (existing behavior)
144
+ if (agentId === undefined) {
145
+ // The mailbox is per-agent, so without a resolved agent id there's no correct key (the bearer is
146
+ // wrong — one agent can hold several). Degrade honestly rather than key on the token.
147
+ ctx.io.err("⚠️ oh-hai: concurrent-watch protection is unavailable (no resolvable agent id — set --account / " +
148
+ "MA2H_AGENT_ID, or log in). Proceeding without a lock — do not start a second watcher for this identity.");
149
+ return undefined;
150
+ }
151
+ const origin = originOf(ctx.config.baseUrl);
152
+ const outcome = acquireWatchLock(io, { origin, agentId, intervalMs, force });
153
+ if (outcome.kind === "held") {
154
+ const h = outcome.holder;
155
+ // `already_running` (exit 10), NOT `conflict`/exit 8: this is a LOCAL precondition conflict with no
156
+ // Hub call, distinct from a Hub 409, so a caller branching on the exit code never treats "stop the
157
+ // other watcher" (non-retryable) as a retryable idempotency conflict. See docs/specs/cli.md §7/§8.
158
+ // `holder` is undefined when a peer just won the create race and its record isn't readable yet.
159
+ const who = h !== undefined ? `(pid ${h.pid} on ${h.hostname}, watching ${h.origin} since ${new Date(h.startedAt).toISOString()})` : "(it just started; its lock is being written)";
160
+ throw new CliError("already_running", `another 'oh-hai inbox watch' is already draining this identity ${who}. Two watchers on one ` +
161
+ `identity race to drain the same mailbox — a directive delivered once is consumed by whichever ` +
162
+ `drains first, so the other agent can silently lose it. Stop the other watcher, or re-run with ` +
163
+ `--force to take over.`);
164
+ }
165
+ if (outcome.kind === "unavailable") {
166
+ ctx.io.err(`⚠️ oh-hai: concurrent-watch protection is unavailable (${outcome.reason}). Proceeding without a ` +
167
+ `lock — do not start a second 'oh-hai inbox watch' for this identity.`);
168
+ return undefined;
169
+ }
170
+ if (outcome.reclaimed?.wasLive) {
171
+ // Only reachable under --force: we stole a lock that still looked live.
172
+ const h = outcome.reclaimed.holder;
173
+ ctx.io.err(`⚠️ oh-hai: --force took over the watch lock held by pid ${h.pid} on ${h.hostname} (held since ` +
174
+ `${new Date(h.startedAt).toISOString()}). If that watcher is still running you now BOTH drain ` +
175
+ `this mailbox and will race for directives.`);
176
+ }
177
+ else if (outcome.reclaimed) {
178
+ const h = outcome.reclaimed.holder;
179
+ ctx.io.err(`oh-hai: reclaimed a stale watch lock from pid ${h.pid} on ${h.hostname} (last active ` +
180
+ `${new Date(h.heartbeatAt).toISOString()}).`);
181
+ }
182
+ return outcome.lock;
87
183
  }
88
184
  /** Hand one directive to the agent runtime. stdout is the directive STREAM.
89
185
  *