@tiens.nguyen/gu-cli 1.0.686
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/README.md +52 -0
- package/agent-model-command.mjs +259 -0
- package/agent-model-label.mjs +159 -0
- package/clear-state.mjs +149 -0
- package/client-expert-api.mjs +736 -0
- package/client-expert-run.mjs +892 -0
- package/client-expert-setup.mjs +616 -0
- package/coding-choice-tags.mjs +69 -0
- package/coding-key-prompt.mjs +229 -0
- package/coding-provider-setup.mjs +808 -0
- package/completed-flush.mjs +105 -0
- package/daemon-control.mjs +462 -0
- package/device-login.mjs +212 -0
- package/doctor-check.mjs +239 -0
- package/embed-model-command.mjs +157 -0
- package/first-run-steps.mjs +171 -0
- package/gonext_agent_chat.py +12299 -0
- package/gonext_mlx_embed.py +155 -0
- package/gonext_probe_agent.py +93 -0
- package/gonext_transcribe.py +130 -0
- package/gu-cli.mjs +4930 -0
- package/gu-repl.mjs +10326 -0
- package/job-pools.mjs +89 -0
- package/model-doctor.mjs +1494 -0
- package/node-version.mjs +40 -0
- package/ollama-setup.mjs +832 -0
- package/package.json +100 -0
- package/platform-tools.mjs +520 -0
- package/poll-errors.mjs +141 -0
- package/proxy-command.mjs +165 -0
- package/proxy-config.mjs +255 -0
- package/proxy-dispatcher.mjs +132 -0
- package/proxy-selftest.mjs +234 -0
- package/proxy-store.mjs +69 -0
- package/rag-job-config.mjs +59 -0
- package/rag-selftest.mjs +215 -0
- package/s3-setup.mjs +85 -0
- package/terminal-copy.mjs +248 -0
- package/terminal-hover.mjs +153 -0
- package/terminal-layout.mjs +2507 -0
- package/terminal-viewport.mjs +602 -0
- package/thinking_words.txt +1003 -0
- package/version-check.mjs +72 -0
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The mandatory first-run steps, and what to say when one of them failed (task #159).
|
|
3
|
+
*
|
|
4
|
+
* WHY THIS MODULE EXISTS. Three of the four things `gu` asks on a fresh folder are not
|
|
5
|
+
* really questions — the product does not work if you answer no. A background worker that
|
|
6
|
+
* never starts means every turn hangs; an unregistered workspace means a coding agent that
|
|
7
|
+
* cannot read the code; a missing smolagents means every turn dies at import. The user's
|
|
8
|
+
* instruction (2026-08-09) was to stop asking and just say what is happening.
|
|
9
|
+
*
|
|
10
|
+
* WHICH CREATES THE PROBLEM THIS MODULE ACTUALLY SOLVES. A prompt has one accidental virtue:
|
|
11
|
+
* the user is present when it fails, so they see the failure. Doing the work silently moves
|
|
12
|
+
* the failure somewhere nobody is looking — and a mandatory step that failed quietly is worse
|
|
13
|
+
* than the prompt it replaced, because the next symptom is a turn that hangs with no
|
|
14
|
+
* explanation. So every step's outcome is RECORDED, and a failure that is still unfixed is
|
|
15
|
+
* REPORTED on the next start, with what to do about it.
|
|
16
|
+
*
|
|
17
|
+
* PURE ON PURPOSE. Nothing here starts a daemon, installs a package or touches the network:
|
|
18
|
+
* it takes facts (is the daemon up? are the libs importable?) and returns decisions and
|
|
19
|
+
* strings. That is what lets the interesting cases — a step that failed twice, a step that
|
|
20
|
+
* fixed itself, a step that is not applicable on this machine — be tested without a machine
|
|
21
|
+
* in each of those states. The IO lives at the call sites in gu-repl.mjs.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* The steps, in the order a first run does them.
|
|
26
|
+
*
|
|
27
|
+
* `mandatory` marks the ones with no workable "no" — those get done and announced. The trust
|
|
28
|
+
* question (allow the agent to RUN build/test commands) is deliberately NOT here: it is the one
|
|
29
|
+
* prompt of the four where "no" leaves a working configuration (a read/edit-only workspace) and
|
|
30
|
+
* where "yes" has a security consequence, so it stays a question until the user says otherwise.
|
|
31
|
+
*
|
|
32
|
+
* `retry` is the command a user can run by hand. Every failure notice ends with one, because
|
|
33
|
+
* "something failed" without a next move is just anxiety.
|
|
34
|
+
*/
|
|
35
|
+
export const FIRST_RUN_STEPS = [
|
|
36
|
+
{
|
|
37
|
+
id: "workspace",
|
|
38
|
+
mandatory: true,
|
|
39
|
+
title: "registering this folder",
|
|
40
|
+
// Present tense, because it is printed WHILE it happens. "Registered" is the past-tense
|
|
41
|
+
// confirmation and belongs in the result line, not here.
|
|
42
|
+
doing: "registering this folder so the agent can read and edit code here",
|
|
43
|
+
why: "the agent cannot read or edit any file in a folder that is not a registered workspace",
|
|
44
|
+
retry: "gu",
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
id: "agentLibs",
|
|
48
|
+
mandatory: true,
|
|
49
|
+
doing: "installing the agent framework (smolagents + openai)",
|
|
50
|
+
why: "every turn fails at import without it — \"No module named 'smolagents'\"",
|
|
51
|
+
retry: "gu doctor",
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
id: "daemon",
|
|
55
|
+
mandatory: true,
|
|
56
|
+
doing: "starting the background worker",
|
|
57
|
+
why: "nothing claims your turns without it, so a question would hang forever",
|
|
58
|
+
retry: "gu start",
|
|
59
|
+
},
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
const BY_ID = new Map(FIRST_RUN_STEPS.map((s) => [s.id, s]));
|
|
63
|
+
|
|
64
|
+
/** The step definition, or undefined for an id nothing knows about. */
|
|
65
|
+
export const stepById = (id) => BY_ID.get(id);
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* What still needs doing, given what is already true of this machine.
|
|
69
|
+
*
|
|
70
|
+
* `satisfied` is a map of stepId -> boolean, gathered by the caller from the real world
|
|
71
|
+
* (daemonStatus().running, hasAgentLibs(), the workspaces file). A step already satisfied is
|
|
72
|
+
* not "skipped" — there is simply nothing to do, and saying "starting the background worker"
|
|
73
|
+
* when one is already running would be a lie.
|
|
74
|
+
*
|
|
75
|
+
* clientMode is honoured here rather than at the call site because of a live failure recorded
|
|
76
|
+
* in gu-repl.mjs:4583 — a model-less droplet started a worker, won the race for a job, and
|
|
77
|
+
* failed it with no smolagents. A client-only machine runs no worker, so the step does not
|
|
78
|
+
* apply to it at all.
|
|
79
|
+
*/
|
|
80
|
+
export function planFirstRun({ satisfied = {}, clientMode = false } = {}) {
|
|
81
|
+
return FIRST_RUN_STEPS.filter((step) => {
|
|
82
|
+
if (step.id === "daemon" && clientMode) return false;
|
|
83
|
+
return satisfied[step.id] !== true;
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Record what happened to one step. Pure: returns the NEW state, never mutates the old one.
|
|
89
|
+
*
|
|
90
|
+
* A failure keeps its first-seen time and counts repeats, so the notice can distinguish "this
|
|
91
|
+
* failed once, probably transient" from "this has failed five times, stop trying to fix it by
|
|
92
|
+
* running the same command again". A success clears the record entirely — a step that recovered
|
|
93
|
+
* has nothing to report, and a stale warning about a problem that no longer exists is how users
|
|
94
|
+
* learn to ignore warnings.
|
|
95
|
+
*/
|
|
96
|
+
export function recordOutcome(state, { id, ok, detail = "", at = new Date().toISOString() } = {}) {
|
|
97
|
+
const next = { ...(state || {}) };
|
|
98
|
+
const failures = { ...(next.failures || {}) };
|
|
99
|
+
if (ok) {
|
|
100
|
+
delete failures[id];
|
|
101
|
+
} else {
|
|
102
|
+
const prev = failures[id];
|
|
103
|
+
failures[id] = {
|
|
104
|
+
detail: String(detail || "").trim(),
|
|
105
|
+
// firstAt survives the repeat so "since yesterday" stays true; lastAt moves.
|
|
106
|
+
firstAt: prev?.firstAt || at,
|
|
107
|
+
lastAt: at,
|
|
108
|
+
count: (prev?.count || 0) + 1,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
next.failures = failures;
|
|
112
|
+
return next;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* The failures worth telling the user about right now.
|
|
117
|
+
*
|
|
118
|
+
* TWO FILTERS, and both matter. A step that is satisfied NOW is dropped even though it failed
|
|
119
|
+
* before — the user may have installed the package by hand, or the port freed up, and nagging
|
|
120
|
+
* about a fixed problem trains people to skip the startup text. A step that does not apply to
|
|
121
|
+
* this machine (the worker on a client-only box) is dropped for the same reason.
|
|
122
|
+
*
|
|
123
|
+
* Returned in FIRST_RUN_STEPS order, not in failure order: the list should read the same way
|
|
124
|
+
* every time, and the order steps happen in is the order that makes causal sense (no workspace
|
|
125
|
+
* explains a lot of downstream noise).
|
|
126
|
+
*/
|
|
127
|
+
export function pendingFailures(state, { satisfied = {}, clientMode = false } = {}) {
|
|
128
|
+
const failures = state?.failures || {};
|
|
129
|
+
return FIRST_RUN_STEPS.filter((step) => {
|
|
130
|
+
if (!failures[step.id]) return false;
|
|
131
|
+
if (satisfied[step.id] === true) return false;
|
|
132
|
+
if (step.id === "daemon" && clientMode) return false;
|
|
133
|
+
return true;
|
|
134
|
+
}).map((step) => ({ ...failures[step.id], id: step.id, step }));
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* The lines shown at the top of a later run, one per unresolved failure.
|
|
139
|
+
*
|
|
140
|
+
* Returns an ARRAY OF PLAIN STRINGS with no colour and no gutter — the caller owns presentation,
|
|
141
|
+
* and a test that asserts on ANSI escapes is asserting on the wrong thing. Empty array means
|
|
142
|
+
* print nothing at all, which is the common case and must stay silent: an "all good" banner on
|
|
143
|
+
* every start is noise.
|
|
144
|
+
*
|
|
145
|
+
* Each line says WHAT failed, WHY it matters, and WHAT TO RUN. The detail (the actual error) is
|
|
146
|
+
* included when there is one, truncated — a pip resolver backtrace is not a startup banner.
|
|
147
|
+
*/
|
|
148
|
+
export function failureNotice(failures = []) {
|
|
149
|
+
const lines = [];
|
|
150
|
+
for (const f of failures) {
|
|
151
|
+
const times = f.count > 1 ? ` (failed ${f.count} times)` : "";
|
|
152
|
+
lines.push(`last time, ${f.step.doing} failed${times} — ${f.step.why}`);
|
|
153
|
+
if (f.detail) lines.push(` ${truncateDetail(f.detail)}`);
|
|
154
|
+
lines.push(` run \`${f.step.retry}\` to try again`);
|
|
155
|
+
}
|
|
156
|
+
return lines;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Keep an error to one readable line.
|
|
161
|
+
*
|
|
162
|
+
* From the TAIL, not the head: the useful part of a Python traceback or an npm error is the last
|
|
163
|
+
* line ("No matching distribution found for smolagents"), while the head is boilerplate. This is
|
|
164
|
+
* the same lesson as the plain-reply truncation fix — truncating a traceback from the front
|
|
165
|
+
* throws away the only sentence that says what went wrong.
|
|
166
|
+
*/
|
|
167
|
+
export function truncateDetail(detail, max = 160) {
|
|
168
|
+
const oneLine = String(detail).replace(/\s+/g, " ").trim();
|
|
169
|
+
if (oneLine.length <= max) return oneLine;
|
|
170
|
+
return "…" + oneLine.slice(oneLine.length - (max - 1));
|
|
171
|
+
}
|