@tiens.nguyen/gu-cli 1.0.797 → 1.0.798
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/gu-repl.mjs +13 -2
- package/package.json +1 -1
- package/terminal-layout.mjs +49 -0
package/gu-repl.mjs
CHANGED
|
@@ -81,6 +81,8 @@ import {
|
|
|
81
81
|
updateRestartNote,
|
|
82
82
|
describeInstallFailure,
|
|
83
83
|
CLI_UPDATE_SHELL_COMMAND,
|
|
84
|
+
cliUpdatePlan,
|
|
85
|
+
installedVersionShortfall,
|
|
84
86
|
isFreshPublishMiss,
|
|
85
87
|
freshPublishNote,
|
|
86
88
|
lazyModuleNames,
|
|
@@ -139,6 +141,7 @@ import { readHooks } from "./hooks-config.mjs";
|
|
|
139
141
|
import { readSkillSource, sourceLabel, SKILL_SOURCE_KEY } from "./skill-source.mjs";
|
|
140
142
|
import { codingBannerLabel, isOllamaCloudUrl, SECRET_KEY_HINT } from "./coding-key-prompt.mjs";
|
|
141
143
|
import { questionEchoText, thoughtPrintSeq } from "./terminal-layout.mjs";
|
|
144
|
+
import { globalPrefixOf } from "./install-path.mjs";
|
|
142
145
|
import { embedNotice, embedState } from "./embed-model-command.mjs";
|
|
143
146
|
import {
|
|
144
147
|
MODELFALLBACK_PREFIX, fallbackAfterTurn, fallbackHistoryLine, fallbackNotice, keyMissingNotice,
|
|
@@ -2503,8 +2506,11 @@ async function runUpdate() {
|
|
|
2503
2506
|
// stderr is captured rather than discarded so the reason can be shown. npm puts the
|
|
2504
2507
|
// useful line (EPERM, EACCES, ETARGET) there, and "it failed" without it leaves the
|
|
2505
2508
|
// user with nothing to act on — sudo and "close gu first" are different fixes.
|
|
2506
|
-
|
|
2507
|
-
|
|
2509
|
+
// Install into the prefix of the gu that is RUNNING, with the npm that matches this node —
|
|
2510
|
+
// not whichever npm happens to be first on PATH (that one may own a different copy).
|
|
2511
|
+
const plan = cliUpdatePlan({ prefix: globalPrefixOf(DIR), execPath: process.execPath, env: process.env });
|
|
2512
|
+
const install = () => spawnSync(plan.command,
|
|
2513
|
+
{ stdio: ["ignore", "ignore", "pipe"], shell: true, encoding: "utf8", env: plan.env });
|
|
2508
2514
|
let r = install();
|
|
2509
2515
|
// JUST PUBLISHED: the update check sees a new version the moment it lands, npm's cache and
|
|
2510
2516
|
// CDN a few minutes later — ETARGET in between. Wait once and retry before calling it a
|
|
@@ -2516,6 +2522,11 @@ async function runUpdate() {
|
|
|
2516
2522
|
}
|
|
2517
2523
|
const failed = describeInstallFailure(r);
|
|
2518
2524
|
if (failed) throw new Error(failed);
|
|
2525
|
+
// npm exiting 0 is not the outcome: read the version now on disk in THIS install.
|
|
2526
|
+
let onDisk = "";
|
|
2527
|
+
try { onDisk = JSON.parse(readFileSync(join(DIR, "package.json"), "utf8")).version ?? ""; } catch { /* unreadable: skip the check */ }
|
|
2528
|
+
const short = installedVersionShortfall(onDisk, cli);
|
|
2529
|
+
if (short) throw new Error(short);
|
|
2519
2530
|
});
|
|
2520
2531
|
// NOT "updated" — the files changed, THIS process did not. Claiming otherwise is the one
|
|
2521
2532
|
// way this feature could lie to someone.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tiens.nguyen/gu-cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.798",
|
|
4
4
|
"description": "Gu CLI — the gu terminal plus the local worker that runs agent / OCR / PDF / embedding jobs on your Mac (Ollama / MLX / OpenAI-compatible).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
package/terminal-layout.mjs
CHANGED
|
@@ -1950,6 +1950,55 @@ export function describeInstallFailure({ status = 0, stderr = "", error = null }
|
|
|
1950
1950
|
*/
|
|
1951
1951
|
export const CLI_UPDATE_SHELL_COMMAND = `${CLI_INSTALL_COMMAND} --prefer-online`;
|
|
1952
1952
|
|
|
1953
|
+
/**
|
|
1954
|
+
* WHERE the update installs, so it lands on the gu that is RUNNING (task #16 follow-up).
|
|
1955
|
+
*
|
|
1956
|
+
* A bare `npm install -g` uses whichever npm is first on PATH — which, with nvm AND Homebrew (or
|
|
1957
|
+
* several nvm versions) installed, is often not the npm that owns this gu. The install then
|
|
1958
|
+
* "succeeds" into the OTHER prefix and the next `gu` still starts the old version. So:
|
|
1959
|
+
* · `--prefix <prefix>` — the global prefix THIS package lives under (globalPrefixOf), when
|
|
1960
|
+
* the caller found one (a checkout / npm link has none, and keeps the plain command);
|
|
1961
|
+
* · the running node's bin dir goes first on PATH, so `npm` is the one that matches this node.
|
|
1962
|
+
* POSIX only: Windows resolves npm.cmd through the shell and keeps its own prefix rules.
|
|
1963
|
+
* → { command, env, prefix }
|
|
1964
|
+
*/
|
|
1965
|
+
export function cliUpdatePlan({ prefix = "", execPath = "", platform = process.platform, env = {} } = {}) {
|
|
1966
|
+
const posix = platform !== "win32";
|
|
1967
|
+
let command = CLI_UPDATE_SHELL_COMMAND;
|
|
1968
|
+
let usedPrefix = "";
|
|
1969
|
+
// A prefix with shell metacharacters is not worth quoting cleverly: fall back to the plain command.
|
|
1970
|
+
if (posix && prefix && !/["$`\\\n]/.test(prefix)) {
|
|
1971
|
+
command += ` --prefix "${prefix}"`;
|
|
1972
|
+
usedPrefix = prefix;
|
|
1973
|
+
}
|
|
1974
|
+
let nextEnv = { ...env };
|
|
1975
|
+
if (posix && execPath && execPath.includes("/")) {
|
|
1976
|
+
const bin = execPath.slice(0, execPath.lastIndexOf("/")) || "/";
|
|
1977
|
+
nextEnv.PATH = env.PATH ? `${bin}:${env.PATH}` : bin;
|
|
1978
|
+
}
|
|
1979
|
+
return { command, env: nextEnv, prefix: usedPrefix };
|
|
1980
|
+
}
|
|
1981
|
+
|
|
1982
|
+
/**
|
|
1983
|
+
* npm exited 0 — but is the gu on disk really the version we meant to install?
|
|
1984
|
+
* → null when fine (or unknowable), otherwise the sentence to show. Compares numeric parts, so a
|
|
1985
|
+
* NEWER version published in between is fine; only an installed version OLDER than wanted fails.
|
|
1986
|
+
*/
|
|
1987
|
+
export function installedVersionShortfall(installed = "", wanted = "") {
|
|
1988
|
+
const parse = (v) => String(v ?? "").split("-")[0].split(".").map((n) => parseInt(n, 10));
|
|
1989
|
+
const a = parse(installed), b = parse(wanted);
|
|
1990
|
+
if (!installed || !wanted || a.some(Number.isNaN) || b.some(Number.isNaN)) return null;
|
|
1991
|
+
for (let i = 0; i < Math.max(a.length, b.length); i++) {
|
|
1992
|
+
const x = a[i] ?? 0, y = b[i] ?? 0;
|
|
1993
|
+
if (x > y) return null;
|
|
1994
|
+
if (x < y) {
|
|
1995
|
+
return `npm finished, but the gu on disk is still ${installed} (wanted ${wanted}) — ` +
|
|
1996
|
+
"run `npm install -g @tiens.nguyen/gu-cli@latest` in a terminal, then restart gu.";
|
|
1997
|
+
}
|
|
1998
|
+
}
|
|
1999
|
+
return null;
|
|
2000
|
+
}
|
|
2001
|
+
|
|
1953
2002
|
/**
|
|
1954
2003
|
* Did the install fail only because npm has not caught up with a JUST-published version?
|
|
1955
2004
|
* (ETARGET / notarget — "No matching version found".) Worth one retry after a short wait, and
|