@tiens.nguyen/gu-cli 1.0.860 → 1.0.862
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 -4
- package/gu_browser.py +33 -4
- package/package.json +1 -1
package/gu-repl.mjs
CHANGED
|
@@ -532,6 +532,10 @@ const SPINNERS = [
|
|
|
532
532
|
// green, cyan, blue, indigo, pink, amber).
|
|
533
533
|
const WAIT_COLORS_256 = [120, 87, 111, 147, 218, 221];
|
|
534
534
|
const color256 = (n, s) => `\x1b[38;5;${n}m${s}\x1b[0m`;
|
|
535
|
+
// The thinking/waiting spinner + playful word (#216): plain white by default, the cycling
|
|
536
|
+
// WAIT_COLORS_256 colour only when this run was started with `gu --color`. White goes through
|
|
537
|
+
// paint(), so NO_COLOR / TERM=dumb still turn it off.
|
|
538
|
+
const waitPaint = (n, s) => (REPL_COLOR ? color256(n, s) : white(s));
|
|
535
539
|
const orange = (s) => color256(208, s); // peak-token label (task #84)
|
|
536
540
|
// Compact token count: 950 → "950", 12300 → "12.3k", 1500000 → "1.5M" (task #83).
|
|
537
541
|
const fmtTokens = (n) => {
|
|
@@ -616,7 +620,7 @@ if (cliVerdict.kind === "run") {
|
|
|
616
620
|
} else if (argv.includes("--help") || argv.includes("-h")) {
|
|
617
621
|
console.log(
|
|
618
622
|
"gu — terminal agent REPL for the current folder\n\n" +
|
|
619
|
-
"Usage: gu [-v|--verbose] [--log] [--license <pro token>]\n" +
|
|
623
|
+
"Usage: gu [-v|--verbose] [--log] [--color] [--license <pro token>]\n" +
|
|
620
624
|
" gu status # worker + a table of the running model servers\n" +
|
|
621
625
|
" gu start|stop|restart|logs # manage the background worker\n" +
|
|
622
626
|
" gu logs <port|pid> [-f] # tail a running model server's log\n" +
|
|
@@ -625,6 +629,8 @@ if (cliVerdict.kind === "run") {
|
|
|
625
629
|
" -v, --verbose show RAG index-file activity in the bottom bar (each shard written)\n" +
|
|
626
630
|
" --log log this run in detail (~/.gonext/worker.log). Without it only starts,\n" +
|
|
627
631
|
" stops and errors are logged. Ends when this gu closes.\n" +
|
|
632
|
+
" --color colour the thinking spinner and its playful word (cycling colours).\n" +
|
|
633
|
+
" Without it they are plain white.\n" +
|
|
628
634
|
" --license <token> pro license: a first install ASKS how this machine runs (hosted\n" +
|
|
629
635
|
" client / MLX host / private). Without it, a first install is\n" +
|
|
630
636
|
" private by default — the whole stack on this machine.\n" +
|
|
@@ -649,6 +655,9 @@ const REPL_VERBOSE = argv.includes("-v") || argv.includes("--verbose");
|
|
|
649
655
|
// worker reads, and gives it back on exit. Per run by the owner's rule — nothing is saved, and a
|
|
650
656
|
// run that is killed leaves a lease for a dead process, which is no lease.
|
|
651
657
|
const REPL_LOG = argv.includes("--log");
|
|
658
|
+
// --color (#216): the thinking spinner and its playful word cycle through colours only on a run
|
|
659
|
+
// started with this flag; by default they are plain white.
|
|
660
|
+
const REPL_COLOR = argv.includes("--color");
|
|
652
661
|
if (REPL_LOG) {
|
|
653
662
|
const ll = await import("./log-level.mjs");
|
|
654
663
|
await ll.takeLease().catch(() => {});
|
|
@@ -4265,8 +4274,8 @@ function pendingStatusRows() {
|
|
|
4265
4274
|
const wc = WAIT_COLORS_256[Math.floor(now / 2500) % WAIT_COLORS_256.length];
|
|
4266
4275
|
const secs = Math.max(0, Math.round((now - _pendingSince) / 1000));
|
|
4267
4276
|
const line =
|
|
4268
|
-
markLine(
|
|
4269
|
-
|
|
4277
|
+
markLine(waitPaint(wc, glyph)) +
|
|
4278
|
+
waitPaint(wc, `${_pendingLabel}…`) +
|
|
4270
4279
|
(secs >= 2 ? dim(` (${secs}s)`) : "");
|
|
4271
4280
|
return [clipVisible(line, width - 1)].concat(
|
|
4272
4281
|
barPinned() || !process.stdout.isTTY ? [] : footerRows()
|
|
@@ -12458,7 +12467,7 @@ async function runAgentTurn(history, { attachJobId = "", ownJob = false } = {})
|
|
|
12458
12467
|
for (const wl of expLines) contentRows.push(clipRow(GUTTER + dim(wl)));
|
|
12459
12468
|
for (let i = 0; i < WORD_GAP; i++) contentRows.push(""); // see WORD_GAP
|
|
12460
12469
|
contentRows.push(
|
|
12461
|
-
clipRow(`${markLine(
|
|
12470
|
+
clipRow(`${markLine(waitPaint(wc, glyph))}${waitPaint(wc, `${thinkWord}…`)}${outLabel}`)
|
|
12462
12471
|
);
|
|
12463
12472
|
// PINNED → the block IS frozen rows; unpinned (a window too short to reserve any) there is
|
|
12464
12473
|
// no zone, so it keeps the old in-place draw at the output position.
|
package/gu_browser.py
CHANGED
|
@@ -291,12 +291,42 @@ def should_offer(now=None):
|
|
|
291
291
|
|
|
292
292
|
|
|
293
293
|
def mark_offered():
|
|
294
|
+
"""→ whether the marker was written (a failed write must not mean "offer again", #217)."""
|
|
294
295
|
try:
|
|
295
296
|
os.makedirs(os.path.dirname(_offered_path()), exist_ok=True)
|
|
296
297
|
with open(_offered_path(), "w") as f:
|
|
297
298
|
f.write(str(int(time.time())))
|
|
299
|
+
return True
|
|
298
300
|
except OSError:
|
|
299
|
-
|
|
301
|
+
return False
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def claim_offer(now=None):
|
|
305
|
+
"""Atomically decide that THIS process may open the store page (#217): True for exactly one of
|
|
306
|
+
several gu processes racing, and only if it was not opened in the last OFFER_EVERY_S. False when
|
|
307
|
+
GU_NO_OPEN_APPS is set (tests) or when the marker cannot be written — never offer without a
|
|
308
|
+
record that we did."""
|
|
309
|
+
if os.environ.get("GU_NO_OPEN_APPS"):
|
|
310
|
+
return False
|
|
311
|
+
lock = _offered_path() + ".lock"
|
|
312
|
+
try:
|
|
313
|
+
os.makedirs(os.path.dirname(lock), exist_ok=True)
|
|
314
|
+
try:
|
|
315
|
+
if time.time() - os.path.getmtime(lock) > 60: # a crashed claimer's leftover
|
|
316
|
+
os.remove(lock)
|
|
317
|
+
except OSError:
|
|
318
|
+
pass
|
|
319
|
+
fd = os.open(lock, os.O_CREAT | os.O_EXCL | os.O_WRONLY)
|
|
320
|
+
except OSError:
|
|
321
|
+
return False # another process holds the claim right now (or no writable folder)
|
|
322
|
+
try:
|
|
323
|
+
os.close(fd)
|
|
324
|
+
return should_offer(now) and mark_offered()
|
|
325
|
+
finally:
|
|
326
|
+
try:
|
|
327
|
+
os.remove(lock)
|
|
328
|
+
except OSError:
|
|
329
|
+
pass
|
|
300
330
|
|
|
301
331
|
|
|
302
332
|
def open_store_page(url=None, platform=None):
|
|
@@ -369,9 +399,8 @@ def ensure_extension(log=None, emit=None, wait_s=120, installs=None):
|
|
|
369
399
|
installs = find_installs() if installs is None else installs
|
|
370
400
|
state = diagnose(installs)
|
|
371
401
|
opened = False
|
|
372
|
-
if state == "missing" and
|
|
373
|
-
opened = open_store_page()
|
|
374
|
-
mark_offered()
|
|
402
|
+
if state == "missing" and claim_offer():
|
|
403
|
+
opened = open_store_page() # claim_offer already recorded it: at most once a day, never per call
|
|
375
404
|
steps = setup_steps(state, installs, opened)
|
|
376
405
|
say = emit or (lambda _t: None)
|
|
377
406
|
for line in steps:
|
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.862",
|
|
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",
|