@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
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
// Update check + install-method detection for `oh-hai upgrade` and doctor's advisory
|
|
2
|
+
// "update available" nudge (#343, the self-update piece of #277). Zero-dependency: a focused
|
|
3
|
+
// semver comparator (the CLI stays dep-free beyond @oh-hai/ma2h-core) and a single
|
|
4
|
+
// unauthenticated GET of the npm packument. `checkForUpdate` NEVER throws — both callers
|
|
5
|
+
// (doctor's advisory check, the upgrade command) receive a structured result and apply their
|
|
6
|
+
// own policy (doctor omits on error; upgrade turns error into a NETWORK exit).
|
|
7
|
+
import { MAX_TIMER_MS } from "./messaging/shared.js";
|
|
8
|
+
/** The published package name (matches cli/package.json "name"). */
|
|
9
|
+
export const PACKAGE_NAME = "@oh-hai/cli";
|
|
10
|
+
/** The npm packument URL — the scoped name is URL-encoded (`/` → `%2F`). The `/<name>/latest`
|
|
11
|
+
* shortcut is unreliable for scoped packages, so we read `dist-tags.latest` off the packument.
|
|
12
|
+
* `dist-tags.latest` is always a STABLE version (never a prerelease), which is exactly what a
|
|
13
|
+
* running build should be compared against. */
|
|
14
|
+
export const REGISTRY_PACKUMENT_URL = "https://registry.npmjs.org/@oh-hai%2Fcli";
|
|
15
|
+
/** The "abbreviated" packument media type: npm's registry returns a much smaller document
|
|
16
|
+
* (name + versions' minimal metadata + `dist-tags`) than the full packument, and `dist-tags.latest`
|
|
17
|
+
* is present in both. We read only `dist-tags.latest`, so the abbreviated form is behavior-equivalent
|
|
18
|
+
* and lighter — worth it since `doctor` fetches this on every run. */
|
|
19
|
+
const ABBREVIATED_PACKUMENT_ACCEPT = "application/vnd.npm.install-v1+json";
|
|
20
|
+
/** Default per-request bound for the registry probe; mirrors doctor's DEFAULT_PROBE_TIMEOUT_MS so
|
|
21
|
+
* offline is an explicit state, never a silent hang. Module-private — no external consumer. */
|
|
22
|
+
const DEFAULT_UPDATE_TIMEOUT_MS = 10_000;
|
|
23
|
+
/** Parse `major.minor.patch[-prerelease]` (with an optional leading `v`). Returns undefined for
|
|
24
|
+
* anything that doesn't match — the comparator treats an unparseable version as "no update" so a
|
|
25
|
+
* garbage `latest` never triggers an upgrade nag. Trailing build metadata is ignored. */
|
|
26
|
+
function parseVersion(value) {
|
|
27
|
+
const m = /^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?/.exec(value.trim());
|
|
28
|
+
if (m === null)
|
|
29
|
+
return undefined;
|
|
30
|
+
const major = Number(m[1]);
|
|
31
|
+
const minor = Number(m[2]);
|
|
32
|
+
const patch = Number(m[3]);
|
|
33
|
+
if (Number.isNaN(major) || Number.isNaN(minor) || Number.isNaN(patch))
|
|
34
|
+
return undefined;
|
|
35
|
+
const preRaw = m[4];
|
|
36
|
+
const pre = preRaw !== undefined && preRaw !== "" ? preRaw.split(".") : [];
|
|
37
|
+
return { core: [major, minor, patch], pre };
|
|
38
|
+
}
|
|
39
|
+
function compareTriple(a, b) {
|
|
40
|
+
if (a[0] !== b[0])
|
|
41
|
+
return a[0] > b[0] ? 1 : -1;
|
|
42
|
+
if (a[1] !== b[1])
|
|
43
|
+
return a[1] > b[1] ? 1 : -1;
|
|
44
|
+
if (a[2] !== b[2])
|
|
45
|
+
return a[2] > b[2] ? 1 : -1;
|
|
46
|
+
return 0;
|
|
47
|
+
}
|
|
48
|
+
/** Compare two prerelease identifiers per semver §11: numeric identifiers compare numerically and
|
|
49
|
+
* have LOWER precedence than alphanumeric ones; alphanumeric compare lexically (ASCII). */
|
|
50
|
+
function comparePreId(a, b) {
|
|
51
|
+
const aNum = /^\d+$/.test(a);
|
|
52
|
+
const bNum = /^\d+$/.test(b);
|
|
53
|
+
if (aNum && bNum) {
|
|
54
|
+
const x = Number(a);
|
|
55
|
+
const y = Number(b);
|
|
56
|
+
return x > y ? 1 : x < y ? -1 : 0;
|
|
57
|
+
}
|
|
58
|
+
if (aNum)
|
|
59
|
+
return -1; // numeric < alphanumeric
|
|
60
|
+
if (bNum)
|
|
61
|
+
return 1;
|
|
62
|
+
return a > b ? 1 : a < b ? -1 : 0;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Semver precedence for the shapes this package publishes (`major.minor.patch[-prerelease]`).
|
|
66
|
+
* Returns -1 (a < b), 0 (equal), or 1 (a > b). A version WITH a prerelease is lower than the same
|
|
67
|
+
* core WITHOUT one (`1.0.0-beta.0` < `1.0.0`). Unparseable input compares as equal (0) so a bad
|
|
68
|
+
* `latest` is never read as newer.
|
|
69
|
+
*/
|
|
70
|
+
export function compareSemver(a, b) {
|
|
71
|
+
const pa = parseVersion(a);
|
|
72
|
+
const pb = parseVersion(b);
|
|
73
|
+
if (pa === undefined || pb === undefined)
|
|
74
|
+
return 0;
|
|
75
|
+
const core = compareTriple(pa.core, pb.core);
|
|
76
|
+
if (core !== 0)
|
|
77
|
+
return core;
|
|
78
|
+
// Equal core: no-prerelease outranks any prerelease.
|
|
79
|
+
if (pa.pre.length === 0 && pb.pre.length === 0)
|
|
80
|
+
return 0;
|
|
81
|
+
if (pa.pre.length === 0)
|
|
82
|
+
return 1;
|
|
83
|
+
if (pb.pre.length === 0)
|
|
84
|
+
return -1;
|
|
85
|
+
const shared = Math.min(pa.pre.length, pb.pre.length);
|
|
86
|
+
for (let i = 0; i < shared; i++) {
|
|
87
|
+
const cmp = comparePreId(pa.pre[i] ?? "", pb.pre[i] ?? "");
|
|
88
|
+
if (cmp !== 0)
|
|
89
|
+
return cmp;
|
|
90
|
+
}
|
|
91
|
+
// All shared identifiers equal → the longer identifier set has higher precedence.
|
|
92
|
+
if (pa.pre.length > pb.pre.length)
|
|
93
|
+
return 1;
|
|
94
|
+
if (pa.pre.length < pb.pre.length)
|
|
95
|
+
return -1;
|
|
96
|
+
return 0;
|
|
97
|
+
}
|
|
98
|
+
/** Split a path on either separator so classification is OS-independent in tests (a Windows
|
|
99
|
+
* execPath fixture still resolves its basename on a posix host). */
|
|
100
|
+
function baseName(path) {
|
|
101
|
+
const parts = path.split(/[/\\]/);
|
|
102
|
+
return parts[parts.length - 1] ?? path;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* True when `modulePath` sits under a GLOBAL npm prefix (safe to `npm i -g`). A project-local
|
|
106
|
+
* install, an `npx` / `npm exec` cache, or a Homebrew `libexec` prefix ALSO lives under
|
|
107
|
+
* `node_modules` but must NOT be treated as global — running `npm i -g` there would mutate an
|
|
108
|
+
* unrelated global prefix (or fight brew's symlink) instead of the actual invocation (#349 review).
|
|
109
|
+
* So match only the global-prefix layout: POSIX `<prefix>/lib/node_modules/…` (covers /usr/local,
|
|
110
|
+
* /opt/homebrew, nvm, ~/.npm-global) or Windows `%AppData%\npm\node_modules\…`; never under `libexec`.
|
|
111
|
+
*/
|
|
112
|
+
function isGlobalNpmPrefix(modulePath) {
|
|
113
|
+
if (/[/\\]libexec[/\\]/i.test(modulePath))
|
|
114
|
+
return false;
|
|
115
|
+
if (/[/\\]lib[/\\]node_modules[/\\]/i.test(modulePath))
|
|
116
|
+
return true;
|
|
117
|
+
return /\\npm\\node_modules\\/i.test(modulePath);
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Classify how the CLI was installed, from injected facts (pure → fully unit-testable). Only a
|
|
121
|
+
* genuine compiled standalone classifies as `binary`, because that method now self-updates in place
|
|
122
|
+
* by replacing `process.execPath` — misclassifying a shared `node`/`bun` interpreter running our
|
|
123
|
+
* script as `binary` would let `oh-hai upgrade` overwrite the user's runtime (#358 review). So the
|
|
124
|
+
* order is deliberate:
|
|
125
|
+
* 1. a `.ts` module path → running from source via tsx (`dev`);
|
|
126
|
+
* 2. a module UNDER `node_modules` → an npm/script install, classified by prefix (GLOBAL prefix →
|
|
127
|
+
* `npm-global`; project-local / npx / brew → `unknown` guidance, never a global mutation). This
|
|
128
|
+
* is checked BEFORE the standalone fallback so a renamed interpreter (`nodejs`, `node24`, a
|
|
129
|
+
* distro wrapper) running an npm dist can never fall through to the self-update path;
|
|
130
|
+
* 3. otherwise, a NON-interpreter executable means the compiled standalone IS the executable
|
|
131
|
+
* (`binary`) — interpreter detection is a broad `node`/`bun`/`deno`-prefix match so renamed Node
|
|
132
|
+
* runtimes are excluded;
|
|
133
|
+
* 4. an interpreter running a built dist outside `node_modules` → a local checkout (`dev`);
|
|
134
|
+
* 5. anything else → `unknown`.
|
|
135
|
+
*/
|
|
136
|
+
export function classifyInstall(facts) {
|
|
137
|
+
const { execPath, modulePath } = facts;
|
|
138
|
+
if (/\.[cm]?ts$/i.test(modulePath))
|
|
139
|
+
return "dev";
|
|
140
|
+
// An npm/script install lives under node_modules — classify it as such regardless of the
|
|
141
|
+
// interpreter's basename, so a distro `nodejs`/`node24`/wrapper never enters the binary path.
|
|
142
|
+
if (/[/\\]node_modules[/\\]/.test(modulePath)) {
|
|
143
|
+
return isGlobalNpmPrefix(modulePath) ? "npm-global" : "unknown";
|
|
144
|
+
}
|
|
145
|
+
// A compiled standalone runs AS its own executable (execPath is the CLI binary). Detect a script
|
|
146
|
+
// interpreter broadly — any `node`/`bun`/`deno`-prefixed basename, including renamed `nodejs` /
|
|
147
|
+
// `node24` — so only a true standalone executable reaches the in-place self-update.
|
|
148
|
+
const execBase = baseName(execPath).toLowerCase();
|
|
149
|
+
const execIsInterpreter = /^(node|bun|deno)/.test(execBase);
|
|
150
|
+
if (!execIsInterpreter)
|
|
151
|
+
return "binary";
|
|
152
|
+
if (modulePath !== "")
|
|
153
|
+
return "dev";
|
|
154
|
+
return "unknown";
|
|
155
|
+
}
|
|
156
|
+
function errorMessage(error) {
|
|
157
|
+
return error instanceof Error ? error.message : String(error);
|
|
158
|
+
}
|
|
159
|
+
function extractLatest(body) {
|
|
160
|
+
if (typeof body !== "object" || body === null)
|
|
161
|
+
return undefined;
|
|
162
|
+
const distTags = body["dist-tags"];
|
|
163
|
+
if (typeof distTags !== "object" || distTags === null)
|
|
164
|
+
return undefined;
|
|
165
|
+
const latest = distTags["latest"];
|
|
166
|
+
return typeof latest === "string" ? latest : undefined;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Fetch the latest published `@oh-hai/cli` version and compare it to `current`. Never throws: any
|
|
170
|
+
* transport failure, non-200, unparseable body, or missing `dist-tags.latest` returns a result
|
|
171
|
+
* with `error` set and `updateAvailable` false. `timeoutMs` bounds the request (defaulted on
|
|
172
|
+
* absent/0/negative, clamped to the timer ceiling).
|
|
173
|
+
*/
|
|
174
|
+
export async function checkForUpdate(current, fetchImpl, timeoutMs) {
|
|
175
|
+
const bound = Math.min(timeoutMs !== undefined && timeoutMs > 0 ? timeoutMs : DEFAULT_UPDATE_TIMEOUT_MS, MAX_TIMER_MS);
|
|
176
|
+
let response;
|
|
177
|
+
try {
|
|
178
|
+
response = await fetchImpl(REGISTRY_PACKUMENT_URL, {
|
|
179
|
+
method: "GET",
|
|
180
|
+
headers: { Accept: ABBREVIATED_PACKUMENT_ACCEPT },
|
|
181
|
+
signal: AbortSignal.timeout(bound),
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
catch (error) {
|
|
185
|
+
return { current, updateAvailable: false, error: `registry unreachable — ${errorMessage(error)}` };
|
|
186
|
+
}
|
|
187
|
+
if (response.status !== 200) {
|
|
188
|
+
return { current, updateAvailable: false, error: `registry returned HTTP ${response.status}` };
|
|
189
|
+
}
|
|
190
|
+
let body;
|
|
191
|
+
try {
|
|
192
|
+
body = response.json !== undefined ? await response.json() : undefined;
|
|
193
|
+
}
|
|
194
|
+
catch (error) {
|
|
195
|
+
return { current, updateAvailable: false, error: `could not parse registry response — ${errorMessage(error)}` };
|
|
196
|
+
}
|
|
197
|
+
const latest = extractLatest(body);
|
|
198
|
+
if (latest === undefined) {
|
|
199
|
+
return { current, updateAvailable: false, error: "registry response had no dist-tags.latest" };
|
|
200
|
+
}
|
|
201
|
+
return { current, latest, updateAvailable: compareSemver(latest, current) > 0 };
|
|
202
|
+
}
|
|
203
|
+
//# sourceMappingURL=update-check.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"update-check.js","sourceRoot":"","sources":["../../src/commands/update-check.ts"],"names":[],"mappings":"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"}
|
|
@@ -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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"}
|