@oh-hai/cli 0.2.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 (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
package/README.md CHANGED
@@ -53,6 +53,24 @@ Bare `oh-hai login` (the browser **device-code** flow) is not available yet —
53
53
  server device-authorization grant + a web approval page (tracked in **#166**); until then it
54
54
  declines with a pointer to `--token-stdin`.
55
55
 
56
+ ### Per-project identity (`use`)
57
+
58
+ Running multiple agents on one machine? Bind a directory to a specific agent so sessions in
59
+ that tree target it automatically — no `--account` on every command. The **simple path stays
60
+ simple**: one agent, zero config, no `use` needed.
61
+
62
+ ```bash
63
+ oh-hai use my-project-agent # bind this directory (and its subtree) to my-project-agent
64
+ oh-hai whoami # → agent: my-project-agent · via: binding (<dir>)
65
+ oh-hai use --list # show all directory bindings
66
+ oh-hai use --unset # remove this directory's binding
67
+ ```
68
+
69
+ Bindings are **user-side** (`~/.config/oh-hai/bindings.json`), scoped per Hub, and **never**
70
+ settable from a repo — a cloned `.oh-hai/config.toml` can't re-route your identity
71
+ ([spec §6](../docs/specs/cli.md)). Precedence: `--account`/`MA2H_AGENT_ID` > directory
72
+ binding > user `default_account`. Offline — the agent need not be logged in yet.
73
+
56
74
  ### Messaging (`notify` / `ask` / `task`)
57
75
 
58
76
  The productized counterparts of the `@oh-hai/scripts` wrappers ([spec §4.4](../docs/specs/cli.md)):
@@ -77,23 +95,48 @@ oh-hai ask submit --envelope @captured.json # idempotent replay of a captured
77
95
 
78
96
  ## Install
79
97
 
80
- ```bash
81
- # npm (global)
82
- npm install -g @oh-hai/cli
98
+ > **New to OH HAI?** For a top-to-bottom walkthrough — install → onboard → your first
99
+ > `notify`/`ask`/`task` — see the [Connect your agent guide](../docs/guides/connect-your-agent.md).
100
+ > This README is the implementer-facing package reference.
83
101
 
84
- # Homebrew (tap)
85
- brew install autnmy/tap/oh-hai
102
+ The headline path is the standalone binary — no Node required. The installer detects your
103
+ OS + arch, downloads the matching binary from the ohhai.app distribution mirror, verifies
104
+ its SHA-256 checksum, and puts `oh-hai` on your `PATH`:
86
105
 
87
- # curl installer (served at ohhai.app once DNS lands — issue #99)
88
- curl -fsSL https://ohhai.app/install | sh
106
+ ```sh
107
+ # macOS / Linux
108
+ curl -fsSL https://ohhai.autonomy.com.ai/install | sh
109
+
110
+ # Windows (PowerShell)
111
+ irm https://ohhai.autonomy.com.ai/install.ps1 | iex
112
+ ```
113
+
114
+ > `ohhai.autonomy.com.ai` is the live Pages domain today; the same scripts are served at
115
+ > `https://ohhai.app/install.sh` and `https://ohhai.app/install.ps1` once the dist Worker
116
+ > is live.
117
+ >
118
+ > The repo is private, so binaries are served from the public **ohhai.app mirror**
119
+ > (`dist-worker/` + an R2 bucket CI fills on each release — issue #357). Until the
120
+ > coordinator provisions the bucket/Worker/secrets (see `dist-worker/README.md`), the
121
+ > binary download 404s and an unauthenticated install falls back to npm. Use the npm
122
+ > install below until then.
123
+
124
+ npm is the secondary channel for node devs (and the automatic fallback when a host has no
125
+ prebuilt binary, e.g. windows-arm64):
126
+
127
+ ```bash
128
+ npm install -g @oh-hai/cli
89
129
  ```
90
130
 
131
+ `OH_HAI_VERSION` pins a version on either channel. (Homebrew was dropped in favor of the
132
+ standalone binaries — epic #277.)
133
+
91
134
  ## Command surface (see [spec §4](../docs/specs/cli.md))
92
135
 
93
136
  ```
94
137
  oh-hai [global flags] <command> [subcommand] [flags]
95
138
 
96
- Auth login | logout | whoami
139
+ Auth login | logout | whoami | use
97
140
  Messaging notify | ask | task
98
141
  Agents agents list | create | revoke
99
142
  Health doctor
@@ -1,5 +1,7 @@
1
+ import { originOf } from "../url.js";
1
2
  import type { ResolvedConfig, TokenSource } from "../config.js";
2
3
  import type { CredentialStore } from "./token-store.js";
4
+ export { originOf };
3
5
  export interface ResolvedToken {
4
6
  token: string | undefined;
5
7
  source: TokenSource;
@@ -12,10 +14,6 @@ export interface ResolvedIdentity {
12
14
  token: string | undefined;
13
15
  source: TokenSource;
14
16
  }
15
- /** Derive the Hub origin (scheme+host+port) from a resolved base URL, for Hub-scoped keying
16
- * (§5.1). Falls back to the raw string if the URL can't be parsed (resolveConfig guarantees a
17
- * valid default, so this is belt-and-suspenders). */
18
- export declare function originOf(baseUrl: string): string;
19
17
  /** Resolve the effective bearer token and where it came from, per §6. `store` may be omitted
20
18
  * when the caller already knows resolution won't touch it (an env token, or no account). */
21
19
  export declare function resolveToken(config: ResolvedConfig, store?: CredentialStore): Promise<ResolvedToken>;
@@ -15,17 +15,10 @@
15
15
  // realizes §6's `keychain || file` as a real dual-read and reports which backend held the
16
16
  // token. The store is optional here so `whoami` can skip opening it (and probing the OS
17
17
  // keychain) when the CI env token is present — resolution short-circuits before it's needed.
18
- /** Derive the Hub origin (scheme+host+port) from a resolved base URL, for Hub-scoped keying
19
- * (§5.1). Falls back to the raw string if the URL can't be parsed (resolveConfig guarantees a
20
- * valid default, so 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
- }
18
+ import { originOf } from "../url.js";
19
+ // `originOf` now has a single home in url.ts (shared with the config layer's binding-origin
20
+ // resolution); re-exported here so the existing importers (`../auth/resolve-token.js`) are unaffected.
21
+ export { originOf };
29
22
  /** Resolve the effective bearer token and where it came from, per §6. `store` may be omitted
30
23
  * when the caller already knows resolution won't touch it (an env token, or no account). */
31
24
  export async function resolveToken(config, store) {
@@ -1 +1 @@
1
- {"version":3,"file":"resolve-token.js","sourceRoot":"","sources":["../../src/auth/resolve-token.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"resolve-token.js","sourceRoot":"","sources":["../../src/auth/resolve-token.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,65 @@
1
+ /** One directory→agent binding, scoped to a Hub origin. `path` is an absolute, normalized directory. */
2
+ export interface Binding {
3
+ path: string;
4
+ account: string;
5
+ origin: string;
6
+ }
7
+ /**
8
+ * Injectable filesystem/environment seam so the store is unit-testable against an in-memory map with
9
+ * zero disk access (mirrors `ConfigFileIo` in config-file.ts). `readFile` returns undefined for a
10
+ * missing/unreadable file; `writeFile` creates any missing parent directories.
11
+ */
12
+ export interface BindingsIo {
13
+ readFile(path: string): string | undefined;
14
+ writeFile(path: string, content: string): void;
15
+ homedir: string;
16
+ xdgConfigHome: string | undefined;
17
+ /** The process working directory — the directory `oh-hai use` binds, and the walk-up anchor for
18
+ * resolution. A single injected source (mirrors `ConfigFileIo.cwd`) so the command that WRITES a
19
+ * binding and the entrypoint that RESOLVES one agree on the same cwd. */
20
+ cwd: string;
21
+ }
22
+ /**
23
+ * Resolve the absolute path of the bindings file under the trusted config dir (`resolveXdgConfigHome`,
24
+ * shared with `loadUserConfig`). That helper enforces the credential-safety guarantee — a relative or
25
+ * blank `$XDG_CONFIG_HOME` must never let a cwd-relative file act as the trusted bindings store — and
26
+ * returns undefined when no absolute config dir exists (e.g. a minimal container with an empty homedir).
27
+ */
28
+ export declare function bindingsFilePath(io: BindingsIo): string | undefined;
29
+ /**
30
+ * Load and parse the bindings file. Undefined config dir, missing file, unparseable JSON, or a
31
+ * non-array `bindings` all degrade to `[]` — a broken bindings file never crashes the CLI. Malformed
32
+ * individual records are dropped; the rest are kept.
33
+ */
34
+ export declare function loadBindings(io: BindingsIo): Binding[];
35
+ /** Whether the binding store can be safely WRITTEN, for the mutating commands (`use <agent>` /
36
+ * `use --unset`) to check BEFORE reporting success:
37
+ * - `"unavailable"` — no trusted absolute config dir (bindingsFilePath undefined); a write would
38
+ * silently no-op, so a caller must NOT claim a persisted binding.
39
+ * - `"unsupported-version"` — the file was written by a NEWER CLI; `loadBindings` read-degrades it to
40
+ * [], but blindly writing a fresh v1 doc would ERASE the newer CLI's bindings — refuse instead.
41
+ * - `"ok"` — absent, malformed (safe to overwrite; loadBindings already degrades it to []), or a v1
42
+ * file. Reads never need this (loadBindings is always safe); only writes do. */
43
+ export declare function bindingStoreStatus(io: BindingsIo): "ok" | "unavailable" | "unsupported-version";
44
+ /**
45
+ * Resolve the binding that applies to `cwd` for `origin`, walking up (git-style): among bindings for
46
+ * this origin whose directory is `cwd` or an ancestor of it, the NEAREST (deepest — longest path) wins.
47
+ * Pure — no I/O. Returns undefined when `cwd` is non-absolute (nothing meaningful to walk) or no
48
+ * binding matches.
49
+ */
50
+ export declare function resolveBinding(bindings: Binding[], cwd: string, origin: string): {
51
+ account: string;
52
+ path: string;
53
+ } | undefined;
54
+ /**
55
+ * Bind `cwd` → `account` for `origin`, upserting by `(path, origin)` so re-binding the same directory
56
+ * for the same Hub replaces the account rather than duplicating. Returns the normalized bound path.
57
+ */
58
+ export declare function setBinding(io: BindingsIo, cwd: string, origin: string, account: string): string;
59
+ /**
60
+ * Remove the binding for `cwd` + `origin`. Idempotent — returns true when a binding was removed, false
61
+ * when there was none (so the caller can report "nothing to unbind" without erroring).
62
+ */
63
+ export declare function unsetBinding(io: BindingsIo, cwd: string, origin: string): boolean;
64
+ /** Production `BindingsIo` backed by the real filesystem, home dir, and environment. */
65
+ export declare function nodeBindingsIo(): BindingsIo;
@@ -0,0 +1,242 @@
1
+ // User-side directory→agent bindings (docs/specs/cli.md §6, #321 — part of #307). A binding maps a
2
+ // project DIRECTORY to the agent that sessions in that tree should act as, scoped to a Hub origin, so a
3
+ // session no longer has to thread `--account <id>` through every invocation. This is the foundational,
4
+ // purely-additive slice of per-project identity: with no binding, resolution behaves exactly as before.
5
+ //
6
+ // CREDENTIAL-SAFETY (§6). The binding is honored ONLY from the USER config dir (`~/.config/oh-hai/`,
7
+ // $XDG_CONFIG_HOME honored), never from a repo. A repo-controlled `.oh-hai/config.toml` is already
8
+ // forbidden from setting `account`; a directory binding cannot come from a repo either, because this
9
+ // loader reads a single user-global file under an ABSOLUTE config dir (the same discipline
10
+ // `loadUserConfig` uses in config-file.ts) — a relative/blank XDG that could resolve against cwd is
11
+ // rejected, so a checked-out file can never masquerade as the bindings store.
12
+ //
13
+ // Storage is `~/.config/oh-hai/bindings.json` — structured relational records don't fit the flat
14
+ // `key = value` TOML dialect, and JSON is zero-dep to read/write. A missing or malformed file degrades
15
+ // to "no bindings" (never throws), matching the config loader's tolerant posture.
16
+ import { mkdirSync, readFileSync, renameSync, statSync, writeFileSync } from "node:fs";
17
+ import { homedir } from "node:os";
18
+ import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
19
+ import { resolveXdgConfigHome, safeOsValue } from "./os.js";
20
+ /**
21
+ * Resolve the absolute path of the bindings file under the trusted config dir (`resolveXdgConfigHome`,
22
+ * shared with `loadUserConfig`). That helper enforces the credential-safety guarantee — a relative or
23
+ * blank `$XDG_CONFIG_HOME` must never let a cwd-relative file act as the trusted bindings store — and
24
+ * returns undefined when no absolute config dir exists (e.g. a minimal container with an empty homedir).
25
+ */
26
+ export function bindingsFilePath(io) {
27
+ const configHome = resolveXdgConfigHome(io);
28
+ return configHome === undefined ? undefined : join(configHome, "oh-hai", "bindings.json");
29
+ }
30
+ /** Coerce one parsed record into a `Binding`, or undefined when it's malformed. A binding is only
31
+ * kept when path/account/origin are all non-empty strings AND the path is absolute — a relative path
32
+ * would resolve against cwd during resolution and break the user-side isolation, so it's dropped. */
33
+ function asBinding(value) {
34
+ if (typeof value !== "object" || value === null) {
35
+ return undefined;
36
+ }
37
+ const record = value;
38
+ const path = record.path;
39
+ const account = record.account;
40
+ const origin = record.origin;
41
+ if (typeof path !== "string" ||
42
+ typeof account !== "string" ||
43
+ typeof origin !== "string" ||
44
+ path.trim() === "" ||
45
+ account.trim() === "" ||
46
+ origin.trim() === "" ||
47
+ !isAbsolute(path)) {
48
+ return undefined;
49
+ }
50
+ // Normalize the path so stored and resolution-time comparisons agree (collapse `.`/`..`, dedupe seps).
51
+ return { path: resolve(path), account, origin };
52
+ }
53
+ /**
54
+ * Load and parse the bindings file. Undefined config dir, missing file, unparseable JSON, or a
55
+ * non-array `bindings` all degrade to `[]` — a broken bindings file never crashes the CLI. Malformed
56
+ * individual records are dropped; the rest are kept.
57
+ */
58
+ export function loadBindings(io) {
59
+ const filePath = bindingsFilePath(io);
60
+ if (filePath === undefined) {
61
+ return [];
62
+ }
63
+ const text = io.readFile(filePath);
64
+ if (text === undefined) {
65
+ return [];
66
+ }
67
+ let parsed;
68
+ try {
69
+ parsed = JSON.parse(text);
70
+ }
71
+ catch {
72
+ return [];
73
+ }
74
+ if (typeof parsed !== "object" || parsed === null) {
75
+ return [];
76
+ }
77
+ // Version gate: this loader understands v1 (the only version written). A file stamped with a NEWER
78
+ // version is from a future CLI whose format we can't safely parse, so ignore it (degrade to []) rather
79
+ // than misread it. A MISSING version is tolerated as v1 — this CLI is the sole writer and always stamps
80
+ // v1, so an absent stamp means a hand-authored/legacy file, not a format we don't understand.
81
+ const version = parsed.version;
82
+ if (version !== undefined && version !== 1) {
83
+ return [];
84
+ }
85
+ const raw = parsed.bindings;
86
+ if (!Array.isArray(raw)) {
87
+ return [];
88
+ }
89
+ const out = [];
90
+ for (const record of raw) {
91
+ const binding = asBinding(record);
92
+ if (binding !== undefined) {
93
+ out.push(binding);
94
+ }
95
+ }
96
+ return out;
97
+ }
98
+ /** Whether the binding store can be safely WRITTEN, for the mutating commands (`use <agent>` /
99
+ * `use --unset`) to check BEFORE reporting success:
100
+ * - `"unavailable"` — no trusted absolute config dir (bindingsFilePath undefined); a write would
101
+ * silently no-op, so a caller must NOT claim a persisted binding.
102
+ * - `"unsupported-version"` — the file was written by a NEWER CLI; `loadBindings` read-degrades it to
103
+ * [], but blindly writing a fresh v1 doc would ERASE the newer CLI's bindings — refuse instead.
104
+ * - `"ok"` — absent, malformed (safe to overwrite; loadBindings already degrades it to []), or a v1
105
+ * file. Reads never need this (loadBindings is always safe); only writes do. */
106
+ export function bindingStoreStatus(io) {
107
+ const filePath = bindingsFilePath(io);
108
+ if (filePath === undefined) {
109
+ return "unavailable";
110
+ }
111
+ const text = io.readFile(filePath);
112
+ if (text === undefined) {
113
+ return "ok"; // absent → a fresh write is fine
114
+ }
115
+ try {
116
+ const parsed = JSON.parse(text);
117
+ if (typeof parsed === "object" && parsed !== null) {
118
+ const version = parsed.version;
119
+ if (version !== undefined && version !== 1) {
120
+ return "unsupported-version";
121
+ }
122
+ }
123
+ }
124
+ catch {
125
+ // Malformed JSON is not a newer-version file — loadBindings degrades it to [], so overwriting a
126
+ // corrupt store is acceptable (nothing recoverable is lost).
127
+ }
128
+ return "ok";
129
+ }
130
+ /** True when `ancestor` is `dir` itself or a parent directory of it — a segment-aware test (NOT a raw
131
+ * string prefix, so `/a/bc` is not treated as an ancestor of `/a/bcd`). Both inputs are assumed
132
+ * already absolute + normalized. */
133
+ function isAncestorOrEqual(ancestor, dir) {
134
+ const rel = relative(ancestor, dir);
135
+ // "" → same dir; a within-tree descendant → a relative path that neither ESCAPES the ancestor nor is
136
+ // absolute (a different Windows drive yields an absolute relative()). The escape test is separator-
137
+ // aware — exactly ".." or a ".." FIRST SEGMENT (".." + sep) — so a descendant directory whose own name
138
+ // merely starts with ".." (e.g. `/proj/..cache/build` → rel `..cache/build`) is NOT misread as an escape.
139
+ return rel === "" || (rel !== ".." && !rel.startsWith(`..${sep}`) && !isAbsolute(rel));
140
+ }
141
+ /** Depth of a normalized absolute path, as a segment count — the unambiguous "nearest ancestor" metric
142
+ * for the walk-up. (For two ancestors of the same directory this agrees with string length, since they
143
+ * are strictly nested; segment count states the intent directly and can't be second-guessed.) */
144
+ function pathDepth(path) {
145
+ return path.split(sep).filter((segment) => segment !== "").length;
146
+ }
147
+ /**
148
+ * Resolve the binding that applies to `cwd` for `origin`, walking up (git-style): among bindings for
149
+ * this origin whose directory is `cwd` or an ancestor of it, the NEAREST (deepest — longest path) wins.
150
+ * Pure — no I/O. Returns undefined when `cwd` is non-absolute (nothing meaningful to walk) or no
151
+ * binding matches.
152
+ */
153
+ export function resolveBinding(bindings, cwd, origin) {
154
+ if (!isAbsolute(cwd)) {
155
+ return undefined;
156
+ }
157
+ const here = resolve(cwd);
158
+ let best;
159
+ for (const binding of bindings) {
160
+ if (binding.origin !== origin) {
161
+ continue;
162
+ }
163
+ if (!isAncestorOrEqual(binding.path, here)) {
164
+ continue;
165
+ }
166
+ // Nearest ancestor = deepest (most path segments); all candidates are ancestors of `here`, so
167
+ // they're strictly nested and the deepest is the most specific binding.
168
+ if (best === undefined || pathDepth(binding.path) > pathDepth(best.path)) {
169
+ best = binding;
170
+ }
171
+ }
172
+ return best === undefined ? undefined : { account: best.account, path: best.path };
173
+ }
174
+ /** Persist the given bindings to the store (creates the config dir). No-op when no absolute config
175
+ * dir exists — writing to a cwd-relative path would defeat the user-side isolation. */
176
+ function writeBindings(io, bindings) {
177
+ const filePath = bindingsFilePath(io);
178
+ if (filePath === undefined) {
179
+ return;
180
+ }
181
+ const doc = { version: 1, bindings };
182
+ io.writeFile(filePath, `${JSON.stringify(doc, null, 2)}\n`);
183
+ }
184
+ /**
185
+ * Bind `cwd` → `account` for `origin`, upserting by `(path, origin)` so re-binding the same directory
186
+ * for the same Hub replaces the account rather than duplicating. Returns the normalized bound path.
187
+ */
188
+ export function setBinding(io, cwd, origin, account) {
189
+ const path = resolve(cwd);
190
+ const kept = loadBindings(io).filter((b) => !(b.path === path && b.origin === origin));
191
+ kept.push({ path, account, origin });
192
+ writeBindings(io, kept);
193
+ return path;
194
+ }
195
+ /**
196
+ * Remove the binding for `cwd` + `origin`. Idempotent — returns true when a binding was removed, false
197
+ * when there was none (so the caller can report "nothing to unbind" without erroring).
198
+ */
199
+ export function unsetBinding(io, cwd, origin) {
200
+ const path = resolve(cwd);
201
+ const all = loadBindings(io);
202
+ const kept = all.filter((b) => !(b.path === path && b.origin === origin));
203
+ if (kept.length === all.length) {
204
+ return false;
205
+ }
206
+ writeBindings(io, kept);
207
+ return true;
208
+ }
209
+ /** Generous ceiling on the bindings file — a real one is a handful of records; larger is a mistake or a
210
+ * hostile plant, so it's skipped rather than read into memory (mirrors config-file.ts). */
211
+ const MAX_BINDINGS_BYTES = 1024 * 1024;
212
+ /** Production `BindingsIo` backed by the real filesystem, home dir, and environment. */
213
+ export function nodeBindingsIo() {
214
+ return {
215
+ readFile: (path) => {
216
+ try {
217
+ const stats = statSync(path);
218
+ if (!stats.isFile() || stats.size > MAX_BINDINGS_BYTES) {
219
+ return undefined;
220
+ }
221
+ return readFileSync(path, "utf8");
222
+ }
223
+ catch {
224
+ return undefined; // missing / unreadable → "no bindings file here"
225
+ }
226
+ },
227
+ writeFile: (path, content) => {
228
+ mkdirSync(dirname(path), { recursive: true });
229
+ // Atomic write: stage to a sibling temp file, then rename(2) over the target. rename is atomic on
230
+ // the same filesystem, so a crash (or a concurrent reader) never observes a half-written or
231
+ // truncated bindings.json — the blast radius of a torn write would be the ENTIRE binding set. The
232
+ // temp lives in the same dir so the rename stays intra-filesystem.
233
+ const tmp = `${path}.${process.pid}.tmp`;
234
+ writeFileSync(tmp, content, "utf8");
235
+ renameSync(tmp, path);
236
+ },
237
+ homedir: safeOsValue(homedir),
238
+ xdgConfigHome: process.env.XDG_CONFIG_HOME,
239
+ cwd: safeOsValue(() => process.cwd()),
240
+ };
241
+ }
242
+ //# sourceMappingURL=bindings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bindings.js","sourceRoot":"","sources":["../src/bindings.ts"],"names":[],"mappings":"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"}