@ouro.bot/cli 0.1.0-alpha.426 → 0.1.0-alpha.428

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 CHANGED
@@ -1,6 +1,24 @@
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.428",
6
+ "changes": [
7
+ "Root `ouro connect` now renders through a dedicated width-aware connect-bay renderer with a framed header, one recommended next move, and separate `Provider core`, `Portable`, and `This machine` sections instead of flattening everything inside one long menu string.",
8
+ "The connect bay keeps using the same live provider verification truth path as `ouro up` and `ouro auth verify`, while tightening lane-specific status and next-action mapping so locked vaults, failed live checks, and missing setup each point at the right repair move.",
9
+ "Added direct connect-bay renderer coverage for wide, narrow, wrapping, lane-summary, and non-TTY branches, plus a nerves event so the new renderer stays under the same file-completeness and audit rules as the rest of the runtime.",
10
+ "Auth, cross-machine, and testing docs now describe the root connect surface as the framed responsive bay it really is, and `@ouro.bot/cli` plus the `ouro.bot` wrapper are version-synced for the connect-bay polish release."
11
+ ]
12
+ },
13
+ {
14
+ "version": "0.1.0-alpha.427",
15
+ "changes": [
16
+ "Root `ouro connect` now runs the same shared live provider verification path as `ouro up` and `ouro auth verify` before it renders the connect bay, so freshly authed providers and failed live checks show their real current state.",
17
+ "The root connect bay now groups capabilities into a clearer `Next best move`, `Provider core`, `Portable`, and `This machine` layout, with truthful lane-specific status for outward and inner providers.",
18
+ "Shared provider health checks now refresh readiness for every selected provider/model pair instead of stopping after the first failure, and `ouro connect` keeps working when provider selection is missing so repair guidance can still render.",
19
+ "Auth/provider/testing docs now describe the live-verifying connect bay behavior, and `@ouro.bot/cli` plus the `ouro.bot` wrapper are version-synced for the truthful connect release."
20
+ ]
21
+ },
4
22
  {
5
23
  "version": "0.1.0-alpha.426",
6
24
  "changes": [
@@ -378,7 +378,9 @@ async function checkAgentConfigWithProviderHealth(agentName, bundlesRoot, deps =
378
378
  });
379
379
  }
380
380
  }
381
+ let firstFailure = null;
381
382
  for (const group of pingGroups.values()) {
383
+ deps.onProgress?.(`checking ${group.provider} / ${group.model}...`);
382
384
  const result = await ping(group.provider, providerCredentialConfig(group.record), { model: group.model });
383
385
  if (!result.ok) {
384
386
  for (const lane of group.lanes) {
@@ -392,7 +394,8 @@ async function checkAgentConfigWithProviderHealth(agentName, bundlesRoot, deps =
392
394
  attempts: pingAttemptCount(result),
393
395
  });
394
396
  }
395
- return failedPingResult(agentName, group.lanes[0], group.provider, group.model, result);
397
+ firstFailure ??= failedPingResult(agentName, group.lanes[0], group.provider, group.model, result);
398
+ continue;
396
399
  }
397
400
  for (const lane of group.lanes) {
398
401
  writeLaneReadiness({
@@ -405,6 +408,8 @@ async function checkAgentConfigWithProviderHealth(agentName, bundlesRoot, deps =
405
408
  });
406
409
  }
407
410
  }
