@tiens.nguyen/gu-cli 1.0.833 → 1.0.834
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/README.md +1 -0
- package/browser-bridge.mjs +274 -0
- package/browser-command.mjs +116 -0
- package/browser-extension/README.md +111 -0
- package/browser-extension/background.js +483 -0
- package/browser-extension/icons/icon128.png +0 -0
- package/browser-extension/icons/icon16.png +0 -0
- package/browser-extension/icons/icon48.png +0 -0
- package/browser-extension/manifest.json +35 -0
- package/browser-extension/popup.html +22 -0
- package/browser-extension/popup.js +22 -0
- package/gonext_agent_chat.py +188 -5
- package/gu-cli.mjs +3 -0
- package/gu-repl.mjs +66 -2
- package/gu_browser.py +263 -0
- package/package.json +9 -1
- package/shell-details.mjs +58 -4
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.834",
|
|
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",
|
|
@@ -125,6 +125,13 @@
|
|
|
125
125
|
"skills.mjs",
|
|
126
126
|
"skill-source.mjs",
|
|
127
127
|
"shell-details.mjs",
|
|
128
|
+
"browser-command.mjs",
|
|
129
|
+
"browser-bridge.mjs",
|
|
130
|
+
"browser-extension/manifest.json",
|
|
131
|
+
"browser-extension/background.js",
|
|
132
|
+
"browser-extension/popup.html",
|
|
133
|
+
"browser-extension/popup.js",
|
|
134
|
+
"browser-extension/icons/",
|
|
128
135
|
"step-group.mjs",
|
|
129
136
|
"mcp-config.mjs",
|
|
130
137
|
"mcp-command.mjs",
|
|
@@ -136,6 +143,7 @@
|
|
|
136
143
|
"gu_mcp.py",
|
|
137
144
|
"gu_skills.py",
|
|
138
145
|
"gu_test_digest.py",
|
|
146
|
+
"gu_browser.py",
|
|
139
147
|
"gu_hooks.py",
|
|
140
148
|
"prompt_scaffold_check.py",
|
|
141
149
|
"gonext_transcribe.py",
|
package/shell-details.mjs
CHANGED
|
@@ -11,9 +11,9 @@
|
|
|
11
11
|
// read from the same two facts the agent uses: is the process group alive, and has the shell
|
|
12
12
|
// written its exit file.
|
|
13
13
|
|
|
14
|
-
import { existsSync, openSync, readSync, closeSync, fstatSync, readFileSync, renameSync, statSync, writeFileSync } from "node:fs";
|
|
14
|
+
import { existsSync, openSync, readSync, closeSync, fstatSync, readFileSync, realpathSync, renameSync, statSync, writeFileSync } from "node:fs";
|
|
15
15
|
import { homedir } from "node:os";
|
|
16
|
-
import { isAbsolute, join, normalize } from "node:path";
|
|
16
|
+
import { basename, dirname, isAbsolute, join, normalize } from "node:path";
|
|
17
17
|
import { spawnSync } from "node:child_process";
|
|
18
18
|
|
|
19
19
|
export const shellsPath = (home = homedir()) => join(home, ".gonext", "shells.json");
|
|
@@ -63,7 +63,7 @@ export function shellState(rec, { alive, exit, now = Date.now() / 1000 } = {}) {
|
|
|
63
63
|
if (live && !ex && !rec?.killed) return { state: "running", code: null, runtime: Math.max(0, now - started), ended: now };
|
|
64
64
|
const ended = Number(rec?.ended) || ex?.at || now;
|
|
65
65
|
const state = rec?.killed ? "killed" : !ex ? "ended" : ex.code === 0 ? "exited" : "failed";
|
|
66
|
-
return { state, code: ex ? ex.code : null, runtime: Math.max(0, ended - started), ended };
|
|
66
|
+
return { state, code: ex ? ex.code : null, runtime: Math.max(0, ended - started), ended, by: String(rec?.killed_by ?? "") };
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
/** 7m 2s — Claude Code's spelling. */
|
|
@@ -79,7 +79,10 @@ export function fmtRuntime(seconds) {
|
|
|
79
79
|
export function statusText(st) {
|
|
80
80
|
if (st.state === "running") return "running";
|
|
81
81
|
if (st.state === "exited" || st.state === "failed") return `exited ${st.code}`;
|
|
82
|
-
|
|
82
|
+
// Who stopped it (#362/#143): a bare "killed" read as a crash when the agent had stopped its own
|
|
83
|
+
// search on purpose.
|
|
84
|
+
if (st.state === "killed") return st.by ? `killed — stopped by ${st.by}` : "killed";
|
|
85
|
+
return "ended (exit code unknown)";
|
|
83
86
|
}
|
|
84
87
|
|
|
85
88
|
/**
|
|
@@ -250,8 +253,59 @@ export async function killShell(rec, { path = shellsPath(), platform = process.p
|
|
|
250
253
|
const mine = shells.find((s) => String(s.id) === String(rec.id));
|
|
251
254
|
if (!mine || readExit(mine)) return; // it finished on its own first: keep the real exit code
|
|
252
255
|
mine.killed = true;
|
|
256
|
+
mine.killed_by = "you (Stop it in Shell details)";
|
|
257
|
+
mine.announced = true; // you stopped it — no recap turn for it
|
|
253
258
|
mine.ended = Date.now() / 1000;
|
|
254
259
|
const tmp = `${path}.${process.pid}.tmp`;
|
|
255
260
|
writeFileSync(tmp, JSON.stringify({ shells }, null, 2));
|
|
256
261
|
renameSync(tmp, path);
|
|
257
262
|
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Shells whose ending the agent has not heard about (#362) — Claude Code tells its model when a
|
|
266
|
+
* background command completes; gu's terminal starts a short recap turn for these. Only this
|
|
267
|
+
* folder's shells (shells.json is shared by every project), only ones that ended while this
|
|
268
|
+
* terminal was open, never one the agent already read (`seen`) or was told about (`announced`),
|
|
269
|
+
* and never one you stopped yourself.
|
|
270
|
+
*/
|
|
271
|
+
export function shellsToRecap(entries, { cwd = process.cwd(), since = 0 } = {}) {
|
|
272
|
+
// The nearest folder that still exists, resolved (macOS: /var → /private/var), plus the rest —
|
|
273
|
+
// a build may have deleted the folder it ran in.
|
|
274
|
+
const real = (p) => {
|
|
275
|
+
const raw = String(p ?? "");
|
|
276
|
+
if (!raw) return raw;
|
|
277
|
+
try { return realpathSync(raw); } catch { /* gone */ }
|
|
278
|
+
const up = dirname(raw);
|
|
279
|
+
return up === raw ? raw : join(real(up), basename(raw));
|
|
280
|
+
};
|
|
281
|
+
const base = real(cwd).replace(/\/+$/, "");
|
|
282
|
+
const under = (wd) => {
|
|
283
|
+
const w = real(wd);
|
|
284
|
+
return w === base || w.startsWith(base + "/");
|
|
285
|
+
};
|
|
286
|
+
return entries
|
|
287
|
+
.filter(({ rec, st }) => st.state !== "running" && st.state !== "killed" && !rec.seen && !rec.announced
|
|
288
|
+
&& st.ended >= since && under(rec.workdir))
|
|
289
|
+
.map((e) => e.rec);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/** Mark these shells announced in shells.json, so neither the agent nor another terminal repeats it. */
|
|
293
|
+
export function markAnnounced(ids, { path = shellsPath() } = {}) {
|
|
294
|
+
const shells = readShells(path);
|
|
295
|
+
let n = 0;
|
|
296
|
+
for (const s of shells) if (ids.includes(String(s.id)) && !s.announced) { s.announced = true; n++; }
|
|
297
|
+
if (!n) return 0;
|
|
298
|
+
const tmp = `${path}.${process.pid}.tmp`;
|
|
299
|
+
writeFileSync(tmp, JSON.stringify({ shells }, null, 2));
|
|
300
|
+
renameSync(tmp, path);
|
|
301
|
+
return n;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/** The recap question the terminal asks for finished shells. */
|
|
305
|
+
export function recapQuestion(entries) {
|
|
306
|
+
const parts = entries.map(({ rec, st }) =>
|
|
307
|
+
`shell ${rec.id} ${statusText(st)} after ${fmtRuntime(st.runtime)} (${String(rec.command ?? "").replace(/\s+/g, " ").trim().slice(0, 120)})`);
|
|
308
|
+
const ids = entries.map(({ rec }) => `shell_output('${rec.id}')`).join(", ");
|
|
309
|
+
return `[gu: background ${entries.length === 1 ? "shell" : "shells"} finished — ${parts.join("; ")}] `
|
|
310
|
+
+ `Read the result with ${ids}, tell me briefly what happened, and continue with the next step if the task needs one.`;
|
|
311
|
+
}
|