@tiens.nguyen/gu-cli 1.0.822 → 1.0.823
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-relaunch.mjs +35 -0
- package/gu-repl.mjs +8 -0
- package/package.json +2 -1
- package/terminal-title.mjs +52 -0
package/gu-relaunch.mjs
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `gu` without the bin path in the macOS window title (#358) — see relaunchPlan in
|
|
3
|
+
* terminal-title.mjs. gu-repl.mjs imports this FIRST, so the process is replaced before any other
|
|
4
|
+
* module has loaded or anything has been drawn.
|
|
5
|
+
*/
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
import { relaunchPlan, restoreAfterRelaunch } from "./terminal-title.mjs";
|
|
8
|
+
|
|
9
|
+
export const RELAUNCHED = restoreAfterRelaunch(process);
|
|
10
|
+
|
|
11
|
+
if (!RELAUNCHED) {
|
|
12
|
+
const plan = relaunchPlan({
|
|
13
|
+
platform: process.platform,
|
|
14
|
+
hasExecve: typeof process.execve === "function",
|
|
15
|
+
env: process.env,
|
|
16
|
+
argv: process.argv,
|
|
17
|
+
execPath: process.execPath,
|
|
18
|
+
script: fileURLToPath(new URL("./gu-repl.mjs", import.meta.url)),
|
|
19
|
+
stdinTTY: !!process.stdin.isTTY,
|
|
20
|
+
stdoutTTY: !!process.stdout.isTTY,
|
|
21
|
+
});
|
|
22
|
+
if (plan) {
|
|
23
|
+
// Node 23–24 flag execve as experimental; that warning would be gu's first line.
|
|
24
|
+
const emitWarning = process.emitWarning;
|
|
25
|
+
process.emitWarning = (w, ...rest) =>
|
|
26
|
+
/execve/i.test(String(w?.message ?? w)) ? undefined : emitWarning.call(process, w, ...rest);
|
|
27
|
+
try {
|
|
28
|
+
process.execve(plan.file, plan.args, plan.env); // does not return when it works
|
|
29
|
+
} catch {
|
|
30
|
+
// not allowed here (sandbox, odd node build): start the normal way, path and all
|
|
31
|
+
} finally {
|
|
32
|
+
process.emitWarning = emitWarning;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
package/gu-repl.mjs
CHANGED
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
* Requires the worker daemon to be running (it claims the job).
|
|
24
24
|
* Commands: /exit /model /new /help Ctrl-C stops following the current run.
|
|
25
25
|
*/
|
|
26
|
+
import { RELAUNCHED } from "./gu-relaunch.mjs"; // FIRST: may replace this process (#358)
|
|
26
27
|
import { readFile, mkdir, writeFile, unlink, rename } from "node:fs/promises";
|
|
27
28
|
import { readStepTokens, readTurnCache } from "./step-tokens.mjs";
|
|
28
29
|
import { snapshotInput, restoreInput, replaceStoppedQuestion, restoredQuestionLetGo, stopNoteLines } from "./turn-stop.mjs";
|
|
@@ -14571,3 +14572,10 @@ function releaseScreenAtExit() {
|
|
|
14571
14572
|
}
|
|
14572
14573
|
|
|
14573
14574
|
main();
|
|
14575
|
+
// Relaunched as `gu` (gu-relaunch.mjs), this file is Node's --import preload, and Node opens its
|
|
14576
|
+
// own REPL on the terminal once the preload finishes — so it does not finish. When nothing is
|
|
14577
|
+
// left to do, gu exits with its own code instead of Node's "unsettled top-level await" (13).
|
|
14578
|
+
if (RELAUNCHED) {
|
|
14579
|
+
process.on("beforeExit", () => process.exit(process.exitCode ?? 0));
|
|
14580
|
+
await new Promise(() => {});
|
|
14581
|
+
}
|
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.823",
|
|
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",
|
|
@@ -116,6 +116,7 @@
|
|
|
116
116
|
"coding-key-prompt.mjs",
|
|
117
117
|
"model-fallback.mjs",
|
|
118
118
|
"terminal-title.mjs",
|
|
119
|
+
"gu-relaunch.mjs",
|
|
119
120
|
"server-keys.mjs",
|
|
120
121
|
"add-server.mjs",
|
|
121
122
|
"powershell-alias.mjs",
|
package/terminal-title.mjs
CHANGED
|
@@ -22,6 +22,8 @@
|
|
|
22
22
|
* the bytes.
|
|
23
23
|
*/
|
|
24
24
|
|
|
25
|
+
import { pathToFileURL } from "node:url";
|
|
26
|
+
|
|
25
27
|
/** What fits on a tab next to its neighbours. Terminals clip a title; they do not wrap it. */
|
|
26
28
|
export const TITLE_MAX = 60;
|
|
27
29
|
|
|
@@ -77,6 +79,56 @@ export function idleTitle(folder) {
|
|
|
77
79
|
return name ? `gu · ${trimTitle(name, TITLE_MAX - 5)}` : "gu";
|
|
78
80
|
}
|
|
79
81
|
|
|
82
|
+
/**
|
|
83
|
+
* THE PATH AFTER THE TITLE (#358). macOS Terminal (and iTerm) put the running process and its
|
|
84
|
+
* arguments next to the title gu sets: `bb3d — gu · bb3d — node /opt/homebrew/Cellar/node/…/bin/gu`.
|
|
85
|
+
* The kernel builds that argv from the `#!/usr/bin/env node` line, so gu cannot change it in
|
|
86
|
+
* place — `process.title` blanks the argv area and Terminal then shows the first ENVIRONMENT
|
|
87
|
+
* variable instead (`gu TMPDIR=/var/folders/…`), and a child process shows as `gu ◂ node …`.
|
|
88
|
+
*
|
|
89
|
+
* What does work: replace the process (process.execve, Node ≥ 23.11) with node whose whole argv
|
|
90
|
+
* is `gu` and which loads this script as an --import preload. Terminal then shows `gu`. The
|
|
91
|
+
* user's flags travel in an env var, and restoreAfterRelaunch puts process.argv back the way
|
|
92
|
+
* it was, so nothing after it can tell. Node starts its own REPL once a preload finishes, so
|
|
93
|
+
* the relaunched gu-repl.mjs never finishes (see its last lines).
|
|
94
|
+
*
|
|
95
|
+
* Only for the plain interactive `gu` on macOS: a subcommand, --help/--version, a pipe (node
|
|
96
|
+
* would read a PROGRAM from a non-terminal stdin), Windows and Linux keep the old start.
|
|
97
|
+
* GU_NO_RELAUNCH=1 turns it off.
|
|
98
|
+
*/
|
|
99
|
+
export const RELAUNCH_ENV = "GU_RELAUNCHED";
|
|
100
|
+
|
|
101
|
+
/** → { file, args, env } for process.execve, or null to start the normal way. */
|
|
102
|
+
export function relaunchPlan({ platform, hasExecve, env = {}, argv = [], execPath, script, stdinTTY, stdoutTTY }) {
|
|
103
|
+
if (platform !== "darwin" || !hasExecve || !stdinTTY || !stdoutTTY || !execPath || !script) return null;
|
|
104
|
+
if (env[RELAUNCH_ENV] != null || env.GU_NO_RELAUNCH === "1") return null;
|
|
105
|
+
const args = argv.slice(2);
|
|
106
|
+
if (args.some((a) => !a.startsWith("-") || a === "--help" || a === "-h" || a === "--version")) return null;
|
|
107
|
+
const state = JSON.stringify({ script, args, nodeOptions: env.NODE_OPTIONS ?? null });
|
|
108
|
+
// A file URL has no spaces (%20) and no quotes, so it is one NODE_OPTIONS word as it stands.
|
|
109
|
+
const nodeOptions = [`--import=${pathToFileURL(script).href}`, env.NODE_OPTIONS].filter(Boolean).join(" ");
|
|
110
|
+
return { file: execPath, args: ["gu"], env: { ...env, [RELAUNCH_ENV]: state, NODE_OPTIONS: nodeOptions } };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* In the relaunched process: put process.argv and NODE_OPTIONS back, and take the marker out of
|
|
115
|
+
* the environment — every process gu starts (the worker, the agent's commands) inherits it, and
|
|
116
|
+
* a NODE_OPTIONS that preloads gu-repl.mjs would start a gu inside each node they run.
|
|
117
|
+
* → true when this process is the relaunched one.
|
|
118
|
+
*/
|
|
119
|
+
export function restoreAfterRelaunch(proc) {
|
|
120
|
+
const raw = proc.env[RELAUNCH_ENV];
|
|
121
|
+
if (raw == null) return false;
|
|
122
|
+
delete proc.env[RELAUNCH_ENV];
|
|
123
|
+
let s;
|
|
124
|
+
try { s = JSON.parse(raw); } catch { return false; }
|
|
125
|
+
if (proc.argv.length !== 1 || !s?.script) return false; // a stale marker, not our relaunch
|
|
126
|
+
if (s.nodeOptions == null) delete proc.env.NODE_OPTIONS;
|
|
127
|
+
else proc.env.NODE_OPTIONS = s.nodeOptions;
|
|
128
|
+
proc.argv.splice(0, proc.argv.length, proc.execPath, s.script, ...(s.args ?? []));
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
|
|
80
132
|
/** Where the agent model answers chat, from a base that may or may not already end in /v1. */
|
|
81
133
|
export function chatEndpoint(url) {
|
|
82
134
|
const root = String(url ?? "").trim().replace(/\/+$/, "");
|