@ouro.bot/cli 0.1.0-alpha.445 → 0.1.0-alpha.447

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.
@@ -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 sections = [
61
- {
62
- title: options.agents.length === 0 ? "Start here" : "Available agents",
63
- lines: options.agents.length === 0
64
- ? ["No agents are set up on this machine yet."]
65
- : options.agents.map((agent) => `${agent} is available.`),
66
- },
67
- ];
68
- const actionRows = actions.map((action, index) => ({
69
- label: action.label,
70
- actor: "agent-runnable",
71
- command: action.command,
72
- ...(index === 0 ? { recommended: true } : {}),
73
- }));
74
- 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)({
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
- 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
+ },
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.renderTerminalBoard)({
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
- 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
+ })),
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 sections = options.snapshot.items.map((item) => ({
125
- title: item.title,
126
- lines: [
127
- `${statusLabel(item.status)} — ${item.summary}`,
128
- ...item.detailLines,
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
- return renderHumanCommandBoard({
132
- title: options.title,
133
- subtitle: options.subtitle,
134
- 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)({
135
186
  isTTY: options.isTTY,
136
187
  columns: options.columns,
137
- sections,
138
- 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
+ ],
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.renderTerminalBoard)({
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 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);
@@ -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
  ? "✓"
@@ -209,6 +209,11 @@ function cacheResult(agentName, result) {
209
209
  cachedPools.set(agentName, result);
210
210
  return result;
211
211
  }
212
+ function selectedProviders(providers) {
213
+ if (!providers || providers.length === 0)
214
+ return [...VALID_PROVIDERS];
215
+ return [...new Set(providers)];
216
+ }
212
217
  function resultForProvider(poolResult, provider) {
213
218
  if (!poolResult.ok)
214
219
  return poolResult;
@@ -227,12 +232,13 @@ function readProviderCredentialPool(agentName) {
227
232
  return cachedPools.get(agentName) ?? missingPool(agentName);
228
233
  }
229
234
  async function refreshProviderCredentialPool(agentName, options = {}) {
235
+ const providersToRead = selectedProviders(options.providers);
230
236
  try {
231
237
  const store = (0, credential_access_1.getCredentialStore)(agentName);
232
238
  options.onProgress?.(`reading vault items for ${agentName}...`);
233
239
  const providers = {};
234
240
  let updatedAt = new Date(0).toISOString();
235
- for (const provider of VALID_PROVIDERS) {
241
+ for (const provider of providersToRead) {
236
242
  const itemName = providerCredentialItemName(provider);
237
243
  options.onProgress?.(`reading ${provider} credentials...`);
238
244
  let raw;
@@ -261,9 +267,19 @@ async function refreshProviderCredentialPool(agentName, options = {}) {
261
267
  component: "config/identity",
262
268
  event: "config.provider_credentials_loaded",
263
269
  message: "loaded provider credentials from vault",
264
- meta: { agentName, providerCount: Object.keys(providers).length },
270
+ meta: {
271
+ agentName,
272
+ providerCount: Object.keys(providers).length,
273
+ requestedProviderCount: providersToRead.length,
274
+ cacheSkipped: options.skipCache === true,
275
+ },
265
276
  });
266
- return cacheResult(agentName, { ok: true, poolPath: providerCredentialsVaultPath(agentName), pool });
277
+ const result = {
278
+ ok: true,
279
+ poolPath: providerCredentialsVaultPath(agentName),
280
+ pool,
281
+ };
282
+ return options.skipCache ? result : cacheResult(agentName, result);
267
283
  }
268
284
  catch (error) {
269
285
  const cached = cachedPools.get(agentName);
@@ -282,7 +298,7 @@ async function refreshProviderCredentialPool(agentName, options = {}) {
282
298
  });
283
299
  if (options.preserveCachedOnFailure && cached?.ok)
284
300
  return cached;
285
- return cacheResult(agentName, result);
301
+ return options.skipCache ? result : cacheResult(agentName, result);
286
302
  }
287
303
  }
288
304
  function createProviderCredentialRecord(input) {