@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.
- package/README.md +51 -8
- package/dist/auth/resolve-token.d.ts +2 -4
- package/dist/auth/resolve-token.js +4 -11
- package/dist/auth/resolve-token.js.map +1 -1
- package/dist/bindings.d.ts +65 -0
- package/dist/bindings.js +242 -0
- package/dist/bindings.js.map +1 -0
- package/dist/cli.js +171 -2
- package/dist/cli.js.map +1 -1
- package/dist/commands/ask.js +21 -17
- package/dist/commands/ask.js.map +1 -1
- package/dist/commands/binary-update.d.ts +61 -0
- package/dist/commands/binary-update.js +160 -0
- package/dist/commands/binary-update.js.map +1 -0
- package/dist/commands/context.d.ts +123 -0
- package/dist/commands/doctor.js +30 -3
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/flags.d.ts +14 -2
- package/dist/commands/flags.js +34 -6
- package/dist/commands/flags.js.map +1 -1
- package/dist/commands/handlers.js +6 -0
- package/dist/commands/handlers.js.map +1 -1
- package/dist/commands/inbox.js +129 -33
- package/dist/commands/inbox.js.map +1 -1
- package/dist/commands/messaging/identity.d.ts +9 -0
- package/dist/commands/messaging/identity.js +21 -0
- package/dist/commands/messaging/identity.js.map +1 -1
- package/dist/commands/messaging/validate.d.ts +6 -5
- package/dist/commands/messaging/validate.js +6 -5
- package/dist/commands/messaging/validate.js.map +1 -1
- package/dist/commands/registry.js +71 -8
- package/dist/commands/registry.js.map +1 -1
- package/dist/commands/setup.d.ts +17 -0
- package/dist/commands/setup.js +103 -3
- package/dist/commands/setup.js.map +1 -1
- package/dist/commands/task.js +21 -24
- package/dist/commands/task.js.map +1 -1
- package/dist/commands/teach.d.ts +13 -0
- package/dist/commands/teach.js +317 -0
- package/dist/commands/teach.js.map +1 -0
- package/dist/commands/update-check.d.ts +49 -0
- package/dist/commands/update-check.js +203 -0
- package/dist/commands/update-check.js.map +1 -0
- package/dist/commands/upgrade.d.ts +2 -0
- package/dist/commands/upgrade.js +178 -0
- package/dist/commands/upgrade.js.map +1 -0
- package/dist/commands/use.d.ts +2 -0
- package/dist/commands/use.js +99 -0
- package/dist/commands/use.js.map +1 -0
- package/dist/commands/whoami.js +113 -27
- package/dist/commands/whoami.js.map +1 -1
- package/dist/config-file.js +4 -20
- package/dist/config-file.js.map +1 -1
- package/dist/config.d.ts +14 -0
- package/dist/config.js +39 -3
- package/dist/config.js.map +1 -1
- package/dist/exit-codes.d.ts +6 -0
- package/dist/exit-codes.js +6 -0
- package/dist/exit-codes.js.map +1 -1
- package/dist/help.js +4 -1
- package/dist/help.js.map +1 -1
- package/dist/os.d.ts +16 -0
- package/dist/os.js +32 -0
- package/dist/os.js.map +1 -0
- package/dist/url.d.ts +7 -0
- package/dist/url.js +14 -0
- package/dist/url.js.map +1 -1
- package/dist/version.d.ts +11 -3
- package/dist/version.js +20 -3
- package/dist/version.js.map +1 -1
- package/dist/watch-lock.d.ts +85 -0
- package/dist/watch-lock.js +358 -0
- package/dist/watch-lock.js.map +1 -0
- 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
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
-
|
|
85
|
-
|
|
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
|
-
|
|
88
|
-
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
export
|
|
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":"AAAA,+FAA+F;AAC/F,2FAA2F;AAC3F,8FAA8F;AAC9F,+EAA+E;AAC/E,EAAE;AACF,8EAA8E;AAC9E,yFAAyF;AACzF,kBAAkB;AAClB,EAAE;AACF,8FAA8F;AAC9F,+FAA+F;AAC/F,+BAA+B;AAC/B,EAAE;AACF,0FAA0F;AAC1F,0FAA0F;AAC1F,wFAAwF;AACxF,6FAA6F;
|
|
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;
|
package/dist/bindings.js
ADDED
|
@@ -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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"}
|