@tiens.nguyen/gu-cli 1.0.755 → 1.0.757
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 +16 -4
- package/gonext_agent_chat.py +0 -2
- package/gu-repl.mjs +15 -5
- package/image-paste.mjs +9 -0
- package/package.json +1 -1
- package/terminal-layout.mjs +16 -0
package/cli-token.mjs
CHANGED
|
@@ -289,26 +289,38 @@ export function barNotice({ token, publicKeyB64, mode, now = new Date(), attempt
|
|
|
289
289
|
if (check.ok) {
|
|
290
290
|
const p = check.payload;
|
|
291
291
|
const ended = p.paidUntil && Date.parse(p.paidUntil) <= now.getTime();
|
|
292
|
-
|
|
292
|
+
// SHORT AND CALM (#292: "make it simpler … seems so serious"): the fact, then the one thing to
|
|
293
|
+
// do. `urgent` is what the bar colours by — only these two need the user to act.
|
|
294
|
+
if (ended) return { kind: "plan-ended", urgent: true, text: `Pro ended ${shortDate(p.paidUntil)} · renew at ${pricingUrl}` };
|
|
293
295
|
if (Number.isInteger(p.activeDevices) && p.activeDevices > p.deviceLimit) {
|
|
294
296
|
return {
|
|
295
297
|
kind: "over-limit",
|
|
298
|
+
urgent: true,
|
|
296
299
|
// PLAIN WORDS (asked: "make it simple to understand"). It read "gu is active on 4 devices —
|
|
297
300
|
// Free covers 1", which leaves the reader to work out that "active" means "used gu in the
|
|
298
301
|
// last 30 days" and "covers" means "the plan allows". Kept short: the bar drops it entirely
|
|
299
302
|
// when it does not fit the terminal's width.
|
|
300
|
-
text: `${planName(p.plan)}
|
|
303
|
+
text: `${planName(p.plan)}: ${computers(p.deviceLimit)} · used on ${p.activeDevices} this month`,
|
|
301
304
|
};
|
|
302
305
|
}
|
|
303
306
|
const age = now.getTime() - Date.parse(p.issuedAt);
|
|
304
307
|
if (age <= STALE_AFTER_DAYS * 86_400_000) return null;
|
|
305
308
|
} else if (mode === "client" && !attempted) {
|
|
306
309
|
return null; // Client mode fetches one by itself; do not complain before it has tried.
|
|
310
|
+
} else if (mode === "expert") {
|
|
311
|
+
// NO TOKEN on a Client Expert machine (#292): not "old" — there is none — and not said twice.
|
|
312
|
+
// Startup already prints the linking steps, and the first turn asks for the token in place, so
|
|
313
|
+
// the bar repeating it was the same message in two places (reported with a screenshot).
|
|
314
|
+
return null;
|
|
315
|
+
} else {
|
|
316
|
+
// Hosted client, its automatic fetch has failed: the bar is the only place this is said.
|
|
317
|
+
return { kind: "missing", urgent: false, text: "gu isn't signed in · run gu login" };
|
|
307
318
|
}
|
|
308
319
|
|
|
320
|
+
// ROUTINE, so not an alarm (#292): a token getting old only means updates will stop one day.
|
|
309
321
|
return mode === "expert"
|
|
310
|
-
? { kind: "stale", text: `
|
|
311
|
-
: { kind: "stale", text: "
|
|
322
|
+
? { kind: "stale", urgent: false, text: `gu token is old · get a new one at ${accountUrl}` }
|
|
323
|
+
: { kind: "stale", urgent: false, text: "gu token is old · run gu login" };
|
|
312
324
|
}
|
|
313
325
|
|
|
314
326
|
/** "Free plan" / "Pro plan" — the name a person uses. */
|
package/gonext_agent_chat.py
CHANGED
|
@@ -12950,8 +12950,6 @@ def run_agent_chat(cfg):
|
|
|
12950
12950
|
task_with_hint = _label_failed_images(task_with_hint, _img_failed)
|
|
12951
12951
|
_log(f"attached {len(_turn_images)} image(s) to the question"
|
|
12952
12952
|
+ (f", {len(_img_failed)} could not be opened" if _img_failed else ""))
|
|
12953
|
-
if _turn_images:
|
|
12954
|
-
_emit({"type": "step", "text": f"Sending {len(_turn_images)} image(s) with the question"})
|
|
12955
12953
|
for _lbl, _why in _img_failed:
|
|
12956
12954
|
_emit({"type": "step", "text": f"⚠ {_lbl} not sent — {_why}"})
|
|
12957
12955
|
else:
|
package/gu-repl.mjs
CHANGED
|
@@ -128,7 +128,7 @@ import { unapprovedProjectServers } from "./mcp-command.mjs";
|
|
|
128
128
|
import { readHooks } from "./hooks-config.mjs";
|
|
129
129
|
import { codingBannerLabel, isOllamaCloudUrl, SECRET_KEY_HINT } from "./coding-key-prompt.mjs";
|
|
130
130
|
import { codingChoiceTags, coderDisplayName, coderWhere, whereOfActiveCoder } from "./coding-choice-tags.mjs";
|
|
131
|
-
import { IMAGE_NEEDS_CLAUDE, clipboardImageToFile, fitImageNotice, createImageStore, imagePasteKey, imagePathFromPaste, isClaudeCoderUrl, isImagePasteKey, storeImageFile } from "./image-paste.mjs";
|
|
131
|
+
import { IMAGE_NEEDS_CLAUDE, clipboardImageToFile, fitImageNotice, imageMarkersForDisplay, createImageStore, imagePasteKey, imagePathFromPaste, isClaudeCoderUrl, isImagePasteKey, storeImageFile } from "./image-paste.mjs";
|
|
132
132
|
import {
|
|
133
133
|
isDoubleClick,
|
|
134
134
|
clickRun,
|
|
@@ -2474,7 +2474,10 @@ function frozenZone() {
|
|
|
2474
2474
|
// it whole → the condition above lets the agents count (or nothing) have the row instead.
|
|
2475
2475
|
const len = visibleLen(tokenNotice.text);
|
|
2476
2476
|
const from = width - 1 - len;
|
|
2477
|
-
|
|
2477
|
+
// Yellow only when the user must act (plan ended, over the computer limit); an old token is
|
|
2478
|
+
// routine and reads in the rule's own dim grey (#292 — the yellow line "seems so serious").
|
|
2479
|
+
const paint = tokenNotice.urgent ? yellow : dim;
|
|
2480
|
+
rows[sep] = clipVisible(dim("\u2500".repeat(Math.max(0, from - 1)) + " ") + paint(tokenNotice.text), width - 1);
|
|
2478
2481
|
} else if (agentsIndicator(otherAgents)) {
|
|
2479
2482
|
// …and when there is no update to offer, the same row carries the agents count (task #195).
|
|
2480
2483
|
// THREE THINGS CANNOT SHARE ONE ROW, so they are ranked by how time-critical they are:
|
|
@@ -9205,9 +9208,15 @@ async function localScaffoldState() {
|
|
|
9205
9208
|
/** Print the startup scaffold line — deferred while a turn owns the transcript. */
|
|
9206
9209
|
function announceScaffold() {
|
|
9207
9210
|
if (!scaffoldNotice || following) return;
|
|
9208
|
-
const { kind, text } = scaffoldNotice;
|
|
9211
|
+
const { kind, text, steps } = scaffoldNotice;
|
|
9209
9212
|
scaffoldNotice = null;
|
|
9210
|
-
|
|
9213
|
+
if (kind === "link") {
|
|
9214
|
+
// A setup step, said calmly (#292): the headline, then the steps on their own lines.
|
|
9215
|
+
console.log(GUTTER + yellow("! " + text));
|
|
9216
|
+
for (const s of steps ?? []) console.log(GUTTER + " " + dim(s));
|
|
9217
|
+
} else {
|
|
9218
|
+
console.log(GUTTER + (kind === "ok" ? accent("✓ " + text) : kind === "warn" ? yellow("⚠ " + text) : red("✗ " + text)));
|
|
9219
|
+
}
|
|
9211
9220
|
if (barPinned()) paintBar();
|
|
9212
9221
|
}
|
|
9213
9222
|
|
|
@@ -11423,7 +11432,8 @@ async function main() {
|
|
|
11423
11432
|
// against the ">>" row. Unlike the live block's separator, this one belongs to the
|
|
11424
11433
|
// TRANSCRIPT: the echo is written exactly once per submitted line and never repainted,
|
|
11425
11434
|
// so there is nothing here for a blank to accumulate against.
|
|
11426
|
-
|
|
11435
|
+
// As typed, not as sent: "[Image #1]", never the file path the agent needs (#51).
|
|
11436
|
+
process.stdout.write(PROMPT_PAST + imageMarkersForDisplay(line) + "\n\n");
|
|
11427
11437
|
}
|
|
11428
11438
|
// ESC AT ANY QUESTION LANDS HERE (see PromptCancelled). Every command below is wrapped as
|
|
11429
11439
|
// one block rather than each flow catching for itself, because the request was to be able to
|
package/image-paste.mjs
CHANGED
|
@@ -214,6 +214,15 @@ export function clipboardImageToFile({ home = homedir(), platform = process.plat
|
|
|
214
214
|
return { path: dest };
|
|
215
215
|
}
|
|
216
216
|
|
|
217
|
+
/**
|
|
218
|
+
* A sent line as the USER should see it again: "[Image #1: /Users/…/x.png]" → "[Image #1]".
|
|
219
|
+
* The file path is for the agent, which reads the image from it; on screen it is noise (the echo
|
|
220
|
+
* of the question showed it). Claude Code shows the placeholder the same way.
|
|
221
|
+
*/
|
|
222
|
+
export function imageMarkersForDisplay(text) {
|
|
223
|
+
return String(text ?? "").replace(/\[Image #(\d+): [^\]\n]+\]/g, "[Image #$1]");
|
|
224
|
+
}
|
|
225
|
+
|
|
217
226
|
/** Placeholders shown in the input, and the files behind them. */
|
|
218
227
|
export function createImageStore() {
|
|
219
228
|
const store = new Map(); // token -> absolute path
|
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.757",
|
|
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
|
@@ -1890,6 +1890,22 @@ export function scaffoldStartupNotice({ local = null, refresh = null, private: p
|
|
|
1890
1890
|
}
|
|
1891
1891
|
if (refresh.ok) return { kind: "ok", text: `scaffold fetched from the gu service (v${refresh.version})` };
|
|
1892
1892
|
const why = String(refresh.error ?? "unknown error").replace(/\.$/, "");
|
|
1893
|
+
// NOT LINKED YET (#292): the one "error" a new Client Expert machine always shows, and it is a
|
|
1894
|
+
// setup step, not a failure — gu asks for the token before the first turn anyway. It used to be
|
|
1895
|
+
// one red sentence ("× scaffold: … Please run `gu token` — copy … then: gu token …") wrapping
|
|
1896
|
+
// mid-word across three rows; now a calm headline and two numbered steps. The account address is
|
|
1897
|
+
// taken from the API's own text, so it follows the site the API was set up for.
|
|
1898
|
+
if (/isn't linked to your gu account/i.test(why)) {
|
|
1899
|
+
const url = (/(https?:\/\/[^\s)]+\/account)/.exec(why)?.[1] ?? "https://gu.getmarsic.cc/account").replace(/^https?:\/\//, "");
|
|
1900
|
+
return {
|
|
1901
|
+
kind: "link",
|
|
1902
|
+
text: "This computer isn't linked to your gu account yet.",
|
|
1903
|
+
steps: [
|
|
1904
|
+
`1. Copy your gu CLI token from ${url}`,
|
|
1905
|
+
"2. Run: gu token <your token> (or just ask something — gu will ask for it)",
|
|
1906
|
+
],
|
|
1907
|
+
};
|
|
1908
|
+
}
|
|
1893
1909
|
return refresh.kept !== undefined
|
|
1894
1910
|
? { kind: "warn", text: `scaffold: couldn't fetch — ${why}. Still using v${refresh.kept}.` }
|
|
1895
1911
|
: { kind: "err", text: `scaffold: ${why}.` };
|