@tiens.nguyen/gu-cli 1.0.835 → 1.0.837
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-extension/actions.js +8 -0
- package/browser-extension/manifest.json +1 -4
- package/gonext_agent_chat.py +355 -36
- package/gu-cli.mjs +34 -3
- package/gu-repl.mjs +71 -16
- package/gu_browser.py +1 -1
- package/gu_powershell.py +675 -0
- package/install-path.mjs +19 -2
- package/package.json +3 -1
- package/shell-details.mjs +55 -20
- package/win-path.mjs +185 -0
package/gu-cli.mjs
CHANGED
|
@@ -139,7 +139,7 @@ Usage:
|
|
|
139
139
|
gu-cli workspace revert [runId] # undo an agent run's file edits from its backups
|
|
140
140
|
gu-cli mongo migrate-from-docker # macOS: move a Docker MongoDB to native (Homebrew)
|
|
141
141
|
gu-cli clear [--scratch|--keep-history] [--yes] # back to a fresh machine (asks first)
|
|
142
|
-
gu-cli fix-path
|
|
142
|
+
gu-cli fix-path [--check] # put npm's global folder on PATH (Windows: check, then ask)
|
|
143
143
|
# (what the install does, if npm skipped it)
|
|
144
144
|
gu-cli fix-powershell [--check] # Windows: drop PowerShell's built-in \`gu\` alias
|
|
145
145
|
# (it is Get-Unique and shadows this CLI)
|
|
@@ -1068,8 +1068,39 @@ if (args[0] === "token-usage" || args[0] === "tokens") {
|
|
|
1068
1068
|
// prefix worked out from where this file is installed, so there is one implementation.
|
|
1069
1069
|
if (args[0] === "fix-path") {
|
|
1070
1070
|
if (process.platform === "win32") {
|
|
1071
|
-
|
|
1072
|
-
|
|
1071
|
+
// #375 (GitHub #156): Windows keeps PATH in the registry — check the SAVED one (what a new
|
|
1072
|
+
// terminal reads), say what to do the way the installer does, and add it only on a yes.
|
|
1073
|
+
const wp = await import("./win-path.mjs");
|
|
1074
|
+
const { fileURLToPath } = await import("node:url");
|
|
1075
|
+
const { dirname, join } = await import("node:path");
|
|
1076
|
+
const { readFileSync } = await import("node:fs");
|
|
1077
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
1078
|
+
const dir = wp.windowsPrefixOf(here) || String(process.env.npm_config_prefix || "");
|
|
1079
|
+
if (!dir) {
|
|
1080
|
+
console.log(` gu is not installed globally with npm (${here}) — there is no PATH to fix.`);
|
|
1081
|
+
console.log(" Install it with: irm https://gu.getmarsic.cc/install.ps1 | iex");
|
|
1082
|
+
process.exit(0);
|
|
1083
|
+
}
|
|
1084
|
+
let version = "?";
|
|
1085
|
+
try { version = JSON.parse(readFileSync(join(here, "package.json"), "utf8")).version; } catch { /* shown as ? */ }
|
|
1086
|
+
const saved = wp.readSavedPaths();
|
|
1087
|
+
const status = wp.pathStatus({ dir, sessionPath: process.env.Path || process.env.PATH || "", ...saved });
|
|
1088
|
+
const psHost = Boolean(process.env.PSModulePath) && !process.env.PROMPT; // cmd.exe sets PROMPT
|
|
1089
|
+
let summary = wp.installSummary({ version, location: wp.guLocation(dir), dir, status, powershell: psHost });
|
|
1090
|
+
if (!status.saved && !args.includes("--check") && process.stdin.isTTY) {
|
|
1091
|
+
const { createInterface } = await import("node:readline/promises");
|
|
1092
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
1093
|
+
const answer = (await rl.question(` Add ${dir} to your user PATH now? [Y/n] `)).trim().toLowerCase();
|
|
1094
|
+
rl.close();
|
|
1095
|
+
if (answer === "" || answer === "y" || answer === "yes") {
|
|
1096
|
+
const r = wp.addToUserPath(dir);
|
|
1097
|
+
if (r.ok) {
|
|
1098
|
+
console.log(" Added. Open a new terminal and gu is there.");
|
|
1099
|
+
summary = wp.installSummary({ version, location: wp.guLocation(dir), dir, status: { saved: true, session: status.session }, powershell: psHost });
|
|
1100
|
+
} else console.log(` Could not change PATH: ${r.error || "PowerShell refused"}`);
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
console.log(wp.formatSummary(summary, { color: Boolean(process.stdout.isTTY) }).replace(/^/gm, " "));
|
|
1073
1104
|
process.exit(0);
|
|
1074
1105
|
}
|
|
1075
1106
|
const { fixPathPrefix } = await import("./install-path.mjs");
|
package/gu-repl.mjs
CHANGED
|
@@ -140,7 +140,7 @@ import { completedFlush, splitLiveStream } from "./completed-flush.mjs";
|
|
|
140
140
|
import { isScaffoldStep, isGroupableStep, stepGroupRows, relativizePaths } from "./step-group.mjs";
|
|
141
141
|
import { clearLogsNoticeText } from "./clear-logs.mjs";
|
|
142
142
|
import { allowIn, allowedIn, bridgeCommand, browserReport, bridgeStatus, extensionDir, findDevInstalls, helpInstall, installExtensionFiles, installSteps, localLeftovers, openExtensionsPage, removeLocalFiles, STORE_URL, stopBridge } from "./browser-command.mjs";
|
|
143
|
-
import { commandLogFile, killShell, logTail, markAnnounced, readShells, recapQuestion, shellsToRecap, statusText as shellStatusText, renderShellDetails, shellPickerLabel, shellState, shellsNotice } from "./shell-details.mjs";
|
|
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";
|
|
146
146
|
import { addAllowRule, listServers as listMcpServers } from "./mcp-config.mjs";
|
|
@@ -9668,20 +9668,49 @@ async function showShells() {
|
|
|
9668
9668
|
}
|
|
9669
9669
|
}
|
|
9670
9670
|
const rec = entries[pick].rec;
|
|
9671
|
-
for
|
|
9672
|
-
|
|
9673
|
-
|
|
9674
|
-
|
|
9675
|
-
|
|
9676
|
-
|
|
9677
|
-
|
|
9678
|
-
|
|
9679
|
-
|
|
9680
|
-
|
|
9681
|
-
|
|
9682
|
-
|
|
9683
|
-
|
|
9684
|
-
|
|
9671
|
+
// During a turn a picker would fight the turn for the keyboard: the details are printed instead.
|
|
9672
|
+
if (following) {
|
|
9673
|
+
printShellDetails(rec);
|
|
9674
|
+
refreshShells();
|
|
9675
|
+
paintBar();
|
|
9676
|
+
return;
|
|
9677
|
+
}
|
|
9678
|
+
// AT THE BOTTOM, ABOVE THE BAR, LIVE; Esc closes (#376, GitHub #157) — Claude Code's Shell
|
|
9679
|
+
// details. Nothing goes into the scrollback: the panel and its choices are one block that the
|
|
9680
|
+
// picker wipes when it closes. Its height is fixed when it opens (what the screen allows, at most
|
|
9681
|
+
// the details plus 15 output lines) and it redraws once a second, so a build's runtime and
|
|
9682
|
+
// newest output move while you watch.
|
|
9683
|
+
shellsViewed.add(String(rec.id));
|
|
9684
|
+
const panel = (rowsWanted) => {
|
|
9685
|
+
const st = shellState(rec);
|
|
9686
|
+
let output = logTail(rec.log, { lines: 40 });
|
|
9687
|
+
let outputFrom = "";
|
|
9688
|
+
if (!output.length) {
|
|
9689
|
+
const lf = commandLogFile(rec.command, rec.workdir);
|
|
9690
|
+
if (lf) {
|
|
9691
|
+
output = logTail(lf, { lines: 40 });
|
|
9692
|
+
outputFrom = lf.startsWith(String(rec.workdir || "\0") + "/") ? lf.slice(String(rec.workdir).length + 1) : lf;
|
|
9693
|
+
}
|
|
9694
|
+
}
|
|
9695
|
+
return shellPanelRows({
|
|
9696
|
+
rec, st, output, outputFrom, width: Math.max(20, process.stdout.columns || 80) - 1, gutter: GUTTER, rows: rowsWanted,
|
|
9697
|
+
footer: "Esc to close",
|
|
9698
|
+
style: { title: (t) => bold(accent(t)), label: bold, running: accent, ok: accent, bad: red, dim },
|
|
9699
|
+
});
|
|
9700
|
+
};
|
|
9701
|
+
const running = shellState(rec).state === "running";
|
|
9702
|
+
const actions = running
|
|
9703
|
+
? [{ id: "kill", label: "Stop it", note: "kills the command and everything it started" }, { id: "close", label: "Close" }]
|
|
9704
|
+
: [{ id: "close", label: "Close" }];
|
|
9705
|
+
const headerRows = Math.max(8, Math.min(24, viewportHeight() - 3 - actions.length));
|
|
9706
|
+
const act = await pickFromList(
|
|
9707
|
+
actions.map((a) => a.label + (a.note ? dim(` · ${a.note}`) : "")),
|
|
9708
|
+
actions.length - 1, // Close: Enter on its own never stops a build
|
|
9709
|
+
{ header: panel, headerRows, refreshMs: 1000 },
|
|
9710
|
+
);
|
|
9711
|
+
if (act >= 0 && actions[act].id === "kill") {
|
|
9712
|
+
await killShell(rec);
|
|
9713
|
+
console.log(GUTTER + accent(`✓ stopped shell ${rec.id}`) + dim(" — the agent sees it as killed next turn.\n"));
|
|
9685
9714
|
}
|
|
9686
9715
|
refreshShells();
|
|
9687
9716
|
paintBar();
|
|
@@ -10795,6 +10824,12 @@ let listDeleteRequested = false;
|
|
|
10795
10824
|
// /use-presets' { o: "open" } (#312, #322). Null unless a caller asks, like ctrl+d above.
|
|
10796
10825
|
let listKeys = null;
|
|
10797
10826
|
let listKeyAction = "";
|
|
10827
|
+
// A PANEL ABOVE THE CHOICES (#376): `listHeader(rows)` returns exactly `listHeaderRows` rows, drawn
|
|
10828
|
+
// at the top of the same in-place block — Shell details sits at the bottom of the screen with its
|
|
10829
|
+
// actions under it, and goes with them (Esc) without leaving a line in the scrollback. A fixed
|
|
10830
|
+
// height, so the once-a-second redraw walks up the same rows it drew.
|
|
10831
|
+
let listHeader = null;
|
|
10832
|
+
let listHeaderRows = 0;
|
|
10798
10833
|
/** #321: the screen under a picker when it opened (content rows, pad, region height), and its tallest draw. */
|
|
10799
10834
|
let listOpenedOver = null;
|
|
10800
10835
|
let listMaxRows = 0;
|
|
@@ -10807,7 +10842,7 @@ function drawList(redraw) {
|
|
|
10807
10842
|
// provider's /v1/models was taller than the terminal: the screen scrolled, the highlighted row
|
|
10808
10843
|
// went off the top, and the ESC[nA walk below then counted rows that had moved — "cannot scroll
|
|
10809
10844
|
// up or down, stuck here". Two rows of margin keep the block clear of the line above it.
|
|
10810
|
-
const maxBlock = Math.max(5, viewportHeight() - 2) - (listNote ? 1 : 0);
|
|
10845
|
+
const maxBlock = Math.max(5, viewportHeight() - 2) - (listNote ? 1 : 0) - (listHeader ? listHeaderRows : 0);
|
|
10811
10846
|
const win = listWindow(listItems.length, listSel, listTop, maxBlock);
|
|
10812
10847
|
listTop = win.top;
|
|
10813
10848
|
const row = (i) => {
|
|
@@ -10821,6 +10856,11 @@ function drawList(redraw) {
|
|
|
10821
10856
|
};
|
|
10822
10857
|
const marker = (text) => clipVisible(GUTTER + dim(text), width - 1);
|
|
10823
10858
|
const rows = [];
|
|
10859
|
+
if (listHeader) {
|
|
10860
|
+
let head = [];
|
|
10861
|
+
try { head = listHeader(listHeaderRows) || []; } catch { /* a panel that fails to draw is blank */ }
|
|
10862
|
+
for (let i = 0; i < listHeaderRows; i++) rows.push(clipVisible(head[i] ?? "", width - 1));
|
|
10863
|
+
}
|
|
10824
10864
|
// Drawn even when empty, so the block keeps a CONSTANT height: every redraw walks up exactly as
|
|
10825
10865
|
// many rows as it drew, and a block that changed height at the ends would leave that walk out
|
|
10826
10866
|
// by a row — the bug physicalRows() exists to prevent.
|
|
@@ -10863,6 +10903,8 @@ function finishList(index) {
|
|
|
10863
10903
|
listMaxRows = 0;
|
|
10864
10904
|
listRowsDrawn = [];
|
|
10865
10905
|
listTop = 0;
|
|
10906
|
+
listHeader = null;
|
|
10907
|
+
listHeaderRows = 0;
|
|
10866
10908
|
const r = listResolve;
|
|
10867
10909
|
const multi = listMulti;
|
|
10868
10910
|
const checked = [...listChecked].sort((a, b) => a - b);
|
|
@@ -11007,6 +11049,19 @@ function pickFromList(items, initialSel = 0, opts = {}) {
|
|
|
11007
11049
|
listPickerActive = true;
|
|
11008
11050
|
listDeadline = 0;
|
|
11009
11051
|
listTimeoutIndex = -1;
|
|
11052
|
+
listHeader = typeof opts.header === "function" ? opts.header : null;
|
|
11053
|
+
listHeaderRows = listHeader ? Math.max(0, opts.headerRows | 0) : 0;
|
|
11054
|
+
if (listHeader && opts.refreshMs > 0) {
|
|
11055
|
+
// A live panel (#376): redrawn in place until the picker closes.
|
|
11056
|
+
const live = setInterval(() => {
|
|
11057
|
+
if (listPickerActive && listHeader) drawList(true);
|
|
11058
|
+
}, opts.refreshMs);
|
|
11059
|
+
const done = listResolve;
|
|
11060
|
+
listResolve = (v) => {
|
|
11061
|
+
clearInterval(live);
|
|
11062
|
+
done(v);
|
|
11063
|
+
};
|
|
11064
|
+
}
|
|
11010
11065
|
if (opts && opts.timeoutMs > 0 && (opts.timeoutIndex ?? -1) >= 0) {
|
|
11011
11066
|
listDeadline = Date.now() + opts.timeoutMs;
|
|
11012
11067
|
listTimeoutIndex = opts.timeoutIndex;
|
package/gu_browser.py
CHANGED
|
@@ -19,7 +19,7 @@ import urllib.request
|
|
|
19
19
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
|
20
20
|
EXT_SRC = os.path.join(HERE, "browser-extension")
|
|
21
21
|
EXT_FILES = ("manifest.json", "background.js", "actions.js", "popup.html", "popup.js",
|
|
22
|
-
"icons/icon16.png", "icons/icon48.png", "icons/icon128.png")
|
|
22
|
+
"icons/icon16.png", "icons/icon32.png", "icons/icon48.png", "icons/icon128.png")
|
|
23
23
|
BRIDGE = os.path.join(HERE, "browser-bridge.mjs")
|
|
24
24
|
|
|
25
25
|
|