@oh-hai/cli 0.1.0-beta.0 → 0.2.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 (83) 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 +211 -12
  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 +135 -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 +8 -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/login.d.ts +13 -0
  26. package/dist/commands/login.js +35 -27
  27. package/dist/commands/login.js.map +1 -1
  28. package/dist/commands/messaging/identity.d.ts +9 -0
  29. package/dist/commands/messaging/identity.js +21 -0
  30. package/dist/commands/messaging/identity.js.map +1 -1
  31. package/dist/commands/messaging/validate.d.ts +6 -5
  32. package/dist/commands/messaging/validate.js +7 -16
  33. package/dist/commands/messaging/validate.js.map +1 -1
  34. package/dist/commands/registry.js +88 -8
  35. package/dist/commands/registry.js.map +1 -1
  36. package/dist/commands/setup.d.ts +26 -0
  37. package/dist/commands/setup.js +221 -0
  38. package/dist/commands/setup.js.map +1 -0
  39. package/dist/commands/task.js +21 -24
  40. package/dist/commands/task.js.map +1 -1
  41. package/dist/commands/teach.d.ts +13 -0
  42. package/dist/commands/teach.js +317 -0
  43. package/dist/commands/teach.js.map +1 -0
  44. package/dist/commands/update-check.d.ts +49 -0
  45. package/dist/commands/update-check.js +203 -0
  46. package/dist/commands/update-check.js.map +1 -0
  47. package/dist/commands/upgrade.d.ts +2 -0
  48. package/dist/commands/upgrade.js +178 -0
  49. package/dist/commands/upgrade.js.map +1 -0
  50. package/dist/commands/use.d.ts +2 -0
  51. package/dist/commands/use.js +99 -0
  52. package/dist/commands/use.js.map +1 -0
  53. package/dist/commands/whoami.js +113 -27
  54. package/dist/commands/whoami.js.map +1 -1
  55. package/dist/config-file.js +4 -20
  56. package/dist/config-file.js.map +1 -1
  57. package/dist/config.d.ts +15 -1
  58. package/dist/config.js +43 -4
  59. package/dist/config.js.map +1 -1
  60. package/dist/exit-codes.d.ts +6 -0
  61. package/dist/exit-codes.js +6 -0
  62. package/dist/exit-codes.js.map +1 -1
  63. package/dist/help.js +5 -1
  64. package/dist/help.js.map +1 -1
  65. package/dist/onboarding/open-url.d.ts +26 -0
  66. package/dist/onboarding/open-url.js +69 -0
  67. package/dist/onboarding/open-url.js.map +1 -0
  68. package/dist/onboarding/status.d.ts +24 -0
  69. package/dist/onboarding/status.js +53 -0
  70. package/dist/onboarding/status.js.map +1 -0
  71. package/dist/os.d.ts +16 -0
  72. package/dist/os.js +32 -0
  73. package/dist/os.js.map +1 -0
  74. package/dist/url.d.ts +8 -0
  75. package/dist/url.js +29 -0
  76. package/dist/url.js.map +1 -0
  77. package/dist/version.d.ts +11 -3
  78. package/dist/version.js +20 -3
  79. package/dist/version.js.map +1 -1
  80. package/dist/watch-lock.d.ts +85 -0
  81. package/dist/watch-lock.js +358 -0
  82. package/dist/watch-lock.js.map +1 -0
  83. package/package.json +2 -2
package/dist/config.d.ts CHANGED
@@ -1,6 +1,11 @@
1
- export declare const DEFAULT_BASE_URL = "http://localhost:8787";
1
+ import { type Binding } from "./bindings.js";
2
+ export declare const DEFAULT_BASE_URL = "https://inbox.ohhai.app";
2
3
  export type OutputFormat = "human" | "json";
3
4
  export type TokenSource = "env" | "keychain" | "file" | "none";
5
+ /** Where the resolved `account` came from (§6 precedence), for `whoami` to explain the resolution.
6
+ * `"binding"` is a user-side directory→agent binding (bindings.ts); `"none"` means nothing was
7
+ * configured — a downstream sole-identity DISCOVERY (resolveIdentity) may still find one. */
8
+ export type AccountSource = "flag" | "env" | "binding" | "user-config" | "none";
4
9
  /** Global flags after parsing (the subset that feeds config resolution). */
