@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.
Files changed (43) hide show
  1. package/README.md +52 -0
  2. package/agent-model-command.mjs +259 -0
  3. package/agent-model-label.mjs +159 -0
  4. package/clear-state.mjs +149 -0
  5. package/client-expert-api.mjs +736 -0
  6. package/client-expert-run.mjs +892 -0
  7. package/client-expert-setup.mjs +616 -0
  8. package/coding-choice-tags.mjs +69 -0
  9. package/coding-key-prompt.mjs +229 -0
  10. package/coding-provider-setup.mjs +808 -0
  11. package/completed-flush.mjs +105 -0
  12. package/daemon-control.mjs +462 -0
  13. package/device-login.mjs +212 -0
  14. package/doctor-check.mjs +239 -0
  15. package/embed-model-command.mjs +157 -0
  16. package/first-run-steps.mjs +171 -0
  17. package/gonext_agent_chat.py +12299 -0
  18. package/gonext_mlx_embed.py +155 -0
  19. package/gonext_probe_agent.py +93 -0
  20. package/gonext_transcribe.py +130 -0
  21. package/gu-cli.mjs +4930 -0
  22. package/gu-repl.mjs +10326 -0
  23. package/job-pools.mjs +89 -0
  24. package/model-doctor.mjs +1494 -0
  25. package/node-version.mjs +40 -0
  26. package/ollama-setup.mjs +832 -0
  27. package/package.json +100 -0
  28. package/platform-tools.mjs +520 -0
  29. package/poll-errors.mjs +141 -0
  30. package/proxy-command.mjs +165 -0
  31. package/proxy-config.mjs +255 -0
  32. package/proxy-dispatcher.mjs +132 -0
  33. package/proxy-selftest.mjs +234 -0
  34. package/proxy-store.mjs +69 -0
  35. package/rag-job-config.mjs +59 -0
  36. package/rag-selftest.mjs +215 -0
  37. package/s3-setup.mjs +85 -0
  38. package/terminal-copy.mjs +248 -0
  39. package/terminal-hover.mjs +153 -0
  40. package/terminal-layout.mjs +2507 -0
  41. package/terminal-viewport.mjs +602 -0
  42. package/thinking_words.txt +1003 -0
  43. 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
+ }