@ouro.bot/cli 0.1.0-alpha.445 → 0.1.0-alpha.446
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 +1 -1
- package/changelog.json +8 -0
- package/dist/heart/daemon/agentic-repair.js +2 -0
- package/dist/heart/daemon/cli-exec.js +2 -0
- package/dist/heart/daemon/connect-bay.js +69 -245
- package/dist/heart/daemon/human-command-screens.js +112 -34
- package/dist/heart/daemon/interactive-repair.js +96 -9
- package/dist/heart/daemon/terminal-ui.js +200 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -99,7 +99,7 @@ Task docs do not live in this repo anymore. Planning and doing docs live in the
|
|
|
99
99
|
- Vault unlock material is local machine state. Prefer macOS Keychain, Windows DPAPI, or Linux Secret Service; plaintext fallback is allowed only by explicit human choice.
|
|
100
100
|
- New vault unlock secrets are confirmed before use and rejected if they do not meet the minimum strength requirements.
|
|
101
101
|
- Provider and runtime credentials are loaded into process memory at startup/auth/unlock/refresh and reused. The remote vault is not queried for every model or sense request.
|
|
102
|
-
- Human TTY commands share one CLI surface family: bare `ouro` opens the home deck, `ouro up
|
|
102
|
+
- Human TTY commands share one CLI surface family: bare `ouro` opens the home deck, `ouro up` uses the boot checklist, `ouro connect`/`ouro auth verify`/`ouro repair` reuse the same readiness truth, and `ouro help`/`ouro whoami`/`ouro versions`/`ouro hatch` render through the same Ouro-branded wizard/guide language instead of raw transcript walls.
|
|
103
103
|
- Human-facing CLI commands that can wait on browser auth, vault IO, daemon startup, daemon restart, provider checks, or connector setup use a shared progress checklist. If a cursor may blink for more than a few seconds, the command should print or animate the current step instead of going quiet.
|
|
104
104
|
- CLI commands that mutate bundle config, such as vault setup or `ouro connect bluebubbles`, run bundle sync after the change when `sync.enabled` is true and report a compact `bundle sync:` line.
|
|
105
105
|
- The daemon discovers bundles dynamically from `~/AgentBundles`.
|
package/changelog.json
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"_note": "This changelog is maintained as part of the PR/version-bump workflow. Agent-curated, not auto-generated. Agents read this file directly via read_file to understand what changed between versions.",
|
|
3
3
|
"versions": [
|
|
4
|
+
{
|
|
5
|
+
"version": "0.1.0-alpha.446",
|
|
6
|
+
"changes": [
|
|
7
|
+
"`ouro connect`, the home deck, readiness repair prompts, and the interactive repair queue now render through one shared wizard language instead of a mix of framed panels and transcript walls. Humans can choose by number or name, see one recommended next step, and keep the same visual footing across setup and repair.",
|
|
8
|
+
"Connector, auth, vault, hatch, and other info-heavy command flows now use a matching guide surface with the same Ouro masthead, ruled sections, and `Next moves` treatment, so the CLI stops jumping between unrelated visual grammars as soon as a command leaves the root menu.",
|
|
9
|
+
"The new surfaces stay truthful to the underlying work: connect still runs the shared live provider verification path before rendering, repair prompts surface the real next command without dead air, and new renderer plus command-layer coverage lock the wizard/guide family into the shipped CLI."
|
|
10
|
+
]
|
|
11
|
+
},
|
|
4
12
|
{
|
|
5
13
|
"version": "0.1.0-alpha.445",
|
|
6
14
|
"changes": [
|
|
@@ -52,6 +52,8 @@ function makeInteractiveRepairDeps(deps) {
|
|
|
52
52
|
runAuthFlow: deps.runAuthFlow ?? (async () => undefined),
|
|
53
53
|
runVaultUnlock: deps.runVaultUnlock,
|
|
54
54
|
skipQueueSummary: deps.skipQueueSummary,
|
|
55
|
+
isTTY: deps.isTTY,
|
|
56
|
+
stdoutColumns: deps.stdoutColumns,
|
|
55
57
|
};
|
|
56
58
|
}
|
|
57
59
|
async function runDeterministicRepair(degraded, deps) {
|
|
@@ -4316,6 +4316,8 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
|
|
|
4316
4316
|
await executeVaultUnlock({ kind: "vault.unlock", agent }, deps);
|
|
4317
4317
|
},
|
|
4318
4318
|
skipQueueSummary: true,
|
|
4319
|
+
isTTY: deps.isTTY ?? process.stdout.isTTY === true,
|
|
4320
|
+
stdoutColumns: deps.stdoutColumns ?? process.stdout.columns,
|
|
4319
4321
|
});
|
|
4320
4322
|
if (repairResult.repairsAttempted) {
|
|
4321
4323
|
repairsAttempted = true;
|
|
@@ -17,52 +17,6 @@ const CONNECT_STATUS_PRIORITY = {
|
|
|
17
17
|
ready: 6,
|
|
18
18
|
attached: 6,
|
|
19
19
|
};
|
|
20
|
-
const RESET = "\x1b[0m";
|
|
21
|
-
const BOLD = "\x1b[1m";
|
|
22
|
-
const TEAL = "\x1b[38;2;78;201;176m";
|
|
23
|
-
const GREEN = "\x1b[38;2;46;204;64m";
|
|
24
|
-
const GOLD = "\x1b[38;2;230;190;50m";
|
|
25
|
-
const BONE = "\x1b[38;2;238;242;234m";
|
|
26
|
-
const MIST = "\x1b[38;2;165;184;168m";
|
|
27
|
-
const ANSI_RE = /\x1b\[[0-9;]*m/g;
|
|
28
|
-
function stripAnsi(text) {
|
|
29
|
-
return text.replace(ANSI_RE, "");
|
|
30
|
-
}
|
|
31
|
-
function visibleLength(text) {
|
|
32
|
-
return stripAnsi(text).length;
|
|
33
|
-
}
|
|
34
|
-
function padAnsi(text, width) {
|
|
35
|
-
const missing = Math.max(0, width - visibleLength(text));
|
|
36
|
-
return `${text}${" ".repeat(missing)}`;
|
|
37
|
-
}
|
|
38
|
-
function wrapPlain(text, width) {
|
|
39
|
-
const normalized = text.trim();
|
|
40
|
-
if (!normalized)
|
|
41
|
-
return [""];
|
|
42
|
-
const words = normalized.split(/\s+/);
|
|
43
|
-
const lines = [];
|
|
44
|
-
let current = "";
|
|
45
|
-
for (const word of words) {
|
|
46
|
-
if (!current) {
|
|
47
|
-
current = word;
|
|
48
|
-
continue;
|
|
49
|
-
}
|
|
50
|
-
const candidate = `${current} ${word}`;
|
|
51
|
-
if (candidate.length <= width) {
|
|
52
|
-
current = candidate;
|
|
53
|
-
continue;
|
|
54
|
-
}
|
|
55
|
-
lines.push(current);
|
|
56
|
-
current = word;
|
|
57
|
-
}
|
|
58
|
-
lines.push(current);
|
|
59
|
-
return lines;
|
|
60
|
-
}
|
|
61
|
-
function tty(text, color, bold = false) {
|
|
62
|
-
if (bold)
|
|
63
|
-
return `${color}${BOLD}${text}${RESET}`;
|
|
64
|
-
return `${color}${text}${RESET}`;
|
|
65
|
-
}
|
|
66
20
|
function escapeRegExp(value) {
|
|
67
21
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
68
22
|
}
|
|
@@ -133,213 +87,65 @@ function resolveProviderHealthCommand(providerHealth, status) {
|
|
|
133
87
|
function isProblemStatus(status) {
|
|
134
88
|
return status !== "ready" && status !== "attached";
|
|
135
89
|
}
|
|
136
|
-
function
|
|
137
|
-
|
|
138
|
-
? "●"
|
|
139
|
-
: status === "not attached"
|
|
140
|
-
? "◌"
|
|
141
|
-
: "◆";
|
|
142
|
-
const label = `${symbol} ${status}`;
|
|
143
|
-
if (status === "ready" || status === "attached")
|
|
144
|
-
return tty(label, GREEN, true);
|
|
145
|
-
if (status === "not attached")
|
|
146
|
-
return tty(label, MIST);
|
|
147
|
-
return tty(label, GOLD, true);
|
|
148
|
-
}
|
|
149
|
-
function sectionTitle(title, width) {
|
|
150
|
-
const plain = `╭─ ${title} `;
|
|
151
|
-
const rule = "─".repeat(Math.max(0, width - plain.length - 1));
|
|
152
|
-
return `${tty("╭─ ", TEAL)}${tty(title, BONE, true)}${tty(` ${rule}╮`, TEAL)}`;
|
|
153
|
-
}
|
|
154
|
-
function bottomRule(width) {
|
|
155
|
-
const line = `╰${"─".repeat(Math.max(0, width - 2))}╯`;
|
|
156
|
-
return tty(line, TEAL);
|
|
157
|
-
}
|
|
158
|
-
function bodyLine(text, width) {
|
|
159
|
-
const padded = padAnsi(text, Math.max(0, width - 4));
|
|
160
|
-
return `${tty("│ ", TEAL)}${padded}${tty(" │", TEAL)}`;
|
|
161
|
-
}
|
|
162
|
-
function panel(title, body, width) {
|
|
163
|
-
const lines = [sectionTitle(title, width)];
|
|
164
|
-
for (const line of body) {
|
|
165
|
-
lines.push(bodyLine(line, width));
|
|
166
|
-
}
|
|
167
|
-
lines.push(bottomRule(width));
|
|
168
|
-
return lines;
|
|
169
|
-
}
|
|
170
|
-
function renderHeader(agent, width) {
|
|
171
|
-
return panel(`${agent} connections`, [
|
|
172
|
-
tty("Set up or review one capability at a time.", BONE, true),
|
|
173
|
-
tty("Everything on this screen was checked live just now.", MIST),
|
|
174
|
-
], width);
|
|
90
|
+
function providerEntrySummary(entry) {
|
|
91
|
+
return entry.description ?? "Selected provider lanes for this machine.";
|
|
175
92
|
}
|
|
176
|
-
function
|
|
177
|
-
if (!entry) {
|
|
178
|
-
return [
|
|
179
|
-
tty("Everything here is ready.", BONE, true),
|
|
180
|
-
tty("Pick what you want to review or refresh.", MIST),
|
|
181
|
-
];
|
|
182
|
-
}
|
|
183
|
-
const lines = [
|
|
184
|
-
`${entry.name} ${statusChip(entry.status)}`,
|
|
185
|
-
];
|
|
186
|
-
if (entry.nextNote)
|
|
187
|
-
lines.push(entry.nextNote);
|
|
188
|
-
if (entry.nextAction)
|
|
189
|
-
lines.push(tty(entry.nextAction, MIST));
|
|
190
|
-
return lines;
|
|
191
|
-
}
|
|
192
|
-
function renderProviderBody(entry, width) {
|
|
193
|
-
const lines = [
|
|
194
|
-
`${entry.option} ${entry.name} ${statusChip(entry.status)}`,
|
|
195
|
-
];
|
|
196
|
-
const lanes = entry.laneSummaries ?? [];
|
|
197
|
-
for (const [index, lane] of lanes.entries()) {
|
|
198
|
-
if (index > 0)
|
|
199
|
-
lines.push("");
|
|
200
|
-
const laneLabel = lane.lane === "outward" ? "Outward lane" : "Inner lane";
|
|
201
|
-
lines.push(tty(laneLabel, BONE, true));
|
|
202
|
-
lines.push(lane.title);
|
|
203
|
-
lines.push(isProblemStatus(lane.status) ? lane.detail : tty(lane.detail, MIST));
|
|
204
|
-
}
|
|
205
|
-
if (lanes.length === 0) {
|
|
206
|
-
for (const detail of entry.detailLines ?? [])
|
|
207
|
-
lines.push(detail);
|
|
208
|
-
}
|
|
209
|
-
return normalizeWrappedBody(lines, width);
|
|
210
|
-
}
|
|
211
|
-
function renderCapabilityBody(entries, width) {
|
|
93
|
+
function providerEntryDetailLines(entry) {
|
|
212
94
|
const lines = [];
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
lines.push("");
|
|
216
|
-
lines.push(`${entry.option} ${entry.name} ${statusChip(entry.status)}`);
|
|
217
|
-
if (entry.description) {
|
|
218
|
-
lines.push(isProblemStatus(entry.status) ? entry.description : tty(entry.description, MIST));
|
|
219
|
-
}
|
|
220
|
-
for (const detail of entry.detailLines ?? []) {
|
|
221
|
-
lines.push(detail);
|
|
222
|
-
}
|
|
95
|
+
if (entry.nextNote && !/^(Outward|Inner) lane: /.test(entry.nextNote)) {
|
|
96
|
+
lines.push(entry.nextNote);
|
|
223
97
|
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
for (const line of lines) {
|
|
229
|
-
if (!line) {
|
|
230
|
-
wrapped.push("");
|
|
231
|
-
continue;
|
|
232
|
-
}
|
|
233
|
-
const plain = stripAnsi(line);
|
|
234
|
-
if (plain.length <= width - 4) {
|
|
235
|
-
wrapped.push(line);
|
|
236
|
-
continue;
|
|
98
|
+
if (entry.laneSummaries && entry.laneSummaries.length > 0) {
|
|
99
|
+
for (const lane of entry.laneSummaries) {
|
|
100
|
+
const laneLabel = lane.lane === "outward" ? "Outward lane" : "Inner lane";
|
|
101
|
+
lines.push(`${laneLabel}: ${lane.title} — ${lane.detail}`);
|
|
237
102
|
}
|
|
238
|
-
|
|
239
|
-
wrapped.push(...segments);
|
|
103
|
+
return lines;
|
|
240
104
|
}
|
|
241
|
-
return
|
|
105
|
+
return [...lines, ...(entry.detailLines ?? [])];
|
|
242
106
|
}
|
|
243
|
-
function
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
lines.push("");
|
|
248
|
-
lines.push(...panelLines);
|
|
249
|
-
}
|
|
250
|
-
return lines;
|
|
251
|
-
}
|
|
252
|
-
function combineColumns(left, right, leftWidth, rightWidth, gap = 2) {
|
|
253
|
-
const total = Math.max(left.length, right.length);
|
|
254
|
-
const lines = [];
|
|
255
|
-
for (let index = 0; index < total; index += 1) {
|
|
256
|
-
const leftLine = left[index] ?? " ".repeat(leftWidth);
|
|
257
|
-
const rightLine = right[index] ?? " ".repeat(rightWidth);
|
|
258
|
-
lines.push(`${padAnsi(leftLine, leftWidth)}${" ".repeat(gap)}${padAnsi(rightLine, rightWidth)}`);
|
|
259
|
-
}
|
|
260
|
-
return lines;
|
|
261
|
-
}
|
|
262
|
-
function renderTtyBay(entries, options) {
|
|
263
|
-
const columns = Math.max(options.columns ?? 108, 72);
|
|
264
|
-
const fullWidth = Math.max(56, columns - 2);
|
|
265
|
-
const masthead = (0, terminal_ui_1.renderOuroMasthead)({
|
|
266
|
-
isTTY: true,
|
|
267
|
-
columns,
|
|
268
|
-
subtitle: "Set up connections one step at a time.",
|
|
269
|
-
}).trimEnd();
|
|
270
|
-
const header = renderHeader(options.agent, fullWidth);
|
|
271
|
-
const nextEntry = entries.find((entry) => isProblemStatus(entry.status));
|
|
272
|
-
const providerEntry = entries.find((entry) => entry.section === "Providers");
|
|
273
|
-
const portableEntries = entries.filter((entry) => entry.section === "Portable");
|
|
274
|
-
const machineEntries = entries.filter((entry) => entry.section === "This machine");
|
|
275
|
-
const wide = columns >= 118;
|
|
276
|
-
const footer = [
|
|
277
|
-
tty("Choose a number, or type the capability name.", MIST),
|
|
278
|
-
options.prompt,
|
|
107
|
+
function capabilityEntryDetailLines(entry) {
|
|
108
|
+
return [
|
|
109
|
+
...(entry.detailLines ?? []),
|
|
110
|
+
...(entry.nextNote ? [entry.nextNote] : []),
|
|
279
111
|
];
|
|
280
|
-
if (!wide) {
|
|
281
|
-
const panels = [
|
|
282
|
-
header,
|
|
283
|
-
panel("Recommended next step", nextMoveBody(nextEntry), fullWidth),
|
|
284
|
-
panel("Providers", renderProviderBody(providerEntry, fullWidth), fullWidth),
|
|
285
|
-
panel("Portable", renderCapabilityBody(portableEntries, fullWidth), fullWidth),
|
|
286
|
-
panel("This machine", renderCapabilityBody(machineEntries, fullWidth), fullWidth),
|
|
287
|
-
];
|
|
288
|
-
return [masthead, "", ...stackPanels(panels), "", ...footer].join("\n");
|
|
289
|
-
}
|
|
290
|
-
const gap = 2;
|
|
291
|
-
const leftWidth = Math.max(52, Math.floor((fullWidth - gap) / 2));
|
|
292
|
-
const rightWidth = Math.max(40, fullWidth - gap - leftWidth);
|
|
293
|
-
const topRow = combineColumns(panel("Recommended next step", nextMoveBody(nextEntry), leftWidth), panel("This machine", renderCapabilityBody(machineEntries, rightWidth), rightWidth), leftWidth, rightWidth, gap);
|
|
294
|
-
const bottomRow = combineColumns(panel("Providers", renderProviderBody(providerEntry, leftWidth), leftWidth), panel("Portable", renderCapabilityBody(portableEntries, rightWidth), rightWidth), leftWidth, rightWidth, gap);
|
|
295
|
-
return [masthead, "", ...header, "", ...topRow, "", ...bottomRow, "", ...footer].join("\n");
|
|
296
112
|
}
|
|
297
|
-
function
|
|
113
|
+
function entryToWizardItem(entry) {
|
|
114
|
+
return {
|
|
115
|
+
key: entry.option,
|
|
116
|
+
label: entry.name,
|
|
117
|
+
status: entry.status,
|
|
118
|
+
...(entry.section === "Providers"
|
|
119
|
+
? { summary: providerEntrySummary(entry) }
|
|
120
|
+
: entry.description
|
|
121
|
+
? { summary: entry.description }
|
|
122
|
+
: {}),
|
|
123
|
+
detailLines: entry.section === "Providers"
|
|
124
|
+
? providerEntryDetailLines(entry)
|
|
125
|
+
: capabilityEntryDetailLines(entry),
|
|
126
|
+
...(entry.nextAction ? { command: entry.nextAction } : {}),
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
function nextStepFor(entries) {
|
|
298
130
|
const nextEntry = entries.find((entry) => isProblemStatus(entry.status));
|
|
299
|
-
const lines = [
|
|
300
|
-
`${options.agent} connections`,
|
|
301
|
-
"Set up or review one capability at a time. Provider status was checked live just now.",
|
|
302
|
-
"",
|
|
303
|
-
"Recommended next step",
|
|
304
|
-
"---------------------",
|
|
305
|
-
];
|
|
306
131
|
if (!nextEntry) {
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
if (nextEntry.nextNote)
|
|
312
|
-
lines.push(nextEntry.nextNote);
|
|
313
|
-
if (nextEntry.nextAction)
|
|
314
|
-
lines.push(`run: ${nextEntry.nextAction}`);
|
|
315
|
-
}
|
|
316
|
-
lines.push("");
|
|
317
|
-
for (const section of ["Providers", "Portable", "This machine"]) {
|
|
318
|
-
lines.push(section);
|
|
319
|
-
lines.push("-".repeat(Math.max(6, section.length + 4)));
|
|
320
|
-
for (const entry of entries.filter((candidate) => candidate.section === section)) {
|
|
321
|
-
lines.push(`${entry.option}. ${entry.name} [${entry.status}]`);
|
|
322
|
-
if (entry.laneSummaries && entry.laneSummaries.length > 0) {
|
|
323
|
-
for (const lane of entry.laneSummaries) {
|
|
324
|
-
const laneLabel = lane.lane === "outward" ? "Outward lane" : "Inner lane";
|
|
325
|
-
lines.push(` ${laneLabel}: ${lane.title}`);
|
|
326
|
-
lines.push(` ${lane.detail}`);
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
else {
|
|
330
|
-
for (const detail of entry.detailLines ?? [])
|
|
331
|
-
lines.push(` ${detail}`);
|
|
332
|
-
}
|
|
333
|
-
if (entry.description)
|
|
334
|
-
lines.push(` ${entry.description}`);
|
|
335
|
-
lines.push("");
|
|
336
|
-
}
|
|
132
|
+
return {
|
|
133
|
+
label: "Everything here is already connected.",
|
|
134
|
+
detail: "Pick any capability if you want to review it, refresh it, or change its setup.",
|
|
135
|
+
};
|
|
337
136
|
}
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
137
|
+
return {
|
|
138
|
+
label: `Start with ${nextEntry.name}.`,
|
|
139
|
+
detail: nextEntry.nextNote ?? nextEntry.description ?? `Status: ${nextEntry.status}.`,
|
|
140
|
+
command: nextEntry.nextAction,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
function sectionToWizard(entries, section, summary) {
|
|
144
|
+
return {
|
|
145
|
+
title: section,
|
|
146
|
+
summary,
|
|
147
|
+
items: entries.filter((entry) => entry.section === section).map((entry) => entryToWizardItem(entry)),
|
|
148
|
+
};
|
|
343
149
|
}
|
|
344
150
|
function summarizeProviderLane(agent, lane, providerHealth) {
|
|
345
151
|
const providerHealthStatus = resolveProviderHealthStatus(providerHealth);
|
|
@@ -450,7 +256,25 @@ function renderConnectBay(entries, options) {
|
|
|
450
256
|
columns: options.columns ?? null,
|
|
451
257
|
},
|
|
452
258
|
});
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
259
|
+
return (0, terminal_ui_1.renderTerminalWizard)({
|
|
260
|
+
isTTY: options.isTTY,
|
|
261
|
+
columns: options.columns,
|
|
262
|
+
masthead: {
|
|
263
|
+
subtitle: "Set up connections one step at a time.",
|
|
264
|
+
},
|
|
265
|
+
title: `Connect ${options.agent}`,
|
|
266
|
+
summary: "Choose one capability to bring online. Each row tells you whether Ouro checked it live just now or is showing saved setup on this machine.",
|
|
267
|
+
nextStep: nextStepFor(entries),
|
|
268
|
+
sections: [
|
|
269
|
+
sectionToWizard(entries, "Providers", "Selected outward and inner lanes for this machine."),
|
|
270
|
+
sectionToWizard(entries, "Portable", "These travel with the agent bundle when their secrets are portable."),
|
|
271
|
+
sectionToWizard(entries, "This machine", "These depend on local attachments or machine-specific setup."),
|
|
272
|
+
],
|
|
273
|
+
footerLines: [
|
|
274
|
+
"6. Not now",
|
|
275
|
+
"Choose a number, or type the capability name.",
|
|
276
|
+
],
|
|
277
|
+
prompt: options.prompt,
|
|
278
|
+
suppressEvent: true,
|
|
279
|
+
});
|
|
456
280
|
}
|
|
@@ -17,6 +17,34 @@ function renderScreenEvent(screen) {
|
|
|
17
17
|
meta: { screen },
|
|
18
18
|
});
|
|
19
19
|
}
|
|
20
|
+
function homeActionSummary(action) {
|
|
21
|
+
switch (action.kind) {
|
|
22
|
+
case "chat":
|
|
23
|
+
return `Open chat with ${action.agent}.`;
|
|
24
|
+
case "up":
|
|
25
|
+
return "Start the local runtime and check what still needs attention.";
|
|
26
|
+
case "connect":
|
|
27
|
+
return "Set up providers, portable tools, and machine-specific attachments.";
|
|
28
|
+
case "repair":
|
|
29
|
+
return "Walk through repairs for anything blocking startup or chat.";
|
|
30
|
+
case "help":
|
|
31
|
+
return "Show the command guide.";
|
|
32
|
+
case "hatch":
|
|
33
|
+
return "Create a new agent on this machine.";
|
|
34
|
+
case "clone":
|
|
35
|
+
return "Bring an existing bundle onto this machine.";
|
|
36
|
+
case "exit":
|
|
37
|
+
return "Leave the prompt.";
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function actionToWizardItem(action) {
|
|
41
|
+
return {
|
|
42
|
+
key: action.key,
|
|
43
|
+
label: action.label,
|
|
44
|
+
summary: homeActionSummary(action),
|
|
45
|
+
...(action.key === "1" ? { recommended: true } : {}),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
20
48
|
function buildOuroHomeActions(agents) {
|
|
21
49
|
if (agents.length === 0) {
|
|
22
50
|
return [
|
|
@@ -57,21 +85,28 @@ function resolveOuroHomeAction(answer, actions) {
|
|
|
57
85
|
function renderOuroHomeScreen(options) {
|
|
58
86
|
renderScreenEvent("home");
|
|
59
87
|
const actions = buildOuroHomeActions(options.agents);
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
:
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
88
|
+
const chatActions = actions.filter((action) => action.kind === "chat");
|
|
89
|
+
const setupActions = actions.filter((action) => action.kind !== "chat");
|
|
90
|
+
const sections = options.agents.length === 0
|
|
91
|
+
? [
|
|
92
|
+
{
|
|
93
|
+
title: "Start here",
|
|
94
|
+
summary: "There are no agents on this machine yet.",
|
|
95
|
+
items: setupActions.map((action) => actionToWizardItem(action)),
|
|
96
|
+
},
|
|
97
|
+
]
|
|
98
|
+
: [
|
|
99
|
+
{
|
|
100
|
+
title: "Agents",
|
|
101
|
+
summary: "Jump straight into conversation or pick a setup path below.",
|
|
102
|
+
items: chatActions.map((action) => actionToWizardItem(action)),
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
title: "System",
|
|
106
|
+
items: setupActions.map((action) => actionToWizardItem(action)),
|
|
107
|
+
},
|
|
108
|
+
];
|
|
109
|
+
return (0, terminal_ui_1.renderTerminalWizard)({
|
|
75
110
|
isTTY: options.isTTY,
|
|
76
111
|
columns: options.columns,
|
|
77
112
|
masthead: {
|
|
@@ -84,13 +119,18 @@ function renderOuroHomeScreen(options) {
|
|
|
84
119
|
? "Create a new agent or clone an existing bundle to get started."
|
|
85
120
|
: "Choose an agent or a setup task without memorizing commands.",
|
|
86
121
|
sections,
|
|
87
|
-
|
|
122
|
+
nextStep: {
|
|
123
|
+
label: options.agents.length === 0 ? "Start by creating or cloning an agent." : `Start with ${actions[0].label}.`,
|
|
124
|
+
detail: options.agents.length === 0
|
|
125
|
+
? "Once one agent bundle exists here, the rest of the command surface becomes interactive."
|
|
126
|
+
: "You can always type the number or the agent name instead of remembering a command.",
|
|
127
|
+
},
|
|
88
128
|
prompt: `Choose [1-${actions.length}] or type a name: `,
|
|
89
129
|
});
|
|
90
130
|
}
|
|
91
131
|
function renderAgentPickerScreen(options) {
|
|
92
132
|
renderScreenEvent("agent-picker");
|
|
93
|
-
return (0, terminal_ui_1.
|
|
133
|
+
return (0, terminal_ui_1.renderTerminalWizard)({
|
|
94
134
|
isTTY: options.isTTY,
|
|
95
135
|
columns: options.columns,
|
|
96
136
|
masthead: {
|
|
@@ -101,7 +141,12 @@ function renderAgentPickerScreen(options) {
|
|
|
101
141
|
sections: [
|
|
102
142
|
{
|
|
103
143
|
title: "Agents",
|
|
104
|
-
|
|
144
|
+
items: options.agents.map((agent, index) => ({
|
|
145
|
+
key: String(index + 1),
|
|
146
|
+
label: agent,
|
|
147
|
+
summary: "Available on this machine.",
|
|
148
|
+
...(index === 0 ? { recommended: true } : {}),
|
|
149
|
+
})),
|
|
105
150
|
},
|
|
106
151
|
],
|
|
107
152
|
prompt: `Choose [1-${options.agents.length}] or type a name: `,
|
|
@@ -116,32 +161,65 @@ function resolveNamedAgentSelection(answer, agents) {
|
|
|
116
161
|
return agents[numbered - 1];
|
|
117
162
|
return agents.find((agent) => agent.toLowerCase() === normalized);
|
|
118
163
|
}
|
|
119
|
-
function statusLabel(status) {
|
|
120
|
-
return status.replace(/-/g, " ");
|
|
121
|
-
}
|
|
122
164
|
function renderHumanReadinessBoard(options) {
|
|
123
165
|
renderScreenEvent("readiness");
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
],
|
|
166
|
+
const issueItems = options.snapshot.items.map((item) => ({
|
|
167
|
+
label: item.title,
|
|
168
|
+
status: item.status,
|
|
169
|
+
summary: item.summary,
|
|
170
|
+
detailLines: item.detailLines,
|
|
130
171
|
}));
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
172
|
+
const actionItems = options.snapshot.nextActions.map((action, index) => ({
|
|
173
|
+
key: String(index + 1),
|
|
174
|
+
label: action.label,
|
|
175
|
+
actor: action.actor,
|
|
176
|
+
command: action.command,
|
|
177
|
+
...(action.recommended ? { recommended: true } : {}),
|
|
178
|
+
}));
|
|
179
|
+
if (options.prompt) {
|
|
180
|
+
actionItems.push({
|
|
181
|
+
key: String(actionItems.length + 1),
|
|
182
|
+
label: "Skip for now",
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
return (0, terminal_ui_1.renderTerminalWizard)({
|
|
135
186
|
isTTY: options.isTTY,
|
|
136
187
|
columns: options.columns,
|
|
137
|
-
|
|
138
|
-
|
|
188
|
+
masthead: {
|
|
189
|
+
subtitle: options.subtitle,
|
|
190
|
+
},
|
|
191
|
+
title: options.title,
|
|
192
|
+
summary: options.snapshot.summary,
|
|
193
|
+
nextStep: options.snapshot.primaryAction
|
|
194
|
+
? {
|
|
195
|
+
label: options.snapshot.primaryAction.label,
|
|
196
|
+
detail: options.snapshot.summary,
|
|
197
|
+
command: options.snapshot.primaryAction.command,
|
|
198
|
+
}
|
|
199
|
+
: options.snapshot.status === "ready" || options.snapshot.status === "attached"
|
|
200
|
+
? {
|
|
201
|
+
label: "Everything needed here is ready.",
|
|
202
|
+
detail: "You can keep going or leave this area alone.",
|
|
203
|
+
}
|
|
204
|
+
: undefined,
|
|
205
|
+
sections: [
|
|
206
|
+
{
|
|
207
|
+
title: "What needs attention",
|
|
208
|
+
items: issueItems,
|
|
209
|
+
},
|
|
210
|
+
...(actionItems.length > 0
|
|
211
|
+
? [{
|
|
212
|
+
title: "Ways forward",
|
|
213
|
+
items: actionItems,
|
|
214
|
+
}]
|
|
215
|
+
: []),
|
|
216
|
+
],
|
|
139
217
|
prompt: options.prompt,
|
|
140
218
|
});
|
|
141
219
|
}
|
|
142
220
|
function renderHumanCommandBoard(options) {
|
|
143
221
|
renderScreenEvent("command-board");
|
|
144
|
-
return (0, terminal_ui_1.
|
|
222
|
+
return (0, terminal_ui_1.renderTerminalGuide)({
|
|
145
223
|
isTTY: options.isTTY,
|
|
146
224
|
columns: options.columns,
|
|
147
225
|
masthead: {
|
|
@@ -11,6 +11,7 @@ exports.isAffirmativeAnswer = isAffirmativeAnswer;
|
|
|
11
11
|
exports.runInteractiveRepair = runInteractiveRepair;
|
|
12
12
|
const runtime_1 = require("../../nerves/runtime");
|
|
13
13
|
const identity_1 = require("../identity");
|
|
14
|
+
const terminal_ui_1 = require("./terminal-ui");
|
|
14
15
|
function isCredentialIssue(degraded) {
|
|
15
16
|
const reason = degraded.errorReason.toLowerCase();
|
|
16
17
|
const hint = degraded.fixHint.toLowerCase();
|
|
@@ -90,6 +91,9 @@ function fallbackCommandsFor(degraded, primaryCommand) {
|
|
|
90
91
|
function renderRepairChoices(prefix, commands) {
|
|
91
92
|
return commands.map((command, index) => ` ${index === 0 ? prefix : "or"}: ${command}`);
|
|
92
93
|
}
|
|
94
|
+
function repairStatusFor(action) {
|
|
95
|
+
return action.kind === "vault-unlock" ? "locked" : "needs credentials";
|
|
96
|
+
}
|
|
93
97
|
function renderRepairQueueSummaryLines(degraded) {
|
|
94
98
|
const repairable = degraded
|
|
95
99
|
.map((entry) => ({ entry, action: runnableRepairActionFor(entry) }))
|
|
@@ -109,6 +113,34 @@ function renderRepairQueueSummaryLines(degraded) {
|
|
|
109
113
|
});
|
|
110
114
|
return lines;
|
|
111
115
|
}
|
|
116
|
+
function renderRepairQueueSummary(degraded, deps) {
|
|
117
|
+
const repairable = degraded
|
|
118
|
+
.map((entry) => ({ entry, action: runnableRepairActionFor(entry) }))
|
|
119
|
+
.filter((item) => item.action !== undefined);
|
|
120
|
+
if (!deps.isTTY) {
|
|
121
|
+
return renderRepairQueueSummaryLines(degraded).join("\n");
|
|
122
|
+
}
|
|
123
|
+
return (0, terminal_ui_1.renderTerminalWizard)({
|
|
124
|
+
isTTY: true,
|
|
125
|
+
columns: deps.stdoutColumns,
|
|
126
|
+
masthead: {
|
|
127
|
+
subtitle: "Repair paths ready.",
|
|
128
|
+
},
|
|
129
|
+
title: "Needs attention before startup can finish",
|
|
130
|
+
summary: "Ouro found repair steps it can open right now. It will walk through them one at a time.",
|
|
131
|
+
sections: [{
|
|
132
|
+
title: "Repair queue",
|
|
133
|
+
items: repairable.map(({ entry, action }) => ({
|
|
134
|
+
label: entry.agent,
|
|
135
|
+
status: repairStatusFor(action),
|
|
136
|
+
summary: action.label,
|
|
137
|
+
detailLines: [entry.errorReason],
|
|
138
|
+
command: action.command,
|
|
139
|
+
})),
|
|
140
|
+
}],
|
|
141
|
+
suppressEvent: true,
|
|
142
|
+
}).trimEnd();
|
|
143
|
+
}
|
|
112
144
|
function renderActionPromptLines(agent, action) {
|
|
113
145
|
const lines = [
|
|
114
146
|
`${agent}`,
|
|
@@ -120,11 +152,66 @@ function renderActionPromptLines(agent, action) {
|
|
|
120
152
|
}
|
|
121
153
|
return lines;
|
|
122
154
|
}
|
|
123
|
-
function
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
155
|
+
function renderActionPrompt(entry, action, deps) {
|
|
156
|
+
if (!deps.isTTY)
|
|
157
|
+
return renderActionPromptLines(entry.agent, action).join("\n");
|
|
158
|
+
const footer = action.kind === "vault-unlock"
|
|
159
|
+
? "Only say yes if you have the saved vault unlock secret."
|
|
160
|
+
: "Say yes if you want Ouro to open the auth flow now.";
|
|
161
|
+
return (0, terminal_ui_1.renderTerminalWizard)({
|
|
162
|
+
isTTY: true,
|
|
163
|
+
columns: deps.stdoutColumns,
|
|
164
|
+
masthead: {
|
|
165
|
+
subtitle: "One repair step at a time.",
|
|
166
|
+
},
|
|
167
|
+
title: `Repair ${entry.agent}`,
|
|
168
|
+
summary: "Ouro found one clear next move for this agent.",
|
|
169
|
+
nextStep: {
|
|
170
|
+
label: action.kind === "vault-unlock"
|
|
171
|
+
? "Unlock the credential vault on this machine."
|
|
172
|
+
: "Refresh provider authentication.",
|
|
173
|
+
detail: action.kind === "vault-unlock"
|
|
174
|
+
? "Use the saved unlock secret if you have it. If not, leave this for later and choose a replacement or recovery path."
|
|
175
|
+
: "This opens the provider login or refresh flow for the selected provider.",
|
|
176
|
+
command: action.command,
|
|
177
|
+
},
|
|
178
|
+
sections: [{
|
|
179
|
+
title: "Why startup paused",
|
|
180
|
+
items: [{
|
|
181
|
+
label: entry.agent,
|
|
182
|
+
status: repairStatusFor(action),
|
|
183
|
+
summary: entry.errorReason,
|
|
184
|
+
}],
|
|
185
|
+
}],
|
|
186
|
+
footerLines: [footer],
|
|
187
|
+
suppressEvent: true,
|
|
188
|
+
}).trimEnd();
|
|
189
|
+
}
|
|
190
|
+
function renderDeferredRepair(agent, commands, deps) {
|
|
191
|
+
if (!deps.isTTY) {
|
|
192
|
+
return [
|
|
193
|
+
`Leaving ${agent} for later.`,
|
|
194
|
+
...renderRepairChoices("next", commands),
|
|
195
|
+
].join("\n");
|
|
196
|
+
}
|
|
197
|
+
return (0, terminal_ui_1.renderTerminalWizard)({
|
|
198
|
+
isTTY: true,
|
|
199
|
+
columns: deps.stdoutColumns,
|
|
200
|
+
masthead: {
|
|
201
|
+
subtitle: "Nothing changed yet.",
|
|
202
|
+
},
|
|
203
|
+
title: `Leaving ${agent} for later`,
|
|
204
|
+
summary: "Ouro did not open this repair flow right now.",
|
|
205
|
+
sections: [{
|
|
206
|
+
title: "Next time",
|
|
207
|
+
items: commands.map((command, index) => ({
|
|
208
|
+
key: String(index + 1),
|
|
209
|
+
label: index === 0 ? "Start here" : "Or try this",
|
|
210
|
+
command,
|
|
211
|
+
})),
|
|
212
|
+
}],
|
|
213
|
+
suppressEvent: true,
|
|
214
|
+
}).trimEnd();
|
|
128
215
|
}
|
|
129
216
|
function renderManualRepairHint(agent, fixHint) {
|
|
130
217
|
return [
|
|
@@ -140,7 +227,7 @@ function renderUnknownRepair(agent, errorReason) {
|
|
|
140
227
|
].join("\n");
|
|
141
228
|
}
|
|
142
229
|
function writeDeclinedRepair(degraded, command, deps) {
|
|
143
|
-
deps.writeStdout(renderDeferredRepair(degraded.agent, fallbackCommandsFor(degraded, command)));
|
|
230
|
+
deps.writeStdout(renderDeferredRepair(degraded.agent, fallbackCommandsFor(degraded, command), deps));
|
|
144
231
|
}
|
|
145
232
|
function runnableRepairActionFor(degraded) {
|
|
146
233
|
const typedAction = degraded.issue?.actions
|
|
@@ -180,10 +267,10 @@ function typedActionToRunnable(degraded, action) {
|
|
|
180
267
|
function writeRepairQueueSummary(degraded, deps) {
|
|
181
268
|
const lines = renderRepairQueueSummaryLines(degraded);
|
|
182
269
|
if (lines.length > 0)
|
|
183
|
-
deps.writeStdout(
|
|
270
|
+
deps.writeStdout(renderRepairQueueSummary(degraded, deps));
|
|
184
271
|
}
|
|
185
272
|
async function attemptVaultUnlock(entry, action, deps) {
|
|
186
|
-
deps.writeStdout(
|
|
273
|
+
deps.writeStdout(renderActionPrompt(entry, action, deps));
|
|
187
274
|
const answer = await deps.promptInput(`Unlock ${entry.agent}'s vault now? [y/N] `);
|
|
188
275
|
if (!isAffirmativeAnswer(answer)) {
|
|
189
276
|
writeDeclinedRepair(entry, action.command, deps);
|
|
@@ -211,7 +298,7 @@ async function attemptVaultUnlock(entry, action, deps) {
|
|
|
211
298
|
}
|
|
212
299
|
}
|
|
213
300
|
async function attemptProviderAuth(entry, action, deps) {
|
|
214
|
-
deps.writeStdout(
|
|
301
|
+
deps.writeStdout(renderActionPrompt(entry, action, deps));
|
|
215
302
|
const answer = await deps.promptInput(`Open the auth flow for ${entry.agent} now? [y/N] `);
|
|
216
303
|
if (!isAffirmativeAnswer(answer)) {
|
|
217
304
|
writeDeclinedRepair(entry, action.command, deps);
|
|
@@ -7,7 +7,9 @@ exports.wrapPlain = wrapPlain;
|
|
|
7
7
|
exports.renderOverwriteFrame = renderOverwriteFrame;
|
|
8
8
|
exports.renderOuroMasthead = renderOuroMasthead;
|
|
9
9
|
exports.formatActionActorLabel = formatActionActorLabel;
|
|
10
|
+
exports.renderTerminalWizard = renderTerminalWizard;
|
|
10
11
|
exports.renderTerminalBoard = renderTerminalBoard;
|
|
12
|
+
exports.renderTerminalGuide = renderTerminalGuide;
|
|
11
13
|
exports.renderTerminalOperation = renderTerminalOperation;
|
|
12
14
|
const runtime_1 = require("../../nerves/runtime");
|
|
13
15
|
const RESET = "\x1b[0m";
|
|
@@ -145,6 +147,154 @@ function renderOuroMasthead(options) {
|
|
|
145
147
|
function formatActionActorLabel(actor) {
|
|
146
148
|
return actor.replace(/-/g, " ");
|
|
147
149
|
}
|
|
150
|
+
function formatWizardStatusLabel(status) {
|
|
151
|
+
return status;
|
|
152
|
+
}
|
|
153
|
+
function isQuietWizardStatus(status) {
|
|
154
|
+
return status === "ready" || status === "attached" || status === "not attached";
|
|
155
|
+
}
|
|
156
|
+
function renderWizardStatusBadge(status, isTTY) {
|
|
157
|
+
const symbol = status === "ready" || status === "attached"
|
|
158
|
+
? "●"
|
|
159
|
+
: status === "not attached"
|
|
160
|
+
? "◌"
|
|
161
|
+
: "◆";
|
|
162
|
+
const label = `${symbol} ${formatWizardStatusLabel(status)}`;
|
|
163
|
+
if (!isTTY)
|
|
164
|
+
return label;
|
|
165
|
+
if (status === "ready" || status === "attached")
|
|
166
|
+
return color(label, GLOW, true);
|
|
167
|
+
if (status === "not attached")
|
|
168
|
+
return color(label, MIST);
|
|
169
|
+
return color(label, ALERT, true);
|
|
170
|
+
}
|
|
171
|
+
function renderWizardActorBadge(actor, isTTY) {
|
|
172
|
+
const label = `[${formatActionActorLabel(actor)}]`;
|
|
173
|
+
if (!isTTY)
|
|
174
|
+
return label;
|
|
175
|
+
if (actor === "human-required")
|
|
176
|
+
return color(label, ALERT, true);
|
|
177
|
+
if (actor === "human-choice")
|
|
178
|
+
return color(label, SCALE, true);
|
|
179
|
+
return color(label, MIST);
|
|
180
|
+
}
|
|
181
|
+
function renderWizardRecommendedBadge(isTTY) {
|
|
182
|
+
if (!isTTY)
|
|
183
|
+
return "[recommended]";
|
|
184
|
+
return color("[recommended]", GLOW, true);
|
|
185
|
+
}
|
|
186
|
+
function wrapWizardDetailLines(lines, width, isTTY, tone) {
|
|
187
|
+
const rendered = [];
|
|
188
|
+
for (const line of lines) {
|
|
189
|
+
const wrapped = wrapPlain(line, width);
|
|
190
|
+
for (const segment of wrapped) {
|
|
191
|
+
rendered.push(isTTY ? color(segment, tone) : segment);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return rendered;
|
|
195
|
+
}
|
|
196
|
+
function renderWizardItem(item, width, isTTY) {
|
|
197
|
+
const badges = [
|
|
198
|
+
...(item.status ? [renderWizardStatusBadge(item.status, isTTY)] : []),
|
|
199
|
+
...(item.actor ? [renderWizardActorBadge(item.actor, isTTY)] : []),
|
|
200
|
+
...(item.recommended ? [renderWizardRecommendedBadge(isTTY)] : []),
|
|
201
|
+
];
|
|
202
|
+
const keyPrefix = item.key ? `${item.key}. ` : "";
|
|
203
|
+
const header = `${keyPrefix}${item.label}${badges.length > 0 ? ` ${badges.join(" ")}` : ""}`;
|
|
204
|
+
const detailWidth = Math.max(18, width - 6);
|
|
205
|
+
const detailTone = item.status && !isQuietWizardStatus(item.status) ? BONE : MIST;
|
|
206
|
+
const lines = [isTTY ? color(header, BONE, true) : header];
|
|
207
|
+
if (item.summary) {
|
|
208
|
+
lines.push(...wrapWizardDetailLines([item.summary], detailWidth, isTTY, detailTone));
|
|
209
|
+
}
|
|
210
|
+
if (item.detailLines && item.detailLines.length > 0) {
|
|
211
|
+
lines.push(...wrapWizardDetailLines(item.detailLines, detailWidth, isTTY, detailTone));
|
|
212
|
+
}
|
|
213
|
+
if (item.command) {
|
|
214
|
+
lines.push(...wrapWizardDetailLines([`run: ${item.command}`], detailWidth, isTTY, MIST));
|
|
215
|
+
}
|
|
216
|
+
return lines.flatMap((line, index) => index === 0 ? [line] : [` ${line}`]);
|
|
217
|
+
}
|
|
218
|
+
function renderWizardSectionTTY(section, width) {
|
|
219
|
+
const lines = [];
|
|
220
|
+
if (section.summary) {
|
|
221
|
+
lines.push(...wrapWizardDetailLines([section.summary], Math.max(18, width - 4), true, MIST));
|
|
222
|
+
}
|
|
223
|
+
for (const [index, item] of section.items.entries()) {
|
|
224
|
+
if (index > 0)
|
|
225
|
+
lines.push("");
|
|
226
|
+
lines.push(...renderWizardItem(item, width, true));
|
|
227
|
+
}
|
|
228
|
+
return renderOperationSectionTTY(section.title, lines, width);
|
|
229
|
+
}
|
|
230
|
+
function renderWizardSectionPlain(section, width) {
|
|
231
|
+
const lines = [];
|
|
232
|
+
if (section.summary) {
|
|
233
|
+
lines.push(...wrapWizardDetailLines([section.summary], Math.max(18, width - 4), false, MIST));
|
|
234
|
+
}
|
|
235
|
+
for (const [index, item] of section.items.entries()) {
|
|
236
|
+
if (index > 0)
|
|
237
|
+
lines.push("");
|
|
238
|
+
lines.push(...renderWizardItem(item, width, false));
|
|
239
|
+
}
|
|
240
|
+
return renderOperationSectionPlain(section.title, lines);
|
|
241
|
+
}
|
|
242
|
+
function renderTerminalWizard(options) {
|
|
243
|
+
if (!options.suppressEvent) {
|
|
244
|
+
(0, runtime_1.emitNervesEvent)({
|
|
245
|
+
component: "daemon",
|
|
246
|
+
event: "daemon.terminal_wizard_rendered",
|
|
247
|
+
message: "rendered shared terminal wizard",
|
|
248
|
+
meta: {
|
|
249
|
+
title: options.title,
|
|
250
|
+
sections: options.sections?.length ?? 0,
|
|
251
|
+
items: options.sections?.reduce((count, section) => count + section.items.length, 0) ?? 0,
|
|
252
|
+
hasNextStep: !!options.nextStep,
|
|
253
|
+
tty: options.isTTY,
|
|
254
|
+
},
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
const width = boardWidth(options.columns);
|
|
258
|
+
const blocks = [];
|
|
259
|
+
blocks.push(renderOuroMasthead({
|
|
260
|
+
isTTY: options.isTTY,
|
|
261
|
+
columns: width,
|
|
262
|
+
subtitle: options.masthead?.subtitle,
|
|
263
|
+
}).trimEnd());
|
|
264
|
+
const introLines = [
|
|
265
|
+
options.isTTY ? color(options.title, BONE, true) : options.title,
|
|
266
|
+
...(options.summary
|
|
267
|
+
? wrapPlain(options.summary, Math.max(20, width - 2)).map((line) => options.isTTY ? color(line, MIST) : line)
|
|
268
|
+
: []),
|
|
269
|
+
];
|
|
270
|
+
blocks.push(introLines.join("\n"));
|
|
271
|
+
if (options.nextStep) {
|
|
272
|
+
const nextStepLines = [
|
|
273
|
+
options.isTTY ? color(options.nextStep.label, BONE, true) : options.nextStep.label,
|
|
274
|
+
...(options.nextStep.detail
|
|
275
|
+
? wrapPlain(options.nextStep.detail, Math.max(18, width - 4)).map((line) => options.isTTY ? color(line, MIST) : line)
|
|
276
|
+
: []),
|
|
277
|
+
...(options.nextStep.command
|
|
278
|
+
? wrapPlain(`run: ${options.nextStep.command}`, Math.max(18, width - 4)).map((line) => options.isTTY ? color(line, MIST) : line)
|
|
279
|
+
: []),
|
|
280
|
+
];
|
|
281
|
+
blocks.push((options.isTTY
|
|
282
|
+
? renderOperationSectionTTY("Recommended next step", nextStepLines, width)
|
|
283
|
+
: renderOperationSectionPlain("Recommended next step", nextStepLines)).join("\n"));
|
|
284
|
+
}
|
|
285
|
+
for (const section of options.sections ?? []) {
|
|
286
|
+
blocks.push((options.isTTY
|
|
287
|
+
? renderWizardSectionTTY(section, width)
|
|
288
|
+
: renderWizardSectionPlain(section, width)).join("\n"));
|
|
289
|
+
}
|
|
290
|
+
if (options.footerLines && options.footerLines.length > 0) {
|
|
291
|
+
blocks.push(options.footerLines.map((line) => options.isTTY ? color(line, MIST) : line).join("\n"));
|
|
292
|
+
}
|
|
293
|
+
if (options.prompt) {
|
|
294
|
+
blocks.push(options.isTTY ? color(options.prompt, BONE, true) : options.prompt);
|
|
295
|
+
}
|
|
296
|
+
return `${blocks.join("\n\n")}\n`;
|
|
297
|
+
}
|
|
148
298
|
function renderActionLine(action) {
|
|
149
299
|
const chips = [`[${formatActionActorLabel(action.actor)}]`];
|
|
150
300
|
if (action.recommended)
|
|
@@ -195,6 +345,56 @@ function renderTerminalBoard(options) {
|
|
|
195
345
|
}
|
|
196
346
|
return `${blocks.join("\n\n")}\n`;
|
|
197
347
|
}
|
|
348
|
+
function renderTerminalGuide(options) {
|
|
349
|
+
if (!options.suppressEvent) {
|
|
350
|
+
(0, runtime_1.emitNervesEvent)({
|
|
351
|
+
component: "daemon",
|
|
352
|
+
event: "daemon.terminal_guide_rendered",
|
|
353
|
+
message: "rendered shared terminal guide",
|
|
354
|
+
meta: {
|
|
355
|
+
title: options.title,
|
|
356
|
+
sections: options.sections?.length ?? 0,
|
|
357
|
+
actions: options.actions?.length ?? 0,
|
|
358
|
+
tty: options.isTTY,
|
|
359
|
+
},
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
const width = boardWidth(options.columns);
|
|
363
|
+
const blocks = [];
|
|
364
|
+
blocks.push(renderOuroMasthead({
|
|
365
|
+
isTTY: options.isTTY,
|
|
366
|
+
columns: width,
|
|
367
|
+
subtitle: options.masthead?.subtitle,
|
|
368
|
+
}).trimEnd());
|
|
369
|
+
const introLines = [
|
|
370
|
+
options.isTTY ? color(options.title, BONE, true) : options.title,
|
|
371
|
+
...(options.summary
|
|
372
|
+
? wrapPlain(options.summary, Math.max(20, width - 2)).map((line) => options.isTTY ? color(line, MIST) : line)
|
|
373
|
+
: []),
|
|
374
|
+
];
|
|
375
|
+
blocks.push(introLines.join("\n"));
|
|
376
|
+
for (const section of options.sections ?? []) {
|
|
377
|
+
const lines = section.lines.map((line) => options.isTTY ? color(line, BONE) : line);
|
|
378
|
+
blocks.push((options.isTTY
|
|
379
|
+
? renderOperationSectionTTY(section.title, lines, width)
|
|
380
|
+
: renderOperationSectionPlain(section.title, lines)).join("\n"));
|
|
381
|
+
}
|
|
382
|
+
const actionList = options.actions ?? [];
|
|
383
|
+
if (actionList.length > 0) {
|
|
384
|
+
const lines = [];
|
|
385
|
+
for (const [index, action] of actionList.entries()) {
|
|
386
|
+
lines.push(options.isTTY ? color(`${index + 1}. ${renderActionLine(action)}`, BONE, true) : `${index + 1}. ${renderActionLine(action)}`);
|
|
387
|
+
lines.push(options.isTTY ? color(`run: ${action.command}`, MIST) : `run: ${action.command}`);
|
|
388
|
+
}
|
|
389
|
+
blocks.push((options.isTTY
|
|
390
|
+
? renderOperationSectionTTY("Next moves", lines, width)
|
|
391
|
+
: renderOperationSectionPlain("Next moves", lines)).join("\n"));
|
|
392
|
+
}
|
|
393
|
+
if (options.prompt) {
|
|
394
|
+
blocks.push(options.isTTY ? color(options.prompt, BONE, true) : options.prompt);
|
|
395
|
+
}
|
|
396
|
+
return `${blocks.join("\n\n")}\n`;
|
|
397
|
+
}
|
|
198
398
|
function formatOperationStep(step) {
|
|
199
399
|
const marker = step.status === "done"
|
|
200
400
|
? "✓"
|