@ouro.bot/cli 0.1.0-alpha.445 → 0.1.0-alpha.447
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/changelog.json +16 -0
- package/dist/heart/daemon/agent-config-check.js +14 -1
- package/dist/heart/daemon/agentic-repair.js +2 -0
- package/dist/heart/daemon/cli-exec.js +38 -13
- package/dist/heart/daemon/connect-bay.js +118 -251
- package/dist/heart/daemon/human-command-screens.js +112 -34
- package/dist/heart/daemon/interactive-repair.js +96 -9
- package/dist/heart/daemon/terminal-ui.js +200 -0
- package/dist/heart/provider-credentials.js +20 -4
- package/dist/repertoire/bitwarden-store.js +66 -25
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -99,7 +99,7 @@ Task docs do not live in this repo anymore. Planning and doing docs live in the
|
|
|
99
99
|
- Vault unlock material is local machine state. Prefer macOS Keychain, Windows DPAPI, or Linux Secret Service; plaintext fallback is allowed only by explicit human choice.
|
|
100
100
|
- New vault unlock secrets are confirmed before use and rejected if they do not meet the minimum strength requirements.
|
|
101
101
|
- Provider and runtime credentials are loaded into process memory at startup/auth/unlock/refresh and reused. The remote vault is not queried for every model or sense request.
|
|
102
|
-
- Human TTY commands share one CLI surface family: bare `ouro` opens the home deck, `ouro up
|
|
102
|
+
- Human TTY commands share one CLI surface family: bare `ouro` opens the home deck, `ouro up` uses the boot checklist, `ouro connect`/`ouro auth verify`/`ouro repair` reuse the same readiness truth, and `ouro help`/`ouro whoami`/`ouro versions`/`ouro hatch` render through the same Ouro-branded wizard/guide language instead of raw transcript walls.
|
|
103
103
|
- Human-facing CLI commands that can wait on browser auth, vault IO, daemon startup, daemon restart, provider checks, or connector setup use a shared progress checklist. If a cursor may blink for more than a few seconds, the command should print or animate the current step instead of going quiet.
|
|
104
104
|
- CLI commands that mutate bundle config, such as vault setup or `ouro connect bluebubbles`, run bundle sync after the change when `sync.enabled` is true and report a compact `bundle sync:` line.
|
|
105
105
|
- The daemon discovers bundles dynamically from `~/AgentBundles`.
|
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.447",
|
|
6
|
+
"changes": [
|
|
7
|
+
"`ouro connect` now checks only the providers selected for the current agent lanes during preflight, and those live reads avoid mutating the older provider snapshot cache. That keeps the command focused on what this machine actually needs instead of paying whole-vault latency for unrelated provider records.",
|
|
8
|
+
"Structured vault reads for `providers/*` and `runtime/config` now reuse one short-lived Bitwarden item listing per store instance and skip redundant `bw sync` calls while the local Bitwarden cache is fresh, cutting repeated vault startup work without adding any disk credential cache.",
|
|
9
|
+
"The connections screen now lets the fresh live provider check outrank stale local credential visibility, so a provider that just passed appears ready and a provider that just failed shows the real live-check failure instead of falling back to misleading `credentials missing` guidance. `@ouro.bot/cli` and the `ouro.bot` wrapper are version-synced for the connect preflight latency release."
|
|
10
|
+
]
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"version": "0.1.0-alpha.446",
|
|
14
|
+
"changes": [
|
|
15
|
+
"`ouro connect`, the home deck, readiness repair prompts, and the interactive repair queue now render through one shared wizard language instead of a mix of framed panels and transcript walls. Humans can choose by number or name, see one recommended next step, and keep the same visual footing across setup and repair.",
|
|
16
|
+
"Connector, auth, vault, hatch, and other info-heavy command flows now use a matching guide surface with the same Ouro masthead, ruled sections, and `Next moves` treatment, so the CLI stops jumping between unrelated visual grammars as soon as a command leaves the root menu.",
|
|
17
|
+
"The new surfaces stay truthful to the underlying work: connect still runs the shared live provider verification path before rendering, repair prompts surface the real next command without dead air, and new renderer plus command-layer coverage lock the wizard/guide family into the shipped CLI."
|
|
18
|
+
]
|
|
19
|
+
},
|
|
4
20
|
{
|
|
5
21
|
"version": "0.1.0-alpha.445",
|
|
6
22
|
"changes": [
|
|
@@ -330,12 +330,20 @@ function selectedProviderPlan(agentName, state) {
|
|
|
330
330
|
...["outward", "inner"].map((lane) => `- ${laneAudienceLabel(lane)}: ${bindingLabel(state.lanes[lane])}`),
|
|
331
331
|
].join("\n");
|
|
332
332
|
}
|
|
333
|
+
function selectedProvidersForState(state) {
|
|
334
|
+
return [...new Set(["outward", "inner"].map((lane) => state.lanes[lane].provider))];
|
|
335
|
+
}
|
|
333
336
|
function mapVaultRefreshProgress(agentName, onProgress) {
|
|
334
337
|
return (message) => {
|
|
335
338
|
if (message.startsWith("reading vault items for ")) {
|
|
336
339
|
onProgress(`${agentName}: opening saved provider credentials in the vault`);
|
|
337
340
|
return;
|
|
338
341
|
}
|
|
342
|
+
const providerRead = message.match(/^reading ([a-z0-9-]+) credentials\.\.\.$/i);
|
|
343
|
+
if (providerRead) {
|
|
344
|
+
onProgress(`${agentName}: reading saved ${providerRead[1]} credentials`);
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
339
347
|
if (message === "parsing provider credentials...") {
|
|
340
348
|
onProgress(`${agentName}: organizing saved provider credentials`);
|
|
341
349
|
}
|
|
@@ -381,7 +389,12 @@ async function checkAgentConfigWithProviderHealth(agentName, bundlesRoot, deps =
|
|
|
381
389
|
return { ok: true };
|
|
382
390
|
deps.onProgress?.(selectedProviderPlan(agentName, stateResult.state));
|
|
383
391
|
const ping = deps.pingProvider ?? (await Promise.resolve().then(() => __importStar(require("../provider-ping")))).pingProvider;
|
|
384
|
-
const
|
|
392
|
+
const providers = selectedProvidersForState(stateResult.state);
|
|
393
|
+
const poolResult = await (0, provider_credentials_1.refreshProviderCredentialPool)(agentName, {
|
|
394
|
+
...(deps.onProgress ? { onProgress: mapVaultRefreshProgress(agentName, deps.onProgress) } : {}),
|
|
395
|
+
providers,
|
|
396
|
+
skipCache: true,
|
|
397
|
+
});
|
|
385
398
|
const pingGroups = new Map();
|
|
386
399
|
const lanes = ["outward", "inner"];
|
|
387
400
|
for (const lane of lanes) {
|
|
@@ -52,6 +52,8 @@ function makeInteractiveRepairDeps(deps) {
|
|
|
52
52
|
runAuthFlow: deps.runAuthFlow ?? (async () => undefined),
|
|
53
53
|
runVaultUnlock: deps.runVaultUnlock,
|
|
54
54
|
skipQueueSummary: deps.skipQueueSummary,
|
|
55
|
+
isTTY: deps.isTTY,
|
|
56
|
+
stdoutColumns: deps.stdoutColumns,
|
|
55
57
|
};
|
|
56
58
|
}
|
|
57
59
|
async function runDeterministicRepair(degraded, deps) {
|
|
@@ -2060,11 +2060,33 @@ async function buildConnectMenu(agent, deps, onProgress) {
|
|
|
2060
2060
|
onProgress?.("loading this machine's settings");
|
|
2061
2061
|
const machineRuntime = await (0, runtime_credentials_1.refreshMachineRuntimeCredentialConfig)(agent, currentMachineId(deps), { preserveCachedOnFailure: true });
|
|
2062
2062
|
const { teamsEnabled, blueBubblesEnabled } = readConnectBaySenseFlags(agent, deps);
|
|
2063
|
-
let perplexityStatus;
|
|
2064
|
-
let perplexityDetailLines;
|
|
2065
2063
|
const perplexityApiKey = runtimeConfig.ok
|
|
2066
2064
|
? readRuntimeConfigString(runtimeConfig.config, "integrations.perplexityApiKey")
|
|
2067
2065
|
: null;
|
|
2066
|
+
const embeddingsApiKey = runtimeConfig.ok
|
|
2067
|
+
? readRuntimeConfigString(runtimeConfig.config, "integrations.openaiEmbeddingsApiKey")
|
|
2068
|
+
: null;
|
|
2069
|
+
const shouldVerifyPerplexity = runtimeConfig.ok && !!perplexityApiKey;
|
|
2070
|
+
const shouldVerifyEmbeddings = runtimeConfig.ok && !!embeddingsApiKey;
|
|
2071
|
+
let perplexityVerification;
|
|
2072
|
+
let embeddingsVerification;
|
|
2073
|
+
if (shouldVerifyPerplexity && shouldVerifyEmbeddings) {
|
|
2074
|
+
onProgress?.("verifying Perplexity search and memory embeddings");
|
|
2075
|
+
[perplexityVerification, embeddingsVerification] = await Promise.all([
|
|
2076
|
+
(0, runtime_capability_check_1.verifyPerplexityCapability)(perplexityApiKey),
|
|
2077
|
+
(0, runtime_capability_check_1.verifyEmbeddingsCapability)(embeddingsApiKey),
|
|
2078
|
+
]);
|
|
2079
|
+
}
|
|
2080
|
+
else if (shouldVerifyPerplexity) {
|
|
2081
|
+
onProgress?.("verifying Perplexity search");
|
|
2082
|
+
perplexityVerification = await (0, runtime_capability_check_1.verifyPerplexityCapability)(perplexityApiKey);
|
|
2083
|
+
}
|
|
2084
|
+
else if (shouldVerifyEmbeddings) {
|
|
2085
|
+
onProgress?.("verifying memory embeddings");
|
|
2086
|
+
embeddingsVerification = await (0, runtime_capability_check_1.verifyEmbeddingsCapability)(embeddingsApiKey);
|
|
2087
|
+
}
|
|
2088
|
+
let perplexityStatus;
|
|
2089
|
+
let perplexityDetailLines;
|
|
2068
2090
|
if (!runtimeConfig.ok) {
|
|
2069
2091
|
perplexityStatus = runtimeConfigReadStatus(runtimeConfig);
|
|
2070
2092
|
perplexityDetailLines = [];
|
|
@@ -2074,16 +2096,15 @@ async function buildConnectMenu(agent, deps, onProgress) {
|
|
|
2074
2096
|
perplexityDetailLines = ["no API key saved yet"];
|
|
2075
2097
|
}
|
|
2076
2098
|
else {
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2099
|
+
perplexityStatus = perplexityVerification?.ok ? "ready" : "needs attention";
|
|
2100
|
+
perplexityDetailLines = [
|
|
2101
|
+
perplexityVerification?.ok
|
|
2102
|
+
? "verified live just now"
|
|
2103
|
+
: `live check failed: ${perplexityVerification.summary}`,
|
|
2104
|
+
];
|
|
2081
2105
|
}
|
|
2082
2106
|
let embeddingsStatus;
|
|
2083
2107
|
let embeddingsDetailLines;
|
|
2084
|
-
const embeddingsApiKey = runtimeConfig.ok
|
|
2085
|
-
? readRuntimeConfigString(runtimeConfig.config, "integrations.openaiEmbeddingsApiKey")
|
|
2086
|
-
: null;
|
|
2087
2108
|
if (!runtimeConfig.ok) {
|
|
2088
2109
|
embeddingsStatus = runtimeConfigReadStatus(runtimeConfig);
|
|
2089
2110
|
embeddingsDetailLines = [];
|
|
@@ -2093,10 +2114,12 @@ async function buildConnectMenu(agent, deps, onProgress) {
|
|
|
2093
2114
|
embeddingsDetailLines = ["no API key saved yet"];
|
|
2094
2115
|
}
|
|
2095
2116
|
else {
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2117
|
+
embeddingsStatus = embeddingsVerification?.ok ? "ready" : "needs attention";
|
|
2118
|
+
embeddingsDetailLines = [
|
|
2119
|
+
embeddingsVerification?.ok
|
|
2120
|
+
? "verified live just now"
|
|
2121
|
+
: `live check failed: ${embeddingsVerification.summary}`,
|
|
2122
|
+
];
|
|
2100
2123
|
}
|
|
2101
2124
|
const teamsStatus = runtimeConfig.ok
|
|
2102
2125
|
? hasRuntimeConfigValue(runtimeConfig.config, "teams.clientId")
|
|
@@ -4316,6 +4339,8 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
|
|
|
4316
4339
|
await executeVaultUnlock({ kind: "vault.unlock", agent }, deps);
|
|
4317
4340
|
},
|
|
4318
4341
|
skipQueueSummary: true,
|
|
4342
|
+
isTTY: deps.isTTY ?? process.stdout.isTTY === true,
|
|
4343
|
+
stdoutColumns: deps.stdoutColumns ?? process.stdout.columns,
|
|
4319
4344
|
});
|
|
4320
4345
|
if (repairResult.repairsAttempted) {
|
|
4321
4346
|
repairsAttempted = true;
|
|
@@ -17,52 +17,6 @@ const CONNECT_STATUS_PRIORITY = {
|
|
|
17
17
|
ready: 6,
|
|
18
18
|
attached: 6,
|
|
19
19
|
};
|
|
20
|
-
const RESET = "\x1b[0m";
|
|
21
|
-
const BOLD = "\x1b[1m";
|
|
22
|
-
const TEAL = "\x1b[38;2;78;201;176m";
|
|
23
|
-
const GREEN = "\x1b[38;2;46;204;64m";
|
|
24
|
-
const GOLD = "\x1b[38;2;230;190;50m";
|
|
25
|
-
const BONE = "\x1b[38;2;238;242;234m";
|
|
26
|
-
const MIST = "\x1b[38;2;165;184;168m";
|
|
27
|
-
const ANSI_RE = /\x1b\[[0-9;]*m/g;
|
|
28
|
-
function stripAnsi(text) {
|
|
29
|
-
return text.replace(ANSI_RE, "");
|
|
30
|
-
}
|
|
31
|
-
function visibleLength(text) {
|
|
32
|
-
return stripAnsi(text).length;
|
|
33
|
-
}
|
|
34
|
-
function padAnsi(text, width) {
|
|
35
|
-
const missing = Math.max(0, width - visibleLength(text));
|
|
36
|
-
return `${text}${" ".repeat(missing)}`;
|
|
37
|
-
}
|
|
38
|
-
function wrapPlain(text, width) {
|
|
39
|
-
const normalized = text.trim();
|
|
40
|
-
if (!normalized)
|
|
41
|
-
return [""];
|
|
42
|
-
const words = normalized.split(/\s+/);
|
|
43
|
-
const lines = [];
|
|
44
|
-
let current = "";
|
|
45
|
-
for (const word of words) {
|
|
46
|
-
if (!current) {
|
|
47
|
-
current = word;
|
|
48
|
-
continue;
|
|
49
|
-
}
|
|
50
|
-
const candidate = `${current} ${word}`;
|
|
51
|
-
if (candidate.length <= width) {
|
|
52
|
-
current = candidate;
|
|
53
|
-
continue;
|
|
54
|
-
}
|
|
55
|
-
lines.push(current);
|
|
56
|
-
current = word;
|
|
57
|
-
}
|
|
58
|
-
lines.push(current);
|
|
59
|
-
return lines;
|
|
60
|
-
}
|
|
61
|
-
function tty(text, color, bold = false) {
|
|
62
|
-
if (bold)
|
|
63
|
-
return `${color}${BOLD}${text}${RESET}`;
|
|
64
|
-
return `${color}${text}${RESET}`;
|
|
65
|
-
}
|
|
66
20
|
function escapeRegExp(value) {
|
|
67
21
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
68
22
|
}
|
|
@@ -130,216 +84,102 @@ function resolveProviderHealthCommand(providerHealth, status) {
|
|
|
130
84
|
}
|
|
131
85
|
return undefined;
|
|
132
86
|
}
|
|
133
|
-
function
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
function sectionTitle(title, width) {
|
|
150
|
-
const plain = `╭─ ${title} `;
|
|
151
|
-
const rule = "─".repeat(Math.max(0, width - plain.length - 1));
|
|
152
|
-
return `${tty("╭─ ", TEAL)}${tty(title, BONE, true)}${tty(` ${rule}╮`, TEAL)}`;
|
|
153
|
-
}
|
|
154
|
-
function bottomRule(width) {
|
|
155
|
-
const line = `╰${"─".repeat(Math.max(0, width - 2))}╯`;
|
|
156
|
-
return tty(line, TEAL);
|
|
87
|
+
function providerHealthTargetLane(providerHealth) {
|
|
88
|
+
const issue = providerHealth?.issue;
|
|
89
|
+
const actionLane = issue?.actions
|
|
90
|
+
.map((action) => "lane" in action && action.lane ? action.lane : undefined)
|
|
91
|
+
.find((lane) => lane === "outward" || lane === "inner");
|
|
92
|
+
if (actionLane)
|
|
93
|
+
return actionLane;
|
|
94
|
+
const text = [issue?.summary, issue?.detail, providerHealth?.error]
|
|
95
|
+
.filter(Boolean)
|
|
96
|
+
.join(" ")
|
|
97
|
+
.toLowerCase();
|
|
98
|
+
if (/\boutward provider\b/.test(text) || /\boutward lane\b/.test(text))
|
|
99
|
+
return "outward";
|
|
100
|
+
if (/\binner provider\b/.test(text) || /\binner lane\b/.test(text))
|
|
101
|
+
return "inner";
|
|
102
|
+
return undefined;
|
|
157
103
|
}
|
|
158
|
-
function
|
|
159
|
-
const
|
|
160
|
-
return
|
|
104
|
+
function providerHealthAppliesToLane(providerHealth, lane) {
|
|
105
|
+
const targetLane = providerHealthTargetLane(providerHealth);
|
|
106
|
+
return !targetLane || targetLane === lane.lane;
|
|
161
107
|
}
|
|
162
|
-
function
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
108
|
+
function providerHealthDetail(providerHealth, status) {
|
|
109
|
+
if (status === "locked")
|
|
110
|
+
return "vault locked on this machine";
|
|
111
|
+
if (status === "needs credentials")
|
|
112
|
+
return "credentials missing";
|
|
113
|
+
if (status === "needs setup") {
|
|
114
|
+
return providerHealth?.issue?.detail ?? providerHealth?.error ?? "needs setup";
|
|
166
115
|
}
|
|
167
|
-
|
|
168
|
-
|
|
116
|
+
const detail = providerHealth?.issue?.detail ?? providerHealth?.error;
|
|
117
|
+
if (!detail)
|
|
118
|
+
return "live check needs attention";
|
|
119
|
+
return /failed live check/i.test(detail) ? detail : `failed live check: ${detail}`;
|
|
169
120
|
}
|
|
170
|
-
function
|
|
171
|
-
return
|
|
172
|
-
tty("Set up or review one capability at a time.", BONE, true),
|
|
173
|
-
tty("Everything on this screen was checked live just now.", MIST),
|
|
174
|
-
], width);
|
|
175
|
-
}
|
|
176
|
-
function nextMoveBody(entry) {
|
|
177
|
-
if (!entry) {
|
|
178
|
-
return [
|
|
179
|
-
tty("Everything here is ready.", BONE, true),
|
|
180
|
-
tty("Pick what you want to review or refresh.", MIST),
|
|
181
|
-
];
|
|
182
|
-
}
|
|
183
|
-
const lines = [
|
|
184
|
-
`${entry.name} ${statusChip(entry.status)}`,
|
|
185
|
-
];
|
|
186
|
-
if (entry.nextNote)
|
|
187
|
-
lines.push(entry.nextNote);
|
|
188
|
-
if (entry.nextAction)
|
|
189
|
-
lines.push(tty(entry.nextAction, MIST));
|
|
190
|
-
return lines;
|
|
121
|
+
function isProblemStatus(status) {
|
|
122
|
+
return status !== "ready" && status !== "attached";
|
|
191
123
|
}
|
|
192
|
-
function
|
|
193
|
-
|
|
194
|
-
`${entry.option} ${entry.name} ${statusChip(entry.status)}`,
|
|
195
|
-
];
|
|
196
|
-
const lanes = entry.laneSummaries ?? [];
|
|
197
|
-
for (const [index, lane] of lanes.entries()) {
|
|
198
|
-
if (index > 0)
|
|
199
|
-
lines.push("");
|
|
200
|
-
const laneLabel = lane.lane === "outward" ? "Outward lane" : "Inner lane";
|
|
201
|
-
lines.push(tty(laneLabel, BONE, true));
|
|
202
|
-
lines.push(lane.title);
|
|
203
|
-
lines.push(isProblemStatus(lane.status) ? lane.detail : tty(lane.detail, MIST));
|
|
204
|
-
}
|
|
205
|
-
if (lanes.length === 0) {
|
|
206
|
-
for (const detail of entry.detailLines ?? [])
|
|
207
|
-
lines.push(detail);
|
|
208
|
-
}
|
|
209
|
-
return normalizeWrappedBody(lines, width);
|
|
124
|
+
function providerEntrySummary(entry) {
|
|
125
|
+
return entry.description ?? "Selected provider lanes for this machine.";
|
|
210
126
|
}
|
|
211
|
-
function
|
|
127
|
+
function providerEntryDetailLines(entry) {
|
|
212
128
|
const lines = [];
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
lines.push("");
|
|
216
|
-
lines.push(`${entry.option} ${entry.name} ${statusChip(entry.status)}`);
|
|
217
|
-
if (entry.description) {
|
|
218
|
-
lines.push(isProblemStatus(entry.status) ? entry.description : tty(entry.description, MIST));
|
|
219
|
-
}
|
|
220
|
-
for (const detail of entry.detailLines ?? []) {
|
|
221
|
-
lines.push(detail);
|
|
222
|
-
}
|
|
129
|
+
if (entry.nextNote && !/^(Outward|Inner) lane: /.test(entry.nextNote)) {
|
|
130
|
+
lines.push(entry.nextNote);
|
|
223
131
|
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
for (const line of lines) {
|
|
229
|
-
if (!line) {
|
|
230
|
-
wrapped.push("");
|
|
231
|
-
continue;
|
|
232
|
-
}
|
|
233
|
-
const plain = stripAnsi(line);
|
|
234
|
-
if (plain.length <= width - 4) {
|
|
235
|
-
wrapped.push(line);
|
|
236
|
-
continue;
|
|
132
|
+
if (entry.laneSummaries && entry.laneSummaries.length > 0) {
|
|
133
|
+
for (const lane of entry.laneSummaries) {
|
|
134
|
+
const laneLabel = lane.lane === "outward" ? "Outward lane" : "Inner lane";
|
|
135
|
+
lines.push(`${laneLabel}: ${lane.title} — ${lane.detail}`);
|
|
237
136
|
}
|
|
238
|
-
|
|
239
|
-
wrapped.push(...segments);
|
|
137
|
+
return lines;
|
|
240
138
|
}
|
|
241
|
-
return
|
|
139
|
+
return [...lines, ...(entry.detailLines ?? [])];
|
|
242
140
|
}
|
|
243
|
-
function
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
lines.push("");
|
|
248
|
-
lines.push(...panelLines);
|
|
249
|
-
}
|
|
250
|
-
return lines;
|
|
251
|
-
}
|
|
252
|
-
function combineColumns(left, right, leftWidth, rightWidth, gap = 2) {
|
|
253
|
-
const total = Math.max(left.length, right.length);
|
|
254
|
-
const lines = [];
|
|
255
|
-
for (let index = 0; index < total; index += 1) {
|
|
256
|
-
const leftLine = left[index] ?? " ".repeat(leftWidth);
|
|
257
|
-
const rightLine = right[index] ?? " ".repeat(rightWidth);
|
|
258
|
-
lines.push(`${padAnsi(leftLine, leftWidth)}${" ".repeat(gap)}${padAnsi(rightLine, rightWidth)}`);
|
|
259
|
-
}
|
|
260
|
-
return lines;
|
|
261
|
-
}
|
|
262
|
-
function renderTtyBay(entries, options) {
|
|
263
|
-
const columns = Math.max(options.columns ?? 108, 72);
|
|
264
|
-
const fullWidth = Math.max(56, columns - 2);
|
|
265
|
-
const masthead = (0, terminal_ui_1.renderOuroMasthead)({
|
|
266
|
-
isTTY: true,
|
|
267
|
-
columns,
|
|
268
|
-
subtitle: "Set up connections one step at a time.",
|
|
269
|
-
}).trimEnd();
|
|
270
|
-
const header = renderHeader(options.agent, fullWidth);
|
|
271
|
-
const nextEntry = entries.find((entry) => isProblemStatus(entry.status));
|
|
272
|
-
const providerEntry = entries.find((entry) => entry.section === "Providers");
|
|
273
|
-
const portableEntries = entries.filter((entry) => entry.section === "Portable");
|
|
274
|
-
const machineEntries = entries.filter((entry) => entry.section === "This machine");
|
|
275
|
-
const wide = columns >= 118;
|
|
276
|
-
const footer = [
|
|
277
|
-
tty("Choose a number, or type the capability name.", MIST),
|
|
278
|
-
options.prompt,
|
|
141
|
+
function capabilityEntryDetailLines(entry) {
|
|
142
|
+
return [
|
|
143
|
+
...(entry.detailLines ?? []),
|
|
144
|
+
...(entry.nextNote ? [entry.nextNote] : []),
|
|
279
145
|
];
|
|
280
|
-
if (!wide) {
|
|
281
|
-
const panels = [
|
|
282
|
-
header,
|
|
283
|
-
panel("Recommended next step", nextMoveBody(nextEntry), fullWidth),
|
|
284
|
-
panel("Providers", renderProviderBody(providerEntry, fullWidth), fullWidth),
|
|
285
|
-
panel("Portable", renderCapabilityBody(portableEntries, fullWidth), fullWidth),
|
|
286
|
-
panel("This machine", renderCapabilityBody(machineEntries, fullWidth), fullWidth),
|
|
287
|
-
];
|
|
288
|
-
return [masthead, "", ...stackPanels(panels), "", ...footer].join("\n");
|
|
289
|
-
}
|
|
290
|
-
const gap = 2;
|
|
291
|
-
const leftWidth = Math.max(52, Math.floor((fullWidth - gap) / 2));
|
|
292
|
-
const rightWidth = Math.max(40, fullWidth - gap - leftWidth);
|
|
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);
|
|
295
|
-
return [masthead, "", ...header, "", ...topRow, "", ...bottomRow, "", ...footer].join("\n");
|
|
296
146
|
}
|
|
297
|
-
function
|
|
147
|
+
function entryToWizardItem(entry) {
|
|
148
|
+
return {
|
|
149
|
+
key: entry.option,
|
|
150
|
+
label: entry.name,
|
|
151
|
+
status: entry.status,
|
|
152
|
+
...(entry.section === "Providers"
|
|
153
|
+
? { summary: providerEntrySummary(entry) }
|
|
154
|
+
: entry.description
|
|
155
|
+
? { summary: entry.description }
|
|
156
|
+
: {}),
|
|
157
|
+
detailLines: entry.section === "Providers"
|
|
158
|
+
? providerEntryDetailLines(entry)
|
|
159
|
+
: capabilityEntryDetailLines(entry),
|
|
160
|
+
...(entry.nextAction ? { command: entry.nextAction } : {}),
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
function nextStepFor(entries) {
|
|
298
164
|
const nextEntry = entries.find((entry) => isProblemStatus(entry.status));
|
|
299
|
-
const lines = [
|
|
300
|
-
`${options.agent} connections`,
|
|
301
|
-
"Set up or review one capability at a time. Provider status was checked live just now.",
|
|
302
|
-
"",
|
|
303
|
-
"Recommended next step",
|
|
304
|
-
"---------------------",
|
|
305
|
-
];
|
|
306
165
|
if (!nextEntry) {
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
if (nextEntry.nextNote)
|
|
312
|
-
lines.push(nextEntry.nextNote);
|
|
313
|
-
if (nextEntry.nextAction)
|
|
314
|
-
lines.push(`run: ${nextEntry.nextAction}`);
|
|
315
|
-
}
|
|
316
|
-
lines.push("");
|
|
317
|
-
for (const section of ["Providers", "Portable", "This machine"]) {
|
|
318
|
-
lines.push(section);
|
|
319
|
-
lines.push("-".repeat(Math.max(6, section.length + 4)));
|
|
320
|
-
for (const entry of entries.filter((candidate) => candidate.section === section)) {
|
|
321
|
-
lines.push(`${entry.option}. ${entry.name} [${entry.status}]`);
|
|
322
|
-
if (entry.laneSummaries && entry.laneSummaries.length > 0) {
|
|
323
|
-
for (const lane of entry.laneSummaries) {
|
|
324
|
-
const laneLabel = lane.lane === "outward" ? "Outward lane" : "Inner lane";
|
|
325
|
-
lines.push(` ${laneLabel}: ${lane.title}`);
|
|
326
|
-
lines.push(` ${lane.detail}`);
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
else {
|
|
330
|
-
for (const detail of entry.detailLines ?? [])
|
|
331
|
-
lines.push(` ${detail}`);
|
|
332
|
-
}
|
|
333
|
-
if (entry.description)
|
|
334
|
-
lines.push(` ${entry.description}`);
|
|
335
|
-
lines.push("");
|
|
336
|
-
}
|
|
166
|
+
return {
|
|
167
|
+
label: "Everything here is already connected.",
|
|
168
|
+
detail: "Pick any capability if you want to review it, refresh it, or change its setup.",
|
|
169
|
+
};
|
|
337
170
|
}
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
171
|
+
return {
|
|
172
|
+
label: `Start with ${nextEntry.name}.`,
|
|
173
|
+
detail: nextEntry.nextNote ?? nextEntry.description ?? `Status: ${nextEntry.status}.`,
|
|
174
|
+
command: nextEntry.nextAction,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
function sectionToWizard(entries, section, summary) {
|
|
178
|
+
return {
|
|
179
|
+
title: section,
|
|
180
|
+
summary,
|
|
181
|
+
items: entries.filter((entry) => entry.section === section).map((entry) => entryToWizardItem(entry)),
|
|
182
|
+
};
|
|
343
183
|
}
|
|
344
184
|
function summarizeProviderLane(agent, lane, providerHealth) {
|
|
345
185
|
const providerHealthStatus = resolveProviderHealthStatus(providerHealth);
|
|
@@ -354,6 +194,23 @@ function summarizeProviderLane(agent, lane, providerHealth) {
|
|
|
354
194
|
};
|
|
355
195
|
}
|
|
356
196
|
const fallbackAction = providerHealthCommand ?? lane.credential.repairCommand;
|
|
197
|
+
if (providerHealth?.ok) {
|
|
198
|
+
return {
|
|
199
|
+
lane: lane.lane,
|
|
200
|
+
status: "ready",
|
|
201
|
+
title: `${lane.provider} / ${lane.model}`,
|
|
202
|
+
detail: "ready",
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
if (providerHealthStatus && providerHealthAppliesToLane(providerHealth, lane)) {
|
|
206
|
+
return {
|
|
207
|
+
lane: lane.lane,
|
|
208
|
+
status: providerHealthStatus,
|
|
209
|
+
title: `${lane.provider} / ${lane.model}`,
|
|
210
|
+
detail: providerHealthDetail(providerHealth, providerHealthStatus),
|
|
211
|
+
action: fallbackAction,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
357
214
|
if (lane.credential.status === "missing") {
|
|
358
215
|
return {
|
|
359
216
|
lane: lane.lane,
|
|
@@ -372,14 +229,6 @@ function summarizeProviderLane(agent, lane, providerHealth) {
|
|
|
372
229
|
action: fallbackAction,
|
|
373
230
|
};
|
|
374
231
|
}
|
|
375
|
-
if (providerHealth?.ok) {
|
|
376
|
-
return {
|
|
377
|
-
lane: lane.lane,
|
|
378
|
-
status: "ready",
|
|
379
|
-
title: `${lane.provider} / ${lane.model}`,
|
|
380
|
-
detail: "ready",
|
|
381
|
-
};
|
|
382
|
-
}
|
|
383
232
|
if (lane.readiness.status === "failed") {
|
|
384
233
|
return {
|
|
385
234
|
lane: lane.lane,
|
|
@@ -450,7 +299,25 @@ function renderConnectBay(entries, options) {
|
|
|
450
299
|
columns: options.columns ?? null,
|
|
451
300
|
},
|
|
452
301
|
});
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
302
|
+
return (0, terminal_ui_1.renderTerminalWizard)({
|
|
303
|
+
isTTY: options.isTTY,
|
|
304
|
+
columns: options.columns,
|
|
305
|
+
masthead: {
|
|
306
|
+
subtitle: "Set up connections one step at a time.",
|
|
307
|
+
},
|
|
308
|
+
title: `Connect ${options.agent}`,
|
|
309
|
+
summary: "Choose one capability to bring online. Each row tells you whether Ouro checked it live just now or is showing saved setup on this machine.",
|
|
310
|
+
nextStep: nextStepFor(entries),
|
|
311
|
+
sections: [
|
|
312
|
+
sectionToWizard(entries, "Providers", "Selected outward and inner lanes for this machine."),
|
|
313
|
+
sectionToWizard(entries, "Portable", "These travel with the agent bundle when their secrets are portable."),
|
|
314
|
+
sectionToWizard(entries, "This machine", "These depend on local attachments or machine-specific setup."),
|
|
315
|
+
],
|
|
316
|
+
footerLines: [
|
|
317
|
+
"6. Not now",
|
|
318
|
+
"Choose a number, or type the capability name.",
|
|
319
|
+
],
|
|
320
|
+
prompt: options.prompt,
|
|
321
|
+
suppressEvent: true,
|
|
322
|
+
});
|
|
456
323
|
}
|