@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.
- 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 +211 -12
- 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 +135 -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 +8 -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/login.d.ts +13 -0
- package/dist/commands/login.js +35 -27
- package/dist/commands/login.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 +7 -16
- package/dist/commands/messaging/validate.js.map +1 -1
- package/dist/commands/registry.js +88 -8
- package/dist/commands/registry.js.map +1 -1
- package/dist/commands/setup.d.ts +26 -0
- package/dist/commands/setup.js +221 -0
- package/dist/commands/setup.js.map +1 -0
- 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 +15 -1
- package/dist/config.js +43 -4
- 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 +5 -1
- package/dist/help.js.map +1 -1
- package/dist/onboarding/open-url.d.ts +26 -0
- package/dist/onboarding/open-url.js +69 -0
- package/dist/onboarding/open-url.js.map +1 -0
- package/dist/onboarding/status.d.ts +24 -0
- package/dist/onboarding/status.js +53 -0
- package/dist/onboarding/status.js.map +1 -0
- 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 +8 -0
- package/dist/url.js +29 -0
- package/dist/url.js.map +1 -0
- 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
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
// `oh-hai upgrade` (docs/specs/cli.md §4 — self-update, #343, the "replaces brew upgrade" piece
|
|
2
|
+
// of #277). Checks the npm registry for a newer `@oh-hai/cli`, then acts by install method:
|
|
3
|
+
// • npm-global → run `npm i -g @oh-hai/cli@latest` (interactive human only; otherwise print it)
|
|
4
|
+
// • binary → guidance to re-run the installer (in-place binary re-pull lands with #344/#345)
|
|
5
|
+
// • dev/unknown → honest guidance (pull+rebuild, or a generic update hint)
|
|
6
|
+
// No fabricated success: a method that can't self-update yet PRINTS what to do, it never claims to
|
|
7
|
+
// have upgraded. `--check` reports availability without acting. `--json` emits the uniform envelope
|
|
8
|
+
// and never spawns a subprocess (machine mode must not mutate a global install).
|
|
9
|
+
import { CliError, buildOk, serializeEnvelope } from "../envelope.js";
|
|
10
|
+
import { sanitizeForTerminal } from "../terminal.js";
|
|
11
|
+
import { cliVersion } from "../version.js";
|
|
12
|
+
import { performBinaryUpdate } from "./binary-update.js";
|
|
13
|
+
import { flagOnStrict, parseCommandArgs } from "./flags.js";
|
|
14
|
+
import { PACKAGE_NAME, checkForUpdate, classifyInstall } from "./update-check.js";
|
|
15
|
+
/** The headline installer (the binary `curl | sh` path, #345). */
|
|
16
|
+
const INSTALL_SCRIPT = "curl -fsSL https://ohhai.app/install | sh";
|
|
17
|
+
/** The npm self-upgrade command (the live secondary channel for node users, #270). */
|
|
18
|
+
const NPM_UPGRADE_CMD = `npm i -g ${PACKAGE_NAME}@latest`;
|
|
19
|
+
/** Emit a success outcome: the uniform --json envelope, else the human line. Registry-supplied
|
|
20
|
+
* strings (`latest`, and any value woven into `line`) are terminal-sanitized on BOTH paths —
|
|
21
|
+
* JSON.stringify does not escape C1/DEL (see doctor.ts), so --json is sanitized too (§5.4). */
|
|
22
|
+
function emit(ctx, outcome) {
|
|
23
|
+
if (ctx.json) {
|
|
24
|
+
ctx.io.log(serializeEnvelope(buildOk("upgrade", {
|
|
25
|
+
current: sanitizeForTerminal(outcome.current),
|
|
26
|
+
latest: sanitizeForTerminal(outcome.latest),
|
|
27
|
+
update_available: outcome.updateAvailable,
|
|
28
|
+
method: outcome.method,
|
|
29
|
+
action: outcome.action,
|
|
30
|
+
})));
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
ctx.io.log(sanitizeForTerminal(outcome.line));
|
|
34
|
+
}
|
|
35
|
+
/** The `binary` case of `upgrade` (#358): a `bun --compile` standalone can't `npm i -g` itself, so it
|
|
36
|
+
* downloads the matching release asset, verifies its SHA-256 against the release `SHA256SUMS`, and
|
|
37
|
+
* atomically swaps itself in place (mirroring packaging/install.sh). It self-updates ONLY when the
|
|
38
|
+
* download/swap seam is wired AND it's a real interactive terminal (TTY stdin + TTY stdout) — the
|
|
39
|
+
* SAME gate the npm-global branch uses: silently replacing the running binary under `--json`, a pipe
|
|
40
|
+
* (`oh-hai upgrade | tee log`), or CI is as unsafe as a silent global npm mutation (#349's
|
|
41
|
+
* principle). Anything else prints installer guidance without touching disk. Every
|
|
42
|
+
* download/checksum/execute/install failure surfaces a clear CliError — never a fabricated success,
|
|
43
|
+
* never an unverified binary written to disk. */
|
|
44
|
+
async function binaryUpgrade(ctx, base, current, latest) {
|
|
45
|
+
// A real terminal session: a human at the TTY (stdin) AND a TTY stdout (not `| tee log`). Absent
|
|
46
|
+
// isStdoutTty (hermetic callers) is treated as a TTY; production wires process.stdout.isTTY.
|
|
47
|
+
const interactive = (ctx.runtime.isInteractive?.() ?? false) && (ctx.runtime.isStdoutTty?.() ?? true);
|
|
48
|
+
// Machine mode (--json), an unwired caller, or a non-interactive/piped session → guidance, no swap.
|
|
49
|
+
if (ctx.json || ctx.runtime.binaryUpdateIo === undefined || !interactive) {
|
|
50
|
+
emit(ctx, {
|
|
51
|
+
...base,
|
|
52
|
+
updateAvailable: true,
|
|
53
|
+
action: "binary-guidance",
|
|
54
|
+
line: `update available: v${current} → v${latest}. Re-run the installer: ${INSTALL_SCRIPT}`,
|
|
55
|
+
});
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
ctx.io.log(sanitizeForTerminal(`Downloading ${PACKAGE_NAME} v${latest} …`));
|
|
59
|
+
const result = await performBinaryUpdate(ctx.runtime.binaryUpdateIo, ctx.runtime.fetchImpl, latest, ctx.config.timeoutMs);
|
|
60
|
+
switch (result.status) {
|
|
61
|
+
case "updated":
|
|
62
|
+
emit(ctx, { ...base, updateAvailable: true, action: "binary-updated", line: `upgraded to v${latest}. Restart oh-hai to use the new version.` });
|
|
63
|
+
return;
|
|
64
|
+
case "unsupported":
|
|
65
|
+
// No prebuilt binary for this host (e.g. windows-arm64) — honest guidance, not a 404 crash.
|
|
66
|
+
emit(ctx, {
|
|
67
|
+
...base,
|
|
68
|
+
updateAvailable: true,
|
|
69
|
+
action: "binary-guidance",
|
|
70
|
+
line: `update available: v${current} → v${latest}. No prebuilt binary for ${result.platform}-${result.arch}; install via \`${NPM_UPGRADE_CMD}\`.`,
|
|
71
|
+
});
|
|
72
|
+
return;
|
|
73
|
+
case "release-pending":
|
|
74
|
+
// npm has v${latest} but its binary release is still building (the npm publish and the binary
|
|
75
|
+
// matrix fire on the same tag; the matrix is slower). Not an error in the code — just early.
|
|
76
|
+
throw new CliError("error", sanitizeForTerminal(`the v${latest} binary release is still publishing — try again in a few minutes, or re-run the installer: ${INSTALL_SCRIPT}`));
|
|
77
|
+
case "checksum-mismatch":
|
|
78
|
+
// A tamper/corruption signal — hard-stop loudly rather than installing unverified bytes.
|
|
79
|
+
throw new CliError("error", sanitizeForTerminal(`checksum verification FAILED for the v${latest} binary — refusing to install (corrupted download or tampering). Re-run to retry.`));
|
|
80
|
+
case "unverified-binary":
|
|
81
|
+
throw new CliError("error", sanitizeForTerminal(`the downloaded v${latest} binary did not run on this host — keeping the current install. Re-run the installer: ${INSTALL_SCRIPT}`));
|
|
82
|
+
case "download-failed":
|
|
83
|
+
throw new CliError(result.network ? "network" : "error", sanitizeForTerminal(`could not download the v${latest} binary — ${result.detail}. Re-run the installer: ${INSTALL_SCRIPT}`));
|
|
84
|
+
case "install-failed":
|
|
85
|
+
throw new CliError("error", sanitizeForTerminal(`could not install the v${latest} binary — ${result.detail}.`));
|
|
86
|
+
default: {
|
|
87
|
+
// Exhaustive: BinaryUpdateResult is a closed union — a new status must add a case above.
|
|
88
|
+
const unreachable = result;
|
|
89
|
+
throw new CliError("error", `unhandled binary-update result: ${String(unreachable)}`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
export async function upgradeCommand(ctx) {
|
|
94
|
+
const { values } = parseCommandArgs(ctx.argv, { check: { type: "boolean" } });
|
|
95
|
+
// Strict parse: `--check` is the no-mutation safety mode, so a typo'd value (`--check=treu`) must
|
|
96
|
+
// fail loudly rather than silently fall through to the live `npm i -g` (#349 review).
|
|
97
|
+
const checkOnly = flagOnStrict(values.check, "check");
|
|
98
|
+
const current = cliVersion();
|
|
99
|
+
const method = ctx.runtime.installFacts !== undefined ? classifyInstall(ctx.runtime.installFacts) : "unknown";
|
|
100
|
+
const result = await checkForUpdate(current, ctx.runtime.fetchImpl, ctx.config.timeoutMs);
|
|
101
|
+
// Registry unreachable / unparseable → a clear NETWORK exit (the user asked explicitly, so a
|
|
102
|
+
// failed check is an error here, unlike doctor's advisory check which just omits the line). Throw
|
|
103
|
+
// a CliError like every other command; cli.ts translates it once into the exit code + --json
|
|
104
|
+
// envelope. The message is sanitized here (cli.ts renders CliError messages raw).
|
|
105
|
+
if (result.error !== undefined) {
|
|
106
|
+
throw new CliError("network", sanitizeForTerminal(`could not check for updates — ${result.error}`));
|
|
107
|
+
}
|
|
108
|
+
const latest = result.latest ?? current;
|
|
109
|
+
const base = { current, latest, method };
|
|
110
|
+
if (!result.updateAvailable) {
|
|
111
|
+
emit(ctx, { ...base, updateAvailable: false, action: "up-to-date", line: `already on the latest version (v${current}).` });
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
// An update exists. `--check` only reports it.
|
|
115
|
+
if (checkOnly) {
|
|
116
|
+
emit(ctx, {
|
|
117
|
+
...base,
|
|
118
|
+
updateAvailable: true,
|
|
119
|
+
action: "check",
|
|
120
|
+
line: `update available: v${current} → v${latest} — run \`oh-hai upgrade\`.`,
|
|
121
|
+
});
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
switch (method) {
|
|
125
|
+
case "npm-global": {
|
|
126
|
+
// A real terminal session needs BOTH a TTY stdin (a human to react) AND a TTY stdout — a piped
|
|
127
|
+
// stdout (`oh-hai upgrade | tee log`) has a TTY stdin but must still PRINT the command, per the
|
|
128
|
+
// documented pipe/machine-safety behavior (#349 review). Absent isStdoutTty (hermetic callers)
|
|
129
|
+
// is treated as a TTY; production wires process.stdout.isTTY.
|
|
130
|
+
const interactive = (ctx.runtime.isInteractive?.() ?? false) && (ctx.runtime.isStdoutTty?.() ?? true);
|
|
131
|
+
// Machine mode / non-interactive / piped / no runner → PRINT the command; never silently mutate
|
|
132
|
+
// a global install in a pipe, in CI, or under --json.
|
|
133
|
+
if (ctx.json || !interactive || ctx.runtime.runCommand === undefined) {
|
|
134
|
+
emit(ctx, {
|
|
135
|
+
...base,
|
|
136
|
+
updateAvailable: true,
|
|
137
|
+
action: "print-command",
|
|
138
|
+
line: `update available: v${current} → v${latest}. Run: ${NPM_UPGRADE_CMD}`,
|
|
139
|
+
});
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
ctx.io.log(sanitizeForTerminal(`Upgrading ${PACKAGE_NAME}: v${current} → v${latest} …`));
|
|
143
|
+
const proc = await ctx.runtime.runCommand("npm", ["i", "-g", `${PACKAGE_NAME}@latest`]);
|
|
144
|
+
if (proc.spawnError !== undefined || (proc.code ?? 1) !== 0) {
|
|
145
|
+
const detail = proc.spawnError ?? (proc.stderr.trim() !== "" ? proc.stderr.trim() : `npm exited ${proc.code}`);
|
|
146
|
+
throw new CliError("error", sanitizeForTerminal(`npm upgrade failed — ${detail}`));
|
|
147
|
+
}
|
|
148
|
+
emit(ctx, { ...base, updateAvailable: true, action: "npm-upgrade", line: `upgraded to v${latest}.` });
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
case "binary":
|
|
152
|
+
await binaryUpgrade(ctx, base, current, latest);
|
|
153
|
+
return;
|
|
154
|
+
case "dev":
|
|
155
|
+
emit(ctx, {
|
|
156
|
+
...base,
|
|
157
|
+
updateAvailable: true,
|
|
158
|
+
action: "dev-guidance",
|
|
159
|
+
line: `update available: v${current} → v${latest}. You're on a source checkout — pull latest and rebuild.`,
|
|
160
|
+
});
|
|
161
|
+
return;
|
|
162
|
+
case "unknown":
|
|
163
|
+
emit(ctx, {
|
|
164
|
+
...base,
|
|
165
|
+
updateAvailable: true,
|
|
166
|
+
action: "unknown-guidance",
|
|
167
|
+
line: `update available: v${current} → v${latest}. Update via \`${NPM_UPGRADE_CMD}\`, or re-run the installer: ${INSTALL_SCRIPT}`,
|
|
168
|
+
});
|
|
169
|
+
return;
|
|
170
|
+
default: {
|
|
171
|
+
// Exhaustive: InstallMethod is a closed union — a new value must add a case above rather than
|
|
172
|
+
// silently fall into generic guidance.
|
|
173
|
+
const unreachable = method;
|
|
174
|
+
throw new CliError("error", `unhandled install method: ${String(unreachable)}`);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
//# sourceMappingURL=upgrade.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upgrade.js","sourceRoot":"","sources":["../../src/commands/upgrade.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// `oh-hai use` (docs/specs/cli.md §4, #321 — part of #307). Bind the current directory to an agent so
|
|
2
|
+
// sessions in that tree act as it with no `--account` juggling. Fully offline and side-effect-local:
|
|
3
|
+
// it only reads/writes the USER-side bindings store (`~/.config/oh-hai/bindings.json`), never a repo
|
|
4
|
+
// and never the network — so a binding can't be planted by a hostile checkout (credential-safety §6).
|
|
5
|
+
//
|
|
6
|
+
// oh-hai use <agent> bind cwd → <agent> for the resolved Hub origin
|
|
7
|
+
// oh-hai use --list list all bindings
|
|
8
|
+
// oh-hai use --unset remove cwd's binding for the resolved Hub origin
|
|
9
|
+
//
|
|
10
|
+
// Binding is offline and does NOT require the agent to have a stored token yet — you may `use` an
|
|
11
|
+
// agent then `login` as it (like `logout`, no Hub call). Resolution + the normal token lookup handle
|
|
12
|
+
// a not-yet-authenticated agent downstream (`whoami` then shows `token: absent`).
|
|
13
|
+
import { isAbsolute } from "node:path";
|
|
14
|
+
import { CliError, buildOk, serializeEnvelope } from "../envelope.js";
|
|
15
|
+
import { originOf } from "../auth/resolve-token.js";
|
|
16
|
+
import { bindingStoreStatus, loadBindings, setBinding, unsetBinding } from "../bindings.js";
|
|
17
|
+
import { sanitizeForTerminal } from "../terminal.js";
|
|
18
|
+
import { flagOn, parseCommandArgs } from "./flags.js";
|
|
19
|
+
export async function useCommand(ctx) {
|
|
20
|
+
// `use` takes one optional operand (the agent id) plus --list / --unset booleans.
|
|
21
|
+
const { values, positionals } = parseCommandArgs(ctx.argv, { list: { type: "boolean" }, unset: { type: "boolean" } }, { maxOperands: 1 });
|
|
22
|
+
// A blank / whitespace-only operand (`use ""`, `use " "`) is treated as ABSENT — otherwise it would
|
|
23
|
+
// report a successful bind while writing a dead record `asBinding` drops on read. Trim so a stray-space
|
|
24
|
+
// agent id is stored clean, matching the account-resolution presence semantics (config.ts).
|
|
25
|
+
const rawAgent = positionals[1];
|
|
26
|
+
const agent = rawAgent !== undefined && rawAgent.trim() !== "" ? rawAgent.trim() : undefined;
|
|
27
|
+
const list = flagOn(values.list);
|
|
28
|
+
const unset = flagOn(values.unset);
|
|
29
|
+
// Exactly one mode: bind (an agent operand), --list, or --unset.
|
|
30
|
+
const modes = (agent !== undefined ? 1 : 0) + (list ? 1 : 0) + (unset ? 1 : 0);
|
|
31
|
+
if (modes > 1) {
|
|
32
|
+
throw new CliError("usage", "choose one: `use <agent>`, `use --list`, or `use --unset`.");
|
|
33
|
+
}
|
|
34
|
+
if (modes === 0) {
|
|
35
|
+
throw new CliError("usage", "expected an <agent> id, or --list / --unset. Run 'oh-hai use --help'.");
|
|
36
|
+
}
|
|
37
|
+
const io = ctx.runtime.bindingsIo;
|
|
38
|
+
if (io === undefined) {
|
|
39
|
+
// Defensive: production always wires nodeBindingsIo(); this only fires if a caller omits it.
|
|
40
|
+
throw new CliError("error", "internal: the bindings store is unavailable.");
|
|
41
|
+
}
|
|
42
|
+
const origin = originOf(ctx.config.baseUrl);
|
|
43
|
+
const cwd = io.cwd;
|
|
44
|
+
if (list) {
|
|
45
|
+
const bindings = loadBindings(io);
|
|
46
|
+
if (ctx.json) {
|
|
47
|
+
ctx.io.log(serializeEnvelope(buildOk("use", { bindings })));
|
|
48
|
+
}
|
|
49
|
+
else if (bindings.length === 0) {
|
|
50
|
+
ctx.io.log("No directory bindings.");
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
for (const b of bindings) {
|
|
54
|
+
// Every field here comes from the bindings FILE, so sanitize each before echoing — a hand-edited
|
|
55
|
+
// record could carry control chars in origin just as in path/account.
|
|
56
|
+
ctx.io.log(`${sanitizeForTerminal(b.path)} → ${sanitizeForTerminal(b.account)} (${sanitizeForTerminal(b.origin)})`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
// Write-path preflight (bind + unset): fail LOUDLY rather than report a success that didn't persist.
|
|
62
|
+
// - a non-absolute cwd (production records "" when `process.cwd()` failed; a relative cwd would write
|
|
63
|
+
// a binding `resolveBinding` then ignores) has nothing meaningful to bind;
|
|
64
|
+
// - an "unavailable" store (no trusted config dir) would silently no-op the write;
|
|
65
|
+
// - an "unsupported-version" file was written by a newer CLI — overwriting it would erase its bindings.
|
|
66
|
+
if (!isAbsolute(cwd)) {
|
|
67
|
+
throw new CliError("error", "cannot bind: no absolute working directory (is the current directory gone?).");
|
|
68
|
+
}
|
|
69
|
+
const status = bindingStoreStatus(io);
|
|
70
|
+
if (status === "unavailable") {
|
|
71
|
+
throw new CliError("error", "cannot persist a binding: no writable user config directory (set $HOME or $XDG_CONFIG_HOME).");
|
|
72
|
+
}
|
|
73
|
+
if (status === "unsupported-version") {
|
|
74
|
+
throw new CliError("error", "refusing to modify a bindings.json written by a newer oh-hai — upgrade the CLI to change bindings.");
|
|
75
|
+
}
|
|
76
|
+
if (unset) {
|
|
77
|
+
const removed = unsetBinding(io, cwd, origin);
|
|
78
|
+
if (ctx.json) {
|
|
79
|
+
ctx.io.log(serializeEnvelope(buildOk("use", { path: cwd, origin, removed })));
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
ctx.io.log(removed
|
|
83
|
+
? `Unbound ${sanitizeForTerminal(cwd)} (${sanitizeForTerminal(origin)}).`
|
|
84
|
+
: `No binding for ${sanitizeForTerminal(cwd)} (${sanitizeForTerminal(origin)}).`);
|
|
85
|
+
}
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
// Bind mode: reaching here, `agent` is defined (modes === 1 with --list/--unset both false), but the
|
|
89
|
+
// mode-count arithmetic doesn't narrow it for the type checker — assert the definedness we proved.
|
|
90
|
+
const agentId = agent;
|
|
91
|
+
const path = setBinding(io, cwd, origin, agentId);
|
|
92
|
+
if (ctx.json) {
|
|
93
|
+
ctx.io.log(serializeEnvelope(buildOk("use", { path, account: agentId, origin })));
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
ctx.io.log(`Bound ${sanitizeForTerminal(path)} → ${sanitizeForTerminal(agentId)} (${sanitizeForTerminal(origin)}).`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=use.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use.js","sourceRoot":"","sources":["../../src/commands/use.ts"],"names":[],"mappings":"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"}
|
package/dist/commands/whoami.js
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
// secret (§5.4): agent id, base URL, token presence + a redacted fingerprint, and where the
|
|
3
3
|
// token resolved from (`keychain` / `file` / `env`). Works fully offline. `--check` adds a
|
|
4
4
|
// network probe of the Hub for token validity: exit 0 valid, 3 on 401/403, 5 unreachable (§9).
|
|
5
|
+
// The probe hits GET /auth/whoami, so a valid token ALSO yields the caller's Hub-attested actor. A
|
|
6
|
+
// `human:<id>` actor is printed as `resolver:` — the exact address an agent uses to route an ask/task
|
|
7
|
+
// to that human (issue #362); an agent-authed CLI validates but has no human resolver to show. An
|
|
8
|
+
// older Hub without that endpoint still validates via a legacy fallback probe (resolver omitted).
|
|
5
9
|
import { createHash } from "node:crypto";
|
|
6
10
|
import { CliError, buildOk, serializeEnvelope } from "../envelope.js";
|
|
7
11
|
import { resolveIdentity } from "../auth/resolve-token.js";
|
|
@@ -18,43 +22,89 @@ export function fingerprintToken(token) {
|
|
|
18
22
|
* set. §9 promises offline is an explicit state, never a silent hang — a Hub that accepts the
|
|
19
23
|
* connection but never responds must still surface a timeout rather than block forever. */
|
|
20
24
|
const DEFAULT_CHECK_TIMEOUT_MS = 10_000;
|
|
21
|
-
/**
|
|
22
|
-
*
|
|
23
|
-
|
|
24
|
-
* authenticated, message not found). `401`/`403` = invalid/revoked; `5xx` = the Hub errored (no
|
|
25
|
-
* validity signal); a transport failure = unreachable/timeout. Any other status (a `2xx`/`3xx`
|
|
26
|
-
* from a proxy that ignores `Authorization`, a wrong `--base-url`) is NOT proof of validity and
|
|
27
|
-
* is reported as such rather than a false "valid". Side-effect-free — a GET creates nothing. */
|
|
28
|
-
async function checkToken(ctx, token) {
|
|
29
|
-
if (token === undefined) {
|
|
30
|
-
throw new CliError("auth", "not authenticated — no stored token to check (run `oh-hai login`).");
|
|
31
|
-
}
|
|
32
|
-
const url = `${ctx.config.baseUrl}/v1/messages/oh-hai-whoami-check-nonexistent`;
|
|
25
|
+
/** Side-effect-free authenticated GET, with the transport-error → §7 exit-code mapping shared by both
|
|
26
|
+
* probes (a timeout is exit 7, any other connection failure exit 5). */
|
|
27
|
+
async function probeGet(ctx, path, token) {
|
|
33
28
|
const init = {
|
|
34
29
|
method: "GET",
|
|
35
30
|
headers: { Authorization: `Bearer ${token}` },
|
|
36
31
|
signal: AbortSignal.timeout(ctx.config.timeoutMs ?? DEFAULT_CHECK_TIMEOUT_MS),
|
|
37
32
|
};
|
|
38
|
-
|
|
33
|
+
// A per-request timeout is exit 7 (§7); a DNS/connection failure is exit 5 (§9). throwTransportError
|
|
34
|
+
// returns `never`, so the rejected-path handler doesn't widen the resolved type.
|
|
35
|
+
return ctx.runtime
|
|
36
|
+
.fetchImpl(`${ctx.config.baseUrl}${path}`, init)
|
|
37
|
+
.catch((error) => throwTransportError(error, ctx.config.baseUrl));
|
|
38
|
+
}
|
|
39
|
+
/** Parse a response body as a JSON object, defensively — a status-only fake, a non-JSON body, or a
|
|
40
|
+
* non-object all collapse to `{}` (mirrors agents.ts readJson). */
|
|
41
|
+
async function readJsonBody(res) {
|
|
42
|
+
if (typeof res.json !== "function")
|
|
43
|
+
return {};
|
|
39
44
|
try {
|
|
40
|
-
|
|
45
|
+
const parsed = await res.json();
|
|
46
|
+
return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)
|
|
47
|
+
? parsed
|
|
48
|
+
: {};
|
|
41
49
|
}
|
|
42
|
-
catch
|
|
43
|
-
|
|
44
|
-
throwTransportError(error, ctx.config.baseUrl);
|
|
50
|
+
catch {
|
|
51
|
+
return {};
|
|
45
52
|
}
|
|
53
|
+
}
|
|
54
|
+
/** The `actor` field of a whoami body — required to be a well-formed MA2H actor (`human:<id>` /
|
|
55
|
+
* `agent:<id>`, the endpoint's only shapes). A proxy `200` returning arbitrary JSON (e.g.
|
|
56
|
+
* `{actor:"ok"}`), or a blank/missing value, is NOT mistaken for a valid Hub identity. */
|
|
57
|
+
function readActor(body) {
|
|
58
|
+
const actor = typeof body.actor === "string" ? body.actor.trim() : "";
|
|
59
|
+
return /^(?:human|agent):.+/.test(actor) ? actor : undefined;
|
|
60
|
+
}
|
|
61
|
+
/** Map a "could not confirm validity" status to the §7 error the two probes share — ALWAYS throws
|
|
62
|
+
* (each probe handles its own "authenticated" status before delegating here). */
|
|
63
|
+
function classifyProbeStatus(status) {
|
|
46
64
|
if (status === 401 || status === 403) {
|
|
47
65
|
throw new CliError("auth", `token is invalid or revoked (Hub returned ${status}).`);
|
|
48
66
|
}
|
|
49
67
|
if (status >= 500) {
|
|
50
68
|
throw new CliError("server", `Hub returned ${status}; token validity could not be confirmed.`);
|
|
51
69
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
70
|
+
throw new CliError("error", `unexpected Hub response ${status}; could not confirm token validity (check --base-url).`);
|
|
71
|
+
}
|
|
72
|
+
/** Legacy validity probe for a Hub that predates GET /auth/whoami: an authenticated GET of a
|
|
73
|
+
* nonexistent message id. The Hub authenticates BEFORE the id lookup, so the expected `404`
|
|
74
|
+
* (authenticated, not found) proves the bearer is valid; every other status is handled uniformly.
|
|
75
|
+
* Yields no resolver id. */
|
|
76
|
+
async function legacyCheckToken(ctx, token) {
|
|
77
|
+
const status = (await probeGet(ctx, "/v1/messages/oh-hai-whoami-check-nonexistent", token)).status;
|
|
78
|
+
if (status === 404)
|
|
79
|
+
return; // authenticated + not found → valid
|
|
80
|
+
classifyProbeStatus(status);
|
|
81
|
+
}
|
|
82
|
+
/** Probe the Hub for token validity via GET /auth/whoami (issue #362). A `200` carrying an `{ actor }`
|
|
83
|
+
* body BOTH proves the presented bearer authenticated AND yields the Hub-attested resolver id (a
|
|
84
|
+
* human's `human:<id>` or an agent's `agent:<id>`), returned for display. `401`/`403` = invalid/revoked
|
|
85
|
+
* (exit 3); `5xx` = the Hub errored, no validity signal (exit 6); a transport failure = unreachable/
|
|
86
|
+
* timeout (exit 5/7). A `404` means the endpoint is absent (an older Hub) — fall back to the legacy
|
|
87
|
+
* probe so `--check` stays valid there (resolver omitted). Any other response — a `2xx`/`3xx` from a
|
|
88
|
+
* proxy that ignored `Authorization`, or a `200` without a real `{ actor }` body — is NOT proof of
|
|
89
|
+
* validity and is reported as such rather than a false "valid". Side-effect-free — a GET creates nothing. */
|
|
90
|
+
async function checkToken(ctx, token) {
|
|
91
|
+
if (token === undefined) {
|
|
92
|
+
throw new CliError("auth", "not authenticated — no stored token to check (run `oh-hai login`).");
|
|
93
|
+
}
|
|
94
|
+
const res = await probeGet(ctx, "/auth/whoami", token);
|
|
95
|
+
if (res.status === 200) {
|
|
96
|
+
const actor = readActor(await readJsonBody(res));
|
|
97
|
+
if (actor !== undefined)
|
|
98
|
+
return actor; // authenticated → the Hub-attested resolver id
|
|
99
|
+
// A 200 without a real actor body isn't the Hub's whoami (likely a proxy ignoring auth).
|
|
100
|
+
throw new CliError("error", "unexpected Hub response (200 without an identity); could not confirm token validity (check --base-url).");
|
|
56
101
|
}
|
|
57
|
-
|
|
102
|
+
if (res.status === 404) {
|
|
103
|
+
// The endpoint is absent (an older Hub) — confirm validity the legacy way; no resolver id.
|
|
104
|
+
await legacyCheckToken(ctx, token);
|
|
105
|
+
return undefined;
|
|
106
|
+
}
|
|
107
|
+
classifyProbeStatus(res.status);
|
|
58
108
|
}
|
|
59
109
|
export async function whoamiCommand(ctx) {
|
|
60
110
|
const { values } = parseCommandArgs(ctx.argv, { check: { type: "boolean" } });
|
|
@@ -65,26 +115,62 @@ export async function whoamiCommand(ctx) {
|
|
|
65
115
|
const resolved = await resolveIdentity(ctx.config, store);
|
|
66
116
|
const account = resolved.account;
|
|
67
117
|
const fingerprint = resolved.token !== undefined ? fingerprintToken(resolved.token) : undefined;
|
|
68
|
-
//
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
118
|
+
// Where `account` resolved (§6), so `whoami` explains the resolution — key once per-project bindings
|
|
119
|
+
// exist (#321). resolveConfig set `accountSource` for the flag/env/binding/user-config layers; when
|
|
120
|
+
// it's "none" (nothing configured) yet an account still resolved, resolveIdentity DISCOVERED the sole
|
|
121
|
+
// stored identity. `binding_path` rides along only for the binding source (pruned otherwise).
|
|
122
|
+
const source = ctx.config.accountSource;
|
|
123
|
+
const accountSource = source === undefined || source === "none"
|
|
124
|
+
? account !== undefined
|
|
125
|
+
? "discovered"
|
|
126
|
+
: undefined
|
|
127
|
+
: source;
|
|
128
|
+
const bindingPath = source === "binding" ? ctx.config.bindingPath : undefined;
|
|
129
|
+
// A failing --check throws before any identity is printed (the command result IS the check). On
|
|
130
|
+
// success it returns the caller's Hub-attested actor (undefined against an older Hub without the
|
|
131
|
+
// endpoint). Only a `human:<id>` actor is a resolver an agent can address to reach a human — a
|
|
132
|
+
// CLI authed with an agent bearer (the common device-code flow) resolves as `agent:<id>`, which the
|
|
133
|
+
// submit paths reject, so it must NOT be presented as a `resolver:` the user can hand out (issue #362).
|
|
134
|
+
const actor = flagOn(values.check) ? await checkToken(ctx, resolved.token) : undefined;
|
|
135
|
+
const resolver = actor !== undefined && actor.startsWith("human:") ? actor : undefined;
|
|
72
136
|
if (ctx.json) {
|
|
73
137
|
const token = { present: resolved.token !== undefined, source: resolved.source };
|
|
74
138
|
if (fingerprint !== undefined) {
|
|
75
139
|
token.fingerprint = fingerprint;
|
|
76
140
|
}
|
|
77
|
-
ctx.io.log(serializeEnvelope(buildOk("whoami", {
|
|
141
|
+
ctx.io.log(serializeEnvelope(buildOk("whoami", {
|
|
142
|
+
agent_id: account,
|
|
143
|
+
base_url: ctx.config.baseUrl,
|
|
144
|
+
token,
|
|
145
|
+
account_source: accountSource,
|
|
146
|
+
binding_path: bindingPath,
|
|
147
|
+
resolver,
|
|
148
|
+
})));
|
|
78
149
|
}
|
|
79
150
|
else {
|
|
80
151
|
// Sanitize before terminal output: a DISCOVERED account is the raw agent id the Hub chose at
|
|
81
152
|
// login time, so a hostile Hub could have stored control characters in it (the raw id is kept
|
|
82
153
|
// for lookup and the JSON envelope, where JSON.stringify escapes them — §5.4 / login parity).
|
|
83
154
|
ctx.io.log(`agent: ${account !== undefined ? sanitizeForTerminal(account) : "(none — set --account/--agent or MA2H_AGENT_ID)"}`);
|
|
155
|
+
// Explain HOW the account resolved (flag / env / binding@<path> / user config / discovered).
|
|
156
|
+
if (accountSource !== undefined) {
|
|
157
|
+
const via = accountSource === "binding" && bindingPath !== undefined
|
|
158
|
+
? `binding (${sanitizeForTerminal(bindingPath)})`
|
|
159
|
+
: accountSource === "user-config"
|
|
160
|
+
? "user config"
|
|
161
|
+
: accountSource;
|
|
162
|
+
ctx.io.log(`via: ${via}`);
|
|
163
|
+
}
|
|
84
164
|
ctx.io.log(`base url: ${ctx.config.baseUrl}`);
|
|
85
165
|
ctx.io.log(resolved.token !== undefined
|
|
86
166
|
? `token: present (…${fingerprint}) [${resolved.source}]`
|
|
87
167
|
: "token: absent");
|
|
168
|
+
// The Hub-attested human resolver id — present only after a successful --check that returned a
|
|
169
|
+
// `human:` actor (an agent-authed CLI has none to hand out). Sanitize: the actor is Hub-supplied,
|
|
170
|
+
// so a hostile Hub could smuggle control chars.
|
|
171
|
+
if (resolver !== undefined) {
|
|
172
|
+
ctx.io.log(`resolver: ${sanitizeForTerminal(resolver)}`);
|
|
173
|
+
}
|
|
88
174
|
}
|
|
89
175
|
}
|
|
90
176
|
//# sourceMappingURL=whoami.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"whoami.js","sourceRoot":"","sources":["../../src/commands/whoami.ts"],"names":[],"mappings":"AAAA,6FAA6F;AAC7F,4FAA4F;AAC5F,2FAA2F;AAC3F,+FAA+F;
|
|
1
|
+
{"version":3,"file":"whoami.js","sourceRoot":"","sources":["../../src/commands/whoami.ts"],"names":[],"mappings":"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"}
|
package/dist/config-file.js
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
import { readFileSync, statSync } from "node:fs";
|
|
16
16
|
import { homedir } from "node:os";
|
|
17
17
|
import { dirname, isAbsolute, join } from "node:path";
|
|
18
|
+
import { resolveXdgConfigHome, safeOsValue } from "./os.js";
|
|
18
19
|
/**
|
|
19
20
|
* Parse the minimal config dialect: flat `key = value` lines plus an optional `[oh-hai]` section.
|
|
20
21
|
* Values are quoted strings (single/double), integers (optional sign + `_` separators), or booleans.
|
|
@@ -143,18 +144,11 @@ function mapProjectConfig(record) {
|
|
|
143
144
|
}
|
|
144
145
|
/** Load `~/.config/oh-hai/config.toml` ($XDG_CONFIG_HOME honored). Undefined when the file is absent. */
|
|
145
146
|
export function loadUserConfig(io) {
|
|
146
|
-
const xdg = io.xdgConfigHome?.trim();
|
|
147
|
-
const home = io.homedir.trim();
|
|
148
147
|
// The user-global config is TRUSTED (it alone may set base_url / default_account), so its directory
|
|
149
148
|
// must be ABSOLUTE — a relative path would resolve against cwd and let a repo-committed file
|
|
150
|
-
// masquerade as user-global config, bypassing per-project rejection.
|
|
151
|
-
//
|
|
152
|
-
|
|
153
|
-
const configHome = xdg !== undefined && xdg !== "" && isAbsolute(xdg)
|
|
154
|
-
? xdg
|
|
155
|
-
: home !== "" && isAbsolute(home)
|
|
156
|
-
? join(home, ".config")
|
|
157
|
-
: undefined;
|
|
149
|
+
// masquerade as user-global config, bypassing per-project rejection. `resolveXdgConfigHome` enforces
|
|
150
|
+
// that (shared with the bindings store); undefined → no absolute config dir, so skip the user config.
|
|
151
|
+
const configHome = resolveXdgConfigHome(io);
|
|
158
152
|
if (configHome === undefined) {
|
|
159
153
|
return undefined;
|
|
160
154
|
}
|
|
@@ -195,16 +189,6 @@ export function loadConfigFiles(io) {
|
|
|
195
189
|
/** Generous ceiling on a config file's size — real configs are a few keys; anything larger is either
|
|
196
190
|
* a mistake or a hostile plant, so it's skipped rather than read into memory. */
|
|
197
191
|
const MAX_CONFIG_BYTES = 1024 * 1024;
|
|
198
|
-
/** Read an OS value that can throw (notably `process.cwd()` when the working dir was removed), degrading
|
|
199
|
-
* to "" so config loading never crashes a command that doesn't even need the value. */
|
|
200
|
-
function safeOsValue(read) {
|
|
201
|
-
try {
|
|
202
|
-
return read();
|
|
203
|
-
}
|
|
204
|
-
catch {
|
|
205
|
-
return "";
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
192
|
/** Production `ConfigFileIo` backed by the real filesystem, home dir, cwd, and environment. */
|
|
209
193
|
export function nodeConfigFileIo() {
|
|
210
194
|
return {
|
package/dist/config-file.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config-file.js","sourceRoot":"","sources":["../src/config-file.ts"],"names":[],"mappings":"AAAA,iGAAiG;AACjG,kGAAkG;AAClG,kGAAkG;AAClG,EAAE;AACF,qBAAqB;AACrB,qGAAqG;AACrG,uGAAuG;AACvG,mGAAmG;AACnG,qGAAqG;AACrG,oEAAoE;AACpE,EAAE;AACF,kGAAkG;AAClG,kGAAkG;AAClG,wEAAwE;AAExE,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACjD,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"config-file.js","sourceRoot":"","sources":["../src/config-file.ts"],"names":[],"mappings":"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"}
|