5
10
  export interface RawFlags {
6
11
  baseUrl?: string | undefined;
@@ -45,12 +50,21 @@ export interface ResolvedConfig {
45
50
  color: boolean;
46
51
  token: string | undefined;
47
52
  tokenSource: TokenSource;
53
+ /** How `account` resolved (§6), for `whoami`. Optional so a hand-built config (tests) may omit it. */
54
+ accountSource?: AccountSource;
55
+ /** The bound directory when `accountSource === "binding"`; omitted otherwise. */
56
+ bindingPath?: string | undefined;
48
57
  }
49
58
  export interface ResolveInput {
50
59
  flags?: RawFlags | undefined;
51
60
  env?: EnvVars | undefined;
52
61
  userConfig?: UserConfig | undefined;
53
62
  projectConfig?: ProjectConfig | undefined;
63
+ /** User-side directory→agent bindings (already loaded from the user dir; bindings.ts). Consulted for
64
+ * the account precedence only — a repo never reaches this, preserving credential-safety (§6). */
65
+ bindings?: Binding[] | undefined;
66
+ /** The process working directory, for walk-up binding resolution. Omitted → bindings are ignored. */
67
+ cwd?: string | undefined;
54
68
  }
55
69
  export interface ResolveResult {
56
70
  config: ResolvedConfig;
package/dist/config.js CHANGED
@@ -11,7 +11,12 @@
11
11
  //
12
12
  // #105 implements the resolution CONTRACT over an already-parsed, in-memory config object.
13
13
  // The TOML file loader + per-project discovery + keychain read land in #106/#107.
14
- export const DEFAULT_BASE_URL = "http://localhost:8787";
14
+ import { resolveBinding } from "./bindings.js";
15
+ import { originOf } from "./url.js";
16
+ // The production Hub — a shipped CLI must reach the real service with zero configuration (#271, R8).
17
+ // Local development points at a local hub by setting MA2H_BASE_URL (or `--base-url`), which outranks
18
+ // this default (§6 precedence).
19
+ export const DEFAULT_BASE_URL = "https://inbox.ohhai.app";
15
20
  /** A string is "present" only when non-empty after trimming; the trimmed value is returned
16
21
  * so stray whitespace never rides along into a base URL / account / token. */
17
22
  function presentString(value) {
@@ -63,8 +68,42 @@ export function resolveConfig(input = {}) {
63
68
  // empty, so fall back to the default rather than yield an unusable "".
64
69
  const resolvedBase = (firstPresent(flags.baseUrl, env.MA2H_BASE_URL, userConfig.base_url) ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
65
70
  const baseUrl = resolvedBase === "" ? DEFAULT_BASE_URL : resolvedBase;
66
- // account: flag > env > USER config only. Project config excluded on purpose.
67
- const account = firstPresent(flags.account, env.MA2H_AGENT_ID, userConfig.default_account);
71
+ // account: flag > env > directory binding > USER config only (§6). Project config excluded on purpose.
72
+ // The directory binding (bindings.ts) is user-side + origin-scoped, so it can supply the account for
73
+ // the current directory tree WITHOUT a repo ever influencing identity — it sits BELOW an explicit
74
+ // --account / MA2H_AGENT_ID (which still win) and ABOVE the user-global default. `accountSource`
75
+ // records which layer won, for `whoami` to explain the resolution.
76
+ const origin = originOf(baseUrl);
77
+ const binding = input.bindings !== undefined && input.cwd !== undefined
78
+ ? resolveBinding(input.bindings, input.cwd, origin)
79
+ : undefined;
80
+ const flagAccount = presentString(flags.account);
81
+ const envAccount = presentString(env.MA2H_AGENT_ID);
82
+ const userAccount = presentString(userConfig.default_account);
83
+ let account;
84
+ let accountSource;
85
+ let bindingPath = undefined;
86
+ if (flagAccount !== undefined) {
87
+ account = flagAccount;
88
+ accountSource = "flag";
89
+ }
90
+ else if (envAccount !== undefined) {
91
+ account = envAccount;
92
+ accountSource = "env";
93
+ }
94
+ else if (binding !== undefined) {
95
+ account = binding.account;
96
+ accountSource = "binding";
97
+ bindingPath = binding.path;
98
+ }
99
+ else if (userAccount !== undefined) {
100
+ account = userAccount;
101
+ accountSource = "user-config";
102
+ }
103
+ else {
104
+ account = undefined;
105
+ accountSource = "none";
106
+ }
68
107
  // output: an explicit --json flag wins EITHER way (--json → json, --json=false → human, upholding
69
108
  // flag > config precedence); only when the flag is absent does the config `output` decide; else human.
70
109
  const output = flags.json === true
@@ -90,7 +129,7 @@ export function resolveConfig(input = {}) {
90
129
  const envToken = presentString(env.MA2H_AGENT_TOKEN);
91
130
  const tokenSource = envToken !== undefined ? "env" : "none";
92
131
  return {
93
- config: { baseUrl, account, output, timeoutMs, color, token: envToken, tokenSource },
132
+ config: { baseUrl, account, output, timeoutMs, color, token: envToken, tokenSource, accountSource, bindingPath },
94
133
  warnings,
95
134
  };
96
135
  }
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
@@ -19,6 +19,11 @@ export declare const ExitCode: {
19
19
  readonly CONFLICT: 8;
20
20
  /** 9 — Invalid request (Hub 400/422/413; version not supported). */
21
21
  readonly INVALID_REQUEST: 9;
22
+ /** 10 — Already running: a LOCAL precondition conflict, no Hub call — currently only a second
23
+ * `inbox watch` on an identity another live watcher already holds (#323). Distinct from `8`
24
+ * (a Hub 409) so a caller branching on the exit code never mistakes a non-retryable "stop the
25
+ * other watcher" for a retryable idempotency conflict. */
26
+ readonly ALREADY_RUNNING: 10;
22
27
  };
23
28
  export type ExitCode = (typeof ExitCode)[keyof typeof ExitCode];
24
29
  /**
@@ -39,6 +44,7 @@ declare const ERROR_CODE_TO_EXIT: {
39
44
  readonly validation_error: 9;
40
45
  readonly payload_too_large: 9;
41
46
  readonly version_not_supported: 9;
47
+ readonly already_running: 10;
42
48
  readonly not_implemented: 1;
43
49
  };
44
50
  export type ErrorCode = keyof typeof ERROR_CODE_TO_EXIT;
@@ -22,6 +22,11 @@ export const ExitCode = {
22
22
  CONFLICT: 8,
23
23
  /** 9 — Invalid request (Hub 400/422/413; version not supported). */
24
24
  INVALID_REQUEST: 9,
25
+ /** 10 — Already running: a LOCAL precondition conflict, no Hub call — currently only a second
26
+ * `inbox watch` on an identity another live watcher already holds (#323). Distinct from `8`
27
+ * (a Hub 409) so a caller branching on the exit code never mistakes a non-retryable "stop the
28
+ * other watcher" for a retryable idempotency conflict. */
29
+ ALREADY_RUNNING: 10,
25
30
  };
26
31
  /**
27
32
  * Stable `error.code` strings (§8) → exit codes (§7). An exit code may cover more
@@ -41,6 +46,7 @@ const ERROR_CODE_TO_EXIT = {
41
46
  validation_error: ExitCode.INVALID_REQUEST,
42
47
  payload_too_large: ExitCode.INVALID_REQUEST,
43
48
  version_not_supported: ExitCode.INVALID_REQUEST,
49
+ already_running: ExitCode.ALREADY_RUNNING,
44
50
  not_implemented: ExitCode.ERROR,
45
51
  };
46
52
  /** Map a (possibly unknown) `error.code` string to its exit code; unknown → generic 1. */
@@ -1 +1 @@
1
- {"version":3,"file":"exit-codes.js","sourceRoot":"","sources":["../src/exit-codes.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,mFAAmF;AACnF,gEAAgE;AAEhE,MAAM,CAAC,MAAM,QAAQ,GAAG;IACtB,mBAAmB;IACnB,OAAO,EAAE,CAAC;IACV,sCAAsC;IACtC,KAAK,EAAE,CAAC;IACR,mEAAmE;IACnE,KAAK,EAAE,CAAC;IACR,mEAAmE;IACnE,IAAI,EAAE,CAAC;IACP,sDAAsD;IACtD,SAAS,EAAE,CAAC;IACZ,oEAAoE;IACpE,OAAO,EAAE,CAAC;IACV,kCAAkC;IAClC,MAAM,EAAE,CAAC;IACT,qEAAqE;IACrE,OAAO,EAAE,CAAC;IACV,4EAA4E;IAC5E,QAAQ,EAAE,CAAC;IACX,oEAAoE;IACpE,eAAe,EAAE,CAAC;CACV,CAAC;AAIX;;;;GAIG;AACH,MAAM,kBAAkB,GAAG;IACzB,KAAK,EAAE,QAAQ,CAAC,KAAK;IACrB,KAAK,EAAE,QAAQ,CAAC,KAAK;IACrB,IAAI,EAAE,QAAQ,CAAC,IAAI;IACnB,SAAS,EAAE,QAAQ,CAAC,SAAS;IAC7B,OAAO,EAAE,QAAQ,CAAC,OAAO;IACzB,MAAM,EAAE,QAAQ,CAAC,MAAM;IACvB,OAAO,EAAE,QAAQ,CAAC,OAAO;IACzB,QAAQ,EAAE,QAAQ,CAAC,QAAQ;IAC3B,WAAW,EAAE,QAAQ,CAAC,eAAe;IACrC,gBAAgB,EAAE,QAAQ,CAAC,eAAe;IAC1C,iBAAiB,EAAE,QAAQ,CAAC,eAAe;IAC3C,qBAAqB,EAAE,QAAQ,CAAC,eAAe;IAC/C,eAAe,EAAE,QAAQ,CAAC,KAAK;CACvB,CAAC;AAIX,0FAA0F;AAC1F,MAAM,UAAU,gBAAgB,CAAC,IAAY;IAC3C,MAAM,KAAK,GAA6B,kBAAkB,CAAC;IAC3D,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,QAAQ,CAAC,KAAK,CAAC;AACvC,CAAC;AAED;;oGAEoG;AACpG,MAAM,UAAU,WAAW,CAAC,IAAY;IACtC,OAAO,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,CAAC,kBAAkB,EAAE,IAAI,CAAC,CAAC;AACxE,CAAC"}
1
+ {"version":3,"file":"exit-codes.js","sourceRoot":"","sources":["../src/exit-codes.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,mFAAmF;AACnF,gEAAgE;AAEhE,MAAM,CAAC,MAAM,QAAQ,GAAG;IACtB,mBAAmB;IACnB,OAAO,EAAE,CAAC;IACV,sCAAsC;IACtC,KAAK,EAAE,CAAC;IACR,mEAAmE;IACnE,KAAK,EAAE,CAAC;IACR,mEAAmE;IACnE,IAAI,EAAE,CAAC;IACP,sDAAsD;IACtD,SAAS,EAAE,CAAC;IACZ,oEAAoE;IACpE,OAAO,EAAE,CAAC;IACV,kCAAkC;IAClC,MAAM,EAAE,CAAC;IACT,qEAAqE;IACrE,OAAO,EAAE,CAAC;IACV,4EAA4E;IAC5E,QAAQ,EAAE,CAAC;IACX,oEAAoE;IACpE,eAAe,EAAE,CAAC;IAClB;;;+DAG2D;IAC3D,eAAe,EAAE,EAAE;CACX,CAAC;AAIX;;;;GAIG;AACH,MAAM,kBAAkB,GAAG;IACzB,KAAK,EAAE,QAAQ,CAAC,KAAK;IACrB,KAAK,EAAE,QAAQ,CAAC,KAAK;IACrB,IAAI,EAAE,QAAQ,CAAC,IAAI;IACnB,SAAS,EAAE,QAAQ,CAAC,SAAS;IAC7B,OAAO,EAAE,QAAQ,CAAC,OAAO;IACzB,MAAM,EAAE,QAAQ,CAAC,MAAM;IACvB,OAAO,EAAE,QAAQ,CAAC,OAAO;IACzB,QAAQ,EAAE,QAAQ,CAAC,QAAQ;IAC3B,WAAW,EAAE,QAAQ,CAAC,eAAe;IACrC,gBAAgB,EAAE,QAAQ,CAAC,eAAe;IAC1C,iBAAiB,EAAE,QAAQ,CAAC,eAAe;IAC3C,qBAAqB,EAAE,QAAQ,CAAC,eAAe;IAC/C,eAAe,EAAE,QAAQ,CAAC,eAAe;IACzC,eAAe,EAAE,QAAQ,CAAC,KAAK;CACvB,CAAC;AAIX,0FAA0F;AAC1F,MAAM,UAAU,gBAAgB,CAAC,IAAY;IAC3C,MAAM,KAAK,GAA6B,kBAAkB,CAAC;IAC3D,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,QAAQ,CAAC,KAAK,CAAC;AACvC,CAAC;AAED;;oGAEoG;AACpG,MAAM,UAAU,WAAW,CAAC,IAAY;IACtC,OAAO,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,CAAC,kBAAkB,EAAE,IAAI,CAAC,CAAC;AACxE,CAAC"}
package/dist/help.js CHANGED
@@ -5,11 +5,15 @@ export const TOP_LEVEL_HELP = [
5
5
  "",
6
6
  "Usage: oh-hai [global flags] <command> [subcommand] [flags]",
7
7
  "",
8
- "Auth: login | logout | whoami",
8
+ "Setup: setup (bare `oh-hai` on a new machine onboards; re-run to add/re-auth)",
9
+ "Auth: login | logout | whoami | use",
10
+ " (use: bind this directory to an agent — per-project identity)",
9
11
  "Messaging: notify | ask | task",
10
12
  "Inbox: inbox watch",
11
13
  "Agents: agents list | create | revoke",
12
14
  "Health: doctor",
15
+ "Teach: teach (write the OH HAI snippet into this machine's agent-instruction files)",
16
+ "Update: upgrade (self-update, or --check for a newer oh-hai)",
13
17
  "",
14
18
  "Global flags: --base-url --account --json --quiet --verbose --no-color --timeout",
15
19
  "Run 'oh-hai <command> --help' for details.",
package/dist/help.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"help.js","sourceRoot":"","sources":["../src/help.ts"],"names":[],"mappings":"AAAA,sFAAsF;AACtF,qEAAqE;AAErE,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,8DAA8D;IAC9D,EAAE;IACF,6DAA6D;IAC7D,EAAE;IACF,qCAAqC;IACrC,iCAAiC;IACjC,yBAAyB;IACzB,2CAA2C;IAC3C,oBAAoB;IACpB,EAAE;IACF,kFAAkF;IAClF,4CAA4C;CAC7C,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC"}
1
+ {"version":3,"file":"help.js","sourceRoot":"","sources":["../src/help.ts"],"names":[],"mappings":"AAAA,sFAAsF;AACtF,qEAAqE;AAErE,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,8DAA8D;IAC9D,EAAE;IACF,6DAA6D;IAC7D,EAAE;IACF,sFAAsF;IACtF,2CAA2C;IAC3C,mFAAmF;IACnF,iCAAiC;IACjC,yBAAyB;IACzB,2CAA2C;IAC3C,oBAAoB;IACpB,4FAA4F;IAC5F,kEAAkE;IAClE,EAAE;IACF,kFAAkF;IAClF,4CAA4C;CAC7C,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC"}
@@ -0,0 +1,26 @@
1
+ /** The minimal child-process surface this util touches — `once('spawn'|'error')` to learn whether
2
+ * the opener launched, and `unref()` so it doesn't keep the event loop (or the CLI) alive. Node's
3
+ * real `ChildProcess` is structurally assignable, so the production `spawn` fits without a cast. */
4
+ export interface SpawnedOpener {
5
+ once(event: "spawn", listener: () => void): unknown;
6
+ once(event: "error", listener: (err: Error) => void): unknown;
7
+ unref(): void;
8
+ }
9
+ /** The `spawn` shape the util needs (file + argv + detached/ignore options). Injected so tests assert
10
+ * the exact opener + argv without launching anything. Node's `child_process.spawn` is assignable. */
11
+ export type SpawnLike = (file: string, args: string[], options: {
12
+ detached: boolean;
13
+ stdio: "ignore";
14
+ }) => SpawnedOpener;
15
+ export interface OpenUrlDeps {
16
+ /** Override the detected platform (tests exercise darwin / win32 / linux without three machines). */
17
+ platform?: NodeJS.Platform;
18
+ /** Inject the spawn implementation (tests). Defaults to the real `node:child_process` spawn. */
19
+ spawn?: SpawnLike;
20
+ }
21
+ /**
22
+ * Try to open `url` in the user's default browser. Resolves `true` when the opener process
23
+ * launched, `false` when it couldn't (non-http(s) URL, missing opener binary, spawn failure).
24
+ * Never throws — a `false` result is the caller's cue to print the URL + code instead.
25
+ */
26
+ export declare function openInBrowser(url: string, deps?: OpenUrlDeps): Promise<boolean>;
@@ -0,0 +1,69 @@
1
+ // Best-effort browser opener for the onboarding flow (docs/specs/cli.md §4; issue #271). The
2
+ // device-code login prompts a human to APPROVE in their browser; "platinum" onboarding opens that
3
+ // URL for them instead of making them copy it. This is deliberately fire-and-forget and NON-fatal:
4
+ //
5
+ // - It launches the platform's own opener via `node:child_process` (no runtime dependency, mirroring
6
+ // the keychain backend's shell-out): macOS `open`, Windows `cmd /c start`, else `xdg-open`.
7
+ // - It returns whether the opener process LAUNCHED — never whether the human finished anything.
8
+ // A missing opener (headless box, no `xdg-open`), an unknown platform, or a non-http(s) URL all
9
+ // resolve `false` so the caller falls back to PRINTING the URL + short code. It never throws.
10
+ // - The child is spawned DETACHED with `stdio: "ignore"` and `unref()`'d so the CLI can exit
11
+ // immediately after; we never wait on the browser or read its output.
12
+ //
13
+ // SECURITY: only http(s) URLs are ever handed to the opener. The verification URL is server-supplied
14
+ // (login.ts already validates the scheme), so this is defense-in-depth — a `file:`/`javascript:` URL
15
+ // must never reach a shell-less `open`/`xdg-open` as a thing to "open".
16
+ import { spawn } from "node:child_process";
17
+ import { isHttpUrl } from "../url.js";
18
+ /** The opener command + argv for a platform. On Windows we deliberately do NOT use `cmd /c start`:
19
+ * even a shell-less spawn there launches `cmd.exe`, whose OWN parser re-interprets `& | < > ^ ( )`
20
+ * before argv splitting — and the URL is Hub-supplied (`verification_uri[_complete]`), guarded only
21
+ * by a scheme check, so `https://h/?x=1&calc.exe` would let a hostile Hub run a local command during
22
+ * onboarding. `rundll32 url.dll,FileProtocolHandler <url>` hands the URL to the OS default-handler as
23
+ * a SINGLE argv element with no command interpreter in the path. `open`/`xdg-open` already take the
24
+ * URL as one argv element (no shell), so they're unaffected. */
25
+ function openerFor(platform, url) {
26
+ if (platform === "darwin")
27
+ return { file: "open", args: [url] };
28
+ if (platform === "win32")
29
+ return { file: "rundll32", args: ["url.dll,FileProtocolHandler", url] };
30
+ return { file: "xdg-open", args: [url] };
31
+ }
32
+ /**
33
+ * Try to open `url` in the user's default browser. Resolves `true` when the opener process
34
+ * launched, `false` when it couldn't (non-http(s) URL, missing opener binary, spawn failure).
35
+ * Never throws — a `false` result is the caller's cue to print the URL + code instead.
36
+ */
37
+ export function openInBrowser(url, deps = {}) {
38
+ if (!isHttpUrl(url))
39
+ return Promise.resolve(false);
40
+ const platform = deps.platform ?? process.platform;
41
+ const spawnImpl = deps.spawn ?? spawn;
42
+ const { file, args } = openerFor(platform, url);
43
+ return new Promise((resolve) => {
44
+ let settled = false;
45
+ const done = (value) => {
46
+ if (!settled) {
47
+ settled = true;
48
+ resolve(value);
49
+ }
50
+ };
51
+ try {
52
+ // Detached + ignored stdio so the opener outlives the CLI and never blocks on I/O.
53
+ const child = spawnImpl(file, args, { detached: true, stdio: "ignore" });
54
+ // `spawn` fires once the child process actually started; `error` fires on ENOENT (opener
55
+ // missing) or a spawn failure. Either resolves the promise exactly once.
56
+ child.once("spawn", () => {
57
+ child.unref();
58
+ done(true);
59
+ });
60
+ child.once("error", () => done(false));
61
+ }
62
+ catch {
63
+ // A synchronous throw from spawn (e.g. an invalid options combination) is still just "couldn't
64
+ // open" — degrade to the print fallback rather than failing onboarding.
65
+ done(false);
66
+ }
67
+ });
68
+ }
69
+ //# sourceMappingURL=open-url.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"open-url.js","sourceRoot":"","sources":["../../src/onboarding/open-url.ts"],"names":[],"mappings":"AAAA,6FAA6F;AAC7F,kGAAkG;AAClG,mGAAmG;AACnG,EAAE;AACF,uGAAuG;AACvG,gGAAgG;AAChG,kGAAkG;AAClG,oGAAoG;AACpG,kGAAkG;AAClG,+FAA+F;AAC/F,0EAA0E;AAC1E,EAAE;AACF,qGAAqG;AACrG,qGAAqG;AACrG,wEAAwE;AAExE,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAE3C,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AA0BtC;;;;;;iEAMiE;AACjE,SAAS,SAAS,CAAC,QAAyB,EAAE,GAAW;IACvD,IAAI,QAAQ,KAAK,QAAQ;QAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC;IAChE,IAAI,QAAQ,KAAK,OAAO;QAAE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,6BAA6B,EAAE,GAAG,CAAC,EAAE,CAAC;IAClG,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC;AAC3C,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,GAAW,EAAE,OAAoB,EAAE;IAC/D,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC;QAAE,OAAO,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IACnD,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,IAAI,OAAO,CAAC,QAAQ,CAAC;IACnD,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,IAAK,KAA8B,CAAC;IAChE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,SAAS,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;IAEhD,OAAO,IAAI,OAAO,CAAU,CAAC,OAAO,EAAE,EAAE;QACtC,IAAI,OAAO,GAAG,KAAK,CAAC;QACpB,MAAM,IAAI,GAAG,CAAC,KAAc,EAAQ,EAAE;YACpC,IAAI,CAAC,OAAO,EAAE,CAAC;gBACb,OAAO,GAAG,IAAI,CAAC;gBACf,OAAO,CAAC,KAAK,CAAC,CAAC;YACjB,CAAC;QACH,CAAC,CAAC;QACF,IAAI,CAAC;YACH,mFAAmF;YACnF,MAAM,KAAK,GAAG,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC;YACzE,yFAAyF;YACzF,yEAAyE;YACzE,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE;gBACvB,KAAK,CAAC,KAAK,EAAE,CAAC;gBACd,IAAI,CAAC,IAAI,CAAC,CAAC;YACb,CAAC,CAAC,CAAC;YACH,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;QACzC,CAAC;QAAC,MAAM,CAAC;YACP,+FAA+F;YAC/F,wEAAwE;YACxE,IAAI,CAAC,KAAK,CAAC,CAAC;QACd,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,24 @@
1
+ import type { ResolvedConfig } from "../config.js";
2
+ import type { CredentialStore } from "../auth/token-store.js";
3
+ export interface MachineStatus {
4
+ /** True when this machine already has a usable credential for the resolved Hub origin. */
5
+ setUp: boolean;
6
+ /** Where the credential came from: the env token, a stored identity, or nothing. */
7
+ source: "env" | "stored" | "none";
8
+ /** The agent id to display, when unambiguous — the env / configured account, or a sole stored
9
+ * identity. Undefined when there is no single one to name (no account on the env token, or multiple
10
+ * stored identities discovered). */
11
+ agentId: string | undefined;
12
+ /** How many resolvable stored identities exist for this origin (0 on the env / configured-account
13
+ * paths, which resolve a single specific credential rather than discovering). */
14
+ storedCount: number;
15
+ }
16
+ /**
17
+ * Resolve whether this machine is set up for the config's Hub, and how, WITHOUT revealing a token.
18
+ * `openStore` is a thunk so the store is opened only when a local lookup is actually needed — the env
19
+ * credential proves set-up with no keychain probe at all (important in CI / locked-keychain envs). The
20
+ * resolution precedence mirrors `resolveIdentity` (resolve-token.ts) exactly, so a machine that reads
21
+ * as "set up" here is one whose credential `notify`/`ask` will actually resolve:
22
+ * env token > the explicitly-configured account's token > the sole discoverable stored identity.
23
+ */
24
+ export declare function machineStatus(config: ResolvedConfig, openStore: () => Promise<CredentialStore>): Promise<MachineStatus>;
@@ -0,0 +1,53 @@
1
+ // "Is this machine set up?" — shared by `oh-hai setup` (#271) and the bare-`oh-hai` first-run
2
+ // dispatch (cli.ts). A machine is set up for a Hub when it already has a usable credential for that
3
+ // Hub's ORIGIN (§5.1 Hub-scoping): either the `MA2H_AGENT_TOKEN` env credential (the CI path) or at
4
+ // least one stored identity in the keychain/file backend whose bearer still resolves.
5
+ //
6
+ // This mirrors resolveIdentity()'s "resolvable stored identity" logic (resolve-token.ts): `list()`
7
+ // reads a keys-only index (redaction-safe — it can drift from the backend), so an identity counts
8
+ // only when its token actually resolves. We count resolvable identities to decide set-up + whether
9
+ // there's a single unambiguous one to name in the "connected as …" message.
10
+ import { originOf } from "../auth/resolve-token.js";
11
+ /**
12
+ * Resolve whether this machine is set up for the config's Hub, and how, WITHOUT revealing a token.
13
+ * `openStore` is a thunk so the store is opened only when a local lookup is actually needed — the env
14
+ * credential proves set-up with no keychain probe at all (important in CI / locked-keychain envs). The
15
+ * resolution precedence mirrors `resolveIdentity` (resolve-token.ts) exactly, so a machine that reads
16
+ * as "set up" here is one whose credential `notify`/`ask` will actually resolve:
17
+ * env token > the explicitly-configured account's token > the sole discoverable stored identity.
18
+ */
19
+ export async function machineStatus(config, openStore) {
20
+ // The env credential (CI path, §5.3) is authoritative and needs no store lookup. Its agent id may be
21
+ // absent (a token with no MA2H_AGENT_ID) — still "set up", just unnamed.
22
+ if (config.tokenSource === "env") {
23
+ return { setUp: true, source: "env", agentId: config.account, storedCount: 0 };
24
+ }
25
+ const origin = originOf(config.baseUrl);
26
+ const store = await openStore();
27
+ // An explicitly-configured account (--account / MA2H_AGENT_ID / default_account) wins: set-up means
28
+ // THAT account resolves, not merely that some other identity does — otherwise bare `oh-hai` would
29
+ // print help while `notify` (which looks up only the configured account) fails auth. Mirrors
30
+ // resolveIdentity's "configured account wins, no discovery" branch.
31
+ if (config.account !== undefined) {
32
+ const found = await store.getWithSource(origin, config.account);
33
+ return found !== undefined
34
+ ? { setUp: true, source: "stored", agentId: config.account, storedCount: 1 }
35
+ : { setUp: false, source: "none", agentId: undefined, storedCount: 0 };
36
+ }
37
+ // No configured account: discover the resolvable stored identities for this origin (a sole one is
38
+ // unambiguous; several stay ambiguous → no single agent to name, but still "set up").
39
+ const listed = (await store.list()).filter((identity) => identity.origin === origin);
40
+ let count = 0;
41
+ let soleAgent;
42
+ for (const identity of listed) {
43
+ const found = await store.getWithSource(origin, identity.agentId);
44
+ if (found !== undefined) {
45
+ count++;
46
+ soleAgent = identity.agentId; // meaningful only when count ends at 1
47
+ }
48
+ }
49
+ if (count === 0)
50
+ return { setUp: false, source: "none", agentId: undefined, storedCount: 0 };
51
+ return { setUp: true, source: "stored", agentId: count === 1 ? soleAgent : undefined, storedCount: count };
52
+ }
53
+ //# sourceMappingURL=status.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status.js","sourceRoot":"","sources":["../../src/onboarding/status.ts"],"names":[],"mappings":"AAAA,8FAA8F;AAC9F,oGAAoG;AACpG,oGAAoG;AACpG,sFAAsF;AACtF,EAAE;AACF,mGAAmG;AACnG,kGAAkG;AAClG,mGAAmG;AACnG,4EAA4E;AAI5E,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAgBpD;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,MAAsB,EACtB,SAAyC;IAEzC,qGAAqG;IACrG,yEAAyE;IACzE,IAAI,MAAM,CAAC,WAAW,KAAK,KAAK,EAAE,CAAC;QACjC,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,WAAW,EAAE,CAAC,EAAE,CAAC;IACjF,CAAC;IACD,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACxC,MAAM,KAAK,GAAG,MAAM,SAAS,EAAE,CAAC;IAEhC,oGAAoG;IACpG,kGAAkG;IAClG,6FAA6F;IAC7F,oEAAoE;IACpE,IAAI,MAAM,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;QACjC,MAAM,KAAK,GAAG,MAAM,KAAK,CAAC,aAAa,CAAC,MAAM,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;QAChE,OAAO,KAAK,KAAK,SAAS;YACxB,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,WAAW,EAAE,CAAC,EAAE;YAC5E,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,CAAC,EAAE,CAAC;IAC3E,CAAC;IAED,kGAAkG;IAClG,sFAAsF;IACtF,MAAM,MAAM,GAAG,CAAC,MAAM,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC;IACrF,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,IAAI,SAA6B,CAAC;IAClC,KAAK,MAAM,QAAQ,IAAI,MAAM,EAAE,CAAC;QAC9B,MAAM,KAAK,GAAG,MAAM,KAAK,CAAC,aAAa,CAAC,MAAM,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAC;QAClE,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACxB,KAAK,EAAE,CAAC;YACR,SAAS,GAAG,QAAQ,CAAC,OAAO,CAAC,CAAC,uCAAuC;QACvE,CAAC;IACH,CAAC;IACD,IAAI,KAAK,KAAK,CAAC;QAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,CAAC,EAAE,CAAC;IAC7F,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC;AAC7G,CAAC"}
package/dist/os.d.ts ADDED
@@ -0,0 +1,16 @@
1
+ /** Read an OS value that can throw (notably `process.cwd()` when the working dir was removed), degrading
2
+ * to "" so a caller never crashes on an OS value it may not even need. */
3
+ export declare function safeOsValue(read: () => string): string;
4
+ /**
5
+ * Resolve the absolute config-home directory (`$XDG_CONFIG_HOME`, else `~/.config`) with the
6
+ * credential-safety discipline the TRUSTED user-side files require: prefer an ABSOLUTE
7
+ * `$XDG_CONFIG_HOME`, else `~/.config` when the home dir is absolute, else undefined. A relative or
8
+ * blank XDG is IGNORED on purpose — a relative path would resolve against cwd and let a repo-committed
9
+ * file masquerade as user-global config (bypassing per-project rejection). This is the single source of
10
+ * truth for that invariant, shared by the user config loader (`config-file.ts`, `config.toml`) and the
11
+ * directory-binding store (`bindings.ts`, `bindings.json`).
12
+ */
13
+ export declare function resolveXdgConfigHome(env: {
14
+ homedir: string;
15
+ xdgConfigHome: string | undefined;
16
+ }): string | undefined;
package/dist/os.js ADDED
@@ -0,0 +1,32 @@
1
+ // Small OS-value helpers shared across the CLI. Kept out of any feature module so a generic guard has a
2
+ // neutral home (not `config-file.ts`, which owns TOML loading, nor a command module).
3
+ import { isAbsolute, join } from "node:path";
4
+ /** Read an OS value that can throw (notably `process.cwd()` when the working dir was removed), degrading
5
+ * to "" so a caller never crashes on an OS value it may not even need. */
6
+ export function safeOsValue(read) {
7
+ try {
8
+ return read();
9
+ }
10
+ catch {
11
+ return "";
12
+ }
13
+ }
14
+ /**
15
+ * Resolve the absolute config-home directory (`$XDG_CONFIG_HOME`, else `~/.config`) with the
16
+ * credential-safety discipline the TRUSTED user-side files require: prefer an ABSOLUTE
17
+ * `$XDG_CONFIG_HOME`, else `~/.config` when the home dir is absolute, else undefined. A relative or
18
+ * blank XDG is IGNORED on purpose — a relative path would resolve against cwd and let a repo-committed
19
+ * file masquerade as user-global config (bypassing per-project rejection). This is the single source of
20
+ * truth for that invariant, shared by the user config loader (`config-file.ts`, `config.toml`) and the
21
+ * directory-binding store (`bindings.ts`, `bindings.json`).
22
+ */
23
+ export function resolveXdgConfigHome(env) {
24
+ const xdg = env.xdgConfigHome?.trim();
25
+ const home = env.homedir.trim();
26
+ return xdg !== undefined && xdg !== "" && isAbsolute(xdg)
27
+ ? xdg
28
+ : home !== "" && isAbsolute(home)
29
+ ? join(home, ".config")
30
+ : undefined;
31
+ }
32
+ //# sourceMappingURL=os.js.map
package/dist/os.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"os.js","sourceRoot":"","sources":["../src/os.ts"],"names":[],"mappings":"AAAA,wGAAwG;AACxG,sFAAsF;AAEtF,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAE7C;2EAC2E;AAC3E,MAAM,UAAU,WAAW,CAAC,IAAkB;IAC5C,IAAI,CAAC;QACH,OAAO,IAAI,EAAE,CAAC;IAChB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,oBAAoB,CAAC,GAGpC;IACC,MAAM,GAAG,GAAG,GAAG,CAAC,aAAa,EAAE,IAAI,EAAE,CAAC;IACtC,MAAM,IAAI,GAAG,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;IAChC,OAAO,GAAG,KAAK,SAAS,IAAI,GAAG,KAAK,EAAE,IAAI,UAAU,CAAC,GAAG,CAAC;QACvD,CAAC,CAAC,GAAG;QACL,CAAC,CAAC,IAAI,KAAK,EAAE,IAAI,UAAU,CAAC,IAAI,CAAC;YAC/B,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,SAAS,CAAC;YACvB,CAAC,CAAC,SAAS,CAAC;AAClB,CAAC"}
package/dist/url.d.ts ADDED
@@ -0,0 +1,8 @@
1
+ export declare function isHttpUrl(value: string): boolean;
2
+ /** Derive the Hub origin (scheme+host+port) from a base URL, for Hub-scoped keying (cli spec §5.1) —
3
+ * the credential store key `<origin>|<agent id>` and the origin-scoped directory bindings. A single
4
+ * definition shared by the config layer (config.ts, binding-origin resolution) and the auth layer
5
+ * (auth/resolve-token.ts, store keying); housed here so neither layer depends on the other. Falls
6
+ * back to the raw string when the URL can't be parsed (resolveConfig guarantees a valid default, so
7
+ * this is belt-and-suspenders). */
8
+ export declare function originOf(baseUrl: string): string;
package/dist/url.js ADDED
@@ -0,0 +1,29 @@
1
+ // Shared http(s) URL scheme guard. A server-supplied URL is safe to hand to a browser opener, or to
2
+ // accept as a callback / verification URL, only when its scheme is http(s) — never file: / javascript:
3
+ // / data: etc. Consolidated here after the guard reached three copies (login.ts device-code flow,
4
+ // onboarding/open-url.ts, messaging/validate.ts); a scheme check is security-relevant, so it should
5
+ // have one definition and one rationale, not three that can drift apart.
6
+ export function isHttpUrl(value) {
7
+ try {
8
+ const { protocol } = new URL(value);
9
+ return protocol === "http:" || protocol === "https:";
10
+ }
11
+ catch {
12
+ return false;
13
+ }
14
+ }
15
+ /** Derive the Hub origin (scheme+host+port) from a base URL, for Hub-scoped keying (cli spec §5.1) —
16
+ * the credential store key `<origin>|<agent id>` and the origin-scoped directory bindings. A single
17
+ * definition shared by the config layer (config.ts, binding-origin resolution) and the auth layer
18
+ * (auth/resolve-token.ts, store keying); housed here so neither layer depends on the other. Falls
19
+ * back to the raw string when the URL can't be parsed (resolveConfig guarantees a valid default, so
20
+ * this is belt-and-suspenders). */
21
+ export function originOf(baseUrl) {
22
+ try {
23
+ return new URL(baseUrl).origin;
24
+ }
25
+ catch {
26
+ return baseUrl;
27
+ }
28
+ }
29
+ //# sourceMappingURL=url.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"url.js","sourceRoot":"","sources":["../src/url.ts"],"names":[],"mappings":"AAAA,oGAAoG;AACpG,uGAAuG;AACvG,kGAAkG;AAClG,oGAAoG;AACpG,yEAAyE;AACzE,MAAM,UAAU,SAAS,CAAC,KAAa;IACrC,IAAI,CAAC;QACH,MAAM,EAAE,QAAQ,EAAE,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC;QACpC,OAAO,QAAQ,KAAK,OAAO,IAAI,QAAQ,KAAK,QAAQ,CAAC;IACvD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;;;;oCAKoC;AACpC,MAAM,UAAU,QAAQ,CAAC,OAAe;IACtC,IAAI,CAAC;QACH,OAAO,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC;IACjC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,OAAO,CAAC;IACjB,CAAC;AACH,CAAC"}
package/dist/version.d.ts CHANGED
@@ -1,8 +1,16 @@
1
1
  /** The MA2H wire protocol version this CLI targets (docs/specs/cli.md §3). */
2
2
  export declare const MA2H_WIRE_VERSION = "v0.3";
3
3
  /**
4
- * The CLI's own version, read from package.json relative to this module. Works both when
5
- * run from `dist/version.js` and from `src/version.ts` via tsx — both sit one level under
6
- * the package root, so `../package.json` resolves to the manifest either way.
4
+ * The CLI's own version.
5
+ *
6
+ * A standalone binary built by `bun build --compile` (issue #344) has no package.json on the
7
+ * real filesystem to read, so the dist workflow inlines the version at compile time: it sets
8
+ * OH_HAI_BUILD_VERSION in the build env and passes `--env='OH_HAI_BUILD_*'`, which freezes this
9
+ * `process.env.OH_HAI_BUILD_VERSION` read into the bundle as a literal. We prefer that value **only
10
+ * when running as the compiled Bun executable** (`process.versions.bun` is set) — so a stray
11
+ * OH_HAI_BUILD_VERSION in the environment can NOT hijack `--version` on the npm/Node path, which
12
+ * stays exactly as before: fall back to reading the manifest relative to this module. That read
13
+ * works both from `dist/version.js` and `src/version.ts` via tsx — both sit one level under the
14
+ * package root, so `../package.json` resolves to the manifest either way.
7
15
  */
8
16
  export declare function cliVersion(): string;
package/dist/version.js CHANGED
@@ -5,11 +5,28 @@ import { fileURLToPath } from "node:url";
5
5
  /** The MA2H wire protocol version this CLI targets (docs/specs/cli.md §3). */
6
6
  export const MA2H_WIRE_VERSION = "v0.3";
7
7
  /**
8
- * The CLI's own version, read from package.json relative to this module. Works both when
9
- * run from `dist/version.js` and from `src/version.ts` via tsx — both sit one level under
10
- * the package root, so `../package.json` resolves to the manifest either way.
8
+ * The CLI's own version.
9
+ *
10
+ * A standalone binary built by `bun build --compile` (issue #344) has no package.json on the
11
+ * real filesystem to read, so the dist workflow inlines the version at compile time: it sets
12
+ * OH_HAI_BUILD_VERSION in the build env and passes `--env='OH_HAI_BUILD_*'`, which freezes this
13
+ * `process.env.OH_HAI_BUILD_VERSION` read into the bundle as a literal. We prefer that value **only
14
+ * when running as the compiled Bun executable** (`process.versions.bun` is set) — so a stray
15
+ * OH_HAI_BUILD_VERSION in the environment can NOT hijack `--version` on the npm/Node path, which
16
+ * stays exactly as before: fall back to reading the manifest relative to this module. That read
17
+ * works both from `dist/version.js` and `src/version.ts` via tsx — both sit one level under the
18
+ * package root, so `../package.json` resolves to the manifest either way.
11
19
  */
12
20
  export function cliVersion() {
21
+ // Build-time injected version — trusted ONLY under Bun (the compiled binary), never on the
22
+ // Node/npm path, so an ambient OH_HAI_BUILD_VERSION can't spoof the npm CLI's version. In the
23
+ // compiled binary `process.env.OH_HAI_BUILD_VERSION` is the frozen `--env` literal, not a live
24
+ // read. Empty string is not a valid version, so an unset/blank value falls through to the manifest.
25
+ const runningUnderBun = typeof process.versions.bun === "string";
26
+ const injected = process.env.OH_HAI_BUILD_VERSION;
27
+ if (runningUnderBun && typeof injected === "string" && injected.length > 0) {
28
+ return injected;
29
+ }
13
30
  try {
14
31
  const manifestUrl = new URL("../package.json", import.meta.url);
15
32
  const raw = readFileSync(fileURLToPath(manifestUrl), "utf8");
@@ -1 +1 @@
1
- {"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,oFAAoF;AACpF,4FAA4F;AAE5F,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,8EAA8E;AAC9E,MAAM,CAAC,MAAM,iBAAiB,GAAG,MAAM,CAAC;AAExC;;;;GAIG;AACH,MAAM,UAAU,UAAU;IACxB,IAAI,CAAC;QACH,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,iBAAiB,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAChE,MAAM,GAAG,GAAG,YAAY,CAAC,aAAa,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC,CAAC;QAC7D,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAA0B,CAAC;QACxD,OAAO,OAAO,MAAM,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC;IACvE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,OAAO,CAAC;IACjB,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,oFAAoF;AACpF,4FAA4F;AAE5F,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,8EAA8E;AAC9E,MAAM,CAAC,MAAM,iBAAiB,GAAG,MAAM,CAAC;AAExC;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,UAAU;IACxB,2FAA2F;IAC3F,8FAA8F;IAC9F,+FAA+F;IAC/F,oGAAoG;IACpG,MAAM,eAAe,GAAG,OAAQ,OAAO,CAAC,QAA6B,CAAC,GAAG,KAAK,QAAQ,CAAC;IACvF,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC;IAClD,IAAI,eAAe,IAAI,OAAO,QAAQ,KAAK,QAAQ,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC3E,OAAO,QAAQ,CAAC;IAClB,CAAC;IACD,IAAI,CAAC;QACH,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,iBAAiB,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAChE,MAAM,GAAG,GAAG,YAAY,CAAC,aAAa,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC,CAAC;QAC7D,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAA0B,CAAC;QACxD,OAAO,OAAO,MAAM,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC;IACvE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,OAAO,CAAC;IACjB,CAAC;AACH,CAAC"}