@tiens.nguyen/gu-cli 1.0.839 → 1.0.840
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/browser-command.mjs +89 -4
- package/gu-repl.mjs +86 -13
- package/package.json +1 -1
package/browser-command.mjs
CHANGED
|
@@ -140,7 +140,7 @@ export function allowIn(folder, allow = true) {
|
|
|
140
140
|
writeFileSync(allowPath(), JSON.stringify({ folders }, null, 2));
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
-
export function browserReport({ connected, version, installType, refused = [], running = true, error = "" }, { allowed = false, storeUrl = STORE_URL } = {}) {
|
|
143
|
+
export function browserReport({ connected, version, installType, refused = [], running = true, error = "" }, { allowed = false, storeUrl = STORE_URL, storeInstalled = false } = {}) {
|
|
144
144
|
// The bridge itself would not start (#170): that is gu's fault, and says so — not "nothing to install".
|
|
145
145
|
if (!running && error) {
|
|
146
146
|
return [
|
|
@@ -174,9 +174,14 @@ export function browserReport({ connected, version, installType, refused = [], r
|
|
|
174
174
|
"opens gu's own Chrome window (a separate profile — not signed in to your",
|
|
175
175
|
"accounts) and works in it with a real mouse and keyboard.",
|
|
176
176
|
// The link on a row of its own (#385): the published one is longer than a row beside the text.
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
177
|
+
// #386: which step is missing — added but not connected, or not added at all.
|
|
178
|
+
...(storeUrl && storeInstalled
|
|
179
|
+
? ["The gu extension is added to Chrome but not connected: keep Chrome open (it",
|
|
180
|
+
"connects within 30 s) — /browser install waits for it."]
|
|
181
|
+
: storeUrl
|
|
182
|
+
? ["To let it use YOUR Chrome (your logins, a tab you share), add the gu",
|
|
183
|
+
"extension — /browser install guides you through it:", ` ${storeUrl}`]
|
|
184
|
+
: ["To let it use YOUR Chrome (your logins, a tab you share): /browser install"]),
|
|
180
185
|
here,
|
|
181
186
|
...twoCopies,
|
|
182
187
|
];
|
|
@@ -319,6 +324,86 @@ export function findDevInstalls({ roots = browserRoots(), extDir = extensionDir(
|
|
|
319
324
|
return found;
|
|
320
325
|
}
|
|
321
326
|
|
|
327
|
+
// THE PUBLISHED EXTENSION, FOUND IN CHROME'S OWN RECORDS (#386, GitHub #176). Its ID is fixed by the
|
|
328
|
+
// store (the last part of the store URL), so a profile has it when its preferences list that ID
|
|
329
|
+
// from anywhere but an unpacked folder. Disabled is told apart: it is installed but cannot connect.
|
|
330
|
+
export const STORE_EXTENSION_ID = "ldfcjapmdbgdkdhlbdkbkiankneabagj";
|
|
331
|
+
|
|
332
|
+
export function findStoreInstalls({ roots = browserRoots(), id = STORE_EXTENSION_ID } = {}) {
|
|
333
|
+
const found = [];
|
|
334
|
+
for (const { browser, dir } of roots) {
|
|
335
|
+
let profiles = [];
|
|
336
|
+
try {
|
|
337
|
+
profiles = readdirSync(dir).filter((p) => p === "Default" || /^Profile \d+$/.test(p));
|
|
338
|
+
} catch {
|
|
339
|
+
continue;
|
|
340
|
+
}
|
|
341
|
+
for (const profile of profiles) {
|
|
342
|
+
for (const file of ["Secure Preferences", "Preferences"]) {
|
|
343
|
+
let e;
|
|
344
|
+
try {
|
|
345
|
+
e = JSON.parse(readFileSync(join(dir, profile, file), "utf8"))?.extensions?.settings?.[id];
|
|
346
|
+
} catch {
|
|
347
|
+
continue;
|
|
348
|
+
}
|
|
349
|
+
if (!e || UNPACKED.has(e.location)) continue;
|
|
350
|
+
const reasons = Array.isArray(e.disable_reasons) ? e.disable_reasons.length : Number(e.disable_reasons) || 0;
|
|
351
|
+
found.push({ browser, profile, id, disabled: e.state === 0 || reasons > 0 });
|
|
352
|
+
break;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
return found;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Where the install stands, and what to tell the user — PURE, so every stage can be tested.
|
|
361
|
+
* → { stage, lines }: "remove-dev" (a developer copy would block the store one, #148), "add" (not
|
|
362
|
+
* in Chrome yet), "enable" (added but switched off), "connect" (added, not connected yet), "done".
|
|
363
|
+
*/
|
|
364
|
+
export function installStage({ dev = [], store = [], status = {}, storeUrl = STORE_URL } = {}) {
|
|
365
|
+
if (status.connected && status.installType === "normal") {
|
|
366
|
+
return { stage: "done", lines: [`✓ gu browser is connected (Chrome Web Store, v${status.version ?? "?"}).`] };
|
|
367
|
+
}
|
|
368
|
+
if (dev.length) {
|
|
369
|
+
return {
|
|
370
|
+
stage: "remove-dev",
|
|
371
|
+
lines: [
|
|
372
|
+
"1. First remove the developer copy of gu browser — only one can connect.",
|
|
373
|
+
" In chrome://extensions (gu opened it), click Remove on this one:",
|
|
374
|
+
...dev.map((d) => ` ${d.browser} · ${d.profile} · ID ${d.id}`),
|
|
375
|
+
" Waiting for it to be removed…",
|
|
376
|
+
],
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
if (!store.length) {
|
|
380
|
+
return {
|
|
381
|
+
stage: "add",
|
|
382
|
+
lines: [
|
|
383
|
+
"1. Add gu browser to Chrome: on its store page, click \"Add to Chrome\".",
|
|
384
|
+
` ${storeUrl}`,
|
|
385
|
+
" (gu opened the page for you.) Waiting for it to be added…",
|
|
386
|
+
],
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
if (store.every((s) => s.disabled)) {
|
|
390
|
+
return {
|
|
391
|
+
stage: "enable",
|
|
392
|
+
lines: [
|
|
393
|
+
"2. gu browser is added but switched off. In chrome://extensions, turn it on.",
|
|
394
|
+
" Waiting for it to be turned on…",
|
|
395
|
+
],
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
return {
|
|
399
|
+
stage: "connect",
|
|
400
|
+
lines: [
|
|
401
|
+
"2. gu browser is added. Waiting for it to connect to gu…",
|
|
402
|
+
" Keep Chrome open — it connects within 30 s, or click its icon → Connect now.",
|
|
403
|
+
],
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
|
|
322
407
|
// gu's own leftovers of the developer install, in ~/.gonext.
|
|
323
408
|
export function localLeftovers() {
|
|
324
409
|
return [extensionDir(), statePath(), seenPath(), logPath()].filter((p) => existsSync(p));
|
package/gu-repl.mjs
CHANGED
|
@@ -139,7 +139,7 @@ import {
|
|
|
139
139
|
import { completedFlush, splitLiveStream } from "./completed-flush.mjs";
|
|
140
140
|
import { isScaffoldStep, isGroupableStep, stepGroupMark, stepGroupRows, relativizePaths } from "./step-group.mjs";
|
|
141
141
|
import { clearLogsNoticeText } from "./clear-logs.mjs";
|
|
142
|
-
import { allowIn, allowedIn, bridgeCommand, browserReport, bridgeStatus, extensionDir, findDevInstalls, helpInstall, installExtensionFiles, installSteps, localLeftovers, openExtensionsPage, openStorePage, removeLocalFiles, STORE_URL, stopBridge } from "./browser-command.mjs";
|
|
142
|
+
import { allowIn, allowedIn, bridgeCommand, browserReport, bridgeStatus, extensionDir, findDevInstalls, helpInstall, installExtensionFiles, installSteps, localLeftovers, findStoreInstalls, installStage, openExtensionsPage, openStorePage, removeLocalFiles, STORE_URL, stopBridge } from "./browser-command.mjs";
|
|
143
143
|
import { commandLogFile, killShell, logTail, markAnnounced, readShells, recapQuestion, shellsToRecap, statusText as shellStatusText, renderShellDetails, shellPanelRows, shellPickerLabel, shellState, shellsNotice } from "./shell-details.mjs";
|
|
144
144
|
import { discoverSkills, matchSkillCommand } from "./skills.mjs";
|
|
145
145
|
import { appendNew, archivePathFor, archivedWorkspaceKey, markReset, seedArchive } from "./conversation-archive.mjs";
|
|
@@ -9620,31 +9620,100 @@ function printShellDetails(rec) {
|
|
|
9620
9620
|
// /browser (#363): the extension's connection, else how to install it.
|
|
9621
9621
|
async function showBrowser() {
|
|
9622
9622
|
const st = await bridgeStatus().catch(() => ({ running: false, connected: false }));
|
|
9623
|
-
const lines = browserReport(st, { allowed: allowedIn(resolve(process.cwd())) });
|
|
9623
|
+
const lines = browserReport(st, { allowed: allowedIn(resolve(process.cwd())), storeInstalled: findStoreInstalls().length > 0 });
|
|
9624
9624
|
console.log("");
|
|
9625
9625
|
for (const [i, l] of lines.entries()) console.log(GUTTER + (i === 0 && st.connected ? accent(l) : l.startsWith("⚠") ? yellow(l) : dim(l)));
|
|
9626
9626
|
console.log("");
|
|
9627
9627
|
paintBar();
|
|
9628
9628
|
}
|
|
9629
9629
|
|
|
9630
|
-
// /browser install (#150): the extension, for the agent to
|
|
9631
|
-
//
|
|
9630
|
+
// /browser install (#150; guided since #386, GitHub #176): the published extension, for the agent to
|
|
9631
|
+
// use YOUR Chrome — the way Claude Code sets up its Chrome extension. gu walks it through to the end:
|
|
9632
|
+
// a developer copy is removed first (it would block the store one, #148), then the store page is
|
|
9633
|
+
// opened, and the panel in place of the input box (#384) shows each stage live until the store copy
|
|
9634
|
+
// is CONNECTED — or Esc stops waiting. gu's own window still needs none of this.
|
|
9632
9635
|
async function browserInstall() {
|
|
9633
|
-
|
|
9636
|
+
if (listPickerActive || approvalActive) return;
|
|
9637
|
+
const done = (line) => {
|
|
9638
|
+
console.log("\n" + GUTTER + line + "\n");
|
|
9639
|
+
paintBar();
|
|
9640
|
+
};
|
|
9634
9641
|
if (!STORE_URL) {
|
|
9642
|
+
// No published listing configured (GU_BROWSER_STORE_URL=""): the developer way, as before.
|
|
9643
|
+
let folder = extensionDir();
|
|
9635
9644
|
try {
|
|
9636
9645
|
folder = installExtensionFiles();
|
|
9637
9646
|
} catch (e) {
|
|
9638
9647
|
console.log(GUTTER + yellow(`could not copy the extension to ${folder}: ${e.message}`));
|
|
9639
9648
|
}
|
|
9649
|
+
await bridgeStatus().catch(() => null);
|
|
9650
|
+
console.log("");
|
|
9651
|
+
for (const l of installSteps(folder, process.platform, "")) console.log(GUTTER + dim(l));
|
|
9652
|
+
if (helpInstall(folder)) console.log(GUTTER + dim("(Opened chrome://extensions and copied the folder to your clipboard.)"));
|
|
9653
|
+
console.log("");
|
|
9654
|
+
paintBar();
|
|
9655
|
+
return;
|
|
9640
9656
|
}
|
|
9641
|
-
await bridgeStatus().catch(() =>
|
|
9642
|
-
|
|
9643
|
-
|
|
9644
|
-
|
|
9645
|
-
|
|
9646
|
-
|
|
9647
|
-
|
|
9657
|
+
let status = await bridgeStatus().catch(() => ({ running: false, connected: false }));
|
|
9658
|
+
const opened = new Set();
|
|
9659
|
+
const state = () => {
|
|
9660
|
+
const st = installStage({ dev: findDevInstalls(), store: findStoreInstalls(), status });
|
|
9661
|
+
// Open the page each stage needs, once.
|
|
9662
|
+
if (st.stage === "remove-dev" && !opened.has("ext")) { opened.add("ext"); openExtensionsPage(); }
|
|
9663
|
+
if (st.stage === "add" && !opened.has("store")) { opened.add("store"); openStorePage(); }
|
|
9664
|
+
if (st.stage === "enable" && !opened.has("ext2")) { opened.add("ext2"); openExtensionsPage(); }
|
|
9665
|
+
return st;
|
|
9666
|
+
};
|
|
9667
|
+
let st = state();
|
|
9668
|
+
if (st.stage === "done") return done(accent(st.lines[0]));
|
|
9669
|
+
if (!barPinned()) {
|
|
9670
|
+
// No zone to wait in: say what to do once.
|
|
9671
|
+
console.log("");
|
|
9672
|
+
for (const l of st.lines) console.log(GUTTER + dim(l));
|
|
9673
|
+
console.log(GUTTER + dim("Then type /browser — it should say connected.") + "\n");
|
|
9674
|
+
paintBar();
|
|
9675
|
+
return;
|
|
9676
|
+
}
|
|
9677
|
+
let polling = false;
|
|
9678
|
+
const poll = setInterval(async () => {
|
|
9679
|
+
if (polling) return;
|
|
9680
|
+
polling = true;
|
|
9681
|
+
try {
|
|
9682
|
+
status = await bridgeStatus({ waitMs: 0 }).catch(() => status);
|
|
9683
|
+
st = state();
|
|
9684
|
+
if (st.stage === "done") {
|
|
9685
|
+
clearInterval(poll);
|
|
9686
|
+
closeZonePanel();
|
|
9687
|
+
done(accent(st.lines[0]) + dim(" The agent can use your Chrome now."));
|
|
9688
|
+
}
|
|
9689
|
+
} finally {
|
|
9690
|
+
polling = false;
|
|
9691
|
+
}
|
|
9692
|
+
}, 2000);
|
|
9693
|
+
openZonePanel({
|
|
9694
|
+
refreshMs: 1000,
|
|
9695
|
+
rows: (width) => {
|
|
9696
|
+
// A row the panel would clip is WRAPPED instead: the store link is 84 characters, and a link
|
|
9697
|
+
// cut short at 80 columns cannot be copied.
|
|
9698
|
+
const room = Math.max(20, width - 1 - GUTTER.length);
|
|
9699
|
+
const wrap = (l) => {
|
|
9700
|
+
const out = [];
|
|
9701
|
+
for (let rest = l; rest.length; rest = rest.slice(room)) out.push(rest.slice(0, room));
|
|
9702
|
+
return out.length ? out : [""];
|
|
9703
|
+
};
|
|
9704
|
+
return [
|
|
9705
|
+
GUTTER + bold(accent("Install gu browser")) + dim(" — so the agent can use your own Chrome"),
|
|
9706
|
+
"",
|
|
9707
|
+
...st.lines.flatMap((l) => wrap(/https?:\/\//.test(l) ? l.trim() : l).map((r) => GUTTER + (/^\d\./.test(l) ? r : dim(r)))),
|
|
9708
|
+
"",
|
|
9709
|
+
GUTTER + dim("Esc to stop waiting · /browser install picks up where you left off"),
|
|
9710
|
+
];
|
|
9711
|
+
},
|
|
9712
|
+
onClose: () => {
|
|
9713
|
+
clearInterval(poll);
|
|
9714
|
+
if (st.stage !== "done") done(dim("(stopped waiting — /browser install picks up where you left off)"));
|
|
9715
|
+
},
|
|
9716
|
+
});
|
|
9648
9717
|
}
|
|
9649
9718
|
|
|
9650
9719
|
// /browser cleanup (#145, #147): remove the developer (unpacked) install so the Chrome Web Store
|
|
@@ -10761,7 +10830,11 @@ async function approvalPrompt(command) {
|
|
|
10761
10830
|
process.stdout.write(
|
|
10762
10831
|
"\n" + yellow("🌐 Allow gu to use a web browser?") + dim(" (no answer in ~3 min = Yes, this time)") + "\n" +
|
|
10763
10832
|
dim(` It wants to: ${what || "open a page"}`) + "\n" +
|
|
10764
|
-
dim(" In your Chrome if the gu extension is there, else in gu's own Chrome window.") + "\n"
|
|
10833
|
+
dim(" In your Chrome if the gu extension is there, else in gu's own Chrome window.") + "\n" +
|
|
10834
|
+
// #386: the way to "your Chrome", said where the question is asked.
|
|
10835
|
+
(STORE_URL && !findStoreInstalls().length
|
|
10836
|
+
? dim(" To use your own Chrome (your logins): /browser install adds the gu extension.") + "\n"
|
|
10837
|
+
: "")
|
|
10765
10838
|
);
|
|
10766
10839
|
// "Yes, this time" is THE default (#380, GitHub #167): highlighted, so Enter allows it once, and
|
|
10767
10840
|
// also what an unanswered question becomes — the countdown used to sit on "No", which read as
|
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.840",
|
|
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",
|