@tiens.nguyen/gu-cli 1.0.727 → 1.0.729
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/cli-token.mjs +13 -4
- package/gu-cli.mjs +27 -10
- package/gu-repl.mjs +16 -0
- package/package.json +1 -1
- package/version-check.mjs +34 -0
package/cli-token.mjs
CHANGED
|
@@ -291,7 +291,11 @@ export function barNotice({ token, publicKeyB64, mode, now = new Date(), attempt
|
|
|
291
291
|
if (Number.isInteger(p.activeDevices) && p.activeDevices > p.deviceLimit) {
|
|
292
292
|
return {
|
|
293
293
|
kind: "over-limit",
|
|
294
|
-
|
|
294
|
+
// PLAIN WORDS (asked: "make it simple to understand"). It read "gu is active on 4 devices —
|
|
295
|
+
// Free covers 1", which leaves the reader to work out that "active" means "used gu in the
|
|
296
|
+
// last 30 days" and "covers" means "the plan allows". Kept short: the bar drops it entirely
|
|
297
|
+
// when it does not fit the terminal's width.
|
|
298
|
+
text: `${planName(p.plan)} allows ${computers(p.deviceLimit)} — you've used gu on ${p.activeDevices} this month`,
|
|
295
299
|
};
|
|
296
300
|
}
|
|
297
301
|
const age = now.getTime() - Date.parse(p.issuedAt);
|
|
@@ -305,6 +309,11 @@ export function barNotice({ token, publicKeyB64, mode, now = new Date(), attempt
|
|
|
305
309
|
: { kind: "stale", text: "Sign in to keep getting gu updates — run gu login" };
|
|
306
310
|
}
|
|
307
311
|
|
|
312
|
+
/** "Free plan" / "Pro plan" — the name a person uses. */
|
|
313
|
+
const planName = (plan) => (plan === "pro" ? "Pro plan" : "Free plan");
|
|
314
|
+
/** "1 computer" / "4 computers" — "computer", not "device": a device is also a phone. */
|
|
315
|
+
const computers = (n) => `${n} ${n === 1 ? "computer" : "computers"}`;
|
|
316
|
+
|
|
308
317
|
/** What `gu token` prints about a valid token — plain lines, no colour, for the command to style. */
|
|
309
318
|
export function tokenSummary(payload, now = new Date()) {
|
|
310
319
|
const lines = [];
|
|
@@ -317,7 +326,7 @@ export function tokenSummary(payload, now = new Date()) {
|
|
|
317
326
|
}
|
|
318
327
|
if (Number.isInteger(payload.activeDevices)) {
|
|
319
328
|
const n = payload.activeDevices;
|
|
320
|
-
lines.push(`
|
|
329
|
+
lines.push(`Used on ${computers(n)} in the last 30 days — the ${planName(payload.plan)} allows ${payload.deviceLimit}.`);
|
|
321
330
|
}
|
|
322
331
|
lines.push(`Issued ${shortDate(payload.issuedAt)}.`);
|
|
323
332
|
return lines;
|
|
@@ -370,8 +379,8 @@ export function enforcementDecision({ token, publicKeyB64, now = new Date(), sit
|
|
|
370
379
|
enforced: true,
|
|
371
380
|
allowed: false,
|
|
372
381
|
why:
|
|
373
|
-
`This account
|
|
374
|
-
|
|
382
|
+
`This account has used gu on ${computers(p.activeDevices)} in the last 30 days, and the ` +
|
|
383
|
+
`${planName(p.plan)} allows ${p.deviceLimit}. Sign out of one, or remove it at ${accountUrl}.`,
|
|
375
384
|
};
|
|
376
385
|
}
|
|
377
386
|
return { enforced: true, allowed: true, why: "" };
|
package/gu-cli.mjs
CHANGED
|
@@ -679,7 +679,6 @@ const currentApiVersion = async () => {
|
|
|
679
679
|
// machine that was already paired, i.e. every machine that could need an upgrade, had no
|
|
680
680
|
// path to one. A published API fix reached nobody.
|
|
681
681
|
const run = await import("./client-expert-run.mjs");
|
|
682
|
-
const { isNewer } = await import("./version-check.mjs");
|
|
683
682
|
const running = await currentVersion();
|
|
684
683
|
|
|
685
684
|
// TOP UP THE SEEDED SETTINGS FIRST, AND UNCONDITIONALLY.
|
|
@@ -719,8 +718,19 @@ const currentApiVersion = async () => {
|
|
|
719
718
|
const dir = run.apiInstallDir(manifest.version);
|
|
720
719
|
const depsIn = await ce.apiDepsInstalled(dir);
|
|
721
720
|
const present = (await run.probeApiInstall(manifest.version)).installed;
|
|
722
|
-
|
|
723
|
-
|
|
721
|
+
// DECIDED FROM WHAT IS RUNNING, not only what is on disk (see upgradePlan). A build already
|
|
722
|
+
// downloaded but never started used to answer "Already on 1.0.161 — nothing newer published"
|
|
723
|
+
// and leave yesterday's 1.0.153 process serving. Reported from the startup scaffold line.
|
|
724
|
+
const { upgradePlan } = await import("./version-check.mjs");
|
|
725
|
+
const liveVersion = String((await ce.probeLocalApi()).body?.version ?? "").trim();
|
|
726
|
+
const plan = upgradePlan({
|
|
727
|
+
published: manifest.version,
|
|
728
|
+
installed: running,
|
|
729
|
+
running: liveVersion,
|
|
730
|
+
ready: present && depsIn,
|
|
731
|
+
});
|
|
732
|
+
if (plan.action === "none") {
|
|
733
|
+
console.log(`\n ${plan.say}\n`);
|
|
724
734
|
process.exit(0);
|
|
725
735
|
}
|
|
726
736
|
// A version already on disk but unusable is a RESUME, not an upgrade — someone whose
|
|
@@ -730,12 +740,12 @@ const currentApiVersion = async () => {
|
|
|
730
740
|
// Caught by running it: "Finishing an interrupted install of 1.0.30" on a box going
|
|
731
741
|
// cleanly from 1.0.29.
|
|
732
742
|
console.log(
|
|
733
|
-
present && !depsIn
|
|
743
|
+
plan.action === "install" && present && !depsIn
|
|
734
744
|
? `\n Finishing an interrupted install of ${manifest.version}`
|
|
735
|
-
: `\n
|
|
745
|
+
: `\n ${plan.say}`
|
|
736
746
|
);
|
|
737
747
|
try {
|
|
738
|
-
if (!present || !depsIn) {
|
|
748
|
+
if (plan.action === "install" && (!present || !depsIn)) {
|
|
739
749
|
await run.downloadApiPackage(manifest, { onProgress: (l) => console.log(` ${l}`) });
|
|
740
750
|
await ce.installApiDeps(dir, {
|
|
741
751
|
onLine: (l) => l && l.length < 160 && console.log(` ${l}`),
|
|
@@ -760,12 +770,19 @@ const currentApiVersion = async () => {
|
|
|
760
770
|
console.log(` Starting ${version}…`);
|
|
761
771
|
await ce.startLocalApi({ apiDir: run.apiInstallDir(version) });
|
|
762
772
|
const health = await ce.waitForApi({});
|
|
773
|
+
// "An API answers" is not "the NEW API answers": an old process that never stopped answers
|
|
774
|
+
// too, and this line once reported the new version while the old one kept serving.
|
|
775
|
+
const serving = String((await ce.probeLocalApi()).body?.version ?? "").trim();
|
|
776
|
+
const wrong = health.ok && Boolean(serving) && serving !== version;
|
|
763
777
|
console.log(
|
|
764
|
-
health.ok
|
|
765
|
-
? ` API
|
|
766
|
-
:
|
|
778
|
+
!health.ok
|
|
779
|
+
? ` API did not come up: ${health.why}\n Log: ${ce.API_LOG_FILE}\n`
|
|
780
|
+
: wrong
|
|
781
|
+
? ` ${serving} is still the one answering on ${ce.LOCAL_API_BASE}, not ${version}.\n` +
|
|
782
|
+
` Stop it (gu-cli api stop), then: gu-cli api start\n`
|
|
783
|
+
: ` API is up on ${ce.LOCAL_API_BASE} (${serving || version})\n`
|
|
767
784
|
);
|
|
768
|
-
process.exit(health.ok ? 0 : 1);
|
|
785
|
+
process.exit(health.ok && !wrong ? 0 : 1);
|
|
769
786
|
}
|
|
770
787
|
|
|
771
788
|
if (sub === "stop") {
|
package/gu-repl.mjs
CHANGED
|
@@ -6333,6 +6333,14 @@ function listJsonFiles(dir) {
|
|
|
6333
6333
|
}
|
|
6334
6334
|
}
|
|
6335
6335
|
|
|
6336
|
+
/** The email of the account this worker key belongs to, or "" (older API, none on record, offline). */
|
|
6337
|
+
let accountEmailPromise = null;
|
|
6338
|
+
async function fetchAccountEmail() {
|
|
6339
|
+
const s = await fetchWorkerSettings();
|
|
6340
|
+
const e = typeof s?.userEmail === "string" ? s.userEmail.trim() : "";
|
|
6341
|
+
return /^[^\s@]+@[^\s@]+$/.test(e) ? e : "";
|
|
6342
|
+
}
|
|
6343
|
+
|
|
6336
6344
|
async function fetchWorkerSettings() {
|
|
6337
6345
|
try {
|
|
6338
6346
|
const r = await fetch(`${apiBase}/api/worker/settings`, {
|
|
@@ -10349,6 +10357,9 @@ async function main() {
|
|
|
10349
10357
|
// Show which models will answer, straight from user settings (also validates auth).
|
|
10350
10358
|
try {
|
|
10351
10359
|
const probe = await probeWithRecovery();
|
|
10360
|
+
// WHICH ACCOUNT, shown under the key in the banner (client mode). Asked for now and read when
|
|
10361
|
+
// the banner is drawn, so startup never waits on it; private mode has no web account to name.
|
|
10362
|
+
accountEmailPromise = isClientExpert() ? Promise.resolve("") : fetchAccountEmail();
|
|
10352
10363
|
// A NEW gu SESSION: where does its scaffold come from? (task #246) Your file ("loaded") or the
|
|
10353
10364
|
// gu service ("fetched" — the refresh the session then reuses). Background: never delays startup.
|
|
10354
10365
|
void checkPromptScaffoldAtStart();
|
|
@@ -10478,6 +10489,11 @@ async function main() {
|
|
|
10478
10489
|
// shown when it is actually routing, and never the credentials — host and user only.
|
|
10479
10490
|
if (bannerProxy) statusRows.push(lbl("proxy") + bannerProxy);
|
|
10480
10491
|
statusRows.push(dim(`key ${workerKey.slice(0, 8)}…`));
|
|
10492
|
+
// Under the key, in client mode: the account this machine signs in as — so a machine paired
|
|
10493
|
+
// to the wrong account is obvious at a glance. Nothing when the API does not say (older API,
|
|
10494
|
+
// or no email on record): an absent line is better than a guessed one.
|
|
10495
|
+
const accountEmail = await (accountEmailPromise ?? Promise.resolve(""));
|
|
10496
|
+
if (accountEmail) statusRows.push(dim(`account ${accountEmail}`));
|
|
10481
10497
|
console.log(renderBanner(statusRows));
|
|
10482
10498
|
}
|
|
10483
10499
|
// Task #127, Phase 3: if the base chat model server isn't answering, offer to set it up
|
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.729",
|
|
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/version-check.mjs
CHANGED
|
@@ -70,3 +70,37 @@ export function updatesAvailable({ cliRunning, cliLatest, apiRunning, apiLatest
|
|
|
70
70
|
const api = isNewer(apiLatest, apiRunning) ? String(apiLatest) : "";
|
|
71
71
|
return { cli, api, any: Boolean(cli || api) };
|
|
72
72
|
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* WHAT `gu api upgrade` SHOULD DO — decided from the version that is RUNNING, not only the one on
|
|
76
|
+
* disk. Pure, so every case is tested.
|
|
77
|
+
*
|
|
78
|
+
* REPORTED: the startup line said "your local gu API is older than this gu — run gu api upgrade",
|
|
79
|
+
* and upgrading did nothing. 1.0.161 was already downloaded (by the in-app updater), so the old
|
|
80
|
+
* check compared "published 1.0.161" with "installed 1.0.161", answered "Already on 1.0.161 —
|
|
81
|
+
* nothing newer published" and exited — while a process started the day before from 1.0.153 kept
|
|
82
|
+
* serving. `gu api status` had learnt this difference long ago; `upgrade` had not.
|
|
83
|
+
*
|
|
84
|
+
* published — the manifest's version installed — the newest usable build on disk
|
|
85
|
+
* running — what /api/health reports ("" when the API is down or too old to say)
|
|
86
|
+
* ready — `installed` has its dependencies (a runnable install)
|
|
87
|
+
* Returns { action: "none" | "install" | "restart", say }:
|
|
88
|
+
* install → download/finish the published build, then restart onto it;
|
|
89
|
+
* restart → nothing to download, but the running process is not the installed build.
|
|
90
|
+
*/
|
|
91
|
+
export function upgradePlan({ published = "", installed = "", running = "", ready = false } = {}) {
|
|
92
|
+
const target = published && (!installed || isNewer(published, installed)) ? published : installed;
|
|
93
|
+
if (target === published && published && (!ready || installed !== published)) {
|
|
94
|
+
return { action: "install", say: `Upgrading the API: ${running || installed || "none"} → ${published}` };
|
|
95
|
+
}
|
|
96
|
+
if (running && (running === target || isNewer(running, target))) {
|
|
97
|
+
// Running something at least as new as anything here — never "restart" onto an older build.
|
|
98
|
+
return { action: "none", say: `Already on ${running} — nothing newer published.` };
|
|
99
|
+
}
|
|
100
|
+
return {
|
|
101
|
+
action: "restart",
|
|
102
|
+
say: running
|
|
103
|
+
? `${target} is installed but ${running} is still running — restarting onto ${target}`
|
|
104
|
+
: `Starting ${target}`,
|
|
105
|
+
};
|
|
106
|
+
}
|