@ouro.bot/cli 0.1.0-alpha.432 → 0.1.0-alpha.433

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.
@@ -426,15 +426,14 @@ function ttyBoardEnabled(deps) {
426
426
  return deps.isTTY ?? process.stdout.isTTY === true;
427
427
  }
428
428
  function renderCommandBoard(deps, options) {
429
- return (0, terminal_ui_1.renderTerminalBoard)({
430
- isTTY: true,
431
- columns: deps.stdoutColumns ?? process.stdout.columns,
432
- masthead: {
433
- subtitle: options.subtitle,
434
- },
429
+ return (0, human_command_screens_1.renderHumanCommandBoard)({
435
430
  title: options.title,
431
+ subtitle: options.subtitle,
436
432
  summary: options.summary,
433
+ isTTY: true,
434
+ columns: deps.stdoutColumns ?? process.stdout.columns,
437
435
  sections: options.sections,
436
+ actions: options.actions,
438
437
  }).trimEnd();
439
438
  }
440
439
  function writeConnectorIntro(deps, options) {
@@ -448,6 +447,57 @@ function writeConnectorIntro(deps, options) {
448
447
  : options.fallbackLines.join("\n");
449
448
  deps.writeStdout(text);
450
449
  }
450
+ function formatLocalUnlockStoreLine(store) {
451
+ return `local unlock store: ${store.kind}${store.secure ? "" : " (explicit plaintext fallback)"}`;
452
+ }
453
+ function writeCommandOutcome(deps, options) {
454
+ const message = ttyBoardEnabled(deps)
455
+ ? renderCommandBoard(deps, {
456
+ title: options.title,
457
+ subtitle: options.subtitle,
458
+ summary: options.summary,
459
+ sections: options.sections,
460
+ })
461
+ : options.fallbackLines.join("\n");
462
+ deps.writeStdout(message);
463
+ return message;
464
+ }
465
+ function writeCapabilityOutcome(deps, options) {
466
+ return writeCommandOutcome(deps, {
467
+ title: "Capability connected",
468
+ subtitle: options.subtitle,
469
+ summary: options.summary,
470
+ sections: [
471
+ {
472
+ title: "What changed",
473
+ lines: options.whatChanged,
474
+ },
475
+ {
476
+ title: "Next moves",
477
+ lines: options.nextMoves,
478
+ },
479
+ ],
480
+ fallbackLines: options.fallbackLines,
481
+ });
482
+ }
483
+ function writeCapabilityAttention(deps, options) {
484
+ return writeCommandOutcome(deps, {
485
+ title: "Capability needs attention",
486
+ subtitle: options.subtitle,
487
+ summary: options.summary,
488
+ sections: [
489
+ {
490
+ title: "What changed",
491
+ lines: options.whatChanged,
492
+ },
493
+ {
494
+ title: "Next moves",
495
+ lines: options.nextMoves,
496
+ },
497
+ ],
498
+ fallbackLines: options.fallbackLines,
499
+ });
500
+ }
451
501
  async function promptForNamedAgent(title, subtitle, agents, deps) {
452
502
  if (!deps.promptInput)
453
503
  throw new Error("agent selection requires interactive input");
@@ -956,10 +1006,6 @@ function pushAgentBundleAfterCliMutation(agent, deps) {
956
1006
  }
957
1007
  return `bundle sync: could not push bundle changes (${result.error})`;
958
1008
  }
959
- function appendBundleSyncSummary(message, agent, deps) {
960
- const syncSummary = pushAgentBundleAfterCliMutation(agent, deps);
961
- return syncSummary ? `${message}\n${syncSummary}` : message;
962
- }
963
1009
  function writeAgentVaultConfig(agentName, configPath, config, vault) {
964
1010
  const nextConfig = {
965
1011
  ...config,
@@ -1144,13 +1190,34 @@ async function executeVaultUnlock(command, deps) {
1144
1190
  finally {
1145
1191
  progress.end();
1146
1192
  }
1147
- const message = [
1193
+ const fallbackLines = [
1148
1194
  `vault unlocked for ${command.agent} on this machine`,
1149
1195
  `vault: ${vault.email} at ${vault.serverUrl}`,
1150
- `local unlock store: ${store.kind}${store.secure ? "" : " (explicit plaintext fallback)"}`,
1151
- ].join("\n");
1152
- deps.writeStdout(message);
1153
- return message;
1196
+ formatLocalUnlockStoreLine(store),
1197
+ ];
1198
+ return writeCommandOutcome(deps, {
1199
+ title: "Credential vault",
1200
+ subtitle: `${command.agent}'s vault is open on this machine again.`,
1201
+ summary: `This machine can now open ${command.agent}'s vault without re-entering the unlock secret every request.`,
1202
+ sections: [
1203
+ {
1204
+ title: "What changed",
1205
+ lines: [
1206
+ `Vault: ${vault.email} at ${vault.serverUrl}`,
1207
+ formatLocalUnlockStoreLine(store),
1208
+ "secret was not printed",
1209
+ ],
1210
+ },
1211
+ {
1212
+ title: "Next moves",
1213
+ lines: [
1214
+ `Run ouro up to let the house retry ${command.agent}.`,
1215
+ `If credentials are still missing, run ouro auth --agent ${command.agent} --provider <provider>.`,
1216
+ ],
1217
+ },
1218
+ ],
1219
+ fallbackLines,
1220
+ });
1154
1221
  }
1155
1222
  async function executeVaultCreate(command, deps) {
1156
1223
  if (command.agent === "SerpentGuide") {
@@ -1203,15 +1270,40 @@ async function executeVaultCreate(command, deps) {
1203
1270
  /* v8 ignore next -- defensive: success path assigns store before continuing @preserve */
1204
1271
  if (!store)
1205
1272
  throw new Error(`vault create failed for ${command.agent}: local unlock material was not saved`);
1206
- const message = appendBundleSyncSummary([
1273
+ const syncSummary = pushAgentBundleAfterCliMutation(command.agent, deps);
1274
+ const fallbackLines = [
1207
1275
  `vault created for ${command.agent}`,
1208
1276
  `vault: ${email} at ${serverUrl}`,
1209
- `local unlock store: ${store.kind}${store.secure ? "" : " (explicit plaintext fallback)"}`,
1277
+ formatLocalUnlockStoreLine(store),
1210
1278
  "All raw credentials for this agent will be stored in this Ouro credential vault.",
1211
1279
  "Keep the vault unlock secret saved outside Ouro. Another machine will need it once.",
1212
- ].join("\n"), command.agent, deps);
1213
- deps.writeStdout(message);
1214
- return message;
1280
+ ...(syncSummary ? [syncSummary] : []),
1281
+ ];
1282
+ return writeCommandOutcome(deps, {
1283
+ title: "Credential vault",
1284
+ subtitle: `${command.agent} has a vault home now.`,
1285
+ summary: `All raw credentials for ${command.agent} now live in this vault.`,
1286
+ sections: [
1287
+ {
1288
+ title: "What changed",
1289
+ lines: [
1290
+ `Vault: ${email} at ${serverUrl}`,
1291
+ formatLocalUnlockStoreLine(store),
1292
+ "This vault will hold provider and runtime credentials for the agent.",
1293
+ "secret was not printed",
1294
+ ...(syncSummary ? [syncSummary] : []),
1295
+ ],
1296
+ },
1297
+ {
1298
+ title: "Next moves",
1299
+ lines: [
1300
+ `Authenticate the providers ${command.agent} should use with ouro auth --agent ${command.agent} --provider <provider>.`,
1301
+ `Then run ouro up so the house can bring ${command.agent} online.`,
1302
+ ],
1303
+ },
1304
+ ],
1305
+ fallbackLines,
1306
+ });
1215
1307
  }
1216
1308
  async function executeVaultReplace(command, deps) {
1217
1309
  if (command.agent === "SerpentGuide") {
@@ -1254,16 +1346,42 @@ async function executeVaultReplace(command, deps) {
1254
1346
  throw new Error(`vault replace failed for ${command.agent}: no vault repair result`);
1255
1347
  if (!repair.ok)
1256
1348
  return repair.message;
1257
- const message = appendBundleSyncSummary([
1349
+ const syncSummary = pushAgentBundleAfterCliMutation(command.agent, deps);
1350
+ const fallbackLines = [
1258
1351
  `vault replaced for ${command.agent}`,
1259
1352
  `vault: ${email} at ${serverUrl}`,
1260
- `local unlock store: ${repair.store.kind}${repair.store.secure ? "" : " (explicit plaintext fallback)"}`,
1353
+ formatLocalUnlockStoreLine(repair.store),
1261
1354
  "imported: none",
1262
1355
  `next: ouro repair --agent ${command.agent}`,
1263
1356
  "Keep the vault unlock secret saved outside Ouro. Another machine will need it once.",
1264
- ].join("\n"), command.agent, deps);
1265
- deps.writeStdout(message);
1266
- return message;
1357
+ ...(syncSummary ? [syncSummary] : []),
1358
+ ];
1359
+ return writeCommandOutcome(deps, {
1360
+ title: "Credential vault",
1361
+ subtitle: `${command.agent} has a fresh vault.`,
1362
+ summary: `${command.agent} now has a fresh empty vault.`,
1363
+ sections: [
1364
+ {
1365
+ title: "What changed",
1366
+ lines: [
1367
+ `Vault: ${email} at ${serverUrl}`,
1368
+ formatLocalUnlockStoreLine(repair.store),
1369
+ "Imported: none",
1370
+ "secret was not printed",
1371
+ ...(syncSummary ? [syncSummary] : []),
1372
+ ],
1373
+ },
1374
+ {
1375
+ title: "Next moves",
1376
+ lines: [
1377
+ `Re-enter provider credentials with ouro auth --agent ${command.agent} --provider <provider>.`,
1378
+ `Re-enter runtime secrets with ouro connect --agent ${command.agent} or ouro vault config set --agent ${command.agent} --key <field>.`,
1379
+ `Then run ouro repair --agent ${command.agent} or ouro up.`,
1380
+ ],
1381
+ },
1382
+ ],
1383
+ fallbackLines,
1384
+ });
1267
1385
  }
1268
1386
  async function executeVaultRecover(command, deps) {
1269
1387
  if (command.agent === "SerpentGuide") {
@@ -1340,19 +1458,47 @@ async function executeVaultRecover(command, deps) {
1340
1458
  progress.end();
1341
1459
  }
1342
1460
  const providerList = [...importedProviders].sort();
1343
- const message = appendBundleSyncSummary([
1461
+ const syncSummary = pushAgentBundleAfterCliMutation(command.agent, deps);
1462
+ const fallbackLines = [
1344
1463
  `vault recovered for ${command.agent}`,
1345
1464
  `vault: ${email} at ${serverUrl}`,
1346
- `local unlock store: ${repair.store.kind}${repair.store.secure ? "" : " (explicit plaintext fallback)"}`,
1465
+ formatLocalUnlockStoreLine(repair.store),
1347
1466
  `sources imported: ${sourceImports.length}`,
1348
1467
  `provider credentials imported: ${providerList.length === 0 ? "none" : providerList.join(", ")}`,
1349
1468
  `runtime credentials imported: ${runtimeFields.length === 0 ? "none" : runtimeFields.join(", ")}`,
1350
1469
  `machine runtime credentials imported: ${machineRuntimeFields.length === 0 ? "none" : machineRuntimeFields.join(", ")}`,
1351
1470
  "credential values were not printed",
1352
1471
  "Keep the vault unlock secret saved outside Ouro. Another machine will need it once.",
1353
- ].join("\n"), command.agent, deps);
1354
- deps.writeStdout(message);
1355
- return message;
1472
+ ...(syncSummary ? [syncSummary] : []),
1473
+ ];
1474
+ return writeCommandOutcome(deps, {
1475
+ title: "Credential vault",
1476
+ subtitle: `${command.agent}'s vault is back in service.`,
1477
+ summary: `Recovered credentials have been moved back into ${command.agent}'s vault.`,
1478
+ sections: [
1479
+ {
1480
+ title: "What changed",
1481
+ lines: [
1482
+ `Vault: ${email} at ${serverUrl}`,
1483
+ formatLocalUnlockStoreLine(repair.store),
1484
+ `Sources imported: ${sourceImports.length}`,
1485
+ `Provider credentials: ${providerList.length === 0 ? "none" : providerList.join(", ")}`,
1486
+ `Runtime credentials: ${runtimeFields.length === 0 ? "none" : runtimeFields.join(", ")}`,
1487
+ `Machine runtime credentials: ${machineRuntimeFields.length === 0 ? "none" : machineRuntimeFields.join(", ")}`,
1488
+ "secret was not printed",
1489
+ ...(syncSummary ? [syncSummary] : []),
1490
+ ],
1491
+ },
1492
+ {
1493
+ title: "Next moves",
1494
+ lines: [
1495
+ `Run ouro auth verify --agent ${command.agent} to re-check the stored providers.`,
1496
+ `Then run ouro up so the house can retry ${command.agent}.`,
1497
+ ],
1498
+ },
1499
+ ],
1500
+ fallbackLines,
1501
+ });
1356
1502
  }
1357
1503
  async function executeVaultStatus(command, deps) {
1358
1504
  if (command.agent === "SerpentGuide") {
@@ -1981,16 +2127,27 @@ async function executeConnectPerplexity(agent, deps) {
1981
2127
  if (!verification.ok) {
1982
2128
  progress.failPhase("verifying Perplexity search", verification.summary);
1983
2129
  progress.end();
1984
- const message = [
1985
- `Perplexity key was saved for ${agent}, but the live check failed.`,
1986
- `stored: ${stored.itemPath}`,
1987
- `live check: ${verification.summary}`,
1988
- "secret was not printed",
1989
- "",
1990
- `Next: rerun \`ouro connect perplexity --agent ${agent}\` with a working key.`,
1991
- ].join("\n");
1992
- deps.writeStdout(message);
1993
- return message;
2130
+ return writeCapabilityAttention(deps, {
2131
+ subtitle: `${agent}'s Perplexity key needs another pass.`,
2132
+ summary: "Perplexity search was saved, but the live check failed.",
2133
+ whatChanged: [
2134
+ `Stored: ${stored.itemPath}`,
2135
+ `Live check: ${verification.summary}`,
2136
+ "secret was not printed",
2137
+ ],
2138
+ nextMoves: [
2139
+ `Rerun ouro connect perplexity --agent ${agent} with a working key.`,
2140
+ `Use ouro connect --agent ${agent} to review the rest of the capability bay.`,
2141
+ ],
2142
+ fallbackLines: [
2143
+ `Perplexity key was saved for ${agent}, but the live check failed.`,
2144
+ `stored: ${stored.itemPath}`,
2145
+ `live check: ${verification.summary}`,
2146
+ "secret was not printed",
2147
+ "",
2148
+ `Next: rerun \`ouro connect perplexity --agent ${agent}\` with a working key.`,
2149
+ ],
2150
+ });
1994
2151
  }
1995
2152
  progress.completePhase("verifying Perplexity search", verification.summary);
1996
2153
  reload = await runCommandProgressPhase(progress, `applying change to running ${agent}`, () => applyRuntimeChangeToRunningAgent(agent, deps, (message) => progress.updateDetail(message)), (result) => result);
@@ -1998,17 +2155,29 @@ async function executeConnectPerplexity(agent, deps) {
1998
2155
  finally {
1999
2156
  progress.end();
2000
2157
  }
2001
- const message = [
2002
- `Perplexity connected for ${agent}`,
2003
- "capability: Perplexity search",
2004
- `stored: ${stored.itemPath}`,
2005
- `running agent: ${reload}`,
2006
- "secret was not printed",
2007
- "",
2008
- "Next: ask the agent to search.",
2009
- ].join("\n");
2010
- deps.writeStdout(message);
2011
- return message;
2158
+ return writeCapabilityOutcome(deps, {
2159
+ subtitle: `${agent}'s search compass is online.`,
2160
+ summary: `Perplexity search is ready to travel with ${agent}.`,
2161
+ whatChanged: [
2162
+ `Capability: Perplexity search`,
2163
+ `Stored: ${stored.itemPath}`,
2164
+ `Running agent: ${reload}`,
2165
+ "secret was not printed",
2166
+ ],
2167
+ nextMoves: [
2168
+ "Ask the agent to search.",
2169
+ `Reopen the connect bay with ouro connect --agent ${agent} whenever you want to review capabilities.`,
2170
+ ],
2171
+ fallbackLines: [
2172
+ `Perplexity connected for ${agent}`,
2173
+ "capability: Perplexity search",
2174
+ `stored: ${stored.itemPath}`,
2175
+ `running agent: ${reload}`,
2176
+ "secret was not printed",
2177
+ "",
2178
+ "Next: ask the agent to search.",
2179
+ ],
2180
+ });
2012
2181
  }
2013
2182
  async function executeConnectEmbeddings(agent, deps) {
2014
2183
  if (agent === "SerpentGuide") {
@@ -2068,16 +2237,27 @@ async function executeConnectEmbeddings(agent, deps) {
2068
2237
  if (!verification.ok) {
2069
2238
  progress.failPhase("verifying memory embeddings", verification.summary);
2070
2239
  progress.end();
2071
- const message = [
2072
- `Embeddings key was saved for ${agent}, but the live check failed.`,
2073
- `stored: ${stored.itemPath}`,
2074
- `live check: ${verification.summary}`,
2075
- "secret was not printed",
2076
- "",
2077
- `Next: rerun \`ouro connect embeddings --agent ${agent}\` with a working key.`,
2078
- ].join("\n");
2079
- deps.writeStdout(message);
2080
- return message;
2240
+ return writeCapabilityAttention(deps, {
2241
+ subtitle: `${agent}'s memory key needs another pass.`,
2242
+ summary: "Memory embeddings were saved, but the live check failed.",
2243
+ whatChanged: [
2244
+ `Stored: ${stored.itemPath}`,
2245
+ `Live check: ${verification.summary}`,
2246
+ "secret was not printed",
2247
+ ],
2248
+ nextMoves: [
2249
+ `Rerun ouro connect embeddings --agent ${agent} with a working key.`,
2250
+ `Use ouro connect --agent ${agent} to review the rest of the capability bay.`,
2251
+ ],
2252
+ fallbackLines: [
2253
+ `Embeddings key was saved for ${agent}, but the live check failed.`,
2254
+ `stored: ${stored.itemPath}`,
2255
+ `live check: ${verification.summary}`,
2256
+ "secret was not printed",
2257
+ "",
2258
+ `Next: rerun \`ouro connect embeddings --agent ${agent}\` with a working key.`,
2259
+ ],
2260
+ });
2081
2261
  }
2082
2262
  progress.completePhase("verifying memory embeddings", verification.summary);
2083
2263
  reload = await runCommandProgressPhase(progress, `applying change to running ${agent}`, () => applyRuntimeChangeToRunningAgent(agent, deps, (message) => progress.updateDetail(message)), (result) => result);
@@ -2085,17 +2265,29 @@ async function executeConnectEmbeddings(agent, deps) {
2085
2265
  finally {
2086
2266
  progress.end();
2087
2267
  }
2088
- const message = [
2089
- `Embeddings connected for ${agent}`,
2090
- "capability: memory embeddings",
2091
- `stored: ${stored.itemPath}`,
2092
- `running agent: ${reload}`,
2093
- "secret was not printed",
2094
- "",
2095
- "Next: ask the agent to search notes or memory.",
2096
- ].join("\n");
2097
- deps.writeStdout(message);
2098
- return message;
2268
+ return writeCapabilityOutcome(deps, {
2269
+ subtitle: `${agent}'s memory index is online.`,
2270
+ summary: `Memory embeddings are ready to travel with ${agent}.`,
2271
+ whatChanged: [
2272
+ "Capability: memory embeddings",
2273
+ `Stored: ${stored.itemPath}`,
2274
+ `Running agent: ${reload}`,
2275
+ "secret was not printed",
2276
+ ],
2277
+ nextMoves: [
2278
+ "Ask the agent to search notes or memory.",
2279
+ `Reopen the connect bay with ouro connect --agent ${agent} whenever you want to review capabilities.`,
2280
+ ],
2281
+ fallbackLines: [
2282
+ `Embeddings connected for ${agent}`,
2283
+ "capability: memory embeddings",
2284
+ `stored: ${stored.itemPath}`,
2285
+ `running agent: ${reload}`,
2286
+ "secret was not printed",
2287
+ "",
2288
+ "Next: ask the agent to search notes or memory.",
2289
+ ],
2290
+ });
2099
2291
  }
2100
2292
  async function executeConnectTeams(agent, deps) {
2101
2293
  if (agent === "SerpentGuide") {
@@ -2103,11 +2295,38 @@ async function executeConnectTeams(agent, deps) {
2103
2295
  }
2104
2296
  const promptInput = requirePromptInput(deps, "Teams setup");
2105
2297
  const promptSecret = requirePromptSecret(deps, "Teams client secret entry");
2106
- deps.writeStdout([
2107
- `Connect Teams for ${agent}`,
2108
- "This connects the Teams sense.",
2109
- "The client secret stays hidden while you type.",
2110
- ].join("\n"));
2298
+ writeConnectorIntro(deps, {
2299
+ title: "Connect Teams",
2300
+ subtitle: `${agent} gets a portable Teams sense.`,
2301
+ summary: "Add the app credentials once, keep the secret hidden, and let Teams travel with this agent.",
2302
+ sections: [
2303
+ {
2304
+ title: "Unlocks",
2305
+ lines: [
2306
+ "Microsoft Teams messages and actions inside Ouro.",
2307
+ ],
2308
+ },
2309
+ {
2310
+ title: "What you need",
2311
+ lines: [
2312
+ "A Teams client ID, client secret, and tenant ID.",
2313
+ "The client secret stays hidden while you type.",
2314
+ ],
2315
+ },
2316
+ {
2317
+ title: "Where it lives",
2318
+ lines: [
2319
+ `${agent}'s vault runtime/config item.`,
2320
+ "It travels with the agent across machines.",
2321
+ ],
2322
+ },
2323
+ ],
2324
+ fallbackLines: [
2325
+ `Connect Teams for ${agent}`,
2326
+ "The client secret stays hidden while you type.",
2327
+ `Ouro stores the setup in ${agent}'s vault runtime/config item.`,
2328
+ ],
2329
+ });
2111
2330
  const clientId = (await promptInput("Teams client ID: ")).trim();
2112
2331
  if (!clientId)
2113
2332
  throw new Error("Teams client ID cannot be blank");
@@ -2139,17 +2358,32 @@ async function executeConnectTeams(agent, deps) {
2139
2358
  finally {
2140
2359
  progress.end();
2141
2360
  }
2142
- const message = appendBundleSyncSummary([
2143
- `Teams connected for ${agent}`,
2144
- "capability: Teams sense",
2145
- `stored: ${stored.itemPath}`,
2146
- "agent.json: senses.teams.enabled = true",
2147
- "secret was not printed",
2148
- "",
2149
- "Next: run `ouro up` so the daemon picks up the Teams sense change.",
2150
- ].join("\n"), agent, deps);
2151
- deps.writeStdout(message);
2152
- return message;
2361
+ const syncSummary = pushAgentBundleAfterCliMutation(agent, deps);
2362
+ return writeCapabilityOutcome(deps, {
2363
+ subtitle: `${agent}'s Teams sense is configured.`,
2364
+ summary: `Teams is ready for ${agent}.`,
2365
+ whatChanged: [
2366
+ "Capability: Teams sense",
2367
+ `Stored: ${stored.itemPath}`,
2368
+ "agent.json: senses.teams.enabled = true",
2369
+ "secret was not printed",
2370
+ ...(syncSummary ? [syncSummary] : []),
2371
+ ],
2372
+ nextMoves: [
2373
+ "Run ouro up so the daemon picks up the Teams sense change.",
2374
+ `Reopen the connect bay with ouro connect --agent ${agent} whenever you want to review capabilities.`,
2375
+ ],
2376
+ fallbackLines: [
2377
+ `Teams connected for ${agent}`,
2378
+ "capability: Teams sense",
2379
+ `stored: ${stored.itemPath}`,
2380
+ "agent.json: senses.teams.enabled = true",
2381
+ "secret was not printed",
2382
+ "",
2383
+ "Next: run `ouro up` so the daemon picks up the Teams sense change.",
2384
+ ...(syncSummary ? [syncSummary] : []),
2385
+ ],
2386
+ });
2153
2387
  }
2154
2388
  async function executeConnectBlueBubbles(agent, deps) {
2155
2389
  if (agent === "SerpentGuide") {
@@ -2157,11 +2391,38 @@ async function executeConnectBlueBubbles(agent, deps) {
2157
2391
  }
2158
2392
  const promptInput = requirePromptInput(deps, "BlueBubbles setup");
2159
2393
  const promptSecret = requirePromptSecret(deps, "BlueBubbles password entry");
2160
- deps.writeStdout([
2161
- `Connect BlueBubbles for ${agent}`,
2162
- "This is a local attachment for this machine.",
2163
- "The app password stays hidden while you type.",
2164
- ].join("\n"));
2394
+ writeConnectorIntro(deps, {
2395
+ title: "Connect BlueBubbles",
2396
+ subtitle: `${agent} gets a local Messages bridge on this machine.`,
2397
+ summary: "Attach this Mac's BlueBubbles bridge without pretending it travels anywhere else.",
2398
+ sections: [
2399
+ {
2400
+ title: "Unlocks",
2401
+ lines: [
2402
+ "Local iMessage bridge access on this machine only.",
2403
+ ],
2404
+ },
2405
+ {
2406
+ title: "What you need",
2407
+ lines: [
2408
+ "A BlueBubbles server URL and app password.",
2409
+ "The app password stays hidden while you type.",
2410
+ ],
2411
+ },
2412
+ {
2413
+ title: "Where it lives",
2414
+ lines: [
2415
+ `${agent}'s machine runtime config for this machine.`,
2416
+ "It does not travel to other machines unless you attach them too.",
2417
+ ],
2418
+ },
2419
+ ],
2420
+ fallbackLines: [
2421
+ `Connect BlueBubbles for ${agent}`,
2422
+ "This is a local attachment for this machine.",
2423
+ "The app password stays hidden while you type.",
2424
+ ],
2425
+ });
2165
2426
  const serverUrl = (await promptInput("BlueBubbles server URL for this machine: ")).trim();
2166
2427
  if (!serverUrl)
2167
2428
  throw new Error("BlueBubbles server URL cannot be blank");
@@ -2206,17 +2467,32 @@ async function executeConnectBlueBubbles(agent, deps) {
2206
2467
  progress.end();
2207
2468
  throw error;
2208
2469
  }
2209
- const message = appendBundleSyncSummary([
2210
- `BlueBubbles attached for ${agent} on this machine`,
2211
- `machine: ${machineId}`,
2212
- `stored: ${stored.itemPath}`,
2213
- "agent.json: senses.bluebubbles.enabled = true",
2214
- "secret was not printed",
2215
- "",
2216
- "Next: point BlueBubbles at this machine's webhook, then run `ouro up`.",
2217
- ].join("\n"), agent, deps);
2218
- deps.writeStdout(message);
2219
- return message;
2470
+ const syncSummary = pushAgentBundleAfterCliMutation(agent, deps);
2471
+ return writeCapabilityOutcome(deps, {
2472
+ subtitle: `${agent}'s local bridge is attached.`,
2473
+ summary: `BlueBubbles is attached on this machine for ${agent}.`,
2474
+ whatChanged: [
2475
+ `Machine: ${machineId}`,
2476
+ `Stored: ${stored.itemPath}`,
2477
+ "agent.json: senses.bluebubbles.enabled = true",
2478
+ "secret was not printed",
2479
+ ...(syncSummary ? [syncSummary] : []),
2480
+ ],
2481
+ nextMoves: [
2482
+ "Point BlueBubbles at this machine's webhook, then run ouro up.",
2483
+ `Attach other machines separately if ${agent} should use BlueBubbles there too.`,
2484
+ ],
2485
+ fallbackLines: [
2486
+ `BlueBubbles attached for ${agent} on this machine`,
2487
+ `machine: ${machineId}`,
2488
+ `stored: ${stored.itemPath}`,
2489
+ "agent.json: senses.bluebubbles.enabled = true",
2490
+ "secret was not printed",
2491
+ "",
2492
+ "Next: point BlueBubbles at this machine's webhook, then run `ouro up`.",
2493
+ ...(syncSummary ? [syncSummary] : []),
2494
+ ],
2495
+ });
2220
2496
  }
2221
2497
  async function executeConnectProviders(agent, deps) {
2222
2498
  const promptInput = deps.promptInput;
@@ -2663,27 +2939,51 @@ async function executeAuthRun(command, deps) {
2663
2939
  // Behavior: ouro auth stores credentials only — does NOT switch provider.
2664
2940
  // Use `ouro auth switch` to change the active provider.
2665
2941
  // Verify the credentials actually work by pinging the provider.
2942
+ let verificationStatus = "not checked";
2666
2943
  /* v8 ignore start -- integration: real API ping after auth @preserve */
2667
2944
  try {
2668
2945
  progress.startPhase(`verifying ${provider}`);
2669
2946
  const credential = await readProviderCredentialRecord(command.agent, provider, deps, {
2670
2947
  onProgress: (message) => progress.updateDetail(message),
2671
2948
  });
2672
- const status = credential.ok
2949
+ verificationStatus = credential.ok
2673
2950
  ? await verifyProviderCredentials(provider, {
2674
2951
  [provider]: { ...credential.record.config, ...credential.record.credentials },
2675
2952
  })
2676
2953
  : `stored but could not be re-read from vault (${credential.error})`;
2677
- progress.completePhase(`verifying ${provider}`, status);
2954
+ progress.completePhase(`verifying ${provider}`, verificationStatus);
2678
2955
  }
2679
2956
  catch (error) {
2680
2957
  // Verification failure is non-blocking — credentials were saved regardless.
2681
- progress.completePhase(`verifying ${provider}`, `skipped (${error instanceof Error ? error.message : String(error)})`);
2958
+ verificationStatus = `skipped (${error instanceof Error ? error.message : String(error)})`;
2959
+ progress.completePhase(`verifying ${provider}`, verificationStatus);
2682
2960
  }
2683
2961
  /* v8 ignore stop */
2684
2962
  progress.end();
2685
- deps.writeStdout(result.message);
2686
- return result.message;
2963
+ return writeCommandOutcome(deps, {
2964
+ title: "Provider auth",
2965
+ subtitle: `${command.agent} just refreshed ${provider}.`,
2966
+ summary: `${command.agent} can now use ${provider} when this lane is selected.`,
2967
+ sections: [
2968
+ {
2969
+ title: "What changed",
2970
+ lines: [
2971
+ `Stored in: ${result.credentialPath}`,
2972
+ `Provider: ${provider}`,
2973
+ `Live check: ${verificationStatus}`,
2974
+ "secret was not printed",
2975
+ ],
2976
+ },
2977
+ {
2978
+ title: "Next moves",
2979
+ lines: [
2980
+ `Choose it for a lane with ouro use --agent ${command.agent} --lane <outward|inner> --provider ${provider} --model <model>.`,
2981
+ `Double-check it any time with ouro auth verify --agent ${command.agent} --provider ${provider}.`,
2982
+ ],
2983
+ },
2984
+ ],
2985
+ fallbackLines: [result.message],
2986
+ });
2687
2987
  }
2688
2988
  async function readinessReportForAgent(agent, deps) {
2689
2989
  const bundlesRoot = deps.bundlesRoot ?? (0, identity_1.getAgentBundlesRoot)();
@@ -3628,12 +3928,13 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
3628
3928
  deps.writeRaw(`${(0, terminal_ui_1.renderOuroMasthead)({
3629
3929
  isTTY: true,
3630
3930
  columns: deps.stdoutColumns ?? process.stdout.columns,
3631
- subtitle: "Bringing the house online.",
3931
+ subtitle: "Preparing the house.",
3632
3932
  }).trimEnd()}\n\n`);
3633
3933
  }
3634
3934
  const progress = new up_progress_1.UpProgress({
3635
3935
  write: deps.writeRaw ?? deps.writeStdout,
3636
3936
  isTTY: outputIsTTY,
3937
+ columns: deps.stdoutColumns ?? process.stdout.columns,
3637
3938
  now: deps.now ?? (() => Date.now()),
3638
3939
  autoRender: true,
3639
3940
  });
@@ -3833,56 +4134,72 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
3833
4134
  });
3834
4135
  }
3835
4136
  else {
3836
- const repairResult = await (0, agentic_repair_1.runAgenticRepair)(daemonResult.stability.degraded, {
3837
- /* v8 ignore start -- production provider discovery wiring @preserve */
3838
- discoverWorkingProvider: async (agentName) => {
3839
- const { discoverWorkingProvider: discover } = await Promise.resolve().then(() => __importStar(require("./provider-discovery")));
3840
- const { pingProvider } = await Promise.resolve().then(() => __importStar(require("../provider-ping")));
3841
- return discover({
3842
- agentName,
3843
- pingProvider: pingProvider,
3844
- });
3845
- },
3846
- createProviderRuntime: agentic_repair_1.createAgenticDiagnosisProviderRuntime,
3847
- readDaemonLogsTail: () => {
3848
- try {
3849
- const fs = require("node:fs");
3850
- const path = require("node:path");
3851
- const logsDir = path.join(process.env["HOME"] ?? "", ".agentstate", "daemon", "logs");
3852
- const files = fs.readdirSync(logsDir).filter((f) => f.endsWith(".log")).sort();
3853
- if (files.length === 0)
3854
- return "(no daemon logs found)";
3855
- const lastLog = fs.readFileSync(path.join(logsDir, files[files.length - 1]), "utf8");
3856
- const lines = lastLog.split("\n");
3857
- return lines.slice(-50).join("\n");
3858
- }
3859
- catch {
3860
- return "(unable to read daemon logs)";
3861
- }
3862
- },
3863
- /* v8 ignore stop */
3864
- runInteractiveRepair: interactive_repair_1.runInteractiveRepair,
3865
- promptInput: deps.promptInput ?? (async () => "n"),
3866
- writeStdout: deps.writeStdout,
3867
- runAuthFlow: async (agent, providerOverride) => {
3868
- await executeAuthRun({
3869
- kind: "auth.run",
3870
- agent,
3871
- ...(providerOverride ? { provider: providerOverride } : {}),
3872
- }, deps);
3873
- },
3874
- runVaultUnlock: async (agent) => {
3875
- await executeVaultUnlock({ kind: "vault.unlock", agent }, deps);
3876
- },
3877
- skipQueueSummary: true,
3878
- });
3879
- if (repairResult.repairsAttempted) {
3880
- const repairedAgents = daemonResult.stability.degraded
3881
- .filter(interactive_repair_1.hasRunnableInteractiveRepair)
3882
- .map((entry) => entry.agent);
4137
+ const typedDegraded = daemonResult.stability.degraded.filter((entry) => (0, readiness_repair_1.isKnownReadinessIssue)(entry.issue));
4138
+ const untypedDegraded = daemonResult.stability.degraded.filter((entry) => !(0, readiness_repair_1.isKnownReadinessIssue)(entry.issue));
4139
+ let repairsAttempted = false;
4140
+ const repairedAgents = new Set();
4141
+ if (typedDegraded.length > 0) {
4142
+ const guidedRepair = await runReadinessRepairForDegraded(typedDegraded, deps);
4143
+ if (guidedRepair.repairsAttempted) {
4144
+ repairsAttempted = true;
4145
+ typedDegraded.forEach((entry) => repairedAgents.add(entry.agent));
4146
+ }
4147
+ }
4148
+ if (untypedDegraded.length > 0) {
4149
+ const repairResult = await (0, agentic_repair_1.runAgenticRepair)(untypedDegraded, {
4150
+ /* v8 ignore start -- production provider discovery wiring @preserve */
4151
+ discoverWorkingProvider: async (agentName) => {
4152
+ const { discoverWorkingProvider: discover } = await Promise.resolve().then(() => __importStar(require("./provider-discovery")));
4153
+ const { pingProvider } = await Promise.resolve().then(() => __importStar(require("../provider-ping")));
4154
+ return discover({
4155
+ agentName,
4156
+ pingProvider: pingProvider,
4157
+ });
4158
+ },
4159
+ createProviderRuntime: agentic_repair_1.createAgenticDiagnosisProviderRuntime,
4160
+ readDaemonLogsTail: () => {
4161
+ try {
4162
+ const fs = require("node:fs");
4163
+ const path = require("node:path");
4164
+ const logsDir = path.join(process.env["HOME"] ?? "", ".agentstate", "daemon", "logs");
4165
+ const files = fs.readdirSync(logsDir).filter((f) => f.endsWith(".log")).sort();
4166
+ if (files.length === 0)
4167
+ return "(no daemon logs found)";
4168
+ const lastLog = fs.readFileSync(path.join(logsDir, files[files.length - 1]), "utf8");
4169
+ const lines = lastLog.split("\n");
4170
+ return lines.slice(-50).join("\n");
4171
+ }
4172
+ catch {
4173
+ return "(unable to read daemon logs)";
4174
+ }
4175
+ },
4176
+ /* v8 ignore stop */
4177
+ runInteractiveRepair: interactive_repair_1.runInteractiveRepair,
4178
+ promptInput: deps.promptInput ?? (async () => "n"),
4179
+ writeStdout: deps.writeStdout,
4180
+ runAuthFlow: async (agent, providerOverride) => {
4181
+ await executeAuthRun({
4182
+ kind: "auth.run",
4183
+ agent,
4184
+ ...(providerOverride ? { provider: providerOverride } : {}),
4185
+ }, deps);
4186
+ },
4187
+ runVaultUnlock: async (agent) => {
4188
+ await executeVaultUnlock({ kind: "vault.unlock", agent }, deps);
4189
+ },
4190
+ skipQueueSummary: true,
4191
+ });
4192
+ if (repairResult.repairsAttempted) {
4193
+ repairsAttempted = true;
4194
+ untypedDegraded
4195
+ .filter(interactive_repair_1.hasRunnableInteractiveRepair)
4196
+ .forEach((entry) => repairedAgents.add(entry.agent));
4197
+ }
4198
+ }
4199
+ if (repairsAttempted) {
3883
4200
  progress.startPhase("post-repair check");
3884
- await reportPostRepairProviderHealth(deps, repairedAgents, (msg) => progress.updateDetail(msg));
3885
- progress.completePhase("post-repair check", providerRepairCountSummary(repairedAgents.length));
4201
+ await reportPostRepairProviderHealth(deps, [...repairedAgents], (msg) => progress.updateDetail(msg));
4202
+ progress.completePhase("post-repair check", providerRepairCountSummary(repairedAgents.size));
3886
4203
  }
3887
4204
  }
3888
4205
  }
@@ -5096,9 +5413,31 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
5096
5413
  await deps.startChat(hatchInput.agentName);
5097
5414
  return "";
5098
5415
  }
5099
- const message = `hatched ${hatchInput.agentName} at ${result.bundleRoot} using specialist identity ${result.selectedIdentity}; ${daemonResult.message}`;
5100
- deps.writeStdout(message);
5101
- return message;
5416
+ return writeCommandOutcome(deps, {
5417
+ title: "Hatch complete",
5418
+ subtitle: `${hatchInput.agentName} just arrived.`,
5419
+ summary: `${hatchInput.agentName} is ready for first contact.`,
5420
+ sections: [
5421
+ {
5422
+ title: "What changed",
5423
+ lines: [
5424
+ `Bundle: ${result.bundleRoot}`,
5425
+ `Specialist identity: ${result.selectedIdentity}`,
5426
+ `House status: ${daemonResult.message}`,
5427
+ ],
5428
+ },
5429
+ {
5430
+ title: "Next moves",
5431
+ lines: [
5432
+ `Talk to them with ouro chat ${hatchInput.agentName}.`,
5433
+ `Use ouro connect --agent ${hatchInput.agentName} to bring more capabilities online.`,
5434
+ ],
5435
+ },
5436
+ ],
5437
+ fallbackLines: [
5438
+ `hatched ${hatchInput.agentName} at ${result.bundleRoot} using specialist identity ${result.selectedIdentity}; ${daemonResult.message}`,
5439
+ ],
5440
+ });
5102
5441
  }
5103
5442
  // ── doctor (local, no daemon socket needed) ──
5104
5443
  if (command.kind === "doctor") {
@@ -5283,7 +5622,17 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
5283
5622
  }
5284
5623
  const fallbackMessage = response.summary ?? response.message ?? (response.ok ? "ok" : `error: ${response.error ?? "unknown error"}`);
5285
5624
  const message = command.kind === "daemon.status"
5286
- ? (0, cli_render_1.formatDaemonStatusOutput)(response, fallbackMessage)
5625
+ ? (() => {
5626
+ const payload = (0, cli_render_1.parseStatusPayload)(response.data);
5627
+ if (payload && ttyBoardEnabled(deps)) {
5628
+ return (0, human_command_screens_1.renderHouseStatusScreen)({
5629
+ payload,
5630
+ isTTY: true,
5631
+ columns: deps.stdoutColumns ?? process.stdout.columns,
5632
+ }).trimEnd();
5633
+ }
5634
+ return (0, cli_render_1.formatDaemonStatusOutput)(response, fallbackMessage);
5635
+ })()
5287
5636
  : fallbackMessage;
5288
5637
  deps.writeStdout(message);
5289
5638
  return message;