@tiens.nguyen/gu-cli 1.0.866 → 1.0.867
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-repl.mjs +1 -1
- package/model-doctor.mjs +13 -1
- package/package.json +1 -1
- package/platform-tools.mjs +86 -0
- package/terminal-layout.mjs +1 -1
package/gu-repl.mjs
CHANGED
|
@@ -2935,7 +2935,7 @@ const footerBuilt = () =>
|
|
|
2935
2935
|
identity: identityLineText(),
|
|
2936
2936
|
token: tokenLineText(),
|
|
2937
2937
|
mode: modeLineText(),
|
|
2938
|
-
// Task #34: "rag:
|
|
2938
|
+
// Task #34: "rag: using grep as fallback" as a soft yellow note beside the mode, not an
|
|
2939
2939
|
// alarming red segment on the token row. yellow goes through paint(), so NO_COLOR turns it off.
|
|
2940
2940
|
// Issue #120: an auto-compaction this turn outranks the rag note (same soft yellow slot).
|
|
2941
2941
|
modeNote: compactLockNote() || compactBarNote(lastPromptBreakdown) || ragBarNote(lastPromptBreakdown),
|
package/model-doctor.mjs
CHANGED
|
@@ -12,6 +12,7 @@ import { open, mkdir, readdir, writeFile } from "node:fs/promises";
|
|
|
12
12
|
import { arch, homedir, platform, totalmem } from "node:os";
|
|
13
13
|
import { join } from "node:path";
|
|
14
14
|
import { fileURLToPath } from "node:url";
|
|
15
|
+
import { ensureSupportedPython, isBuildToolsMissing } from "./platform-tools.mjs";
|
|
15
16
|
|
|
16
17
|
// Minimum RAM to run the default 14B-4bit chat model comfortably on Apple Silicon.
|
|
17
18
|
const MIN_CHAT_RAM_GB = 16;
|
|
@@ -144,7 +145,8 @@ export function isPipMissing(output) {
|
|
|
144
145
|
/** Run pip once. Resolves { code, err } — never rejects, so the caller owns the policy. */
|
|
145
146
|
function runPip(python, pkgs, extraArgs = []) {
|
|
146
147
|
return new Promise((resolve) => {
|
|
147
|
-
|
|
148
|
+
// --prefer-binary: take an older wheel over a newer source build (a compiler is not a given, #225).
|
|
149
|
+
const child = spawn(python, ["-m", "pip", "install", "-U", "--prefer-binary", ...extraArgs, ...pkgs], {
|
|
148
150
|
// stderr is PIPED so the PEP 668 marker can be recognised, and echoed as it arrives so
|
|
149
151
|
// the user still watches a real install rather than a frozen line.
|
|
150
152
|
stdio: ["ignore", "inherit", "pipe"],
|
|
@@ -198,9 +200,19 @@ async function ensureVenv(log) {
|
|
|
198
200
|
* venv cannot be built, --break-system-packages, which is announced rather than done quietly.
|
|
199
201
|
*/
|
|
200
202
|
async function pipInstall(pkgs, log) {
|
|
203
|
+
// A Python newer than the wheels (3.15 -> PyYAML builds from source -> "Visual C++ 14.0 is required"): #225.
|
|
204
|
+
const sp = await ensureSupportedPython({ log });
|
|
205
|
+
if (!sp.ok) throw new Error(sp.message);
|
|
206
|
+
if (sp.switched) await pinPythonForDaemon({ log });
|
|
201
207
|
log(` installing ${pkgs.join(" ")} … (pip, this can take a minute)`);
|
|
202
208
|
let r = await runPip(python3Bin(), pkgs);
|
|
203
209
|
if (r.code === 0) return;
|
|
210
|
+
if (isBuildToolsMissing(r.err)) {
|
|
211
|
+
throw new Error(
|
|
212
|
+
"pip had to compile a package and this machine has no C++ compiler. Use Python 3.13 or 3.12 (64-bit), which " +
|
|
213
|
+
"has prebuilt packages: " + pythonInstallHint() + ". Then run gu doctor again."
|
|
214
|
+
);
|
|
215
|
+
}
|
|
204
216
|
const noPip = isPipMissing(r.err);
|
|
205
217
|
if (!noPip && !isExternallyManaged(r.err)) {
|
|
206
218
|
throw new Error(`pip install ${pkgs.join(" ")} exited ${r.code}`);
|
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.867",
|
|
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",
|
package/platform-tools.mjs
CHANGED
|
@@ -233,6 +233,92 @@ export async function resolvePython() {
|
|
|
233
233
|
: { cmd: "python3", args: [] };
|
|
234
234
|
}
|
|
235
235
|
|
|
236
|
+
/* ------------------------------------------- a Python new enough to lack wheels (#225) ---- */
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* The newest Python that gu's packages (PyYAML via smolagents, pillow, ...) ship prebuilt wheels for.
|
|
240
|
+
* Above it pip BUILDS FROM SOURCE, which on Windows asks for Visual C++ 14 and ends in
|
|
241
|
+
* "Microsoft Visual C++ 14.0 or greater is required" (reported with Python 3.15). Raise it as wheels land.
|
|
242
|
+
*/
|
|
243
|
+
export const PYTHON_WHEELS_MAX = [3, 14];
|
|
244
|
+
|
|
245
|
+
/** "3.15.0" / "3.15" -> [3, 15], or null when it is not a version. */
|
|
246
|
+
export function parsePyVersion(out) {
|
|
247
|
+
const m = /^\s*(\d+)\.(\d+)/.exec(String(out ?? ""));
|
|
248
|
+
return m ? [Number(m[1]), Number(m[2])] : null;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/** Is this [major, minor] newer than the newest Python with wheels? */
|
|
252
|
+
export function pythonTooNew(v, max = PYTHON_WHEELS_MAX) {
|
|
253
|
+
return Array.isArray(v) && (v[0] > max[0] || (v[0] === max[0] && v[1] > max[1]));
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/** Older interpreters to fall back to, best first (3.13, 3.12 are the most widely wheeled). */
|
|
257
|
+
export function altPythonCandidates(platform = process.platform) {
|
|
258
|
+
const minors = [13, 12, 14, 11, 10];
|
|
259
|
+
return platform === "win32"
|
|
260
|
+
? minors.map((m) => ({ cmd: "py", args: [`-3.${m}`] }))
|
|
261
|
+
: minors.map((m) => ({ cmd: `python3.${m}`, args: [] }));
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const EXE_PROBE = ["-c", "import sys; print(sys.executable)"];
|
|
265
|
+
const VER_PROBE = ["-c", "import sys; print('%d.%d' % sys.version_info[:2])"];
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* The first older interpreter that runs, as { cmd, args, exe } (exe = its real path), or null.
|
|
269
|
+
* `probe(cmd, args)` resolves stdout or null; injectable for the tests.
|
|
270
|
+
*/
|
|
271
|
+
export async function findSupportedPython({ platform = process.platform, probe = (c, a) => run(c, a, { timeout: 8000 }) } = {}) {
|
|
272
|
+
for (const c of altPythonCandidates(platform)) {
|
|
273
|
+
const exe = String((await probe(c.cmd, [...c.args, ...EXE_PROBE])) ?? "").trim();
|
|
274
|
+
if (exe && !/\n/.test(exe)) return { ...c, exe };
|
|
275
|
+
}
|
|
276
|
+
return null;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Make sure the Python gu will install packages into is one that has wheels.
|
|
281
|
+
* - GONEXT_PROBE_PYTHON set: the user chose; leave it.
|
|
282
|
+
* - default Python fine: nothing to do.
|
|
283
|
+
* - default too new: switch to an older one (sets GONEXT_PROBE_PYTHON in `env`, so every spawn in
|
|
284
|
+
* this process follows) or say plainly what to install. Never starts a build that cannot succeed.
|
|
285
|
+
* Resolves { ok, switched?, from?, message? }.
|
|
286
|
+
*/
|
|
287
|
+
export async function ensureSupportedPython({
|
|
288
|
+
platform = process.platform,
|
|
289
|
+
env = process.env,
|
|
290
|
+
probe = (c, a) => run(c, a, { timeout: 8000 }),
|
|
291
|
+
log = () => {},
|
|
292
|
+
} = {}) {
|
|
293
|
+
if ((env.GONEXT_PROBE_PYTHON ?? env.GONEXT_MLX_LM_PYTHON ?? "").trim()) return { ok: true };
|
|
294
|
+
let version = null;
|
|
295
|
+
for (const c of pythonCandidates(platform)) {
|
|
296
|
+
version = parsePyVersion(await probe(c.cmd, [...c.args, ...VER_PROBE]));
|
|
297
|
+
if (version) break;
|
|
298
|
+
}
|
|
299
|
+
if (!version || !pythonTooNew(version)) return { ok: true };
|
|
300
|
+
const from = version.join(".");
|
|
301
|
+
const alt = await findSupportedPython({ platform, probe });
|
|
302
|
+
if (alt) {
|
|
303
|
+
env.GONEXT_PROBE_PYTHON = alt.exe;
|
|
304
|
+
log(` Python ${from} is too new for gu's packages (no prebuilt wheels yet) - using ${alt.exe} instead.`);
|
|
305
|
+
return { ok: true, switched: alt.exe, from };
|
|
306
|
+
}
|
|
307
|
+
const how = platform === "win32" ? "winget install Python.Python.3.13, or python.org" : "your package manager, or python.org";
|
|
308
|
+
return {
|
|
309
|
+
ok: false,
|
|
310
|
+
from,
|
|
311
|
+
message:
|
|
312
|
+
`Python ${from} is too new: gu's packages (PyYAML and others) have no prebuilt ${platform === "win32" ? "Windows " : ""}` +
|
|
313
|
+
`wheels for it yet, so pip would try to compile them. Install Python 3.13 (${how}), open a new terminal, then run gu doctor again.`,
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/** pip's "I had to compile and there is no compiler" failure. */
|
|
318
|
+
export function isBuildToolsMissing(output) {
|
|
319
|
+
return /Microsoft Visual C\+\+ 14\.0 or greater is required|Failed building wheel for|error: command '(?:gcc|cl\.exe)' failed/i.test(String(output ?? ""));
|
|
320
|
+
}
|
|
321
|
+
|
|
236
322
|
/** How to SAY the resolved interpreter in a log line or error message. */
|
|
237
323
|
export const pythonLabel = (py) => [py.cmd, ...py.args].join(" ");
|
|
238
324
|
|
package/terminal-layout.mjs
CHANGED
|
@@ -148,7 +148,7 @@ export function ragSegmentLabel(b) {
|
|
|
148
148
|
switch (b.ragState) {
|
|
149
149
|
// Task #34: no "✗" — search still works through grep, so this is a notice, not an error.
|
|
150
150
|
case "unreachable":
|
|
151
|
-
return "rag:
|
|
151
|
+
return "rag: using grep as fallback";
|
|
152
152
|
case "no-index":
|
|
153
153
|
return "rag — not indexed";
|
|
154
154
|
case "unavailable":
|