@ouro.bot/cli 0.1.0-alpha.444 → 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.
@@ -7,7 +7,6 @@ exports.renderAgentPickerScreen = renderAgentPickerScreen;
7
7
  exports.resolveNamedAgentSelection = resolveNamedAgentSelection;
8
8
  exports.renderHumanReadinessBoard = renderHumanReadinessBoard;
9
9
  exports.renderHumanCommandBoard = renderHumanCommandBoard;
10
- exports.renderHouseStatusScreen = renderHouseStatusScreen;
11
10
  const runtime_1 = require("../../nerves/runtime");
12
11
  const terminal_ui_1 = require("./terminal-ui");
13
12
  function renderScreenEvent(screen) {
@@ -18,6 +17,34 @@ function renderScreenEvent(screen) {
18
17
  meta: { screen },
19
18
  });
20
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
+ }
21
48
  function buildOuroHomeActions(agents) {
22
49
  if (agents.length === 0) {
23
50
  return [
@@ -58,21 +85,28 @@ function resolveOuroHomeAction(answer, actions) {
58
85
  function renderOuroHomeScreen(options) {
59
86
  renderScreenEvent("home");
60
87
  const actions = buildOuroHomeActions(options.agents);
61
- const sections = [
62
- {
63
- title: options.agents.length === 0 ? "Start here" : "Available agents",
64
- lines: options.agents.length === 0
65
- ? ["No agents are set up on this machine yet."]
66
- : options.agents.map((agent) => `${agent} is available.`),
67
- },
68
- ];
69
- const actionRows = actions.map((action, index) => ({
70
- label: action.label,
71
- actor: "agent-runnable",
72
- command: action.command,
73
- ...(index === 0 ? { recommended: true } : {}),
74
- }));
75
- return (0, terminal_ui_1.renderTerminalBoard)({
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)({
76
110
  isTTY: options.isTTY,
77
111
  columns: options.columns,
78
112
  masthead: {
@@ -85,13 +119,18 @@ function renderOuroHomeScreen(options) {
85
119
  ? "Create a new agent or clone an existing bundle to get started."
86
120
  : "Choose an agent or a setup task without memorizing commands.",
87
121
  sections,
88
- actions: actionRows,
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
+ },
89
128
  prompt: `Choose [1-${actions.length}] or type a name: `,
90
129
  });
91
130
  }
92
131
  function renderAgentPickerScreen(options) {
93
132
  renderScreenEvent("agent-picker");
94
- return (0, terminal_ui_1.renderTerminalBoard)({
133
+ return (0, terminal_ui_1.renderTerminalWizard)({
95
134
  isTTY: options.isTTY,
96
135
  columns: options.columns,
97
136
  masthead: {
@@ -102,7 +141,12 @@ function renderAgentPickerScreen(options) {
102
141
  sections: [
103
142
  {
104
143
  title: "Agents",
105
- lines: options.agents.map((agent, index) => `${index + 1}. ${agent}`),
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
+ })),
106
150
  },
107
151
  ],
108
152
  prompt: `Choose [1-${options.agents.length}] or type a name: `,
@@ -117,32 +161,65 @@ function resolveNamedAgentSelection(answer, agents) {
117
161
  return agents[numbered - 1];
118
162
  return agents.find((agent) => agent.toLowerCase() === normalized);
119
163
  }
120
- function statusLabel(status) {
121
- return status.replace(/-/g, " ");
122
- }
123
164
  function renderHumanReadinessBoard(options) {
124
165
  renderScreenEvent("readiness");
125
- const sections = options.snapshot.items.map((item) => ({
126
- title: item.title,
127
- lines: [
128
- `${statusLabel(item.status)} — ${item.summary}`,
129
- ...item.detailLines,
130
- ],
166
+ const issueItems = options.snapshot.items.map((item) => ({
167
+ label: item.title,
168
+ status: item.status,
169
+ summary: item.summary,
170
+ detailLines: item.detailLines,
131
171
  }));
132
- return renderHumanCommandBoard({
133
- title: options.title,
134
- subtitle: options.subtitle,
135
- summary: options.snapshot.summary,
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)({
136
186
  isTTY: options.isTTY,
137
187
  columns: options.columns,
138
- sections,
139
- actions: options.snapshot.nextActions,
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
+ ],
140
217
  prompt: options.prompt,
141
218
  });
142
219
  }
143
220
  function renderHumanCommandBoard(options) {
144
221
  renderScreenEvent("command-board");
145
- return (0, terminal_ui_1.renderTerminalBoard)({
222
+ return (0, terminal_ui_1.renderTerminalGuide)({
146
223
  isTTY: options.isTTY,
147
224
  columns: options.columns,
148
225
  masthead: {
@@ -155,82 +232,3 @@ function renderHumanCommandBoard(options) {
155
232
  prompt: options.prompt,
156
233
  });
157
234
  }
158
- function renderHouseStatusScreen(options) {
159
- renderScreenEvent("house-status");
160
- const sections = [
161
- {
162
- title: "Runtime",
163
- lines: [
164
- `Daemon: ${options.payload.overview.daemon}`,
165
- `Health: ${options.payload.overview.health}`,
166
- `Outlook: ${options.payload.overview.outlookUrl}`,
167
- `Updated: ${options.payload.overview.lastUpdated}`,
168
- ],
169
- },
170
- ];
171
- if (options.payload.agents.length > 0) {
172
- sections.push({
173
- title: "Agents",
174
- lines: options.payload.agents.map((agent) => `${agent.name} — ${agent.enabled ? "enabled" : "disabled"}`),
175
- });
176
- }
177
- if (options.payload.providers.length > 0) {
178
- sections.push({
179
- title: "Providers",
180
- lines: options.payload.providers.map((provider) => {
181
- const detail = [provider.readiness, provider.detail, provider.source, provider.credential].filter(Boolean).join("; ");
182
- return `${provider.agent} ${provider.lane} — ${provider.provider} / ${provider.model}${detail ? ` — ${detail}` : ""}`;
183
- }),
184
- });
185
- }
186
- if (options.payload.senses.length > 0) {
187
- sections.push({
188
- title: "Senses",
189
- lines: options.payload.senses.map((sense) => {
190
- const status = sense.enabled ? sense.status : "disabled";
191
- return `${sense.agent} — ${sense.label ?? sense.sense} — ${status}${sense.detail ? ` — ${sense.detail}` : ""}`;
192
- }),
193
- });
194
- }
195
- if (options.payload.workers.length > 0) {
196
- sections.push({
197
- title: "Workers",
198
- lines: options.payload.workers.map((worker) => {
199
- const details = [`restarts: ${worker.restartCount}`];
200
- if (worker.pid !== null)
201
- details.unshift(`pid ${worker.pid}`);
202
- if (worker.lastExitCode !== null)
203
- details.push(`exit=${worker.lastExitCode}`);
204
- if (worker.lastSignal !== null)
205
- details.push(`signal=${worker.lastSignal}`);
206
- if (worker.errorReason)
207
- details.push(`error: ${worker.errorReason}`);
208
- if (worker.fixHint)
209
- details.push(`fix: ${worker.fixHint}`);
210
- return `${worker.agent} — ${worker.worker} — ${worker.status} — ${details.join("; ")}`;
211
- }),
212
- });
213
- }
214
- if (options.payload.sync.length > 0) {
215
- sections.push({
216
- title: "Git sync",
217
- lines: options.payload.sync.map((row) => {
218
- if (!row.enabled)
219
- return `${row.agent} — disabled`;
220
- if (row.gitInitialized === false)
221
- return `${row.agent} — needs git init`;
222
- if (row.remoteUrl)
223
- return `${row.agent} — ${row.remote} -> ${row.remoteUrl}`;
224
- return `${row.agent} — local only`;
225
- }),
226
- });
227
- }
228
- return renderHumanCommandBoard({
229
- title: "Ouro status",
230
- subtitle: "Current runtime status for this machine.",
231
- summary: "What is running, what is stopped, and what needs attention.",
232
- isTTY: options.isTTY,
233
- columns: options.columns,
234
- sections,
235
- });
236
- }
@@ -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 renderDeferredRepair(agent, commands) {
124
- return [
125
- `Leaving ${agent} for later.`,
126
- ...renderRepairChoices("next", commands),
127
- ].join("\n");
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(lines.join("\n"));
270
+ deps.writeStdout(renderRepairQueueSummary(degraded, deps));
184
271
  }
185
272
  async function attemptVaultUnlock(entry, action, deps) {
186
- deps.writeStdout(renderActionPromptLines(entry.agent, action).join("\n"));
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(renderActionPromptLines(entry.agent, action).join("\n"));
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);
@@ -17,6 +17,7 @@ exports.renderWaitingForDaemon = renderWaitingForDaemon;
17
17
  exports.pollDaemonStartup = pollDaemonStartup;
18
18
  const cli_render_1 = require("./cli-render");
19
19
  const runtime_1 = require("../../nerves/runtime");
20
+ const terminal_ui_1 = require("./terminal-ui");
20
21
  // ── Constants ──
21
22
  const SPINNER_FRAMES = "⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏";
22
23
  const STABILITY_THRESHOLD_MS = 5_000;
@@ -248,16 +249,7 @@ function colorStatus(status) {
248
249
  return `${statusColor}${status}${RESET}`;
249
250
  }
250
251
  function renderStartupLines(lines, prevLineCount, isTTY) {
251
- if (!isTTY)
252
- return lines.join("\n") + "\n";
253
- let output = "";
254
- if (prevLineCount > 0) {
255
- output += `\x1b[${prevLineCount}A`;
256
- }
257
- for (const line of lines) {
258
- output += `\x1b[2K${line}\n`;
259
- }
260
- return output;
252
+ return (0, terminal_ui_1.renderOverwriteFrame)(lines, prevLineCount, isTTY);
261
253
  }
262
254
  /* v8 ignore start -- process liveness check: uses real process.kill(0), tested via deployment @preserve */
263
255
  function defaultIsProcessAlive(pid) {