@tiens.nguyen/gu-cli 1.0.692 → 1.0.693
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/coding-key-prompt.mjs +1 -1
- package/daemon-control.mjs +2 -2
- package/gonext_agent_chat.py +6 -6
- package/gonext_mlx_embed.py +2 -2
- package/gonext_transcribe.py +1 -1
- package/gu-cli.mjs +2 -2
- package/gu-repl.mjs +7 -26
- package/install-path.mjs +138 -0
- package/package.json +3 -1
package/coding-key-prompt.mjs
CHANGED
|
@@ -84,7 +84,7 @@ export async function promptCodingKeyIfMissing(payload, { askSecret, saveCodingK
|
|
|
84
84
|
return { asked: false, saved: Boolean(ok), keyless: true, model, url: payload.codingKeyUrl };
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
log(dim("\
|
|
87
|
+
log(dim("\ngu: your agent coding model (") + cyan(model) + dim(") needs an API key."));
|
|
88
88
|
log(dim(" Until it's set, coding falls back to your local model. Enter the key now,"));
|
|
89
89
|
log(dim(" or press Enter to skip (I'll ask again next time).\n"));
|
|
90
90
|
|
package/daemon-control.mjs
CHANGED
|
@@ -251,7 +251,7 @@ export async function findRunningDaemonPids() {
|
|
|
251
251
|
* True when `pid` is actually a gu polling daemon (not an unrelated process that reused
|
|
252
252
|
* the PID). Reads that one pid's command line — cheap and exact (review M1).
|
|
253
253
|
*/
|
|
254
|
-
function
|
|
254
|
+
function pidIsGuDaemon(pid) {
|
|
255
255
|
if (!pid || !Number.isFinite(pid)) return Promise.resolve(false);
|
|
256
256
|
return new Promise((resolve) => {
|
|
257
257
|
const one = processCommandCommand(pid);
|
|
@@ -324,7 +324,7 @@ export async function readBusyState() {
|
|
|
324
324
|
export async function daemonStatus() {
|
|
325
325
|
const [pids, pidFilePid] = await Promise.all([findRunningDaemonPids(), readPidFile()]);
|
|
326
326
|
const pidFileIsDaemon =
|
|
327
|
-
pidFilePid != null && (pids.includes(pidFilePid) || (await
|
|
327
|
+
pidFilePid != null && (pids.includes(pidFilePid) || (await pidIsGuDaemon(pidFilePid)));
|
|
328
328
|
const allPids = pidFileIsDaemon && !pids.includes(pidFilePid) ? [...pids, pidFilePid] : pids;
|
|
329
329
|
return {
|
|
330
330
|
running: allPids.length > 0,
|
package/gonext_agent_chat.py
CHANGED
|
@@ -5508,7 +5508,7 @@ def _rag_read_allowed(path: str) -> str:
|
|
|
5508
5508
|
base = _WS_ACTIVE or (_WS_ROOTS[0]["path"] if _WS_ROOTS else None)
|
|
5509
5509
|
if base is None:
|
|
5510
5510
|
raise ValueError(
|
|
5511
|
-
"No workspace is active — register a folder with `
|
|
5511
|
+
"No workspace is active — register a folder with `gu-local-worker "
|
|
5512
5512
|
"workspace add <path>` before reading files."
|
|
5513
5513
|
)
|
|
5514
5514
|
expanded = os.path.join(base, _ws_strip_echoed_root(base, expanded))
|
|
@@ -5842,7 +5842,7 @@ def _ws_write_allowed(path: str) -> str:
|
|
|
5842
5842
|
if w is None:
|
|
5843
5843
|
raise ValueError(
|
|
5844
5844
|
"Writes are only allowed inside a registered workspace "
|
|
5845
|
-
"(register one with:
|
|
5845
|
+
"(register one with: gu-local-worker workspace add <path>)."
|
|
5846
5846
|
)
|
|
5847
5847
|
rel = os.path.relpath(rp, w["path"])
|
|
5848
5848
|
if rel == ".git" or rel.startswith(".git" + os.sep) or (os.sep + ".git" + os.sep) in (os.sep + rel + os.sep):
|
|
@@ -5884,7 +5884,7 @@ def _ws_snapshot(path: str) -> str:
|
|
|
5884
5884
|
|
|
5885
5885
|
|
|
5886
5886
|
def _ws_record_change(action: str, path: str, backup) -> None:
|
|
5887
|
-
"""Track a change AND persist the run manifest so `
|
|
5887
|
+
"""Track a change AND persist the run manifest so `gu-local-worker workspace
|
|
5888
5888
|
revert` can restore edits / delete created files after the process is gone."""
|
|
5889
5889
|
import os
|
|
5890
5890
|
_WS_CHANGES.append({"action": action, "path": path, "backup": backup or None})
|
|
@@ -6772,7 +6772,7 @@ def run_agent_chat(cfg):
|
|
|
6772
6772
|
# Ollama?" or "did the user pick openai?" separately — reasoning_effort, streaming, the
|
|
6773
6773
|
# #113 reasoning repairs, and this step budget — now reads `coding_backend`.
|
|
6774
6774
|
#
|
|
6775
|
-
# The worker (
|
|
6775
|
+
# The worker (gu-local-worker.mjs) resolves this first, because it must decide
|
|
6776
6776
|
# whether to fetch the coding API key BEFORE python starts, and passes the answer down
|
|
6777
6777
|
# as codingBackend. Trust it only when we ended up on the URL it judged: the fallbacks
|
|
6778
6778
|
# above can swap coding_base_url for the chat model's, which is a different server and
|
|
@@ -7094,7 +7094,7 @@ def run_agent_chat(cfg):
|
|
|
7094
7094
|
)
|
|
7095
7095
|
|
|
7096
7096
|
# ---- Workspaces (local folders the agent may read/edit/test code in) ----
|
|
7097
|
-
# Registered on the Mac via `
|
|
7097
|
+
# Registered on the Mac via `gu-local-worker workspace add <path>`; the worker
|
|
7098
7098
|
# passes the registry through cfg. Only these roots are writable — the security
|
|
7099
7099
|
# boundary for the coding tools.
|
|
7100
7100
|
global _WS_ROOTS, _WS_ACTIVE
|
|
@@ -11556,7 +11556,7 @@ def run_agent_chat(cfg):
|
|
|
11556
11556
|
"To give the user a FILE or a ZIP of a folder, use the create_download "
|
|
11557
11557
|
"tool instead — it needs no shell and works here. "
|
|
11558
11558
|
"(To enable real commands, re-register with: "
|
|
11559
|
-
"
|
|
11559
|
+
"gu-local-worker workspace add <path> --allow-run)")
|
|
11560
11560
|
_last_obs["text"] = msg
|
|
11561
11561
|
return msg
|
|
11562
11562
|
argv = shlex.split(command or "")
|
package/gonext_mlx_embed.py
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
"""
|
|
3
|
-
|
|
3
|
+
Gu MLX embeddings server — an OpenAI-compatible /v1/embeddings endpoint for MLX
|
|
4
4
|
embedding models (e.g. Qwen3-Embedding-8B), which `mlx_lm.server` does NOT provide.
|
|
5
5
|
|
|
6
|
-
The
|
|
6
|
+
The Gu worker's RAG tools call `POST {url}/v1/embeddings` first, so pointing the
|
|
7
7
|
"RAG embedding server URL" (web Settings → Agent → RAG) at this server makes MLX
|
|
8
8
|
embeddings work with no other changes.
|
|
9
9
|
|
package/gonext_transcribe.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
"""Voice transcription for the
|
|
2
|
+
"""Voice transcription for the Gu local worker (Audio support, task #21).
|
|
3
3
|
|
|
4
4
|
Reads a JSON job on stdin: {"audioPath": str, "model": str, "language": str?}.
|
|
5
5
|
Transcribes with mlx-whisper and prints {"text": "..."} on stdout.
|
package/gu-cli.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* Gu local worker:
|
|
4
4
|
* - `gu-cli set <workerKey> [--api-base URL]`
|
|
5
5
|
* writes ~/.gonext/worker.env
|
|
6
6
|
* - `gu-cli ws-ping-test` — POST worker ws-ping (needs GONEXT_* env)
|
|
@@ -2927,7 +2927,7 @@ async function runOcrJob(job) {
|
|
|
2927
2927
|
}
|
|
2928
2928
|
console.log(`[gu-worker] OCR source jobId=${jobId} ${sourceLabel}`);
|
|
2929
2929
|
|
|
2930
|
-
const tempDir = await mkdtemp(join(tmpdir(), "
|
|
2930
|
+
const tempDir = await mkdtemp(join(tmpdir(), "gu-ocr-worker-"));
|
|
2931
2931
|
let extractedText = "";
|
|
2932
2932
|
try {
|
|
2933
2933
|
const imagePath = join(
|
package/gu-repl.mjs
CHANGED
|
@@ -783,7 +783,6 @@ const COMMANDS = [
|
|
|
783
783
|
// The two synonyms were dispatched but declared nowhere (found by slash-command-wiring.test.mjs):
|
|
784
784
|
// they worked, Tab-completed to nothing, and appeared in no help. Aliases is what the registry
|
|
785
785
|
// already has for exactly this — /quit, /rag-local.
|
|
786
|
-
{ name: "/ask-again", aliases: ["/daemon-ask", "/model-setup-ask"], desc: "forget the remembered \"no\" for the model-setup prompt in this folder" },
|
|
787
786
|
{ name: "/host-mode", desc: "undo client mode — run agent turns on THIS machine again" },
|
|
788
787
|
{ name: "/server", desc: "pick a deployment server (host/user) for this session" },
|
|
789
788
|
{ name: "/proxy", usage: "/proxy [show|set|on|off|clear|test]", desc: "route MODEL calls through an HTTP proxy (local calls always go direct)" },
|
|
@@ -1795,7 +1794,7 @@ async function refreshAvailableUpdates() {
|
|
|
1795
1794
|
const { updatesAvailable } = await import("./version-check.mjs");
|
|
1796
1795
|
const cliLatest = await (async () => {
|
|
1797
1796
|
try {
|
|
1798
|
-
const r = await fetch("https://registry.npmjs.org/@tiens.nguyen%
|
|
1797
|
+
const r = await fetch("https://registry.npmjs.org/@tiens.nguyen%2Fgu-cli/latest", {
|
|
1799
1798
|
signal: AbortSignal.timeout(6000),
|
|
1800
1799
|
});
|
|
1801
1800
|
return r.ok ? (await r.json())?.version ?? "" : "";
|
|
@@ -9350,7 +9349,7 @@ async function main() {
|
|
|
9350
9349
|
servers: await (await import("./model-doctor.mjs")).discoverModelServers(),
|
|
9351
9350
|
});
|
|
9352
9351
|
const statusRows = [
|
|
9353
|
-
bold(cyan("
|
|
9352
|
+
bold(cyan("GU AGENT")) + dim(` v${REPL_VERSION}`),
|
|
9354
9353
|
// Task #134: a client machine OVERRIDES the account's agent model locally (the
|
|
9355
9354
|
// account value points at the model host's own server). Show what will actually be
|
|
9356
9355
|
// used, not a value this machine can't reach.
|
|
@@ -9422,7 +9421,7 @@ async function main() {
|
|
|
9422
9421
|
const skipDeps = await loadSessionSkip(resolve(process.cwd()), "skipDepsPrompt");
|
|
9423
9422
|
if (!skipDeps) {
|
|
9424
9423
|
console.log(
|
|
9425
|
-
dim("\
|
|
9424
|
+
dim("\ngu: the agent framework (smolagents + openai) isn't installed here —") +
|
|
9426
9425
|
dim("\n every turn will fail without it. It's a small pip install; no models.")
|
|
9427
9426
|
);
|
|
9428
9427
|
if (await askOr(false, () => askYesNo("Install the agent framework now?", true))) {
|
|
@@ -9469,10 +9468,10 @@ async function main() {
|
|
|
9469
9468
|
// to a local model setup here — its model is remote by design.
|
|
9470
9469
|
if (!clientModeBanner && !(await md.probeModelServer(chatUrl))) {
|
|
9471
9470
|
console.log(
|
|
9472
|
-
dim(`\
|
|
9471
|
+
dim(`\ngu: the chat model server (${md.modelRoot(chatUrl)}) isn't responding.`)
|
|
9473
9472
|
);
|
|
9474
9473
|
// Task #135: remember a "no" for this folder. `gu-cli doctor` still works
|
|
9475
|
-
// explicitly;
|
|
9474
|
+
// explicitly; `gu doctor` runs the same setup whenever it is wanted.
|
|
9476
9475
|
const skipSetup = await loadSessionSkip(resolve(process.cwd()), "skipModelSetupPrompt");
|
|
9477
9476
|
// Esc = "not now" rather than "yes": this branch DOWNLOADS a model, and a cancelled
|
|
9478
9477
|
// question must never be read as consent to start one.
|
|
@@ -9483,8 +9482,8 @@ async function main() {
|
|
|
9483
9482
|
);
|
|
9484
9483
|
if (skipSetup) {
|
|
9485
9484
|
console.log(
|
|
9486
|
-
dim(" Set-up skipped (remembered for this folder) — `gu doctor`
|
|
9487
|
-
dim("
|
|
9485
|
+
dim(" Set-up skipped (remembered for this folder) — `gu doctor` runs it ") +
|
|
9486
|
+
dim("any time.\n")
|
|
9488
9487
|
);
|
|
9489
9488
|
}
|
|
9490
9489
|
if (go) {
|
|
@@ -9980,24 +9979,6 @@ async function main() {
|
|
|
9980
9979
|
);
|
|
9981
9980
|
continue;
|
|
9982
9981
|
}
|
|
9983
|
-
if (line === "/ask-again" || line === "/daemon-ask" || line === "/model-setup-ask") {
|
|
9984
|
-
// Task #135: the way BACK from a remembered "no". Without this the choice is a
|
|
9985
|
-
// one-way door — a setup that later changes (model installed, daemon wanted) would
|
|
9986
|
-
// have no route to the prompt again short of editing the session file by hand.
|
|
9987
|
-
await saveSessionSkip(cwd, "skipDaemonPrompt", false);
|
|
9988
|
-
await saveSessionSkip(cwd, "skipModelSetupPrompt", false);
|
|
9989
|
-
await saveSessionSkip(cwd, "skipDepsPrompt", false);
|
|
9990
|
-
// The background worker is no longer among these: it is started and announced rather than
|
|
9991
|
-
// asked about (task #159), so there is no remembered "no" left to undo. `gu stop` is
|
|
9992
|
-
// the way to not have one running. Clearing its old key anyway, harmlessly, so a session
|
|
9993
|
-
// file written by an earlier version does not keep a setting nothing reads.
|
|
9994
|
-
console.log(
|
|
9995
|
-
cyan("startup prompts re-enabled") +
|
|
9996
|
-
dim(" — you'll be asked about model setup again next time you run `gu` here.\n") +
|
|
9997
|
-
dim(" (the background worker is started automatically now — `gu stop` to stop it.)\n")
|
|
9998
|
-
);
|
|
9999
|
-
continue;
|
|
10000
|
-
}
|
|
10001
9982
|
if (line === "/rag" || line === "/rag-cloud" || line === "/rag-local") {
|
|
10002
9983
|
// ONE QUESTION, THREE ANSWERS (task #180). This was two commands that toggled one flag,
|
|
10003
9984
|
// so the command you typed did not determine the state you got — and the reply named a
|
package/install-path.mjs
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* postinstall: make `gu` / `gu-cli` runnable from the NEXT terminal without the user
|
|
4
|
+
* ever editing a profile by hand.
|
|
5
|
+
*
|
|
6
|
+
* WHY: the support loop that prompted this file is always the same — a user on macOS or
|
|
7
|
+
* Ubuntu runs `npm install -g @tiens.nguyen/gu-cli`, npm puts the shims in a global bin
|
|
8
|
+
* dir their shell never heard of, and the very first command answers "gu: command not
|
|
9
|
+
* found". The CLI works; the PATH does not. Users file it as "the install is broken".
|
|
10
|
+
*
|
|
11
|
+
* WHAT THIS DOES, in order of preference:
|
|
12
|
+
* 1. Nothing, when the global bin dir is already on PATH (the common case with nvm,
|
|
13
|
+
* Homebrew-node, or a repointed npm prefix).
|
|
14
|
+
* 2. Appends ONE idempotent, marker-wrapped `export PATH=...` block to the user's
|
|
15
|
+
* shell rc file — .zshrc on macOS (default shell since Catalina), .bashrc on
|
|
16
|
+
* Ubuntu/Debian — creating the file only when that shell is the platform default.
|
|
17
|
+
* 3. Prints the exact `export` line for the CURRENT terminal, because a child process
|
|
18
|
+
* physically cannot `source` anything into its parent shell.
|
|
19
|
+
*
|
|
20
|
+
* HARD RULES:
|
|
21
|
+
* - NEVER fail the install. Every path through this file ends in exit 0; npm treats a
|
|
22
|
+
* postinstall failure as a failed install and the user gets nothing.
|
|
23
|
+
* - NEVER run on Windows (PATH there lives in the registry, and `gu`'s PowerShell
|
|
24
|
+
* alias problem is powershell-alias.mjs's job, explicitly interactive).
|
|
25
|
+
* - NEVER touch an rc file twice: the marker pair makes re-runs no-ops.
|
|
26
|
+
* - Under `sudo npm i -g`, write to the REAL user's home (SUDO_USER) and chown the
|
|
27
|
+
* file back, or the user's next login shell reads a root-owned rc file.
|
|
28
|
+
*/
|
|
29
|
+
import { appendFileSync, existsSync, readFileSync, chownSync, writeFileSync } from "node:fs";
|
|
30
|
+
import { homedir, platform, tmpdir } from "node:os";
|
|
31
|
+
import { join, resolve } from "node:path";
|
|
32
|
+
import { fileURLToPath } from "node:url";
|
|
33
|
+
|
|
34
|
+
const BEGIN = "# >>> gu-cli PATH >>>";
|
|
35
|
+
const END = "# <<< gu-cli PATH <<<";
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Self-execute ONLY as the postinstall entry point.
|
|
39
|
+
*
|
|
40
|
+
* WHY THE GUARD: scripts/preflight.mjs step 4 does `await import()` on every shipped
|
|
41
|
+
* module to prove it resolves. The first version of this file ran main() and called
|
|
42
|
+
* process.exit(0) at import time — the import KILLED the preflight runner mid-step,
|
|
43
|
+
* exit code 0, summary never printed. A module that exits its importer is a landmine;
|
|
44
|
+
* only the direct `node install-path.mjs` invocation (npm's postinstall) may run it.
|
|
45
|
+
*/
|
|
46
|
+
const isEntryPoint = (() => {
|
|
47
|
+
try {
|
|
48
|
+
return process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url);
|
|
49
|
+
} catch {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
})();
|
|
53
|
+
|
|
54
|
+
if (isEntryPoint) {
|
|
55
|
+
try {
|
|
56
|
+
main();
|
|
57
|
+
} catch (e) {
|
|
58
|
+
// A postinstall that throws uninstalls the package. Report, swallow, succeed.
|
|
59
|
+
console.log(`gu-cli: PATH setup skipped (${e.message}) — see \`gu-cli doctor\` if \`gu\` is not found.`);
|
|
60
|
+
}
|
|
61
|
+
process.exit(0);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function main() {
|
|
65
|
+
if (platform() === "win32") return; // registry PATH + PowerShell alias: not here
|
|
66
|
+
if (process.env.CI) return; // no user shell to help in CI
|
|
67
|
+
const prefix = process.env.npm_config_prefix;
|
|
68
|
+
if (!prefix) return; // not an npm install context
|
|
69
|
+
const binDir = join(prefix, "bin");
|
|
70
|
+
if (resolve(binDir).startsWith(resolve(tmpdir()))) return; // preflight's throwaway prefix
|
|
71
|
+
|
|
72
|
+
const onPath = (process.env.PATH || "").split(":").some((p) => p === binDir);
|
|
73
|
+
if (onPath) return; // nothing to fix — most installs
|
|
74
|
+
|
|
75
|
+
const home = realUserHome();
|
|
76
|
+
const targets = rcFilesFor(home);
|
|
77
|
+
const block = `${BEGIN}\n# npm's global bin dir was not on PATH; added by @tiens.nguyen/gu-cli postinstall.\nexport PATH="${binDir}:$PATH"\n${END}\n`;
|
|
78
|
+
|
|
79
|
+
let wrote = null;
|
|
80
|
+
for (const rc of targets) {
|
|
81
|
+
if (ensureBlock(rc, block)) { wrote = wrote || rc; }
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (wrote) {
|
|
85
|
+
console.log(`gu-cli: added ${binDir} to PATH in ${wrote}.`);
|
|
86
|
+
console.log(`gu-cli: open a NEW terminal to use \`gu\`, or run this once in the current one:`);
|
|
87
|
+
console.log(` export PATH="${binDir}:$PATH"`);
|
|
88
|
+
} else if (targets.length === 0) {
|
|
89
|
+
console.log(`gu-cli: ${binDir} is not on your PATH and no shell profile was found to update.`);
|
|
90
|
+
console.log(` export PATH="${binDir}:$PATH"`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** The installer's home — the invoking user's, not root's, under sudo. */
|
|
95
|
+
function realUserHome() {
|
|
96
|
+
const sudoUser = process.env.SUDO_USER;
|
|
97
|
+
if (sudoUser && process.getuid && process.getuid() === 0) {
|
|
98
|
+
return platform() === "darwin" ? join("/Users", sudoUser) : join("/home", sudoUser);
|
|
99
|
+
}
|
|
100
|
+
return homedir();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Which rc files to maintain, in order. macOS default is zsh; Ubuntu default is bash.
|
|
105
|
+
* A non-default shell's rc is updated only if the file already exists — we fix the
|
|
106
|
+
* setups people HAVE, we don't seed shells they don't use.
|
|
107
|
+
*/
|
|
108
|
+
function rcFilesFor(home) {
|
|
109
|
+
const zshrc = join(home, ".zshrc");
|
|
110
|
+
const bashrc = join(home, ".bashrc");
|
|
111
|
+
const out = [];
|
|
112
|
+
if (platform() === "darwin") {
|
|
113
|
+
out.push(zshrc);
|
|
114
|
+
if (existsSync(bashrc)) out.push(bashrc);
|
|
115
|
+
} else {
|
|
116
|
+
out.push(bashrc);
|
|
117
|
+
if (existsSync(zshrc)) out.push(zshrc);
|
|
118
|
+
}
|
|
119
|
+
return out;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** Append the block unless the marker is already there. True when the file changed. */
|
|
123
|
+
function ensureBlock(rcFile, block) {
|
|
124
|
+
const prior = existsSync(rcFile) ? readFileSync(rcFile, "utf8") : "";
|
|
125
|
+
if (prior.includes(BEGIN)) return false;
|
|
126
|
+
if (!existsSync(rcFile)) writeFileSync(rcFile, block);
|
|
127
|
+
else appendFileSync(rcFile, (prior.endsWith("\n") || prior === "" ? "" : "\n") + block);
|
|
128
|
+
chownBackToSudoUser(rcFile);
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** `sudo npm i -g` would otherwise leave a root-owned .zshrc the user cannot edit. */
|
|
133
|
+
function chownBackToSudoUser(file) {
|
|
134
|
+
const { SUDO_UID, SUDO_GID } = process.env;
|
|
135
|
+
if (SUDO_UID && SUDO_GID && process.getuid && process.getuid() === 0) {
|
|
136
|
+
try { chownSync(file, Number(SUDO_UID), Number(SUDO_GID)); } catch { /* best effort */ }
|
|
137
|
+
}
|
|
138
|
+
}
|
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.693",
|
|
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",
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
"gu": "gu-repl.mjs"
|
|
19
19
|
},
|
|
20
20
|
"scripts": {
|
|
21
|
+
"postinstall": "node install-path.mjs",
|
|
21
22
|
"test": "python3 -m unittest discover tests -v && node --test",
|
|
22
23
|
"test:js": "node --test",
|
|
23
24
|
"test:keys": "node --test tests/key-choice-regression.test.mjs tests/coding-key-line.test.mjs tests/coding-key-picker.test.mjs tests/coding-key-prompt.test.mjs tests/coding-key-notice.test.mjs tests/coding-key-fallback-line.test.mjs tests/manage-coding-providers.test.mjs tests/add-coding-provider.test.mjs tests/coding-provider-setup.test.mjs",
|
|
@@ -78,6 +79,7 @@
|
|
|
78
79
|
"model-doctor.mjs",
|
|
79
80
|
"coding-key-prompt.mjs",
|
|
80
81
|
"powershell-alias.mjs",
|
|
82
|
+
"install-path.mjs",
|
|
81
83
|
"gonext_probe_agent.py",
|
|
82
84
|
"gonext_agent_chat.py",
|
|
83
85
|
"gonext_transcribe.py",
|