@ouro.bot/cli 0.1.0-alpha.441 → 0.1.0-alpha.443
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/changelog.json +16 -0
- package/dist/heart/daemon/agent-config-check.js +34 -2
- package/dist/heart/daemon/cli-exec.js +92 -15
- package/dist/heart/daemon/cli-help.js +4 -4
- package/dist/heart/daemon/connect-bay.js +17 -18
- package/dist/heart/daemon/human-command-screens.js +15 -15
- package/dist/heart/daemon/provider-ping-progress.js +9 -4
- package/dist/heart/daemon/terminal-ui.js +6 -15
- package/dist/heart/daemon/up-progress.js +57 -11
- package/package.json +1 -1
package/changelog.json
CHANGED
|
@@ -1,6 +1,22 @@
|
|
|
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.443",
|
|
6
|
+
"changes": [
|
|
7
|
+
"`ouro up` now renders as a real boot checklist instead of a loose progress wall: the screen shows the full startup path up front, marks what is running now, and keeps pending steps visible so humans can see where Ouro is in the bring-up flow.",
|
|
8
|
+
"Provider checks during startup now narrate the providers each selected lane is actually using, translate noisy vault chatter into plain language, and keep the live-check copy truthful instead of implying that every configured provider was checked.",
|
|
9
|
+
"`ouro up` no longer reports success just because the daemon answered once during startup. Before handing control back, Ouro now performs one final daemon-status handoff check and fails with a clear diagnosis if the background service stopped before boot actually finished."
|
|
10
|
+
]
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"version": "0.1.0-alpha.442",
|
|
14
|
+
"changes": [
|
|
15
|
+
"The shared CLI masthead now lands as a clean `OUROBOROS` wordmark instead of the older circle treatment, with compact/plain render paths trimmed down so the banner feels intentional rather than doubled or noisy.",
|
|
16
|
+
"The main human-facing screens now use warmer but more concrete first-run language: `ouro up`, the home screen, status, help, hatch, vault outcomes, and the connections screen now explain what Ouro is actually doing without leaning on unexplained `house` or `bay` metaphors.",
|
|
17
|
+
"Failed live provider checks in the connections screen now stay in the honest `needs attention` bucket instead of being downgraded to `needs credentials`, and hermetic runtime plus packaged-install coverage lock the wording into the shipped CLI."
|
|
18
|
+
]
|
|
19
|
+
},
|
|
4
20
|
{
|
|
5
21
|
"version": "0.1.0-alpha.441",
|
|
6
22
|
"changes": [
|
|
@@ -318,6 +318,33 @@ function failedPingResult(agentName, lane, provider, model, result) {
|
|
|
318
318
|
function credentialRecordForLane(pool, provider) {
|
|
319
319
|
return pool.providers[provider];
|
|
320
320
|
}
|
|
321
|
+
function laneAudienceLabel(lane) {
|
|
322
|
+
return lane === "outward" ? "chat" : "inner dialog";
|
|
323
|
+
}
|
|
324
|
+
function bindingLabel(binding) {
|
|
325
|
+
return `${binding.provider} / ${binding.model}`;
|
|
326
|
+
}
|
|
327
|
+
function selectedProviderPlan(agentName, state) {
|
|
328
|
+
return [
|
|
329
|
+
`${agentName}: checking the providers this agent uses right now`,
|
|
330
|
+
...["outward", "inner"].map((lane) => `- ${laneAudienceLabel(lane)}: ${bindingLabel(state.lanes[lane])}`),
|
|
331
|
+
].join("\n");
|
|
332
|
+
}
|
|
333
|
+
function mapVaultRefreshProgress(agentName, onProgress) {
|
|
334
|
+
return (message) => {
|
|
335
|
+
if (message.startsWith("reading vault items for ")) {
|
|
336
|
+
onProgress(`${agentName}: opening saved provider credentials in the vault`);
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
if (message === "parsing provider credentials...") {
|
|
340
|
+
onProgress(`${agentName}: organizing saved provider credentials`);
|
|
341
|
+
}
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
function providerPingSubject(agentName, lanes) {
|
|
345
|
+
const laneList = lanes.map((lane) => laneAudienceLabel(lane)).join(" + ");
|
|
346
|
+
return `${agentName} (${laneList})`;
|
|
347
|
+
}
|
|
321
348
|
/**
|
|
322
349
|
* Structural validation only. Live provider credential validation belongs to
|
|
323
350
|
* checkAgentConfigWithProviderHealth(), which reads the agent vault and pings.
|
|
@@ -352,8 +379,9 @@ async function checkAgentConfigWithProviderHealth(agentName, bundlesRoot, deps =
|
|
|
352
379
|
return stateResult.result;
|
|
353
380
|
if (stateResult.disabled)
|
|
354
381
|
return { ok: true };
|
|
382
|
+
deps.onProgress?.(selectedProviderPlan(agentName, stateResult.state));
|
|
355
383
|
const ping = deps.pingProvider ?? (await Promise.resolve().then(() => __importStar(require("../provider-ping")))).pingProvider;
|
|
356
|
-
const poolResult = await (0, provider_credentials_1.refreshProviderCredentialPool)(agentName, deps.onProgress ? { onProgress: deps.onProgress } : undefined);
|
|
384
|
+
const poolResult = await (0, provider_credentials_1.refreshProviderCredentialPool)(agentName, deps.onProgress ? { onProgress: mapVaultRefreshProgress(agentName, deps.onProgress) } : undefined);
|
|
357
385
|
const pingGroups = new Map();
|
|
358
386
|
const lanes = ["outward", "inner"];
|
|
359
387
|
for (const lane of lanes) {
|
|
@@ -390,7 +418,11 @@ async function checkAgentConfigWithProviderHealth(agentName, bundlesRoot, deps =
|
|
|
390
418
|
const result = await ping(group.provider, providerCredentialConfig(group.record), {
|
|
391
419
|
model: group.model,
|
|
392
420
|
...(deps.onProgress
|
|
393
|
-
? (0, provider_ping_progress_1.createProviderPingProgressReporter)({
|
|
421
|
+
? (0, provider_ping_progress_1.createProviderPingProgressReporter)({
|
|
422
|
+
provider: group.provider,
|
|
423
|
+
model: group.model,
|
|
424
|
+
subject: providerPingSubject(agentName, group.lanes),
|
|
425
|
+
}, deps.onProgress)
|
|
394
426
|
: {}),
|
|
395
427
|
});
|
|
396
428
|
return { group, result };
|
|
@@ -160,7 +160,6 @@ async function checkAgentProviders(deps, agentsOverride, onProgress) {
|
|
|
160
160
|
const degraded = [];
|
|
161
161
|
for (const agent of [...new Set(agents)]) {
|
|
162
162
|
try {
|
|
163
|
-
onProgress?.(`${agent}: checking providers...`);
|
|
164
163
|
const result = await checkAgentProviderHealth(agent, bundlesRoot, deps, onProgress);
|
|
165
164
|
if (result.ok)
|
|
166
165
|
continue;
|
|
@@ -358,9 +357,14 @@ function writeProviderRepairSummary(deps, title, degraded) {
|
|
|
358
357
|
}
|
|
359
358
|
function providerRepairCountSummary(count) {
|
|
360
359
|
if (count === 0)
|
|
361
|
-
return "
|
|
360
|
+
return "selected providers answered live checks";
|
|
362
361
|
return `${count} ${count === 1 ? "needs" : "need"} attention`;
|
|
363
362
|
}
|
|
363
|
+
function bootPhasePlan(daemonAlive) {
|
|
364
|
+
return daemonAlive
|
|
365
|
+
? ["update check", "system setup", "starting daemon", "provider checks", "final daemon check"]
|
|
366
|
+
: ["update check", "system setup", "provider checks", "starting daemon", "final daemon check"];
|
|
367
|
+
}
|
|
364
368
|
function createHumanCommandProgress(deps, commandName) {
|
|
365
369
|
return new up_progress_1.CommandProgress({
|
|
366
370
|
write: deps.writeRaw ?? deps.writeStdout,
|
|
@@ -387,8 +391,67 @@ function daemonProgressSummary(result) {
|
|
|
387
391
|
if (result.alreadyRunning)
|
|
388
392
|
return "already running";
|
|
389
393
|
if (result.message.includes("replaced"))
|
|
390
|
-
return "replacement
|
|
391
|
-
return "
|
|
394
|
+
return "replacement answered";
|
|
395
|
+
return "background service answered";
|
|
396
|
+
}
|
|
397
|
+
function finalDaemonFailureMessage(deps, reason) {
|
|
398
|
+
const lines = [`background service stopped before boot finished: ${reason}`];
|
|
399
|
+
const recentLogLines = deps.readRecentDaemonLogLines?.(DEFAULT_DAEMON_STARTUP_LOG_LINES) ?? [];
|
|
400
|
+
if (recentLogLines.length > 0) {
|
|
401
|
+
lines.push("recent daemon logs:");
|
|
402
|
+
lines.push(...recentLogLines.map((line) => ` ${line}`));
|
|
403
|
+
}
|
|
404
|
+
lines.push("Run `ouro up` again or `ouro doctor` for a deeper diagnosis.");
|
|
405
|
+
return lines.join("\n");
|
|
406
|
+
}
|
|
407
|
+
async function verifyDaemonReadyForHandoff(deps) {
|
|
408
|
+
const socketAlive = await deps.checkSocketAlive(deps.socketPath);
|
|
409
|
+
if (!socketAlive) {
|
|
410
|
+
return {
|
|
411
|
+
ok: false,
|
|
412
|
+
summary: "background service stopped",
|
|
413
|
+
message: finalDaemonFailureMessage(deps, "the daemon socket is no longer answering"),
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
try {
|
|
417
|
+
const response = await deps.sendCommand(deps.socketPath, { kind: "daemon.status" });
|
|
418
|
+
if (!response.ok) {
|
|
419
|
+
const reason = response.error ?? response.message ?? "daemon status did not answer cleanly";
|
|
420
|
+
return {
|
|
421
|
+
ok: false,
|
|
422
|
+
summary: "daemon status did not answer",
|
|
423
|
+
message: finalDaemonFailureMessage(deps, reason),
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
const payload = (0, cli_render_1.parseStatusPayload)(response.data);
|
|
427
|
+
if (!payload) {
|
|
428
|
+
return {
|
|
429
|
+
ok: true,
|
|
430
|
+
summary: "daemon answered",
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
if (payload.overview.daemon !== "running") {
|
|
434
|
+
return {
|
|
435
|
+
ok: false,
|
|
436
|
+
summary: `daemon reported ${payload.overview.daemon}`,
|
|
437
|
+
message: finalDaemonFailureMessage(deps, `the daemon reported state ${payload.overview.daemon}`),
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
const workerCount = payload.workers.length;
|
|
441
|
+
return {
|
|
442
|
+
ok: true,
|
|
443
|
+
summary: workerCount === 0
|
|
444
|
+
? "daemon answered"
|
|
445
|
+
: `${workerCount} worker${workerCount === 1 ? "" : "s"} still answering`,
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
catch (error) {
|
|
449
|
+
return {
|
|
450
|
+
ok: false,
|
|
451
|
+
summary: "daemon status did not answer",
|
|
452
|
+
message: finalDaemonFailureMessage(deps, error instanceof Error ? error.message : String(error)),
|
|
453
|
+
};
|
|
454
|
+
}
|
|
392
455
|
}
|
|
393
456
|
async function reportPostRepairProviderHealth(deps, repairedAgents, onProgress) {
|
|
394
457
|
const remainingDegraded = await checkAgentProviders(deps, repairedAgents, onProgress);
|
|
@@ -1211,7 +1274,7 @@ async function executeVaultUnlock(command, deps) {
|
|
|
1211
1274
|
{
|
|
1212
1275
|
title: "Next moves",
|
|
1213
1276
|
lines: [
|
|
1214
|
-
`Run ouro up
|
|
1277
|
+
`Run ouro up so Ouro can check ${command.agent} again.`,
|
|
1215
1278
|
`If credentials are still missing, run ouro auth --agent ${command.agent} --provider <provider>.`,
|
|
1216
1279
|
],
|
|
1217
1280
|
},
|
|
@@ -1281,7 +1344,7 @@ async function executeVaultCreate(command, deps) {
|
|
|
1281
1344
|
];
|
|
1282
1345
|
return writeCommandOutcome(deps, {
|
|
1283
1346
|
title: "Credential vault",
|
|
1284
|
-
subtitle: `${command.agent}
|
|
1347
|
+
subtitle: `${command.agent}'s credential vault is ready.`,
|
|
1285
1348
|
summary: `All raw credentials for ${command.agent} now live in this vault.`,
|
|
1286
1349
|
sections: [
|
|
1287
1350
|
{
|
|
@@ -1298,7 +1361,7 @@ async function executeVaultCreate(command, deps) {
|
|
|
1298
1361
|
title: "Next moves",
|
|
1299
1362
|
lines: [
|
|
1300
1363
|
`Authenticate the providers ${command.agent} should use with ouro auth --agent ${command.agent} --provider <provider>.`,
|
|
1301
|
-
`Then run ouro up so
|
|
1364
|
+
`Then run ouro up so Ouro can bring ${command.agent} online.`,
|
|
1302
1365
|
],
|
|
1303
1366
|
},
|
|
1304
1367
|
],
|
|
@@ -1493,7 +1556,7 @@ async function executeVaultRecover(command, deps) {
|
|
|
1493
1556
|
title: "Next moves",
|
|
1494
1557
|
lines: [
|
|
1495
1558
|
`Run ouro auth verify --agent ${command.agent} to re-check the stored providers.`,
|
|
1496
|
-
`Then run ouro up so
|
|
1559
|
+
`Then run ouro up so Ouro can check ${command.agent} again.`,
|
|
1497
1560
|
],
|
|
1498
1561
|
},
|
|
1499
1562
|
],
|
|
@@ -1999,7 +2062,7 @@ async function buildConnectMenu(agent, deps, onProgress) {
|
|
|
1999
2062
|
{
|
|
2000
2063
|
option: "1",
|
|
2001
2064
|
name: "Providers",
|
|
2002
|
-
section: "
|
|
2065
|
+
section: "Providers",
|
|
2003
2066
|
status: providerSummary.status,
|
|
2004
2067
|
detailLines: providerSummary.detailLines,
|
|
2005
2068
|
laneSummaries: providerSummary.laneSummaries,
|
|
@@ -2166,7 +2229,7 @@ async function executeConnectPerplexity(agent, deps) {
|
|
|
2166
2229
|
],
|
|
2167
2230
|
nextMoves: [
|
|
2168
2231
|
"Ask the agent to search.",
|
|
2169
|
-
`Reopen the
|
|
2232
|
+
`Reopen the connections screen with ouro connect --agent ${agent} whenever you want to review capabilities.`,
|
|
2170
2233
|
],
|
|
2171
2234
|
fallbackLines: [
|
|
2172
2235
|
`Perplexity connected for ${agent}`,
|
|
@@ -2247,7 +2310,7 @@ async function executeConnectEmbeddings(agent, deps) {
|
|
|
2247
2310
|
],
|
|
2248
2311
|
nextMoves: [
|
|
2249
2312
|
`Rerun ouro connect embeddings --agent ${agent} with a working key.`,
|
|
2250
|
-
`Use ouro connect --agent ${agent} to review the rest of the
|
|
2313
|
+
`Use ouro connect --agent ${agent} to review the rest of the connections screen.`,
|
|
2251
2314
|
],
|
|
2252
2315
|
fallbackLines: [
|
|
2253
2316
|
`Embeddings key was saved for ${agent}, but the live check failed.`,
|
|
@@ -2276,7 +2339,7 @@ async function executeConnectEmbeddings(agent, deps) {
|
|
|
2276
2339
|
],
|
|
2277
2340
|
nextMoves: [
|
|
2278
2341
|
"Ask the agent to search notes or memory.",
|
|
2279
|
-
`Reopen the
|
|
2342
|
+
`Reopen the connections screen with ouro connect --agent ${agent} whenever you want to review capabilities.`,
|
|
2280
2343
|
],
|
|
2281
2344
|
fallbackLines: [
|
|
2282
2345
|
`Embeddings connected for ${agent}`,
|
|
@@ -2371,7 +2434,7 @@ async function executeConnectTeams(agent, deps) {
|
|
|
2371
2434
|
],
|
|
2372
2435
|
nextMoves: [
|
|
2373
2436
|
"Run ouro up so the daemon picks up the Teams sense change.",
|
|
2374
|
-
`Reopen the
|
|
2437
|
+
`Reopen the connections screen with ouro connect --agent ${agent} whenever you want to review capabilities.`,
|
|
2375
2438
|
],
|
|
2376
2439
|
fallbackLines: [
|
|
2377
2440
|
`Teams connected for ${agent}`,
|
|
@@ -3939,6 +4002,8 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
|
|
|
3939
4002
|
now: deps.now ?? (() => Date.now()),
|
|
3940
4003
|
autoRender: true,
|
|
3941
4004
|
});
|
|
4005
|
+
const daemonAliveAtBootStart = await deps.checkSocketAlive(deps.socketPath);
|
|
4006
|
+
progress.setPhasePlan?.(bootPhasePlan(daemonAliveAtBootStart));
|
|
3942
4007
|
// ── versioned CLI update check ──
|
|
3943
4008
|
if (deps.checkForCliUpdate) {
|
|
3944
4009
|
progress.startPhase("update check");
|
|
@@ -4067,6 +4132,7 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
|
|
|
4067
4132
|
promptInput: deps.promptInput,
|
|
4068
4133
|
});
|
|
4069
4134
|
const daemonAliveBeforeStart = await deps.checkSocketAlive(deps.socketPath);
|
|
4135
|
+
progress.setPhasePlan?.(bootPhasePlan(daemonAliveBeforeStart));
|
|
4070
4136
|
let providerChecksAlreadyRun = false;
|
|
4071
4137
|
if (!daemonAliveBeforeStart) {
|
|
4072
4138
|
progress.startPhase("provider checks");
|
|
@@ -4120,6 +4186,17 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
|
|
|
4120
4186
|
daemonResult.stability = mergeStartupStability(daemonResult.stability, providerDegraded);
|
|
4121
4187
|
progress.completePhase("provider checks", providerRepairCountSummary(providerDegraded.length));
|
|
4122
4188
|
}
|
|
4189
|
+
progress.startPhase("final daemon check");
|
|
4190
|
+
const finalDaemonCheck = await verifyDaemonReadyForHandoff(deps);
|
|
4191
|
+
if (!finalDaemonCheck.ok) {
|
|
4192
|
+
;
|
|
4193
|
+
progress.failPhase?.("final daemon check", finalDaemonCheck.summary);
|
|
4194
|
+
progress.end();
|
|
4195
|
+
const message = finalDaemonCheck.message ?? "background service stopped before boot finished";
|
|
4196
|
+
deps.writeStdout(message);
|
|
4197
|
+
return message;
|
|
4198
|
+
}
|
|
4199
|
+
progress.completePhase("final daemon check", finalDaemonCheck.summary);
|
|
4123
4200
|
progress.end();
|
|
4124
4201
|
// Interactive repair for degraded agents (Unit 5) — skipped by --no-repair (Unit 6)
|
|
4125
4202
|
if (daemonResult.stability?.degraded && daemonResult.stability.degraded.length > 0) {
|
|
@@ -5379,7 +5456,7 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
|
|
|
5379
5456
|
if (ttyBoardEnabled(deps)) {
|
|
5380
5457
|
deps.writeStdout(renderCommandBoard(deps, {
|
|
5381
5458
|
title: "Hatch an agent",
|
|
5382
|
-
subtitle: "Let’s
|
|
5459
|
+
subtitle: "Let’s set up a new agent.",
|
|
5383
5460
|
summary: "Ouro will walk through the essentials, then hand the conversation to the specialist.",
|
|
5384
5461
|
sections: [
|
|
5385
5462
|
{
|
|
@@ -5430,7 +5507,7 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
|
|
|
5430
5507
|
lines: [
|
|
5431
5508
|
`Bundle: ${result.bundleRoot}`,
|
|
5432
5509
|
`Specialist identity: ${result.selectedIdentity}`,
|
|
5433
|
-
`
|
|
5510
|
+
`Runtime status: ${daemonResult.message}`,
|
|
5434
5511
|
],
|
|
5435
5512
|
},
|
|
5436
5513
|
{
|
|
@@ -14,7 +14,7 @@ exports.getCommandHelp = getCommandHelp;
|
|
|
14
14
|
exports.COMMAND_REGISTRY = {
|
|
15
15
|
up: {
|
|
16
16
|
category: "Lifecycle",
|
|
17
|
-
description: "
|
|
17
|
+
description: "Start and check Ouro: bring up the background runtime, refresh what this machine needs, and show anything that still needs attention. In a human TTY, bare `ouro` opens the home screen instead; noninteractive shells still route bare `ouro` to `ouro up`.",
|
|
18
18
|
usage: "ouro [up] [--no-repair]",
|
|
19
19
|
example: "ouro up --no-repair",
|
|
20
20
|
},
|
|
@@ -32,7 +32,7 @@ exports.COMMAND_REGISTRY = {
|
|
|
32
32
|
},
|
|
33
33
|
status: {
|
|
34
34
|
category: "Lifecycle",
|
|
35
|
-
description: "Show
|
|
35
|
+
description: "Show Ouro status for this machine",
|
|
36
36
|
usage: "ouro status",
|
|
37
37
|
example: "ouro status",
|
|
38
38
|
},
|
|
@@ -165,7 +165,7 @@ exports.COMMAND_REGISTRY = {
|
|
|
165
165
|
},
|
|
166
166
|
connect: {
|
|
167
167
|
category: "Auth",
|
|
168
|
-
description: "
|
|
168
|
+
description: "Set up providers, portable integrations, and local senses from one guided screen",
|
|
169
169
|
usage: "ouro connect [providers|perplexity|embeddings|teams|bluebubbles] [--agent <name>]",
|
|
170
170
|
example: "ouro connect",
|
|
171
171
|
subcommands: ["providers", "perplexity", "embeddings", "teams", "bluebubbles"],
|
|
@@ -278,7 +278,7 @@ const SUBCOMMAND_HELP = {
|
|
|
278
278
|
example: "ouro connect perplexity",
|
|
279
279
|
},
|
|
280
280
|
"connect providers": {
|
|
281
|
-
description: "Open provider
|
|
281
|
+
description: "Open provider setup from the connections screen without remembering the auth command",
|
|
282
282
|
usage: "ouro connect providers [--agent <name>]",
|
|
283
283
|
example: "ouro connect providers",
|
|
284
284
|
},
|
|
@@ -87,9 +87,8 @@ function resolveProviderHealthStatus(providerHealth) {
|
|
|
87
87
|
return "needs setup";
|
|
88
88
|
if (issue?.kind === "provider-credentials-missing")
|
|
89
89
|
return "needs credentials";
|
|
90
|
-
if (issue?.kind === "provider-live-check-failed")
|
|
91
|
-
return
|
|
92
|
-
}
|
|
90
|
+
if (issue?.kind === "provider-live-check-failed")
|
|
91
|
+
return "needs attention";
|
|
93
92
|
const error = String(providerHealth.error).toLowerCase();
|
|
94
93
|
const fix = String(providerHealth.fix).toLowerCase();
|
|
95
94
|
if (error.includes("failed live check"))
|
|
@@ -169,8 +168,8 @@ function panel(title, body, width) {
|
|
|
169
168
|
return lines;
|
|
170
169
|
}
|
|
171
170
|
function renderHeader(agent, width) {
|
|
172
|
-
return panel(`${agent}
|
|
173
|
-
tty("
|
|
171
|
+
return panel(`${agent} connections`, [
|
|
172
|
+
tty("Set up or review one capability at a time.", BONE, true),
|
|
174
173
|
tty("Everything on this screen was checked live just now.", MIST),
|
|
175
174
|
], width);
|
|
176
175
|
}
|
|
@@ -266,23 +265,23 @@ function renderTtyBay(entries, options) {
|
|
|
266
265
|
const masthead = (0, terminal_ui_1.renderOuroMasthead)({
|
|
267
266
|
isTTY: true,
|
|
268
267
|
columns,
|
|
269
|
-
subtitle: "
|
|
268
|
+
subtitle: "Set up connections one step at a time.",
|
|
270
269
|
}).trimEnd();
|
|
271
270
|
const header = renderHeader(options.agent, fullWidth);
|
|
272
271
|
const nextEntry = entries.find((entry) => isProblemStatus(entry.status));
|
|
273
|
-
const providerEntry = entries.find((entry) => entry.section === "
|
|
272
|
+
const providerEntry = entries.find((entry) => entry.section === "Providers");
|
|
274
273
|
const portableEntries = entries.filter((entry) => entry.section === "Portable");
|
|
275
274
|
const machineEntries = entries.filter((entry) => entry.section === "This machine");
|
|
276
275
|
const wide = columns >= 118;
|
|
277
276
|
const footer = [
|
|
278
|
-
tty("
|
|
277
|
+
tty("Choose a number, or type the capability name.", MIST),
|
|
279
278
|
options.prompt,
|
|
280
279
|
];
|
|
281
280
|
if (!wide) {
|
|
282
281
|
const panels = [
|
|
283
282
|
header,
|
|
284
|
-
panel("
|
|
285
|
-
panel("
|
|
283
|
+
panel("Recommended next step", nextMoveBody(nextEntry), fullWidth),
|
|
284
|
+
panel("Providers", renderProviderBody(providerEntry, fullWidth), fullWidth),
|
|
286
285
|
panel("Portable", renderCapabilityBody(portableEntries, fullWidth), fullWidth),
|
|
287
286
|
panel("This machine", renderCapabilityBody(machineEntries, fullWidth), fullWidth),
|
|
288
287
|
];
|
|
@@ -291,18 +290,18 @@ function renderTtyBay(entries, options) {
|
|
|
291
290
|
const gap = 2;
|
|
292
291
|
const leftWidth = Math.max(52, Math.floor((fullWidth - gap) / 2));
|
|
293
292
|
const rightWidth = Math.max(40, fullWidth - gap - leftWidth);
|
|
294
|
-
const topRow = combineColumns(panel("
|
|
295
|
-
const bottomRow = combineColumns(panel("
|
|
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);
|
|
296
295
|
return [masthead, "", ...header, "", ...topRow, "", ...bottomRow, "", ...footer].join("\n");
|
|
297
296
|
}
|
|
298
297
|
function renderNonTtyBay(entries, options) {
|
|
299
298
|
const nextEntry = entries.find((entry) => isProblemStatus(entry.status));
|
|
300
299
|
const lines = [
|
|
301
|
-
`${options.agent}
|
|
302
|
-
"
|
|
300
|
+
`${options.agent} connections`,
|
|
301
|
+
"Set up or review one capability at a time. Provider status was checked live just now.",
|
|
303
302
|
"",
|
|
304
|
-
"
|
|
305
|
-
"
|
|
303
|
+
"Recommended next step",
|
|
304
|
+
"---------------------",
|
|
306
305
|
];
|
|
307
306
|
if (!nextEntry) {
|
|
308
307
|
lines.push("Everything here is ready. Pick what you want to review or refresh.");
|
|
@@ -315,7 +314,7 @@ function renderNonTtyBay(entries, options) {
|
|
|
315
314
|
lines.push(`run: ${nextEntry.nextAction}`);
|
|
316
315
|
}
|
|
317
316
|
lines.push("");
|
|
318
|
-
for (const section of ["
|
|
317
|
+
for (const section of ["Providers", "Portable", "This machine"]) {
|
|
319
318
|
lines.push(section);
|
|
320
319
|
lines.push("-".repeat(Math.max(6, section.length + 4)));
|
|
321
320
|
for (const entry of entries.filter((candidate) => candidate.section === section)) {
|
|
@@ -338,7 +337,7 @@ function renderNonTtyBay(entries, options) {
|
|
|
338
337
|
}
|
|
339
338
|
lines.push("6. Not now");
|
|
340
339
|
lines.push("");
|
|
341
|
-
lines.push("
|
|
340
|
+
lines.push("Choose a number, or type the capability name.");
|
|
342
341
|
lines.push(options.prompt);
|
|
343
342
|
return lines.join("\n");
|
|
344
343
|
}
|
|
@@ -21,7 +21,7 @@ function renderScreenEvent(screen) {
|
|
|
21
21
|
function buildOuroHomeActions(agents) {
|
|
22
22
|
if (agents.length === 0) {
|
|
23
23
|
return [
|
|
24
|
-
{ key: "1", label: "
|
|
24
|
+
{ key: "1", label: "Create a new agent", kind: "hatch", command: "ouro hatch" },
|
|
25
25
|
{ key: "2", label: "Clone an existing bundle", kind: "clone", command: "ouro clone <remote>" },
|
|
26
26
|
{ key: "3", label: "Show help", kind: "help", command: "ouro --help" },
|
|
27
27
|
{ key: "4", label: "Exit", kind: "exit", command: "exit" },
|
|
@@ -36,9 +36,9 @@ function buildOuroHomeActions(agents) {
|
|
|
36
36
|
}));
|
|
37
37
|
return [
|
|
38
38
|
...actions,
|
|
39
|
-
{ key: String(actions.length + 1), label: "
|
|
40
|
-
{ key: String(actions.length + 2), label: "
|
|
41
|
-
{ key: String(actions.length + 3), label: "
|
|
39
|
+
{ key: String(actions.length + 1), label: "Start or check Ouro", kind: "up", command: "ouro up" },
|
|
40
|
+
{ key: String(actions.length + 2), label: "Set up connections", kind: "connect", command: "ouro connect --agent <agent>" },
|
|
41
|
+
{ key: String(actions.length + 3), label: "Fix setup issues", kind: "repair", command: "ouro repair --agent <agent>" },
|
|
42
42
|
{ key: String(actions.length + 4), label: "Show help", kind: "help", command: "ouro --help" },
|
|
43
43
|
{ key: String(actions.length + 5), label: "Exit", kind: "exit", command: "exit" },
|
|
44
44
|
];
|
|
@@ -60,10 +60,10 @@ function renderOuroHomeScreen(options) {
|
|
|
60
60
|
const actions = buildOuroHomeActions(options.agents);
|
|
61
61
|
const sections = [
|
|
62
62
|
{
|
|
63
|
-
title: options.agents.length === 0 ? "Start here" : "
|
|
63
|
+
title: options.agents.length === 0 ? "Start here" : "Available agents",
|
|
64
64
|
lines: options.agents.length === 0
|
|
65
|
-
? ["No agents are
|
|
66
|
-
: options.agents.map((agent) => `${agent} is
|
|
65
|
+
? ["No agents are set up on this machine yet."]
|
|
66
|
+
: options.agents.map((agent) => `${agent} is available.`),
|
|
67
67
|
},
|
|
68
68
|
];
|
|
69
69
|
const actionRows = actions.map((action, index) => ({
|
|
@@ -77,13 +77,13 @@ function renderOuroHomeScreen(options) {
|
|
|
77
77
|
columns: options.columns,
|
|
78
78
|
masthead: {
|
|
79
79
|
subtitle: options.agents.length === 0
|
|
80
|
-
? "No agents are
|
|
81
|
-
: "
|
|
80
|
+
? "No agents are set up on this machine yet."
|
|
81
|
+
: "Choose an agent or a setup task.",
|
|
82
82
|
},
|
|
83
83
|
title: "Ouro home",
|
|
84
84
|
summary: options.agents.length === 0
|
|
85
|
-
? "
|
|
86
|
-
: "Choose
|
|
85
|
+
? "Create a new agent or clone an existing bundle to get started."
|
|
86
|
+
: "Choose an agent or a setup task without memorizing commands.",
|
|
87
87
|
sections,
|
|
88
88
|
actions: actionRows,
|
|
89
89
|
prompt: `Choose [1-${actions.length}] or type a name: `,
|
|
@@ -159,7 +159,7 @@ function renderHouseStatusScreen(options) {
|
|
|
159
159
|
renderScreenEvent("house-status");
|
|
160
160
|
const sections = [
|
|
161
161
|
{
|
|
162
|
-
title: "
|
|
162
|
+
title: "Runtime",
|
|
163
163
|
lines: [
|
|
164
164
|
`Daemon: ${options.payload.overview.daemon}`,
|
|
165
165
|
`Health: ${options.payload.overview.health}`,
|
|
@@ -226,9 +226,9 @@ function renderHouseStatusScreen(options) {
|
|
|
226
226
|
});
|
|
227
227
|
}
|
|
228
228
|
return renderHumanCommandBoard({
|
|
229
|
-
title: "
|
|
230
|
-
subtitle: "
|
|
231
|
-
summary: "What is
|
|
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
232
|
isTTY: options.isTTY,
|
|
233
233
|
columns: options.columns,
|
|
234
234
|
sections,
|
|
@@ -33,11 +33,16 @@ function providerRetryTiming(delayMs) {
|
|
|
33
33
|
return `in ${rounded}`;
|
|
34
34
|
}
|
|
35
35
|
function formatProviderAttemptProgress(context, attempt, maxAttempts) {
|
|
36
|
-
|
|
36
|
+
const prefix = context.subject ? `${context.subject}: ` : "";
|
|
37
|
+
return `${prefix}checking ${formatProviderPingLabel(context)} (attempt ${attempt} of ${maxAttempts})...`;
|
|
37
38
|
}
|
|
38
|
-
function formatProviderRetryProgress(record, maxAttempts) {
|
|
39
|
+
function formatProviderRetryProgress(context, record, maxAttempts) {
|
|
39
40
|
const nextAttempt = Math.min(record.attempt + 1, maxAttempts);
|
|
40
|
-
|
|
41
|
+
const retryDetail = `${providerRetryReason(record)}; retrying ${providerRetryTiming(record.delayMs)} (attempt ${nextAttempt} of ${maxAttempts})`;
|
|
42
|
+
if (context.subject) {
|
|
43
|
+
return `${context.subject}: ${retryDetail} while checking ${formatProviderPingLabel(context)}`;
|
|
44
|
+
}
|
|
45
|
+
return `${formatProviderPingLabel(record)}: ${retryDetail}`;
|
|
41
46
|
}
|
|
42
47
|
function createProviderPingProgressReporter(context, onProgress) {
|
|
43
48
|
return {
|
|
@@ -70,7 +75,7 @@ function createProviderPingProgressReporter(context, onProgress) {
|
|
|
70
75
|
classification: record.classification ?? "unknown",
|
|
71
76
|
},
|
|
72
77
|
});
|
|
73
|
-
onProgress(formatProviderRetryProgress(record, maxAttempts));
|
|
78
|
+
onProgress(formatProviderRetryProgress(context, record, maxAttempts));
|
|
74
79
|
},
|
|
75
80
|
};
|
|
76
81
|
}
|
|
@@ -104,26 +104,17 @@ function mastheadArt(columns) {
|
|
|
104
104
|
}
|
|
105
105
|
return rows.map((row) => row.join(" "));
|
|
106
106
|
}
|
|
107
|
-
return [
|
|
108
|
-
" O U R O B O R O S",
|
|
109
|
-
" -----------------",
|
|
110
|
-
];
|
|
107
|
+
return [MASTHEAD_WORD];
|
|
111
108
|
}
|
|
112
109
|
function renderOuroMasthead(options) {
|
|
113
|
-
const lines = mastheadArt(options.columns);
|
|
114
|
-
const
|
|
115
|
-
const branded = [
|
|
116
|
-
...lines,
|
|
117
|
-
MASTHEAD_WORD,
|
|
118
|
-
subtitle,
|
|
119
|
-
];
|
|
110
|
+
const lines = options.isTTY ? mastheadArt(options.columns) : [MASTHEAD_WORD];
|
|
111
|
+
const branded = options.subtitle ? [...lines, options.subtitle] : lines;
|
|
120
112
|
if (!options.isTTY) {
|
|
121
113
|
return `${branded.join("\n")}\n`;
|
|
122
114
|
}
|
|
123
115
|
const ttyLines = [
|
|
124
116
|
...lines.map((line, index) => color(line, index < 2 ? GLOW : SCALE, true)),
|
|
125
|
-
color(
|
|
126
|
-
color(subtitle, MIST),
|
|
117
|
+
...(options.subtitle ? [color(options.subtitle, MIST)] : []),
|
|
127
118
|
];
|
|
128
119
|
return `${ttyLines.join("\n")}\n`;
|
|
129
120
|
}
|
|
@@ -210,11 +201,11 @@ function renderTerminalOperation(options) {
|
|
|
210
201
|
summary: options.summary,
|
|
211
202
|
sections: [
|
|
212
203
|
{
|
|
213
|
-
title: "Right now",
|
|
204
|
+
title: options.currentTitle ?? "Right now",
|
|
214
205
|
lines: currentLines,
|
|
215
206
|
},
|
|
216
207
|
{
|
|
217
|
-
title: "Progress",
|
|
208
|
+
title: options.stepsTitle ?? "Progress",
|
|
218
209
|
lines: progressLines,
|
|
219
210
|
},
|
|
220
211
|
],
|
|
@@ -22,6 +22,22 @@ const BOLD = "\x1b[1m";
|
|
|
22
22
|
const DIM = "\x1b[2m";
|
|
23
23
|
const GREEN = "\x1b[38;2;46;204;64m";
|
|
24
24
|
const RED = "\x1b[38;2;255;106;106m";
|
|
25
|
+
const BASE_UP_PHASE_PLAN = [
|
|
26
|
+
"update check",
|
|
27
|
+
"system setup",
|
|
28
|
+
"provider checks",
|
|
29
|
+
"starting daemon",
|
|
30
|
+
"final daemon check",
|
|
31
|
+
];
|
|
32
|
+
const FRIENDLY_UP_PHASE_LABELS = {
|
|
33
|
+
"update check": "Check for updates",
|
|
34
|
+
"system setup": "Prepare this machine",
|
|
35
|
+
"agent updates": "Update installed agents",
|
|
36
|
+
"bundle cleanup": "Clean up stale bundles",
|
|
37
|
+
"provider checks": "Check the providers your agents use right now",
|
|
38
|
+
"starting daemon": "Start the background service",
|
|
39
|
+
"final daemon check": "Confirm the background service stayed up",
|
|
40
|
+
};
|
|
25
41
|
function splitDetailLines(detail) {
|
|
26
42
|
if (!detail)
|
|
27
43
|
return [];
|
|
@@ -45,6 +61,7 @@ class UpProgress {
|
|
|
45
61
|
completed = [];
|
|
46
62
|
currentPhase = null;
|
|
47
63
|
currentDetail = null;
|
|
64
|
+
upPhasePlan = BASE_UP_PHASE_PLAN;
|
|
48
65
|
prevLineCount = 0;
|
|
49
66
|
ended = false;
|
|
50
67
|
renderTimer = null;
|
|
@@ -96,6 +113,13 @@ class UpProgress {
|
|
|
96
113
|
return;
|
|
97
114
|
this.write(` ${label}\n`);
|
|
98
115
|
}
|
|
116
|
+
setPhasePlan(labels) {
|
|
117
|
+
const nextPlan = [...new Set(labels.map((label) => label.trim()).filter((label) => label.length > 0))];
|
|
118
|
+
this.upPhasePlan = nextPlan.length > 0 ? nextPlan : BASE_UP_PHASE_PLAN;
|
|
119
|
+
if (this.isTTY && this.eventScope === "up") {
|
|
120
|
+
this.flushRender();
|
|
121
|
+
}
|
|
122
|
+
}
|
|
99
123
|
/**
|
|
100
124
|
* Update the sub-step detail on the current spinner phase. Rendered as
|
|
101
125
|
* "label (Xs) -- detail" in TTY mode. In non-TTY mode, writes changed
|
|
@@ -272,39 +296,61 @@ class UpProgress {
|
|
|
272
296
|
}
|
|
273
297
|
return lines;
|
|
274
298
|
}
|
|
299
|
+
renderUpStepLabel(label) {
|
|
300
|
+
return FRIENDLY_UP_PHASE_LABELS[label] ?? label;
|
|
301
|
+
}
|
|
275
302
|
renderUpScreen(now) {
|
|
276
|
-
const
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
303
|
+
const seenLabels = new Set();
|
|
304
|
+
const steps = this.completed.map((phase) => {
|
|
305
|
+
seenLabels.add(phase.label);
|
|
306
|
+
return {
|
|
307
|
+
label: this.renderUpStepLabel(phase.label),
|
|
308
|
+
status: phase.status === "failure" ? "failed" : "done",
|
|
309
|
+
detail: phase.detail,
|
|
310
|
+
};
|
|
311
|
+
});
|
|
312
|
+
let currentStepLabel = this.completed.some((phase) => phase.status === "failure")
|
|
313
|
+
? "Boot paused."
|
|
314
|
+
: this.completed.length > 0
|
|
315
|
+
? "Boot checklist complete."
|
|
316
|
+
: "Waiting to begin.";
|
|
282
317
|
let currentStepDetails = [];
|
|
283
318
|
if (this.currentPhase) {
|
|
284
319
|
const elapsed = now - this.currentPhase.startedAt;
|
|
285
320
|
const elapsedSec = (elapsed / 1000).toFixed(1);
|
|
286
321
|
const frameIndex = Math.floor(elapsed / 80) % SPINNER_FRAMES.length;
|
|
287
322
|
const spinner = SPINNER_FRAMES[frameIndex];
|
|
288
|
-
currentStepLabel = `${spinner} ${this.currentPhase.label} (${elapsedSec}s)`;
|
|
323
|
+
currentStepLabel = `${spinner} ${this.renderUpStepLabel(this.currentPhase.label)} (${elapsedSec}s)`;
|
|
289
324
|
currentStepDetails = splitDetailLines(this.currentPhase.detail);
|
|
290
325
|
steps.push({
|
|
291
|
-
label: this.currentPhase.label,
|
|
326
|
+
label: this.renderUpStepLabel(this.currentPhase.label),
|
|
292
327
|
status: "active",
|
|
293
328
|
});
|
|
329
|
+
seenLabels.add(this.currentPhase.label);
|
|
330
|
+
}
|
|
331
|
+
for (const label of this.upPhasePlan) {
|
|
332
|
+
if (!seenLabels.has(label)) {
|
|
333
|
+
steps.push({
|
|
334
|
+
label: this.renderUpStepLabel(label),
|
|
335
|
+
status: "pending",
|
|
336
|
+
});
|
|
337
|
+
}
|
|
294
338
|
}
|
|
295
339
|
return (0, terminal_ui_1.renderTerminalOperation)({
|
|
296
340
|
isTTY: true,
|
|
297
341
|
columns: this.columns,
|
|
298
342
|
masthead: {
|
|
299
|
-
subtitle: "
|
|
343
|
+
subtitle: "Booting the local agent runtime.",
|
|
300
344
|
},
|
|
301
|
-
title: "
|
|
302
|
-
summary: "Ouro
|
|
345
|
+
title: "Ouro boot checklist",
|
|
346
|
+
summary: "Ouro will check for updates, prepare this machine, verify the providers your agents use right now, start the background service, and make sure it stays up.",
|
|
303
347
|
currentStep: {
|
|
304
348
|
label: currentStepLabel,
|
|
305
349
|
detailLines: currentStepDetails,
|
|
306
350
|
},
|
|
307
351
|
steps,
|
|
352
|
+
currentTitle: "Doing now",
|
|
353
|
+
stepsTitle: "Boot checklist",
|
|
308
354
|
suppressEvent: true,
|
|
309
355
|
}).trimEnd().split("\n");
|
|
310
356
|
}
|