411
+ if (firstFailure)
412
+ return firstFailure;
408
413
  (0, runtime_1.emitNervesEvent)({
409
414
  component: "daemon",
410
415
  event: "daemon.agent_config_valid",
@@ -93,6 +93,7 @@ const stale_bundle_prune_1 = require("./stale-bundle-prune");
93
93
  const up_progress_1 = require("./up-progress");
94
94
  const provider_ping_1 = require("../provider-ping");
95
95
  const agent_discovery_1 = require("./agent-discovery");
96
+ const connect_bay_1 = require("./connect-bay");
96
97
  // ── ensureDaemonRunning ──
97
98
  const DEFAULT_DAEMON_STARTUP_TIMEOUT_MS = 10_000;
98
99
  const DEFAULT_DAEMON_STARTUP_POLL_INTERVAL_MS = 500;
@@ -1625,29 +1626,43 @@ function enableAgentSense(agent, sense, deps) {
1625
1626
  };
1626
1627
  fs.writeFileSync(configPath, `${JSON.stringify(raw, null, 2)}\n`, "utf-8");
1627
1628
  }
1629
+ const CONNECT_MENU_PROMPT = "Choose [1-6] or type a name: ";
1630
+ function connectMenuIsTTY(deps) {
1631
+ return deps.isTTY ?? process.stdout.isTTY === true;
1632
+ }
1633
+ function readConnectBaySenseFlags(agent, deps) {
1634
+ const configPath = path.join(providerCliAgentRoot({ agent }, deps), "agent.json");
1635
+ const parsed = JSON.parse(fs.readFileSync(configPath, "utf-8"));
1636
+ return {
1637
+ teamsEnabled: parsed.senses?.teams?.enabled === true,
1638
+ blueBubblesEnabled: parsed.senses?.bluebubbles?.enabled === true,
1639
+ };
1640
+ }
1628
1641
  async function buildConnectMenu(agent, deps, onProgress) {
1642
+ const bundlesRoot = path.dirname(providerCliAgentRoot({ agent }, deps));
1643
+ let providerHealth;
1644
+ try {
1645
+ onProgress?.("checking selected providers");
1646
+ providerHealth = await checkAgentProviderHealth(agent, bundlesRoot, deps, onProgress);
1647
+ }
1648
+ catch (error) {
1649
+ providerHealth = {
1650
+ ok: false,
1651
+ error: error instanceof Error ? error.message : String(error),
1652
+ fix: `Run 'ouro auth verify --agent ${agent}' to inspect provider health.`,
1653
+ };
1654
+ }
1629
1655
  const providerVisibility = (0, provider_visibility_1.buildAgentProviderVisibility)({
1630
1656
  agentName: agent,
1631
1657
  agentRoot: providerCliAgentRoot({ agent }, deps),
1632
1658
  homeDir: providerCliHomeDir(deps),
1633
1659
  });
1634
- const providerStatus = providerVisibility.lanes.some((lane) => lane.status === "unconfigured")
1635
- ? "needs setup"
1636
- : providerVisibility.lanes.some((lane) => lane.status === "configured" && lane.credential.status !== "present")
1637
- ? "needs auth"
1638
- : providerVisibility.lanes.some((lane) => lane.status === "configured" && (lane.readiness.status === "failed" || lane.readiness.status === "stale"))
1639
- ? "needs attention"
1640
- : "ready";
1641
- const providerDetail = providerVisibility.lanes.map((lane) => lane.status === "configured"
1642
- ? `${lane.lane}: ${lane.provider} / ${lane.model}`
1643
- : `${lane.lane}: choose provider/model`).join(" | ");
1660
+ const providerSummary = (0, connect_bay_1.summarizeProvidersForConnect)(agent, providerVisibility, providerHealth);
1644
1661
  onProgress?.("loading portable settings");
1645
1662
  const runtimeConfig = await (0, runtime_credentials_1.refreshRuntimeCredentialConfig)(agent, { preserveCachedOnFailure: true });
1646
1663
  onProgress?.("loading this machine's settings");
1647
1664
  const machineRuntime = await (0, runtime_credentials_1.refreshMachineRuntimeCredentialConfig)(agent, currentMachineId(deps), { preserveCachedOnFailure: true });
1648
- const agentConfig = (0, auth_flow_1.readAgentConfigForAgent)(agent, deps.bundlesRoot).config;
1649
- const teamsEnabled = agentConfig.senses?.teams?.enabled === true;
1650
- const blueBubblesEnabled = agentConfig.senses?.bluebubbles?.enabled === true;
1665
+ const { teamsEnabled, blueBubblesEnabled } = readConnectBaySenseFlags(agent, deps);
1651
1666
  const perplexityStatus = runtimeConfig.ok
1652
1667
  ? hasRuntimeConfigValue(runtimeConfig.config, "integrations.perplexityApiKey") ? "ready" : "missing"
1653
1668
  : runtimeConfigReadStatus(runtimeConfig);
@@ -1669,29 +1684,77 @@ async function buildConnectMenu(agent, deps, onProgress) {
1669
1684
  ? "attached"
1670
1685
  : "not attached"
1671
1686
  : machineRuntimeReadStatus(machineRuntime);
1672
- return [
1673
- `${agent} // connect bay`,
1674
- "Bring one capability online at a time.",
1675
- "",
1676
- ` 1. [${providerStatus}] Providers`,
1677
- ` ${providerDetail}`,
1678
- "",
1679
- ` 2. [${perplexityStatus}] Perplexity search`,
1680
- " Portable. Web search via Perplexity.",
1681
- "",
1682
- ` 3. [${embeddingsStatus}] Memory embeddings`,
1683
- " Portable. Memory retrieval and note search.",
1684
- "",
1685
- ` 4. [${teamsStatus}] Teams`,
1686
- " Portable. Microsoft Teams sense credentials.",
1687
- "",
1688
- ` 5. [${blueBubblesStatus}] BlueBubbles iMessage`,
1689
- " This machine only. Local Mac Messages bridge.",
1690
- "",
1691
- " 6. Cancel",
1692
- "",
1693
- "Choose [1-6] or type a name: ",
1694
- ].join("\n");
1687
+ const entries = [
1688
+ {
1689
+ option: "1",
1690
+ name: "Providers",
1691
+ section: "Provider core",
1692
+ status: providerSummary.status,
1693
+ detailLines: providerSummary.detailLines,
1694
+ laneSummaries: providerSummary.laneSummaries,
1695
+ nextAction: providerSummary.nextAction,
1696
+ nextNote: providerSummary.nextNote,
1697
+ },
1698
+ {
1699
+ option: "2",
1700
+ name: "Perplexity search",
1701
+ section: "Portable",
1702
+ status: perplexityStatus,
1703
+ description: "Web search via Perplexity.",
1704
+ nextAction: (0, connect_bay_1.connectEntryNeedsAttention)({
1705
+ option: "2",
1706
+ name: "Perplexity search",
1707
+ section: "Portable",
1708
+ status: perplexityStatus,
1709
+ }) ? `ouro connect perplexity --agent ${agent}` : undefined,
1710
+ },
1711
+ {
1712
+ option: "3",
1713
+ name: "Memory embeddings",
1714
+ section: "Portable",
1715
+ status: embeddingsStatus,
1716
+ description: "Memory retrieval and note search.",
1717
+ nextAction: (0, connect_bay_1.connectEntryNeedsAttention)({
1718
+ option: "3",
1719
+ name: "Memory embeddings",
1720
+ section: "Portable",
1721
+ status: embeddingsStatus,
1722
+ }) ? `ouro connect embeddings --agent ${agent}` : undefined,
1723
+ },
1724
+ {
1725
+ option: "4",
1726
+ name: "Teams",
1727
+ section: "Portable",
1728
+ status: teamsStatus,
1729
+ description: "Microsoft Teams sense credentials.",
1730
+ nextAction: (0, connect_bay_1.connectEntryNeedsAttention)({
1731
+ option: "4",
1732
+ name: "Teams",
1733
+ section: "Portable",
1734
+ status: teamsStatus,
1735
+ }) ? `ouro connect teams --agent ${agent}` : undefined,
1736
+ },
1737
+ {
1738
+ option: "5",
1739
+ name: "BlueBubbles iMessage",
1740
+ section: "This machine",
1741
+ status: blueBubblesStatus,
1742
+ description: "Local Mac Messages bridge.",
1743
+ nextAction: (0, connect_bay_1.connectEntryNeedsAttention)({
1744
+ option: "5",
1745
+ name: "BlueBubbles iMessage",
1746
+ section: "This machine",
1747
+ status: blueBubblesStatus,
1748
+ }) ? `ouro connect bluebubbles --agent ${agent}` : undefined,
1749
+ },
1750
+ ];
1751
+ const isTTY = connectMenuIsTTY(deps);
1752
+ return (0, connect_bay_1.renderConnectBay)(entries, {
1753
+ agent,
1754
+ isTTY,
1755
+ columns: deps.stdoutColumns ?? process.stdout.columns,
1756
+ prompt: CONNECT_MENU_PROMPT,
1757
+ });
1695
1758
  }
1696
1759
  async function executeConnectPerplexity(agent, deps) {
1697
1760
  if (agent === "SerpentGuide") {
@@ -0,0 +1,428 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.summarizeProviderLane = summarizeProviderLane;
4
+ exports.summarizeProvidersForConnect = summarizeProvidersForConnect;
5
+ exports.connectEntryNeedsAttention = connectEntryNeedsAttention;
6
+ exports.renderConnectBay = renderConnectBay;
7
+ const runtime_1 = require("../../nerves/runtime");
8
+ const CONNECT_STATUS_PRIORITY = {
9
+ "needs attention": 0,
10
+ locked: 1,
11
+ "needs credentials": 2,
12
+ "needs setup": 3,
13
+ missing: 4,
14
+ "not attached": 5,
15
+ ready: 6,
16
+ attached: 6,
17
+ };
18
+ const RESET = "\x1b[0m";
19
+ const BOLD = "\x1b[1m";
20
+ const TEAL = "\x1b[38;2;78;201;176m";
21
+ const GREEN = "\x1b[38;2;46;204;64m";
22
+ const GOLD = "\x1b[38;2;230;190;50m";
23
+ const BONE = "\x1b[38;2;238;242;234m";
24
+ const MIST = "\x1b[38;2;165;184;168m";
25
+ const ANSI_RE = /\x1b\[[0-9;]*m/g;
26
+ function stripAnsi(text) {
27
+ return text.replace(ANSI_RE, "");
28
+ }
29
+ function visibleLength(text) {
30
+ return stripAnsi(text).length;
31
+ }
32
+ function padAnsi(text, width) {
33
+ const missing = Math.max(0, width - visibleLength(text));
34
+ return `${text}${" ".repeat(missing)}`;
35
+ }
36
+ function wrapPlain(text, width) {
37
+ const normalized = text.trim();
38
+ if (!normalized)
39
+ return [""];
40
+ const words = normalized.split(/\s+/);
41
+ const lines = [];
42
+ let current = "";
43
+ for (const word of words) {
44
+ if (!current) {
45
+ current = word;
46
+ continue;
47
+ }
48
+ const candidate = `${current} ${word}`;
49
+ if (candidate.length <= width) {
50
+ current = candidate;
51
+ continue;
52
+ }
53
+ lines.push(current);
54
+ current = word;
55
+ }
56
+ lines.push(current);
57
+ return lines;
58
+ }
59
+ function tty(text, color, bold = false) {
60
+ if (bold)
61
+ return `${color}${BOLD}${text}${RESET}`;
62
+ return `${color}${text}${RESET}`;
63
+ }
64
+ function escapeRegExp(value) {
65
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
66
+ }
67
+ function cleanExtractedCommand(command) {
68
+ const cleaned = command?.trim().replace(/[`'",;:.)]+$/g, "").trim();
69
+ return cleaned && cleaned.length > 0 ? cleaned : undefined;
70
+ }
71
+ function extractCommand(fixHint, commandPrefix) {
72
+ const escapedPrefix = escapeRegExp(commandPrefix);
73
+ const commandBody = `${escapedPrefix}(?=\\s|$)[^\`'"]*`;
74
+ const quoted = fixHint.match(new RegExp(`[\`'"](${commandBody})[\`'"]`, "i"))?.[1];
75
+ const unquoted = fixHint.match(new RegExp(`(${escapedPrefix}(?=\\s|$)[^\\n,;.]+)`, "i"))?.[1];
76
+ return cleanExtractedCommand(quoted) ?? cleanExtractedCommand(unquoted);
77
+ }
78
+ function resolveProviderHealthStatus(providerHealth) {
79
+ if (!providerHealth || providerHealth.ok)
80
+ return undefined;
81
+ const error = String(providerHealth.error).toLowerCase();
82
+ const fix = String(providerHealth.fix).toLowerCase();
83
+ if (error.includes("failed live check"))
84
+ return "needs attention";
85
+ if (error.includes("has no credentials"))
86
+ return "needs credentials";
87
+ if (error.includes("missing") && error.includes("provider"))
88
+ return "needs setup";
89
+ if (error.includes("vault is locked") || error.includes("vault locked"))
90
+ return "locked";
91
+ if (fix.includes("ouro auth verify"))
92
+ return "needs attention";
93
+ if (fix.includes("ouro auth"))
94
+ return "needs credentials";
95
+ if (fix.includes("ouro use"))
96
+ return "needs setup";
97
+ if (fix.includes("vault unlock"))
98
+ return "locked";
99
+ return "needs attention";
100
+ }
101
+ function resolveProviderHealthCommand(fixHint, status) {
102
+ if (!fixHint)
103
+ return undefined;
104
+ const prefixes = status === "locked"
105
+ ? ["ouro vault unlock", "ouro vault replace", "ouro vault recover"]
106
+ : status === "needs credentials"
107
+ ? ["ouro auth", "ouro connect", "ouro provider refresh", "ouro up"]
108
+ : status === "needs setup"
109
+ ? ["ouro use", "ouro connect", "ouro auth", "ouro up"]
110
+ : ["ouro auth verify", "ouro auth", "ouro provider refresh", "ouro use", "ouro connect", "ouro vault unlock", "ouro up"];
111
+ for (const prefix of prefixes) {
112
+ const command = extractCommand(fixHint, prefix);
113
+ if (command)
114
+ return command;
115
+ }
116
+ return undefined;
117
+ }
118
+ function isProblemStatus(status) {
119
+ return status !== "ready" && status !== "attached";
120
+ }
121
+ function statusChip(status) {
122
+ const symbol = status === "ready" || status === "attached"
123
+ ? "●"
124
+ : status === "not attached"
125
+ ? "◌"
126
+ : "◆";
127
+ const label = `${symbol} ${status}`;
128
+ if (status === "ready" || status === "attached")
129
+ return tty(label, GREEN, true);
130
+ if (status === "not attached")
131
+ return tty(label, MIST);
132
+ return tty(label, GOLD, true);
133
+ }
134
+ function sectionTitle(title, width) {
135
+ const plain = `╭─ ${title} `;
136
+ const rule = "─".repeat(Math.max(0, width - plain.length - 1));
137
+ return `${tty("╭─ ", TEAL)}${tty(title, BONE, true)}${tty(` ${rule}╮`, TEAL)}`;
138
+ }
139
+ function bottomRule(width) {
140
+ const line = `╰${"─".repeat(Math.max(0, width - 2))}╯`;
141
+ return tty(line, TEAL);
142
+ }
143
+ function bodyLine(text, width) {
144
+ const padded = padAnsi(text, Math.max(0, width - 4));
145
+ return `${tty("│ ", TEAL)}${padded}${tty(" │", TEAL)}`;
146
+ }
147
+ function panel(title, body, width) {
148
+ const lines = [sectionTitle(title, width)];
149
+ for (const line of body) {
150
+ lines.push(bodyLine(line, width));
151
+ }
152
+ lines.push(bottomRule(width));
153
+ return lines;
154
+ }
155
+ function renderHeader(agent, width) {
156
+ return panel(`${agent} connect bay`, [
157
+ tty("Bring one capability online.", BONE, true),
158
+ tty("Everything on this screen was checked live just now.", MIST),
159
+ ], width);
160
+ }
161
+ function nextMoveBody(entry) {
162
+ if (!entry) {
163
+ return [
164
+ tty("Everything here is ready.", BONE, true),
165
+ tty("Pick what you want to review or refresh.", MIST),
166
+ ];
167
+ }
168
+ const lines = [
169
+ `${entry.name} ${statusChip(entry.status)}`,
170
+ ];
171
+ if (entry.nextNote)
172
+ lines.push(entry.nextNote);
173
+ if (entry.nextAction)
174
+ lines.push(tty(entry.nextAction, MIST));
175
+ return lines;
176
+ }
177
+ function renderProviderBody(entry, width) {
178
+ const lines = [
179
+ `${entry.option} ${entry.name} ${statusChip(entry.status)}`,
180
+ ];
181
+ const lanes = entry.laneSummaries ?? [];
182
+ for (const [index, lane] of lanes.entries()) {
183
+ if (index > 0)
184
+ lines.push("");
185
+ const laneLabel = lane.lane === "outward" ? "Outward lane" : "Inner lane";
186
+ lines.push(tty(laneLabel, BONE, true));
187
+ lines.push(lane.title);
188
+ lines.push(isProblemStatus(lane.status) ? lane.detail : tty(lane.detail, MIST));
189
+ }
190
+ if (lanes.length === 0) {
191
+ for (const detail of entry.detailLines ?? [])
192
+ lines.push(detail);
193
+ }
194
+ return normalizeWrappedBody(lines, width);
195
+ }
196
+ function renderCapabilityBody(entries, width) {
197
+ const lines = [];
198
+ for (const [index, entry] of entries.entries()) {
199
+ if (index > 0)
200
+ lines.push("");
201
+ lines.push(`${entry.option} ${entry.name} ${statusChip(entry.status)}`);
202
+ if (entry.description) {
203
+ lines.push(isProblemStatus(entry.status) ? entry.description : tty(entry.description, MIST));
204
+ }
205
+ for (const detail of entry.detailLines ?? []) {
206
+ lines.push(detail);
207
+ }
208
+ }
209
+ return normalizeWrappedBody(lines, width);
210
+ }
211
+ function normalizeWrappedBody(lines, width) {
212
+ const wrapped = [];
213
+ for (const line of lines) {
214
+ if (!line) {
215
+ wrapped.push("");
216
+ continue;
217
+ }
218
+ const plain = stripAnsi(line);
219
+ if (plain.length <= width - 4) {
220
+ wrapped.push(line);
221
+ continue;
222
+ }
223
+ const segments = wrapPlain(plain, width - 4);
224
+ wrapped.push(...segments);
225
+ }
226
+ return wrapped;
227
+ }
228
+ function stackPanels(panels) {
229
+ const lines = [];
230
+ for (const [index, panelLines] of panels.entries()) {
231
+ if (index > 0)
232
+ lines.push("");
233
+ lines.push(...panelLines);
234
+ }
235
+ return lines;
236
+ }
237
+ function combineColumns(left, right, leftWidth, rightWidth, gap = 2) {
238
+ const total = Math.max(left.length, right.length);
239
+ const lines = [];
240
+ for (let index = 0; index < total; index += 1) {
241
+ const leftLine = left[index] ?? " ".repeat(leftWidth);
242
+ const rightLine = right[index] ?? " ".repeat(rightWidth);
243
+ lines.push(`${padAnsi(leftLine, leftWidth)}${" ".repeat(gap)}${padAnsi(rightLine, rightWidth)}`);
244
+ }
245
+ return lines;
246
+ }
247
+ function renderTtyBay(entries, options) {
248
+ const columns = Math.max(options.columns ?? 108, 72);
249
+ const fullWidth = Math.max(56, columns - 2);
250
+ const header = renderHeader(options.agent, fullWidth);
251
+ const nextEntry = entries.find((entry) => isProblemStatus(entry.status));
252
+ const providerEntry = entries.find((entry) => entry.section === "Provider core");
253
+ const portableEntries = entries.filter((entry) => entry.section === "Portable");
254
+ const machineEntries = entries.filter((entry) => entry.section === "This machine");
255
+ const wide = columns >= 118;
256
+ const footer = [
257
+ tty("Pick a path. Type the number or the name.", MIST),
258
+ options.prompt,
259
+ ];
260
+ if (!wide) {
261
+ const panels = [
262
+ header,
263
+ panel("Next best move", nextMoveBody(nextEntry), fullWidth),
264
+ panel("Provider core", renderProviderBody(providerEntry, fullWidth), fullWidth),
265
+ panel("Portable", renderCapabilityBody(portableEntries, fullWidth), fullWidth),
266
+ panel("This machine", renderCapabilityBody(machineEntries, fullWidth), fullWidth),
267
+ ];
268
+ return [...stackPanels(panels), "", ...footer].join("\n");
269
+ }
270
+ const gap = 2;
271
+ const leftWidth = Math.max(52, Math.floor((fullWidth - gap) / 2));
272
+ const rightWidth = Math.max(40, fullWidth - gap - leftWidth);
273
+ const topRow = combineColumns(panel("Next best move", nextMoveBody(nextEntry), leftWidth), panel("This machine", renderCapabilityBody(machineEntries, rightWidth), rightWidth), leftWidth, rightWidth, gap);
274
+ const bottomRow = combineColumns(panel("Provider core", renderProviderBody(providerEntry, leftWidth), leftWidth), panel("Portable", renderCapabilityBody(portableEntries, rightWidth), rightWidth), leftWidth, rightWidth, gap);
275
+ return [...header, "", ...topRow, "", ...bottomRow, "", ...footer].join("\n");
276
+ }
277
+ function renderNonTtyBay(entries, options) {
278
+ const nextEntry = entries.find((entry) => isProblemStatus(entry.status));
279
+ const lines = [
280
+ `${options.agent} connect bay`,
281
+ "Bring one capability online. Provider status was checked live just now.",
282
+ "",
283
+ "Next best move",
284
+ "--------------",
285
+ ];
286
+ if (!nextEntry) {
287
+ lines.push("Everything here is ready. Pick what you want to review or refresh.");
288
+ }
289
+ else {
290
+ lines.push(`${nextEntry.name} - ${nextEntry.status}`);
291
+ if (nextEntry.nextNote)
292
+ lines.push(nextEntry.nextNote);
293
+ if (nextEntry.nextAction)
294
+ lines.push(`run: ${nextEntry.nextAction}`);
295
+ }
296
+ lines.push("");
297
+ for (const section of ["Provider core", "Portable", "This machine"]) {
298
+ lines.push(section);
299
+ lines.push("-".repeat(Math.max(6, section.length + 4)));
300
+ for (const entry of entries.filter((candidate) => candidate.section === section)) {
301
+ lines.push(`${entry.option}. ${entry.name} [${entry.status}]`);
302
+ if (entry.laneSummaries && entry.laneSummaries.length > 0) {
303
+ for (const lane of entry.laneSummaries) {
304
+ const laneLabel = lane.lane === "outward" ? "Outward lane" : "Inner lane";
305
+ lines.push(` ${laneLabel}: ${lane.title}`);
306
+ lines.push(` ${lane.detail}`);
307
+ }
308
+ }
309
+ else {
310
+ for (const detail of entry.detailLines ?? [])
311
+ lines.push(` ${detail}`);
312
+ }
313
+ if (entry.description)
314
+ lines.push(` ${entry.description}`);
315
+ lines.push("");
316
+ }
317
+ }
318
+ lines.push("6. Not now");
319
+ lines.push("");
320
+ lines.push("Pick a path. Type the number or the name.");
321
+ lines.push(options.prompt);
322
+ return lines.join("\n");
323
+ }
324
+ function summarizeProviderLane(agent, lane, providerHealth) {
325
+ const providerHealthStatus = resolveProviderHealthStatus(providerHealth);
326
+ const providerHealthCommand = resolveProviderHealthCommand(providerHealth?.fix, providerHealthStatus);
327
+ if (lane.status === "unconfigured") {
328
+ return {
329
+ lane: lane.lane,
330
+ status: "needs setup",
331
+ title: "choose provider and model",
332
+ detail: "needs setup",
333
+ action: lane.repairCommand,
334
+ };
335
+ }
336
+ const fallbackAction = providerHealthCommand ?? lane.credential.repairCommand;
337
+ if (lane.credential.status === "missing") {
338
+ return {
339
+ lane: lane.lane,
340
+ status: "needs credentials",
341
+ title: `${lane.provider} / ${lane.model}`,
342
+ detail: "credentials missing",
343
+ action: fallbackAction,
344
+ };
345
+ }
346
+ if (lane.credential.status === "invalid-pool") {
347
+ return {
348
+ lane: lane.lane,
349
+ status: providerHealthStatus === "locked" ? "locked" : "needs attention",
350
+ title: `${lane.provider} / ${lane.model}`,
351
+ detail: providerHealthStatus === "locked" ? "vault locked on this machine" : "vault unavailable",
352
+ action: fallbackAction,
353
+ };
354
+ }
355
+ if (lane.readiness.status === "failed") {
356
+ return {
357
+ lane: lane.lane,
358
+ status: "needs attention",
359
+ title: `${lane.provider} / ${lane.model}`,
360
+ detail: `failed live check: ${lane.readiness.error ?? "unknown error"}`,
361
+ action: providerHealth?.fix ?? `ouro auth --agent ${agent} --provider ${lane.provider}`,
362
+ };
363
+ }
364
+ if (lane.readiness.status === "stale") {
365
+ return {
366
+ lane: lane.lane,
367
+ status: "needs attention",
368
+ title: `${lane.provider} / ${lane.model}`,
369
+ detail: ["live check is stale", lane.readiness.reason].filter(Boolean).join(": "),
370
+ action: providerHealth?.fix,
371
+ };
372
+ }
373
+ if (lane.readiness.status === "ready") {
374
+ return {
375
+ lane: lane.lane,
376
+ status: "ready",
377
+ title: `${lane.provider} / ${lane.model}`,
378
+ detail: "ready",
379
+ };
380
+ }
381
+ return {
382
+ lane: lane.lane,
383
+ status: "needs attention",
384
+ title: `${lane.provider} / ${lane.model}`,
385
+ detail: "live check did not complete yet",
386
+ action: providerHealth?.fix,
387
+ };
388
+ }
389
+ function summarizeProvidersForConnect(agent, visibility, providerHealth) {
390
+ const laneSummaries = visibility.lanes.map((lane) => summarizeProviderLane(agent, lane, providerHealth));
391
+ const worstLaneStatus = laneSummaries.reduce((worst, lane) => CONNECT_STATUS_PRIORITY[lane.status] < CONNECT_STATUS_PRIORITY[worst] ? lane.status : worst, "ready");
392
+ const providerHealthStatus = resolveProviderHealthStatus(providerHealth);
393
+ const providerHealthCommand = resolveProviderHealthCommand(providerHealth?.fix, providerHealthStatus);
394
+ const nextLane = laneSummaries.find((lane) => isProblemStatus(lane.status));
395
+ return {
396
+ status: providerHealthStatus ?? worstLaneStatus,
397
+ laneSummaries,
398
+ detailLines: laneSummaries.flatMap((lane) => [
399
+ `${lane.lane === "outward" ? "Outward lane" : "Inner lane"}: ${lane.title}`,
400
+ lane.detail,
401
+ ]),
402
+ nextAction: providerHealthCommand ?? nextLane?.action,
403
+ nextNote: providerHealthStatus === "locked"
404
+ ? "Unlock this agent's credential vault on this machine."
405
+ : nextLane
406
+ ? `${nextLane.lane === "outward" ? "Outward lane" : "Inner lane"}: ${nextLane.detail}`
407
+ : undefined,
408
+ };
409
+ }
410
+ function connectEntryNeedsAttention(entry) {
411
+ return isProblemStatus(entry.status);
412
+ }
413
+ function renderConnectBay(entries, options) {
414
+ (0, runtime_1.emitNervesEvent)({
415
+ component: "daemon",
416
+ event: "daemon.connect_bay_rendered",
417
+ message: "rendered connect bay",
418
+ meta: {
419
+ agent: options.agent,
420
+ isTTY: options.isTTY,
421
+ entryCount: entries.length,
422
+ columns: options.columns ?? null,
423
+ },
424
+ });
425
+ if (!options.isTTY)
426
+ return renderNonTtyBay(entries, options);
427
+ return renderTtyBay(entries, options);
428
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.426",
3
+ "version": "0.1.0-alpha.428",
4
4
  "main": "dist/heart/daemon/ouro-entry.js",
5
5
  "bin": {
6
6
  "cli": "dist/heart/daemon/ouro-bot-entry.js",