@ouro.bot/cli 0.1.0-alpha.422 → 0.1.0-alpha.423
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 +3 -0
- package/changelog.json +10 -0
- package/dist/heart/daemon/cli-exec.js +335 -56
- package/dist/heart/daemon/cli-help.js +19 -4
- package/dist/heart/daemon/cli-parse.js +10 -4
- package/dist/heart/daemon/up-progress.js +43 -2
- package/dist/mind/prompt.js +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -179,7 +179,10 @@ ouro vault status --agent <name>
|
|
|
179
179
|
ouro vault config set --agent <name> --key teams.clientSecret
|
|
180
180
|
ouro vault config status --agent <name> --scope all
|
|
181
181
|
ouro connect --agent <name>
|
|
182
|
+
ouro connect providers --agent <name>
|
|
182
183
|
ouro connect perplexity --agent <name>
|
|
184
|
+
ouro connect embeddings --agent <name>
|
|
185
|
+
ouro connect teams --agent <name>
|
|
183
186
|
ouro connect bluebubbles --agent <name>
|
|
184
187
|
ouro auth --agent <name>
|
|
185
188
|
ouro auth --agent <name> --provider <provider>
|
package/changelog.json
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
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.423",
|
|
6
|
+
"changes": [
|
|
7
|
+
"`ouro connect` now opens a real connect bay with one guided surface for provider auth, Perplexity, OpenAI embeddings, Teams, and BlueBubbles instead of a tiny menu that still required humans to memorize follow-up commands.",
|
|
8
|
+
"Added guided `ouro connect providers`, `ouro connect embeddings`, and `ouro connect teams` flows, plus richer connect-bay routing that distinguishes portable runtime config from machine-local attachments without printing secrets.",
|
|
9
|
+
"Credential/runtime changes now reuse one shared runtime-apply helper that checks daemon socket access, requests restart, polls daemon status, times out cleanly, and falls back clearly when daemon status is unavailable.",
|
|
10
|
+
"Command progress rendering now marks failed phases honestly with a failure state instead of a success checkmark plus `failed`, and the auth/provider docs, README, testing guide, OAUTH setup guide, and prompt truth text all point at the new connect-bay workflow.",
|
|
11
|
+
"`@ouro.bot/cli` and the `ouro.bot` wrapper are version-synced for the connect-bay and runtime-apply progress release."
|
|
12
|
+
]
|
|
13
|
+
},
|
|
4
14
|
{
|
|
5
15
|
"version": "0.1.0-alpha.422",
|
|
6
16
|
"changes": [
|
|
@@ -71,6 +71,7 @@ const auth_flow_1 = require("../auth/auth-flow");
|
|
|
71
71
|
const provider_credentials_1 = require("../provider-credentials");
|
|
72
72
|
const runtime_credentials_1 = require("../runtime-credentials");
|
|
73
73
|
const provider_binding_resolver_1 = require("../provider-binding-resolver");
|
|
74
|
+
const provider_visibility_1 = require("../provider-visibility");
|
|
74
75
|
const provider_state_1 = require("../provider-state");
|
|
75
76
|
const machine_identity_1 = require("../machine-identity");
|
|
76
77
|
const provider_models_1 = require("../provider-models");
|
|
@@ -197,7 +198,7 @@ async function runCommandProgressPhase(progress, label, run, detail) {
|
|
|
197
198
|
return result;
|
|
198
199
|
}
|
|
199
200
|
catch (error) {
|
|
200
|
-
progress.
|
|
201
|
+
progress.failPhase(label, "failed");
|
|
201
202
|
throw error;
|
|
202
203
|
}
|
|
203
204
|
}
|
|
@@ -1227,6 +1228,50 @@ function currentMachineId(deps) {
|
|
|
1227
1228
|
now: () => providerCliNow(deps),
|
|
1228
1229
|
}).machineId;
|
|
1229
1230
|
}
|
|
1231
|
+
const DEFAULT_RUNTIME_APPLY_TIMEOUT_MS = 15_000;
|
|
1232
|
+
const DEFAULT_RUNTIME_APPLY_POLL_INTERVAL_MS = 500;
|
|
1233
|
+
const DEFAULT_DAEMON_STATUS_TIMEOUT_MS = 4_000;
|
|
1234
|
+
const DEFAULT_AGENT_RESTART_TIMEOUT_MS = 8_000;
|
|
1235
|
+
const CONNECT_PROVIDER_CHOICES = ["openai-codex", "anthropic", "minimax", "azure", "github-copilot"];
|
|
1236
|
+
function hasRuntimeConfigValue(config, key) {
|
|
1237
|
+
const segments = key.split(".");
|
|
1238
|
+
let cursor = config;
|
|
1239
|
+
for (const segment of segments) {
|
|
1240
|
+
if (!cursor || typeof cursor !== "object" || Array.isArray(cursor))
|
|
1241
|
+
return false;
|
|
1242
|
+
cursor = cursor[segment];
|
|
1243
|
+
}
|
|
1244
|
+
return typeof cursor === "string" && cursor.trim().length > 0;
|
|
1245
|
+
}
|
|
1246
|
+
function runtimeConfigReadStatus(runtime) {
|
|
1247
|
+
if (runtime.reason === "missing")
|
|
1248
|
+
return "missing";
|
|
1249
|
+
if (/locked/i.test(runtime.error))
|
|
1250
|
+
return "locked";
|
|
1251
|
+
return "needs attention";
|
|
1252
|
+
}
|
|
1253
|
+
function cliNowMs(deps) {
|
|
1254
|
+
return (deps.now ?? Date.now)();
|
|
1255
|
+
}
|
|
1256
|
+
function cliSleep(deps, ms) {
|
|
1257
|
+
if (deps.sleep)
|
|
1258
|
+
return deps.sleep(ms);
|
|
1259
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1260
|
+
}
|
|
1261
|
+
async function withCliTimeout(timeoutMs, label, run) {
|
|
1262
|
+
let timer;
|
|
1263
|
+
try {
|
|
1264
|
+
return await Promise.race([
|
|
1265
|
+
run(),
|
|
1266
|
+
new Promise((_, reject) => {
|
|
1267
|
+
timer = setTimeout(() => reject(new Error(label)), timeoutMs);
|
|
1268
|
+
}),
|
|
1269
|
+
]);
|
|
1270
|
+
}
|
|
1271
|
+
finally {
|
|
1272
|
+
clearTimeout(timer);
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1230
1275
|
async function promptRuntimeConfigValue(command, deps) {
|
|
1231
1276
|
if (command.value !== undefined)
|
|
1232
1277
|
return command.value;
|
|
@@ -1242,7 +1287,7 @@ async function promptRuntimeConfigValue(command, deps) {
|
|
|
1242
1287
|
function runtimeScopeLabel(scope) {
|
|
1243
1288
|
return scope === "machine" ? "this machine's vault runtime config item" : "the agent vault runtime/config item";
|
|
1244
1289
|
}
|
|
1245
|
-
async function
|
|
1290
|
+
async function storeRuntimeConfigKeys(input) {
|
|
1246
1291
|
const machineId = input.scope === "machine" ? currentMachineId(input.deps) : undefined;
|
|
1247
1292
|
input.onProgress?.("checking existing runtime config");
|
|
1248
1293
|
const current = input.scope === "machine"
|
|
@@ -1251,23 +1296,77 @@ async function storeRuntimeConfigKey(input) {
|
|
|
1251
1296
|
if (!current.ok && current.reason !== "missing") {
|
|
1252
1297
|
throw new Error(`cannot read existing runtime credentials from ${current.itemPath}: ${current.error}`);
|
|
1253
1298
|
}
|
|
1254
|
-
|
|
1255
|
-
|
|
1299
|
+
let nextConfig = current.ok ? current.config : {};
|
|
1300
|
+
for (const entry of input.entries) {
|
|
1301
|
+
input.onProgress?.(`storing ${entry.key} in ${current.itemPath}`);
|
|
1302
|
+
nextConfig = setRuntimeConfigValue(nextConfig, entry.key, entry.value);
|
|
1303
|
+
}
|
|
1256
1304
|
const stored = input.scope === "machine"
|
|
1257
1305
|
? await (0, runtime_credentials_1.upsertMachineRuntimeCredentialConfig)(input.agent, machineId, nextConfig, providerCliNow(input.deps))
|
|
1258
1306
|
: await (0, runtime_credentials_1.upsertRuntimeCredentialConfig)(input.agent, nextConfig, providerCliNow(input.deps));
|
|
1259
|
-
input.onProgress?.(`stored ${input.key}; credential
|
|
1307
|
+
input.onProgress?.(`stored ${input.entries.map((entry) => entry.key).join(", ")}; credential values were not printed`);
|
|
1260
1308
|
return { revision: stored.revision, itemPath: stored.itemPath, ...(machineId ? { machineId } : {}) };
|
|
1261
1309
|
}
|
|
1262
|
-
async function
|
|
1310
|
+
async function storeRuntimeConfigKey(input) {
|
|
1311
|
+
return storeRuntimeConfigKeys({
|
|
1312
|
+
agent: input.agent,
|
|
1313
|
+
entries: [{ key: input.key, value: input.value }],
|
|
1314
|
+
scope: input.scope,
|
|
1315
|
+
deps: input.deps,
|
|
1316
|
+
onProgress: input.onProgress,
|
|
1317
|
+
});
|
|
1318
|
+
}
|
|
1319
|
+
function readRuntimeApplyWorker(payload, agent) {
|
|
1320
|
+
return payload.workers.find((worker) => worker.agent === agent) ?? null;
|
|
1321
|
+
}
|
|
1322
|
+
async function applyRuntimeChangeToRunningAgent(agent, deps, onProgress) {
|
|
1263
1323
|
try {
|
|
1324
|
+
onProgress?.("checking daemon socket");
|
|
1264
1325
|
const alive = await deps.checkSocketAlive(deps.socketPath);
|
|
1265
1326
|
if (!alive)
|
|
1266
|
-
return "daemon is not running; next `ouro up` will load
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1327
|
+
return "daemon is not running; next `ouro up` will load the change";
|
|
1328
|
+
onProgress?.("requesting restart from daemon");
|
|
1329
|
+
const response = await withCliTimeout(DEFAULT_AGENT_RESTART_TIMEOUT_MS, "daemon restart request timed out", () => deps.sendCommand(deps.socketPath, { kind: "agent.restart", agent }));
|
|
1330
|
+
if (!response.ok)
|
|
1331
|
+
return `daemon restart skipped: ${response.error ?? response.message ?? "unknown daemon error"}`;
|
|
1332
|
+
const deadline = cliNowMs(deps) + DEFAULT_RUNTIME_APPLY_TIMEOUT_MS;
|
|
1333
|
+
onProgress?.(`waiting for ${agent} to report running state`);
|
|
1334
|
+
while (cliNowMs(deps) < deadline) {
|
|
1335
|
+
try {
|
|
1336
|
+
const statusResponse = await withCliTimeout(DEFAULT_DAEMON_STATUS_TIMEOUT_MS, "daemon status timed out", () => deps.sendCommand(deps.socketPath, { kind: "daemon.status" }));
|
|
1337
|
+
if (statusResponse.ok) {
|
|
1338
|
+
const payload = (0, cli_render_1.parseStatusPayload)(statusResponse.data);
|
|
1339
|
+
if (!payload) {
|
|
1340
|
+
onProgress?.("daemon status did not include structured worker state");
|
|
1341
|
+
return "restart requested; daemon status is unavailable, so verify with `ouro status` if needed";
|
|
1342
|
+
}
|
|
1343
|
+
const worker = readRuntimeApplyWorker(payload, agent);
|
|
1344
|
+
if (!worker) {
|
|
1345
|
+
onProgress?.(`still waiting: ${agent} is not listed by daemon`);
|
|
1346
|
+
}
|
|
1347
|
+
else if (worker.status === "running") {
|
|
1348
|
+
onProgress?.(`daemon reports ${agent}/${worker.worker} running`);
|
|
1349
|
+
return `restarted ${agent} and the daemon reports it running`;
|
|
1350
|
+
}
|
|
1351
|
+
else if (worker.status === "crashed") {
|
|
1352
|
+
return `restart requested, but ${agent}/${worker.worker} crashed before reporting running${worker.errorReason ? `: ${worker.errorReason}` : worker.fixHint ? `: ${worker.fixHint}` : ""}`;
|
|
1353
|
+
}
|
|
1354
|
+
else {
|
|
1355
|
+
onProgress?.(`still waiting: ${agent}/${worker.worker} is ${worker.status}`);
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
else {
|
|
1359
|
+
onProgress?.(`still waiting: daemon status returned ${statusResponse.error ?? statusResponse.message ?? "unknown error"}`);
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
catch (error) {
|
|
1363
|
+
onProgress?.(`still waiting: ${error instanceof Error ? error.message : String(error)}`);
|
|
1364
|
+
}
|
|
1365
|
+
if (cliNowMs(deps) >= deadline - DEFAULT_RUNTIME_APPLY_POLL_INTERVAL_MS)
|
|
1366
|
+
break;
|
|
1367
|
+
await cliSleep(deps, DEFAULT_RUNTIME_APPLY_POLL_INTERVAL_MS);
|
|
1368
|
+
}
|
|
1369
|
+
return `restart requested, but ${agent} did not report running before timeout. The change is stored; run \`ouro status\` or \`ouro up\` if it still looks stale.`;
|
|
1271
1370
|
}
|
|
1272
1371
|
catch (error) {
|
|
1273
1372
|
return `daemon restart skipped: ${error instanceof Error ? error.message : String(error)}`;
|
|
@@ -1401,23 +1500,70 @@ function enableAgentSense(agent, sense, deps) {
|
|
|
1401
1500
|
};
|
|
1402
1501
|
fs.writeFileSync(configPath, `${JSON.stringify(raw, null, 2)}\n`, "utf-8");
|
|
1403
1502
|
}
|
|
1404
|
-
function
|
|
1503
|
+
async function buildConnectMenu(agent, deps) {
|
|
1504
|
+
const providerVisibility = (0, provider_visibility_1.buildAgentProviderVisibility)({
|
|
1505
|
+
agentName: agent,
|
|
1506
|
+
agentRoot: providerCliAgentRoot({ agent }, deps),
|
|
1507
|
+
homeDir: providerCliHomeDir(deps),
|
|
1508
|
+
});
|
|
1509
|
+
const providerStatus = providerVisibility.lanes.some((lane) => lane.status === "unconfigured")
|
|
1510
|
+
? "needs setup"
|
|
1511
|
+
: providerVisibility.lanes.some((lane) => lane.status === "configured" && lane.credential.status !== "present")
|
|
1512
|
+
? "needs auth"
|
|
1513
|
+
: providerVisibility.lanes.some((lane) => lane.status === "configured" && (lane.readiness.status === "failed" || lane.readiness.status === "stale"))
|
|
1514
|
+
? "needs attention"
|
|
1515
|
+
: "ready";
|
|
1516
|
+
const providerDetail = providerVisibility.lanes.map((lane) => lane.status === "configured"
|
|
1517
|
+
? `${lane.lane}: ${lane.provider} / ${lane.model}`
|
|
1518
|
+
: `${lane.lane}: choose provider/model`).join(" | ");
|
|
1519
|
+
const runtimeConfig = await (0, runtime_credentials_1.refreshRuntimeCredentialConfig)(agent, { preserveCachedOnFailure: true });
|
|
1520
|
+
const machineRuntime = await (0, runtime_credentials_1.refreshMachineRuntimeCredentialConfig)(agent, currentMachineId(deps), { preserveCachedOnFailure: true });
|
|
1521
|
+
const agentConfig = (0, auth_flow_1.readAgentConfigForAgent)(agent, deps.bundlesRoot).config;
|
|
1522
|
+
const teamsEnabled = agentConfig.senses?.teams?.enabled === true;
|
|
1523
|
+
const blueBubblesEnabled = agentConfig.senses?.bluebubbles?.enabled === true;
|
|
1524
|
+
const perplexityStatus = runtimeConfig.ok
|
|
1525
|
+
? hasRuntimeConfigValue(runtimeConfig.config, "integrations.perplexityApiKey") ? "ready" : "missing"
|
|
1526
|
+
: runtimeConfigReadStatus(runtimeConfig);
|
|
1527
|
+
const embeddingsStatus = runtimeConfig.ok
|
|
1528
|
+
? hasRuntimeConfigValue(runtimeConfig.config, "integrations.openaiEmbeddingsApiKey") ? "ready" : "missing"
|
|
1529
|
+
: runtimeConfigReadStatus(runtimeConfig);
|
|
1530
|
+
const teamsStatus = runtimeConfig.ok
|
|
1531
|
+
? hasRuntimeConfigValue(runtimeConfig.config, "teams.clientId")
|
|
1532
|
+
&& hasRuntimeConfigValue(runtimeConfig.config, "teams.clientSecret")
|
|
1533
|
+
&& hasRuntimeConfigValue(runtimeConfig.config, "teams.tenantId")
|
|
1534
|
+
&& teamsEnabled
|
|
1535
|
+
? "ready"
|
|
1536
|
+
: "missing"
|
|
1537
|
+
: runtimeConfigReadStatus(runtimeConfig);
|
|
1538
|
+
const blueBubblesStatus = machineRuntime.ok
|
|
1539
|
+
? hasRuntimeConfigValue(machineRuntime.config, "bluebubbles.serverUrl")
|
|
1540
|
+
&& hasRuntimeConfigValue(machineRuntime.config, "bluebubbles.password")
|
|
1541
|
+
&& blueBubblesEnabled
|
|
1542
|
+
? "attached"
|
|
1543
|
+
: "not attached"
|
|
1544
|
+
: machineRuntimeReadStatus(machineRuntime);
|
|
1405
1545
|
return [
|
|
1406
|
-
|
|
1407
|
-
"
|
|
1546
|
+
`${agent} // connect bay`,
|
|
1547
|
+
"Bring one capability online at a time.",
|
|
1408
1548
|
"",
|
|
1409
|
-
|
|
1410
|
-
|
|
1549
|
+
` 1. [${providerStatus}] Providers`,
|
|
1550
|
+
` ${providerDetail}`,
|
|
1411
1551
|
"",
|
|
1412
|
-
|
|
1413
|
-
"
|
|
1552
|
+
` 2. [${perplexityStatus}] Perplexity search`,
|
|
1553
|
+
" Portable. Web search via Perplexity.",
|
|
1414
1554
|
"",
|
|
1415
|
-
|
|
1416
|
-
|
|
1555
|
+
` 3. [${embeddingsStatus}] Memory embeddings`,
|
|
1556
|
+
" Portable. Memory retrieval and note search.",
|
|
1417
1557
|
"",
|
|
1418
|
-
|
|
1558
|
+
` 4. [${teamsStatus}] Teams`,
|
|
1559
|
+
" Portable. Microsoft Teams sense credentials.",
|
|
1419
1560
|
"",
|
|
1420
|
-
|
|
1561
|
+
` 5. [${blueBubblesStatus}] BlueBubbles iMessage`,
|
|
1562
|
+
" This machine only. Local Mac Messages bridge.",
|
|
1563
|
+
"",
|
|
1564
|
+
" 6. Cancel",
|
|
1565
|
+
"",
|
|
1566
|
+
"Choose [1-6] or type a name: ",
|
|
1421
1567
|
].join("\n");
|
|
1422
1568
|
}
|
|
1423
1569
|
async function executeConnectPerplexity(agent, deps) {
|
|
@@ -1437,30 +1583,24 @@ async function executeConnectPerplexity(agent, deps) {
|
|
|
1437
1583
|
let stored;
|
|
1438
1584
|
let reload;
|
|
1439
1585
|
try {
|
|
1440
|
-
progress
|
|
1441
|
-
stored = await storeRuntimeConfigKey({
|
|
1586
|
+
stored = await runCommandProgressPhase(progress, "saving Perplexity search", () => storeRuntimeConfigKey({
|
|
1442
1587
|
agent,
|
|
1443
1588
|
key: "integrations.perplexityApiKey",
|
|
1444
1589
|
value: key,
|
|
1445
1590
|
scope: "agent",
|
|
1446
1591
|
deps,
|
|
1447
1592
|
onProgress: (message) => progress.updateDetail(message),
|
|
1448
|
-
});
|
|
1449
|
-
progress
|
|
1450
|
-
progress.startPhase(`reloading ${agent}`);
|
|
1451
|
-
reload = await reloadRunningAgentAfterCredentialChange(agent, deps);
|
|
1452
|
-
progress.completePhase(`reloading ${agent}`, reload);
|
|
1453
|
-
progress.end();
|
|
1593
|
+
}), () => "secret stored");
|
|
1594
|
+
reload = await runCommandProgressPhase(progress, `applying change to running ${agent}`, () => applyRuntimeChangeToRunningAgent(agent, deps, (message) => progress.updateDetail(message)), (result) => result);
|
|
1454
1595
|
}
|
|
1455
|
-
|
|
1596
|
+
finally {
|
|
1456
1597
|
progress.end();
|
|
1457
|
-
throw error;
|
|
1458
1598
|
}
|
|
1459
1599
|
const message = [
|
|
1460
1600
|
`Perplexity connected for ${agent}`,
|
|
1461
1601
|
"capability: Perplexity search",
|
|
1462
1602
|
`stored: ${stored.itemPath}`,
|
|
1463
|
-
`
|
|
1603
|
+
`running agent: ${reload}`,
|
|
1464
1604
|
"secret was not printed",
|
|
1465
1605
|
"",
|
|
1466
1606
|
"Next: ask the agent to search.",
|
|
@@ -1468,6 +1608,102 @@ async function executeConnectPerplexity(agent, deps) {
|
|
|
1468
1608
|
deps.writeStdout(message);
|
|
1469
1609
|
return message;
|
|
1470
1610
|
}
|
|
1611
|
+
async function executeConnectEmbeddings(agent, deps) {
|
|
1612
|
+
if (agent === "SerpentGuide") {
|
|
1613
|
+
throw new Error("SerpentGuide has no persistent runtime credentials. Connect embeddings on the hatchling agent instead.");
|
|
1614
|
+
}
|
|
1615
|
+
const promptSecret = requirePromptSecret(deps, "OpenAI embeddings API key entry");
|
|
1616
|
+
deps.writeStdout([
|
|
1617
|
+
`Connect embeddings for ${agent}`,
|
|
1618
|
+
"The API key stays hidden while you type.",
|
|
1619
|
+
`Ouro stores it in ${agent}'s vault runtime/config item.`,
|
|
1620
|
+
].join("\n"));
|
|
1621
|
+
const key = (await promptSecret("OpenAI embeddings API key: ")).trim();
|
|
1622
|
+
if (!key)
|
|
1623
|
+
throw new Error("OpenAI embeddings API key cannot be blank");
|
|
1624
|
+
const progress = createHumanCommandProgress(deps, "connect embeddings");
|
|
1625
|
+
let stored;
|
|
1626
|
+
let reload;
|
|
1627
|
+
try {
|
|
1628
|
+
stored = await runCommandProgressPhase(progress, "saving memory embeddings", () => storeRuntimeConfigKey({
|
|
1629
|
+
agent,
|
|
1630
|
+
key: "integrations.openaiEmbeddingsApiKey",
|
|
1631
|
+
value: key,
|
|
1632
|
+
scope: "agent",
|
|
1633
|
+
deps,
|
|
1634
|
+
onProgress: (message) => progress.updateDetail(message),
|
|
1635
|
+
}), () => "secret stored");
|
|
1636
|
+
reload = await runCommandProgressPhase(progress, `applying change to running ${agent}`, () => applyRuntimeChangeToRunningAgent(agent, deps, (message) => progress.updateDetail(message)), (result) => result);
|
|
1637
|
+
}
|
|
1638
|
+
finally {
|
|
1639
|
+
progress.end();
|
|
1640
|
+
}
|
|
1641
|
+
const message = [
|
|
1642
|
+
`Embeddings connected for ${agent}`,
|
|
1643
|
+
"capability: memory embeddings",
|
|
1644
|
+
`stored: ${stored.itemPath}`,
|
|
1645
|
+
`running agent: ${reload}`,
|
|
1646
|
+
"secret was not printed",
|
|
1647
|
+
"",
|
|
1648
|
+
"Next: ask the agent to search notes or memory.",
|
|
1649
|
+
].join("\n");
|
|
1650
|
+
deps.writeStdout(message);
|
|
1651
|
+
return message;
|
|
1652
|
+
}
|
|
1653
|
+
async function executeConnectTeams(agent, deps) {
|
|
1654
|
+
if (agent === "SerpentGuide") {
|
|
1655
|
+
throw new Error("SerpentGuide has no persistent runtime credentials. Connect Teams on the hatchling agent instead.");
|
|
1656
|
+
}
|
|
1657
|
+
const promptInput = requirePromptInput(deps, "Teams setup");
|
|
1658
|
+
const promptSecret = requirePromptSecret(deps, "Teams client secret entry");
|
|
1659
|
+
deps.writeStdout([
|
|
1660
|
+
`Connect Teams for ${agent}`,
|
|
1661
|
+
"This connects the Teams sense.",
|
|
1662
|
+
"The client secret stays hidden while you type.",
|
|
1663
|
+
].join("\n"));
|
|
1664
|
+
const clientId = (await promptInput("Teams client ID: ")).trim();
|
|
1665
|
+
if (!clientId)
|
|
1666
|
+
throw new Error("Teams client ID cannot be blank");
|
|
1667
|
+
const clientSecret = (await promptSecret("Teams client secret: ")).trim();
|
|
1668
|
+
if (!clientSecret)
|
|
1669
|
+
throw new Error("Teams client secret cannot be blank");
|
|
1670
|
+
const tenantId = (await promptInput("Teams tenant ID: ")).trim();
|
|
1671
|
+
if (!tenantId)
|
|
1672
|
+
throw new Error("Teams tenant ID cannot be blank");
|
|
1673
|
+
const managedIdentityClientId = (await promptInput("Teams managed identity client ID [blank to skip]: ")).trim();
|
|
1674
|
+
const progress = createHumanCommandProgress(deps, "connect teams");
|
|
1675
|
+
let stored;
|
|
1676
|
+
try {
|
|
1677
|
+
stored = await runCommandProgressPhase(progress, "saving Teams setup", () => storeRuntimeConfigKeys({
|
|
1678
|
+
agent,
|
|
1679
|
+
entries: [
|
|
1680
|
+
{ key: "teams.clientId", value: clientId },
|
|
1681
|
+
{ key: "teams.clientSecret", value: clientSecret },
|
|
1682
|
+
{ key: "teams.tenantId", value: tenantId },
|
|
1683
|
+
...(managedIdentityClientId ? [{ key: "teams.managedIdentityClientId", value: managedIdentityClientId }] : []),
|
|
1684
|
+
],
|
|
1685
|
+
scope: "agent",
|
|
1686
|
+
deps,
|
|
1687
|
+
onProgress: (message) => progress.updateDetail(message),
|
|
1688
|
+
}), () => "secret stored");
|
|
1689
|
+
progress.updateDetail("enabling Teams in agent.json");
|
|
1690
|
+
enableAgentSense(agent, "teams", deps);
|
|
1691
|
+
}
|
|
1692
|
+
finally {
|
|
1693
|
+
progress.end();
|
|
1694
|
+
}
|
|
1695
|
+
const message = appendBundleSyncSummary([
|
|
1696
|
+
`Teams connected for ${agent}`,
|
|
1697
|
+
"capability: Teams sense",
|
|
1698
|
+
`stored: ${stored.itemPath}`,
|
|
1699
|
+
"agent.json: senses.teams.enabled = true",
|
|
1700
|
+
"secret was not printed",
|
|
1701
|
+
"",
|
|
1702
|
+
"Next: run `ouro up` so the daemon picks up the Teams sense change.",
|
|
1703
|
+
].join("\n"), agent, deps);
|
|
1704
|
+
deps.writeStdout(message);
|
|
1705
|
+
return message;
|
|
1706
|
+
}
|
|
1471
1707
|
async function executeConnectBlueBubbles(agent, deps) {
|
|
1472
1708
|
if (agent === "SerpentGuide") {
|
|
1473
1709
|
throw new Error("SerpentGuide has no persistent runtime credentials. Attach BlueBubbles on the hatchling agent instead.");
|
|
@@ -1535,39 +1771,86 @@ async function executeConnectBlueBubbles(agent, deps) {
|
|
|
1535
1771
|
deps.writeStdout(message);
|
|
1536
1772
|
return message;
|
|
1537
1773
|
}
|
|
1774
|
+
async function executeConnectProviders(agent, deps) {
|
|
1775
|
+
const promptInput = deps.promptInput;
|
|
1776
|
+
if (!promptInput) {
|
|
1777
|
+
const message = [
|
|
1778
|
+
`Provider auth for ${agent}`,
|
|
1779
|
+
"Run one of:",
|
|
1780
|
+
...CONNECT_PROVIDER_CHOICES.map((provider) => ` ouro auth --agent ${agent} --provider ${provider}`),
|
|
1781
|
+
].join("\n");
|
|
1782
|
+
deps.writeStdout(message);
|
|
1783
|
+
return message;
|
|
1784
|
+
}
|
|
1785
|
+
const choice = (await promptInput([
|
|
1786
|
+
`Which provider should ${agent} use credentials for?`,
|
|
1787
|
+
...CONNECT_PROVIDER_CHOICES.map((provider) => ` - ${provider}`),
|
|
1788
|
+
"",
|
|
1789
|
+
"Provider: ",
|
|
1790
|
+
].join("\n"))).trim().toLowerCase();
|
|
1791
|
+
if (!(0, cli_parse_2.isAgentProvider)(choice)) {
|
|
1792
|
+
throw new Error(`Unknown provider '${choice}'. Use ${CONNECT_PROVIDER_CHOICES.join("|")}.`);
|
|
1793
|
+
}
|
|
1794
|
+
return executeAuthRun({ kind: "auth.run", agent, provider: choice }, deps);
|
|
1795
|
+
}
|
|
1796
|
+
function machineRuntimeReadStatus(runtime) {
|
|
1797
|
+
if (runtime.reason === "missing")
|
|
1798
|
+
return "not attached";
|
|
1799
|
+
if (/locked/i.test(runtime.error))
|
|
1800
|
+
return "locked";
|
|
1801
|
+
return "needs attention";
|
|
1802
|
+
}
|
|
1803
|
+
function connectMenuTarget(answer) {
|
|
1804
|
+
const normalized = answer.trim().toLowerCase();
|
|
1805
|
+
if (normalized === "1" || normalized === "providers" || normalized === "provider" || normalized === "auth")
|
|
1806
|
+
return "providers";
|
|
1807
|
+
if (normalized === "2" || normalized === "perplexity" || normalized === "perplexity-search" || normalized === "search")
|
|
1808
|
+
return "perplexity";
|
|
1809
|
+
if (normalized === "3" || normalized === "embeddings" || normalized === "embedding" || normalized === "memory" || normalized === "note-search" || normalized === "notes")
|
|
1810
|
+
return "embeddings";
|
|
1811
|
+
if (normalized === "4" || normalized === "teams" || normalized === "msteams" || normalized === "microsoft-teams")
|
|
1812
|
+
return "teams";
|
|
1813
|
+
if (normalized === "5" || normalized === "bluebubbles" || normalized === "imessage" || normalized === "messages")
|
|
1814
|
+
return "bluebubbles";
|
|
1815
|
+
return "cancel";
|
|
1816
|
+
}
|
|
1538
1817
|
async function executeConnect(command, deps) {
|
|
1818
|
+
if (command.target === "providers")
|
|
1819
|
+
return executeConnectProviders(command.agent, deps);
|
|
1539
1820
|
if (command.target === "perplexity")
|
|
1540
1821
|
return executeConnectPerplexity(command.agent, deps);
|
|
1822
|
+
if (command.target === "embeddings")
|
|
1823
|
+
return executeConnectEmbeddings(command.agent, deps);
|
|
1824
|
+
if (command.target === "teams")
|
|
1825
|
+
return executeConnectTeams(command.agent, deps);
|
|
1541
1826
|
if (command.target === "bluebubbles")
|
|
1542
1827
|
return executeConnectBlueBubbles(command.agent, deps);
|
|
1828
|
+
const menu = await buildConnectMenu(command.agent, deps);
|
|
1543
1829
|
const promptInput = deps.promptInput;
|
|
1544
1830
|
if (!promptInput) {
|
|
1545
1831
|
const message = [
|
|
1546
|
-
|
|
1832
|
+
menu.replace(/\nChoose \[1-6\] or type a name: $/, ""),
|
|
1547
1833
|
"",
|
|
1834
|
+
`Run: ouro connect providers --agent ${command.agent}`,
|
|
1548
1835
|
`Run: ouro connect perplexity --agent ${command.agent}`,
|
|
1549
|
-
`
|
|
1836
|
+
`Run: ouro connect embeddings --agent ${command.agent}`,
|
|
1837
|
+
`Run: ouro connect teams --agent ${command.agent}`,
|
|
1838
|
+
`Run: ouro connect bluebubbles --agent ${command.agent}`,
|
|
1550
1839
|
].join("\n");
|
|
1551
1840
|
deps.writeStdout(message);
|
|
1552
1841
|
return message;
|
|
1553
1842
|
}
|
|
1554
|
-
const answer = (await promptInput(
|
|
1555
|
-
if (answer === "
|
|
1843
|
+
const answer = connectMenuTarget(await promptInput(menu));
|
|
1844
|
+
if (answer === "providers")
|
|
1845
|
+
return executeConnectProviders(command.agent, deps);
|
|
1846
|
+
if (answer === "perplexity")
|
|
1556
1847
|
return executeConnectPerplexity(command.agent, deps);
|
|
1557
|
-
|
|
1558
|
-
|
|
1848
|
+
if (answer === "embeddings")
|
|
1849
|
+
return executeConnectEmbeddings(command.agent, deps);
|
|
1850
|
+
if (answer === "teams")
|
|
1851
|
+
return executeConnectTeams(command.agent, deps);
|
|
1852
|
+
if (answer === "bluebubbles")
|
|
1559
1853
|
return executeConnectBlueBubbles(command.agent, deps);
|
|
1560
|
-
}
|
|
1561
|
-
if (answer === "3" || answer === "provider" || answer === "providers" || answer === "auth") {
|
|
1562
|
-
const message = [
|
|
1563
|
-
"Provider auth is its own flow:",
|
|
1564
|
-
` ouro auth --agent ${command.agent} --provider <provider>`,
|
|
1565
|
-
"",
|
|
1566
|
-
"Use `ouro connect` for integrations and local senses after provider auth is ready.",
|
|
1567
|
-
].join("\n");
|
|
1568
|
-
deps.writeStdout(message);
|
|
1569
|
-
return message;
|
|
1570
|
-
}
|
|
1571
1854
|
const message = "connect cancelled.";
|
|
1572
1855
|
deps.writeStdout(message);
|
|
1573
1856
|
return message;
|
|
@@ -1896,13 +2179,9 @@ async function executeProviderRefresh(command, deps) {
|
|
|
1896
2179
|
deps.writeStdout(message);
|
|
1897
2180
|
return message;
|
|
1898
2181
|
}
|
|
1899
|
-
progress
|
|
1900
|
-
const reload = await reloadRunningAgentAfterCredentialChange(command.agent, deps);
|
|
1901
|
-
progress.completePhase(`reloading ${command.agent}`, reload);
|
|
2182
|
+
const reload = await runCommandProgressPhase(progress, `applying change to running ${command.agent}`, () => applyRuntimeChangeToRunningAgent(command.agent, deps, (message) => progress.updateDetail(message)), (result) => result);
|
|
1902
2183
|
progress.end();
|
|
1903
|
-
lines.push(
|
|
1904
|
-
? "daemon is not running; the next start will load the refreshed snapshot"
|
|
1905
|
-
: reload);
|
|
2184
|
+
lines.push(`running agent: ${reload}`);
|
|
1906
2185
|
const message = lines.join("\n");
|
|
1907
2186
|
deps.writeStdout(message);
|
|
1908
2187
|
return message;
|
|
@@ -165,10 +165,10 @@ exports.COMMAND_REGISTRY = {
|
|
|
165
165
|
},
|
|
166
166
|
connect: {
|
|
167
167
|
category: "Auth",
|
|
168
|
-
description: "Connect integrations and local senses
|
|
169
|
-
usage: "ouro connect [perplexity|bluebubbles] --agent <name>",
|
|
170
|
-
example: "ouro connect
|
|
171
|
-
subcommands: ["perplexity", "bluebubbles"],
|
|
168
|
+
description: "Connect providers, portable integrations, and local senses from one guided bay",
|
|
169
|
+
usage: "ouro connect [providers|perplexity|embeddings|teams|bluebubbles] --agent <name>",
|
|
170
|
+
example: "ouro connect --agent ouroboros",
|
|
171
|
+
subcommands: ["providers", "perplexity", "embeddings", "teams", "bluebubbles"],
|
|
172
172
|
},
|
|
173
173
|
use: {
|
|
174
174
|
category: "Auth",
|
|
@@ -277,6 +277,21 @@ const SUBCOMMAND_HELP = {
|
|
|
277
277
|
usage: "ouro connect perplexity --agent <name>",
|
|
278
278
|
example: "ouro connect perplexity --agent ouroboros",
|
|
279
279
|
},
|
|
280
|
+
"connect providers": {
|
|
281
|
+
description: "Open provider auth from the connect bay without remembering the auth command",
|
|
282
|
+
usage: "ouro connect providers --agent <name>",
|
|
283
|
+
example: "ouro connect providers --agent ouroboros",
|
|
284
|
+
},
|
|
285
|
+
"connect embeddings": {
|
|
286
|
+
description: "Connect memory embeddings for this agent",
|
|
287
|
+
usage: "ouro connect embeddings --agent <name>",
|
|
288
|
+
example: "ouro connect embeddings --agent ouroboros",
|
|
289
|
+
},
|
|
290
|
+
"connect teams": {
|
|
291
|
+
description: "Connect Microsoft Teams credentials and enable the Teams sense",
|
|
292
|
+
usage: "ouro connect teams --agent <name>",
|
|
293
|
+
example: "ouro connect teams --agent ouroboros",
|
|
294
|
+
},
|
|
280
295
|
"connect bluebubbles": {
|
|
281
296
|
description: "Attach BlueBubbles iMessage to this machine only",
|
|
282
297
|
usage: "ouro connect bluebubbles --agent <name>",
|
|
@@ -83,7 +83,7 @@ function usage() {
|
|
|
83
83
|
" ouro config model --agent <name> <model-name>",
|
|
84
84
|
" ouro config models --agent <name>",
|
|
85
85
|
" ouro auth --agent <name> [--provider <provider>]",
|
|
86
|
-
" ouro connect [perplexity|bluebubbles] --agent <name>",
|
|
86
|
+
" ouro connect [providers|perplexity|embeddings|teams|bluebubbles] --agent <name>",
|
|
87
87
|
" ouro auth verify --agent <name> [--provider <provider>]",
|
|
88
88
|
" ouro auth switch --agent <name> --provider <provider>",
|
|
89
89
|
" ouro vault create --agent <name> --email <email> [--server <url>] [--store <store>]",
|
|
@@ -588,18 +588,24 @@ function parseVaultConfigCommand(args) {
|
|
|
588
588
|
function normalizeConnectTarget(value) {
|
|
589
589
|
if (!value)
|
|
590
590
|
return undefined;
|
|
591
|
+
if (value === "providers" || value === "provider" || value === "auth")
|
|
592
|
+
return "providers";
|
|
591
593
|
if (value === "perplexity" || value === "perplexity-search")
|
|
592
594
|
return "perplexity";
|
|
595
|
+
if (value === "embeddings" || value === "embedding" || value === "memory" || value === "note-search" || value === "notes")
|
|
596
|
+
return "embeddings";
|
|
597
|
+
if (value === "teams" || value === "msteams" || value === "microsoft-teams")
|
|
598
|
+
return "teams";
|
|
593
599
|
if (value === "bluebubbles" || value === "imessage" || value === "messages")
|
|
594
600
|
return "bluebubbles";
|
|
595
|
-
throw new Error("Usage: ouro connect [perplexity|bluebubbles] --agent <name>");
|
|
601
|
+
throw new Error("Usage: ouro connect [providers|perplexity|embeddings|teams|bluebubbles] --agent <name>");
|
|
596
602
|
}
|
|
597
603
|
function parseConnectCommand(args) {
|
|
598
604
|
const { agent, rest } = extractAgentFlag(args);
|
|
599
605
|
if (!agent)
|
|
600
|
-
throw new Error("Usage: ouro connect --agent <name> [perplexity|bluebubbles]");
|
|
606
|
+
throw new Error("Usage: ouro connect --agent <name> [providers|perplexity|embeddings|teams|bluebubbles]");
|
|
601
607
|
if (rest.length > 1)
|
|
602
|
-
throw new Error("Usage: ouro connect [perplexity|bluebubbles] --agent <name>");
|
|
608
|
+
throw new Error("Usage: ouro connect [providers|perplexity|embeddings|teams|bluebubbles] --agent <name>");
|
|
603
609
|
const target = normalizeConnectTarget(rest[0]);
|
|
604
610
|
return { kind: "connect", agent, ...(target ? { target } : {}) };
|
|
605
611
|
}
|
|
@@ -20,6 +20,7 @@ const RESET = "\x1b[0m";
|
|
|
20
20
|
const BOLD = "\x1b[1m";
|
|
21
21
|
const DIM = "\x1b[2m";
|
|
22
22
|
const GREEN = "\x1b[38;2;46;204;64m";
|
|
23
|
+
const RED = "\x1b[38;2;255;106;106m";
|
|
23
24
|
// ── UpProgress class ──
|
|
24
25
|
class UpProgress {
|
|
25
26
|
write;
|
|
@@ -109,7 +110,7 @@ class UpProgress {
|
|
|
109
110
|
return;
|
|
110
111
|
}
|
|
111
112
|
const elapsedMs = this.now() - this.currentPhase.startedAt;
|
|
112
|
-
this.completed.push({ label, detail });
|
|
113
|
+
this.completed.push({ status: "success", label, detail });
|
|
113
114
|
this.currentPhase = null;
|
|
114
115
|
this.currentDetail = null;
|
|
115
116
|
this.stopAutoRender();
|
|
@@ -137,6 +138,41 @@ class UpProgress {
|
|
|
137
138
|
this.write(` \u2713 ${label}${detailStr}\n`);
|
|
138
139
|
}
|
|
139
140
|
}
|
|
141
|
+
failPhase(label, detail) {
|
|
142
|
+
if (!this.currentPhase) {
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
const elapsedMs = this.now() - this.currentPhase.startedAt;
|
|
146
|
+
this.completed.push({ status: "failure", label, detail });
|
|
147
|
+
this.currentPhase = null;
|
|
148
|
+
this.currentDetail = null;
|
|
149
|
+
this.stopAutoRender();
|
|
150
|
+
if (this.eventScope === "command") {
|
|
151
|
+
(0, runtime_1.emitNervesEvent)({
|
|
152
|
+
level: "warn",
|
|
153
|
+
component: "daemon",
|
|
154
|
+
event: "daemon.cli_progress_phase_failed",
|
|
155
|
+
message: `phase failed: ${label}`,
|
|
156
|
+
meta: { command: this.commandName, phase: label, detail: detail ?? null, elapsedMs },
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
(0, runtime_1.emitNervesEvent)({
|
|
161
|
+
level: "warn",
|
|
162
|
+
component: "daemon",
|
|
163
|
+
event: "daemon.up_phase_failed",
|
|
164
|
+
message: `phase failed: ${label}`,
|
|
165
|
+
meta: { phase: label, detail: detail ?? null, elapsedMs },
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
if (this.isTTY) {
|
|
169
|
+
this.flushRender();
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
const detailStr = detail ? ` \u2014 ${detail}` : "";
|
|
173
|
+
this.write(` \u2717 ${label}${detailStr}\n`);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
140
176
|
/**
|
|
141
177
|
* Build an ANSI string for in-place terminal display. Returns empty
|
|
142
178
|
* string in non-TTY mode (output is written eagerly in completePhase).
|
|
@@ -149,7 +185,12 @@ class UpProgress {
|
|
|
149
185
|
// Completed phases
|
|
150
186
|
for (const phase of this.completed) {
|
|
151
187
|
const detailStr = phase.detail ? ` ${DIM}\u2014 ${phase.detail}${RESET}` : "";
|
|
152
|
-
|
|
188
|
+
if (phase.status === "failure") {
|
|
189
|
+
lines.push(` ${RED}\u2717${RESET} ${phase.label}${detailStr}`);
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
lines.push(` ${GREEN}\u2713${RESET} ${phase.label}${detailStr}`);
|
|
193
|
+
}
|
|
153
194
|
}
|
|
154
195
|
// Current phase with spinner
|
|
155
196
|
if (this.currentPhase) {
|
package/dist/mind/prompt.js
CHANGED
|
@@ -451,8 +451,8 @@ function senseRuntimeGuidance(channel, preReadStatusLines) {
|
|
|
451
451
|
lines.push("- running = enabled and currently active");
|
|
452
452
|
lines.push("- error = enabled but unhealthy");
|
|
453
453
|
lines.push("If asked how to enable another sense, I explain the relevant agent.json senses entry and required agent-vault runtime/config fields instead of guessing.");
|
|
454
|
-
lines.push("teams setup truth:
|
|
455
|
-
lines.push("bluebubbles setup truth: run `ouro connect bluebubbles --agent <agent
|
|
454
|
+
lines.push("teams setup truth: run `ouro connect teams --agent <agent>` from the connect bay; it stores Teams runtime/config fields and enables `senses.teams.enabled`.");
|
|
455
|
+
lines.push("bluebubbles setup truth: run `ouro connect bluebubbles --agent <agent>` from the connect bay; it stores this machine's BlueBubbles URL/password/listener config in the agent vault machine runtime item.");
|
|
456
456
|
if (channel === "cli") {
|
|
457
457
|
lines.push("cli is interactive: it is available when the user opens it, not something `ouro up` daemonizes.");
|
|
458
458
|
}
|