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