@tiens.nguyen/gu-cli 1.0.727 → 1.0.728

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 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
- text: `gu is active on ${p.activeDevices} devices — ${p.plan === "pro" ? "Pro" : "Free"} covers ${p.deviceLimit}`,
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(`Active on ${n} ${n === 1 ? "device" : "devices"} in the last 30 days — ${payload.plan === "pro" ? "Pro" : "Free"} covers ${payload.deviceLimit}.`);
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 is active on ${p.activeDevices} devices and Pro covers ${p.deviceLimit}. ` +
374
- `Sign out of one, or remove it at ${accountUrl}.`,
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
- if (!isNewer(manifest.version, running) && depsIn) {
723
- console.log(`\n Already on ${running} — nothing newer published.\n`);
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 Upgrading the API: ${running || "none"} → ${manifest.version}`
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 is up on ${ce.LOCAL_API_BASE} (${version})\n`
766
- : ` API did not come up: ${health.why}\n Log: ${ce.API_LOG_FILE}\n`
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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tiens.nguyen/gu-cli",
3
- "version": "1.0.727",
3
+ "version": "1.0.728",
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
+ }