@ouro.bot/cli 0.1.0-alpha.6 → 0.1.0-alpha.61

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.
Files changed (119) hide show
  1. package/AdoptionSpecialist.ouro/agent.json +70 -9
  2. package/AdoptionSpecialist.ouro/psyche/SOUL.md +5 -2
  3. package/AdoptionSpecialist.ouro/psyche/identities/monty.md +2 -2
  4. package/README.md +147 -205
  5. package/assets/ouroboros.png +0 -0
  6. package/changelog.json +334 -0
  7. package/dist/heart/active-work.js +178 -0
  8. package/dist/heart/bridges/manager.js +358 -0
  9. package/dist/heart/bridges/state-machine.js +135 -0
  10. package/dist/heart/bridges/store.js +123 -0
  11. package/dist/heart/config.js +57 -23
  12. package/dist/heart/core.js +236 -90
  13. package/dist/heart/cross-chat-delivery.js +146 -0
  14. package/dist/heart/daemon/agent-discovery.js +81 -0
  15. package/dist/heart/daemon/auth-flow.js +351 -0
  16. package/dist/heart/daemon/daemon-cli.js +1175 -232
  17. package/dist/heart/daemon/daemon-entry.js +55 -6
  18. package/dist/heart/daemon/daemon-runtime-sync.js +212 -0
  19. package/dist/heart/daemon/daemon.js +189 -10
  20. package/dist/heart/daemon/hatch-animation.js +10 -3
  21. package/dist/heart/daemon/hatch-flow.js +4 -82
  22. package/dist/heart/daemon/hooks/bundle-meta.js +92 -0
  23. package/dist/heart/daemon/launchd.js +159 -0
  24. package/dist/heart/daemon/log-tailer.js +4 -3
  25. package/dist/heart/daemon/message-router.js +17 -8
  26. package/dist/heart/daemon/ouro-bot-entry.js +0 -0
  27. package/dist/heart/daemon/ouro-bot-global-installer.js +128 -0
  28. package/dist/heart/daemon/ouro-entry.js +0 -0
  29. package/dist/heart/daemon/ouro-path-installer.js +178 -0
  30. package/dist/heart/daemon/ouro-uti.js +11 -2
  31. package/dist/heart/daemon/process-manager.js +14 -1
  32. package/dist/heart/daemon/run-hooks.js +37 -0
  33. package/dist/heart/daemon/runtime-logging.js +58 -15
  34. package/dist/heart/daemon/runtime-metadata.js +219 -0
  35. package/dist/heart/daemon/runtime-mode.js +67 -0
  36. package/dist/heart/daemon/sense-manager.js +307 -0
  37. package/dist/heart/daemon/skill-management-installer.js +73 -0
  38. package/dist/heart/daemon/socket-client.js +202 -0
  39. package/dist/heart/daemon/specialist-orchestrator.js +53 -84
  40. package/dist/heart/daemon/specialist-prompt.js +64 -5
  41. package/dist/heart/daemon/specialist-tools.js +213 -58
  42. package/dist/heart/daemon/staged-restart.js +114 -0
  43. package/dist/heart/daemon/thoughts.js +379 -0
  44. package/dist/heart/daemon/update-checker.js +111 -0
  45. package/dist/heart/daemon/update-hooks.js +138 -0
  46. package/dist/heart/daemon/wrapper-publish-guard.js +86 -0
  47. package/dist/heart/delegation.js +62 -0
  48. package/dist/heart/identity.js +122 -19
  49. package/dist/heart/kicks.js +1 -19
  50. package/dist/heart/model-capabilities.js +40 -0
  51. package/dist/heart/progress-story.js +42 -0
  52. package/dist/heart/providers/anthropic.js +74 -9
  53. package/dist/heart/providers/azure.js +86 -7
  54. package/dist/heart/providers/minimax.js +4 -0
  55. package/dist/heart/providers/openai-codex.js +12 -3
  56. package/dist/heart/safe-workspace.js +228 -0
  57. package/dist/heart/sense-truth.js +61 -0
  58. package/dist/heart/session-activity.js +169 -0
  59. package/dist/heart/session-recall.js +116 -0
  60. package/dist/heart/streaming.js +100 -22
  61. package/dist/heart/target-resolution.js +123 -0
  62. package/dist/heart/turn-coordinator.js +28 -0
  63. package/dist/mind/associative-recall.js +14 -2
  64. package/dist/mind/bundle-manifest.js +70 -0
  65. package/dist/mind/context.js +27 -11
  66. package/dist/mind/first-impressions.js +16 -2
  67. package/dist/mind/friends/channel.js +35 -0
  68. package/dist/mind/friends/group-context.js +144 -0
  69. package/dist/mind/friends/store-file.js +19 -0
  70. package/dist/mind/friends/trust-explanation.js +74 -0
  71. package/dist/mind/friends/types.js +8 -0
  72. package/dist/mind/memory.js +27 -26
  73. package/dist/mind/pending.js +72 -9
  74. package/dist/mind/phrases.js +1 -0
  75. package/dist/mind/prompt.js +299 -77
  76. package/dist/mind/token-estimate.js +8 -12
  77. package/dist/nerves/cli-logging.js +15 -2
  78. package/dist/nerves/coverage/run-artifacts.js +1 -1
  79. package/dist/repertoire/ado-client.js +4 -2
  80. package/dist/repertoire/coding/feedback.js +134 -0
  81. package/dist/repertoire/coding/index.js +4 -1
  82. package/dist/repertoire/coding/manager.js +62 -4
  83. package/dist/repertoire/coding/spawner.js +3 -3
  84. package/dist/repertoire/coding/tools.js +41 -2
  85. package/dist/repertoire/data/ado-endpoints.json +188 -0
  86. package/dist/repertoire/skills.js +3 -26
  87. package/dist/repertoire/tasks/board.js +12 -0
  88. package/dist/repertoire/tasks/index.js +23 -9
  89. package/dist/repertoire/tasks/transitions.js +1 -2
  90. package/dist/repertoire/tools-base.js +629 -251
  91. package/dist/repertoire/tools-bluebubbles.js +93 -0
  92. package/dist/repertoire/tools-teams.js +58 -25
  93. package/dist/repertoire/tools.js +92 -48
  94. package/dist/senses/bluebubbles-client.js +210 -5
  95. package/dist/senses/bluebubbles-entry.js +2 -0
  96. package/dist/senses/bluebubbles-inbound-log.js +109 -0
  97. package/dist/senses/bluebubbles-media.js +339 -0
  98. package/dist/senses/bluebubbles-model.js +12 -4
  99. package/dist/senses/bluebubbles-mutation-log.js +45 -5
  100. package/dist/senses/bluebubbles-runtime-state.js +109 -0
  101. package/dist/senses/bluebubbles-session-cleanup.js +72 -0
  102. package/dist/senses/bluebubbles.js +890 -45
  103. package/dist/senses/cli-layout.js +87 -0
  104. package/dist/senses/cli.js +345 -144
  105. package/dist/senses/continuity.js +94 -0
  106. package/dist/senses/debug-activity.js +148 -0
  107. package/dist/senses/inner-dialog-worker.js +47 -18
  108. package/dist/senses/inner-dialog.js +330 -84
  109. package/dist/senses/pipeline.js +278 -0
  110. package/dist/senses/teams.js +570 -129
  111. package/dist/senses/trust-gate.js +112 -2
  112. package/package.json +14 -3
  113. package/subagents/README.md +4 -70
  114. package/dist/heart/daemon/specialist-session.js +0 -142
  115. package/dist/heart/daemon/subagent-installer.js +0 -125
  116. package/dist/inner-worker-entry.js +0 -4
  117. package/subagents/work-doer.md +0 -233
  118. package/subagents/work-merger.md +0 -624
  119. package/subagents/work-planner.md +0 -373
@@ -35,11 +35,12 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.ensureDaemonRunning = ensureDaemonRunning;
37
37
  exports.parseOuroCommand = parseOuroCommand;
38
+ exports.discoverExistingCredentials = discoverExistingCredentials;
38
39
  exports.createDefaultOuroCliDeps = createDefaultOuroCliDeps;
39
40
  exports.runOuroCli = runOuroCli;
40
41
  const child_process_1 = require("child_process");
42
+ const crypto_1 = require("crypto");
41
43
  const fs = __importStar(require("fs"));
42
- const net = __importStar(require("net"));
43
44
  const os = __importStar(require("os"));
44
45
  const path = __importStar(require("path"));
45
46
  const identity_1 = require("../identity");
@@ -47,16 +48,201 @@ const runtime_1 = require("../../nerves/runtime");
47
48
  const store_file_1 = require("../../mind/friends/store-file");
48
49
  const types_1 = require("../../mind/friends/types");
49
50
  const ouro_uti_1 = require("./ouro-uti");
50
- const subagent_installer_1 = require("./subagent-installer");
51
+ const ouro_path_installer_1 = require("./ouro-path-installer");
52
+ const skill_management_installer_1 = require("./skill-management-installer");
51
53
  const hatch_flow_1 = require("./hatch-flow");
52
54
  const specialist_orchestrator_1 = require("./specialist-orchestrator");
55
+ const specialist_prompt_1 = require("./specialist-prompt");
56
+ const specialist_tools_1 = require("./specialist-tools");
57
+ const runtime_metadata_1 = require("./runtime-metadata");
58
+ const runtime_mode_1 = require("./runtime-mode");
59
+ const daemon_runtime_sync_1 = require("./daemon-runtime-sync");
60
+ const agent_discovery_1 = require("./agent-discovery");
61
+ const update_hooks_1 = require("./update-hooks");
62
+ const bundle_meta_1 = require("./hooks/bundle-meta");
63
+ const bundle_manifest_1 = require("../../mind/bundle-manifest");
64
+ const tasks_1 = require("../../repertoire/tasks");
65
+ const thoughts_1 = require("./thoughts");
66
+ const ouro_bot_global_installer_1 = require("./ouro-bot-global-installer");
67
+ const launchd_1 = require("./launchd");
68
+ const socket_client_1 = require("./socket-client");
69
+ const session_activity_1 = require("../session-activity");
70
+ const auth_flow_1 = require("./auth-flow");
71
+ function stringField(value) {
72
+ return typeof value === "string" ? value : null;
73
+ }
74
+ function numberField(value) {
75
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
76
+ }
77
+ function booleanField(value) {
78
+ return typeof value === "boolean" ? value : null;
79
+ }
80
+ function parseStatusPayload(data) {
81
+ if (!data || typeof data !== "object" || Array.isArray(data))
82
+ return null;
83
+ const raw = data;
84
+ const overview = raw.overview;
85
+ const senses = raw.senses;
86
+ const workers = raw.workers;
87
+ if (!overview || typeof overview !== "object" || Array.isArray(overview))
88
+ return null;
89
+ if (!Array.isArray(senses) || !Array.isArray(workers))
90
+ return null;
91
+ const parsedOverview = {
92
+ daemon: stringField(overview.daemon) ?? "unknown",
93
+ health: stringField(overview.health) ?? "unknown",
94
+ socketPath: stringField(overview.socketPath) ?? "unknown",
95
+ version: stringField(overview.version) ?? "unknown",
96
+ lastUpdated: stringField(overview.lastUpdated) ?? "unknown",
97
+ repoRoot: stringField(overview.repoRoot) ?? "unknown",
98
+ configFingerprint: stringField(overview.configFingerprint) ?? "unknown",
99
+ workerCount: numberField(overview.workerCount) ?? 0,
100
+ senseCount: numberField(overview.senseCount) ?? 0,
101
+ entryPath: stringField(overview.entryPath) ?? "unknown",
102
+ mode: stringField(overview.mode) ?? "unknown",
103
+ };
104
+ const parsedSenses = senses.map((entry) => {
105
+ if (!entry || typeof entry !== "object" || Array.isArray(entry))
106
+ return null;
107
+ const row = entry;
108
+ const agent = stringField(row.agent);
109
+ const sense = stringField(row.sense);
110
+ const status = stringField(row.status);
111
+ const detail = stringField(row.detail);
112
+ const enabled = booleanField(row.enabled);
113
+ if (!agent || !sense || !status || detail === null || enabled === null)
114
+ return null;
115
+ return {
116
+ agent,
117
+ sense,
118
+ label: stringField(row.label) ?? undefined,
119
+ enabled,
120
+ status,
121
+ detail,
122
+ };
123
+ });
124
+ const parsedWorkers = workers.map((entry) => {
125
+ if (!entry || typeof entry !== "object" || Array.isArray(entry))
126
+ return null;
127
+ const row = entry;
128
+ const agent = stringField(row.agent);
129
+ const worker = stringField(row.worker);
130
+ const status = stringField(row.status);
131
+ const restartCount = numberField(row.restartCount);
132
+ const hasPid = Object.prototype.hasOwnProperty.call(row, "pid");
133
+ const pid = row.pid === null ? null : numberField(row.pid);
134
+ const pidInvalid = !hasPid || (row.pid !== null && pid === null);
135
+ if (!agent || !worker || !status || restartCount === null || pidInvalid)
136
+ return null;
137
+ return {
138
+ agent,
139
+ worker,
140
+ status,
141
+ pid,
142
+ restartCount,
143
+ };
144
+ });
145
+ if (parsedSenses.some((row) => row === null) || parsedWorkers.some((row) => row === null))
146
+ return null;
147
+ return {
148
+ overview: parsedOverview,
149
+ senses: parsedSenses,
150
+ workers: parsedWorkers,
151
+ };
152
+ }
153
+ function humanizeSenseName(sense, label) {
154
+ if (label)
155
+ return label;
156
+ if (sense === "cli")
157
+ return "CLI";
158
+ if (sense === "bluebubbles")
159
+ return "BlueBubbles";
160
+ if (sense === "teams")
161
+ return "Teams";
162
+ return sense;
163
+ }
164
+ function formatTable(headers, rows) {
165
+ const widths = headers.map((header, index) => Math.max(header.length, ...rows.map((row) => row[index].length)));
166
+ const renderRow = (row) => `| ${row.map((cell, index) => cell.padEnd(widths[index])).join(" | ")} |`;
167
+ const divider = `|-${widths.map((width) => "-".repeat(width)).join("-|-")}-|`;
168
+ return [
169
+ renderRow(headers),
170
+ divider,
171
+ ...rows.map(renderRow),
172
+ ].join("\n");
173
+ }
174
+ function formatDaemonStatusOutput(response, fallback) {
175
+ const payload = parseStatusPayload(response.data);
176
+ if (!payload)
177
+ return fallback;
178
+ const overviewRows = [
179
+ ["Daemon", payload.overview.daemon],
180
+ ["Socket", payload.overview.socketPath],
181
+ ["Version", payload.overview.version],
182
+ ["Last Updated", payload.overview.lastUpdated],
183
+ ["Entry Path", payload.overview.entryPath],
184
+ ["Mode", payload.overview.mode],
185
+ ["Workers", String(payload.overview.workerCount)],
186
+ ["Senses", String(payload.overview.senseCount)],
187
+ ["Health", payload.overview.health],
188
+ ];
189
+ const senseRows = payload.senses.map((row) => [
190
+ row.agent,
191
+ humanizeSenseName(row.sense, row.label),
192
+ row.enabled ? "ON" : "OFF",
193
+ row.status,
194
+ row.detail,
195
+ ]);
196
+ const workerRows = payload.workers.map((row) => [
197
+ row.agent,
198
+ row.worker,
199
+ row.status,
200
+ row.pid === null ? "n/a" : String(row.pid),
201
+ String(row.restartCount),
202
+ ]);
203
+ return [
204
+ "Overview",
205
+ formatTable(["Item", "Value"], overviewRows),
206
+ "",
207
+ "Senses",
208
+ formatTable(["Agent", "Sense", "Enabled", "State", "Detail"], senseRows),
209
+ "",
210
+ "Workers",
211
+ formatTable(["Agent", "Worker", "State", "PID", "Restarts"], workerRows),
212
+ ].join("\n");
213
+ }
53
214
  async function ensureDaemonRunning(deps) {
54
215
  const alive = await deps.checkSocketAlive(deps.socketPath);
55
216
  if (alive) {
56
- return {
57
- alreadyRunning: true,
58
- message: `daemon already running (${deps.socketPath})`,
217
+ const localRuntime = (0, runtime_metadata_1.getRuntimeMetadata)();
218
+ let runningRuntimePromise = null;
219
+ const fetchRunningRuntimeMetadata = async () => {
220
+ runningRuntimePromise ??= (async () => {
221
+ const status = await deps.sendCommand(deps.socketPath, { kind: "daemon.status" });
222
+ const payload = parseStatusPayload(status.data);
223
+ return {
224
+ version: payload?.overview.version ?? "unknown",
225
+ lastUpdated: payload?.overview.lastUpdated ?? "unknown",
226
+ repoRoot: payload?.overview.repoRoot ?? "unknown",
227
+ configFingerprint: payload?.overview.configFingerprint ?? "unknown",
228
+ };
229
+ })();
230
+ return runningRuntimePromise;
59
231
  };
232
+ return (0, daemon_runtime_sync_1.ensureCurrentDaemonRuntime)({
233
+ socketPath: deps.socketPath,
234
+ localVersion: localRuntime.version,
235
+ localLastUpdated: localRuntime.lastUpdated,
236
+ localRepoRoot: localRuntime.repoRoot,
237
+ localConfigFingerprint: localRuntime.configFingerprint,
238
+ fetchRunningVersion: async () => (await fetchRunningRuntimeMetadata()).version,
239
+ fetchRunningRuntimeMetadata,
240
+ stopDaemon: async () => {
241
+ await deps.sendCommand(deps.socketPath, { kind: "daemon.stop" });
242
+ },
243
+ cleanupStaleSocket: deps.cleanupStaleSocket,
244
+ startDaemonProcess: deps.startDaemonProcess,
245
+ });
60
246
  }
61
247
  deps.cleanupStaleSocket(deps.socketPath);
62
248
  const started = await deps.startDaemonProcess(deps.socketPath);
@@ -65,17 +251,86 @@ async function ensureDaemonRunning(deps) {
65
251
  message: `daemon started (pid ${started.pid ?? "unknown"})`,
66
252
  };
67
253
  }
254
+ /**
255
+ * Extract `--agent <name>` from an args array, returning the agent name and
256
+ * the remaining args with the flag pair removed.
257
+ */
258
+ function extractAgentFlag(args) {
259
+ const idx = args.indexOf("--agent");
260
+ if (idx === -1 || idx + 1 >= args.length)
261
+ return { rest: args };
262
+ const agent = args[idx + 1];
263
+ const rest = [...args.slice(0, idx), ...args.slice(idx + 2)];
264
+ return { agent, rest };
265
+ }
68
266
  function usage() {
69
267
  return [
70
268
  "Usage:",
71
269
  " ouro [up]",
72
- " ouro stop|status|logs|hatch",
270
+ " ouro stop|down|status|logs|hatch",
271
+ " ouro -v|--version",
272
+ " ouro auth --agent <name> [--provider <provider>]",
73
273
  " ouro chat <agent>",
74
274
  " ouro msg --to <agent> [--session <id>] [--task <ref>] <message>",
75
275
  " ouro poke <agent> --task <task-id>",
76
276
  " ouro link <agent> --friend <id> --provider <provider> --external-id <external-id>",
277
+ " ouro task board [<status>] [--agent <name>]",
278
+ " ouro task create <title> [--type <type>] [--agent <name>]",
279
+ " ouro task update <id> <status> [--agent <name>]",
280
+ " ouro task show <id> [--agent <name>]",
281
+ " ouro task actionable|deps|sessions [--agent <name>]",
282
+ " ouro reminder create <title> --body <body> [--at <iso>] [--cadence <interval>] [--category <category>] [--agent <name>]",
283
+ " ouro friend list [--agent <name>]",
284
+ " ouro friend show <id> [--agent <name>]",
285
+ " ouro friend create --name <name> [--trust <level>] [--agent <name>]",
286
+ " ouro thoughts [--last <n>] [--json] [--follow] [--agent <name>]",
287
+ " ouro friend link <agent> --friend <id> --provider <p> --external-id <eid>",
288
+ " ouro friend unlink <agent> --friend <id> --provider <p> --external-id <eid>",
289
+ " ouro whoami [--agent <name>]",
290
+ " ouro session list [--agent <name>]",
77
291
  ].join("\n");
78
292
  }
293
+ function formatVersionOutput() {
294
+ return (0, runtime_metadata_1.getRuntimeMetadata)().version;
295
+ }
296
+ function buildStoppedStatusPayload(socketPath) {
297
+ const metadata = (0, runtime_metadata_1.getRuntimeMetadata)();
298
+ const repoRoot = (0, identity_1.getRepoRoot)();
299
+ return {
300
+ overview: {
301
+ daemon: "stopped",
302
+ health: "warn",
303
+ socketPath,
304
+ version: metadata.version,
305
+ lastUpdated: metadata.lastUpdated,
306
+ repoRoot: metadata.repoRoot,
307
+ configFingerprint: metadata.configFingerprint,
308
+ workerCount: 0,
309
+ senseCount: 0,
310
+ entryPath: path.join(repoRoot, "dist", "heart", "daemon", "daemon-entry.js"),
311
+ mode: (0, runtime_mode_1.detectRuntimeMode)(repoRoot),
312
+ },
313
+ senses: [],
314
+ workers: [],
315
+ };
316
+ }
317
+ function daemonUnavailableStatusOutput(socketPath) {
318
+ return [
319
+ formatDaemonStatusOutput({
320
+ ok: true,
321
+ summary: "daemon not running",
322
+ data: buildStoppedStatusPayload(socketPath),
323
+ }, "daemon not running"),
324
+ "",
325
+ "daemon not running; run `ouro up`",
326
+ ].join("\n");
327
+ }
328
+ function isDaemonUnavailableError(error) {
329
+ const code = typeof error === "object" && error !== null && "code" in error
330
+ ? String(error.code ?? "")
331
+ : "";
332
+ return code === "ENOENT" || code === "ECONNREFUSED";
333
+ }
79
334
  function parseMessageCommand(args) {
80
335
  let to;
81
336
  let sessionId;
@@ -127,7 +382,7 @@ function parsePokeCommand(args) {
127
382
  throw new Error(`Usage\n${usage()}`);
128
383
  return { kind: "task.poke", agent, taskId };
129
384
  }
130
- function parseLinkCommand(args) {
385
+ function parseLinkCommand(args, kind = "friend.link") {
131
386
  const agent = args[0];
132
387
  if (!agent)
133
388
  throw new Error(`Usage\n${usage()}`);
@@ -159,7 +414,7 @@ function parseLinkCommand(args) {
159
414
  throw new Error(`Unknown identity provider '${providerRaw}'. Use aad|local|teams-conversation.`);
160
415
  }
161
416
  return {
162
- kind: "friend.link",
417
+ kind,
163
418
  agent,
164
419
  friendId,
165
420
  provider: providerRaw,
@@ -236,13 +491,197 @@ function parseHatchCommand(args) {
236
491
  migrationPath,
237
492
  };
238
493
  }
494
+ function parseTaskCommand(args) {
495
+ const { agent, rest: cleaned } = extractAgentFlag(args);
496
+ const [sub, ...rest] = cleaned;
497
+ if (!sub)
498
+ throw new Error(`Usage\n${usage()}`);
499
+ if (sub === "board") {
500
+ const status = rest[0];
501
+ return status
502
+ ? { kind: "task.board", status, ...(agent ? { agent } : {}) }
503
+ : { kind: "task.board", ...(agent ? { agent } : {}) };
504
+ }
505
+ if (sub === "create") {
506
+ const title = rest[0];
507
+ if (!title)
508
+ throw new Error(`Usage\n${usage()}`);
509
+ let type;
510
+ for (let i = 1; i < rest.length; i++) {
511
+ if (rest[i] === "--type" && rest[i + 1]) {
512
+ type = rest[i + 1];
513
+ i += 1;
514
+ }
515
+ }
516
+ return type
517
+ ? { kind: "task.create", title, type, ...(agent ? { agent } : {}) }
518
+ : { kind: "task.create", title, ...(agent ? { agent } : {}) };
519
+ }
520
+ if (sub === "update") {
521
+ const id = rest[0];
522
+ const status = rest[1];
523
+ if (!id || !status)
524
+ throw new Error(`Usage\n${usage()}`);
525
+ return { kind: "task.update", id, status, ...(agent ? { agent } : {}) };
526
+ }
527
+ if (sub === "show") {
528
+ const id = rest[0];
529
+ if (!id)
530
+ throw new Error(`Usage\n${usage()}`);
531
+ return { kind: "task.show", id, ...(agent ? { agent } : {}) };
532
+ }
533
+ if (sub === "actionable")
534
+ return { kind: "task.actionable", ...(agent ? { agent } : {}) };
535
+ if (sub === "deps")
536
+ return { kind: "task.deps", ...(agent ? { agent } : {}) };
537
+ if (sub === "sessions")
538
+ return { kind: "task.sessions", ...(agent ? { agent } : {}) };
539
+ throw new Error(`Usage\n${usage()}`);
540
+ }
541
+ function parseAuthCommand(args) {
542
+ const { agent, rest } = extractAgentFlag(args);
543
+ let provider;
544
+ for (let i = 0; i < rest.length; i += 1) {
545
+ if (rest[i] === "--provider") {
546
+ const value = rest[i + 1];
547
+ if (!isAgentProvider(value))
548
+ throw new Error(`Usage\n${usage()}`);
549
+ provider = value;
550
+ i += 1;
551
+ continue;
552
+ }
553
+ }
554
+ if (!agent)
555
+ throw new Error(`Usage\n${usage()}`);
556
+ return provider ? { kind: "auth.run", agent, provider } : { kind: "auth.run", agent };
557
+ }
558
+ function parseReminderCommand(args) {
559
+ const { agent, rest: cleaned } = extractAgentFlag(args);
560
+ const [sub, ...rest] = cleaned;
561
+ if (!sub)
562
+ throw new Error(`Usage\n${usage()}`);
563
+ if (sub === "create") {
564
+ const title = rest[0];
565
+ if (!title)
566
+ throw new Error(`Usage\n${usage()}`);
567
+ let body;
568
+ let scheduledAt;
569
+ let cadence;
570
+ let category;
571
+ let requester;
572
+ for (let i = 1; i < rest.length; i++) {
573
+ if (rest[i] === "--body" && rest[i + 1]) {
574
+ body = rest[i + 1];
575
+ i += 1;
576
+ }
577
+ else if (rest[i] === "--at" && rest[i + 1]) {
578
+ scheduledAt = rest[i + 1];
579
+ i += 1;
580
+ }
581
+ else if (rest[i] === "--cadence" && rest[i + 1]) {
582
+ cadence = rest[i + 1];
583
+ i += 1;
584
+ }
585
+ else if (rest[i] === "--category" && rest[i + 1]) {
586
+ category = rest[i + 1];
587
+ i += 1;
588
+ }
589
+ else if (rest[i] === "--requester" && rest[i + 1]) {
590
+ requester = rest[i + 1];
591
+ i += 1;
592
+ }
593
+ }
594
+ if (!body)
595
+ throw new Error(`Usage\n${usage()}`);
596
+ if (!scheduledAt && !cadence)
597
+ throw new Error(`Usage\n${usage()}`);
598
+ return {
599
+ kind: "reminder.create",
600
+ title,
601
+ body,
602
+ ...(scheduledAt ? { scheduledAt } : {}),
603
+ ...(cadence ? { cadence } : {}),
604
+ ...(category ? { category } : {}),
605
+ ...(requester ? { requester } : {}),
606
+ ...(agent ? { agent } : {}),
607
+ };
608
+ }
609
+ throw new Error(`Usage\n${usage()}`);
610
+ }
611
+ function parseSessionCommand(args) {
612
+ const { agent, rest: cleaned } = extractAgentFlag(args);
613
+ const [sub] = cleaned;
614
+ if (!sub)
615
+ throw new Error(`Usage\n${usage()}`);
616
+ if (sub === "list")
617
+ return { kind: "session.list", ...(agent ? { agent } : {}) };
618
+ throw new Error(`Usage\n${usage()}`);
619
+ }
620
+ function parseThoughtsCommand(args) {
621
+ const { agent, rest: cleaned } = extractAgentFlag(args);
622
+ let last;
623
+ let json = false;
624
+ let follow = false;
625
+ for (let i = 0; i < cleaned.length; i++) {
626
+ if (cleaned[i] === "--last" && i + 1 < cleaned.length) {
627
+ last = Number.parseInt(cleaned[i + 1], 10);
628
+ i++;
629
+ }
630
+ if (cleaned[i] === "--json")
631
+ json = true;
632
+ if (cleaned[i] === "--follow" || cleaned[i] === "-f")
633
+ follow = true;
634
+ }
635
+ return { kind: "thoughts", ...(agent ? { agent } : {}), ...(last ? { last } : {}), ...(json ? { json } : {}), ...(follow ? { follow } : {}) };
636
+ }
637
+ function parseFriendCommand(args) {
638
+ const { agent, rest: cleaned } = extractAgentFlag(args);
639
+ const [sub, ...rest] = cleaned;
640
+ if (!sub)
641
+ throw new Error(`Usage\n${usage()}`);
642
+ if (sub === "list")
643
+ return { kind: "friend.list", ...(agent ? { agent } : {}) };
644
+ if (sub === "show") {
645
+ const friendId = rest[0];
646
+ if (!friendId)
647
+ throw new Error(`Usage\n${usage()}`);
648
+ return { kind: "friend.show", friendId, ...(agent ? { agent } : {}) };
649
+ }
650
+ if (sub === "create") {
651
+ let name;
652
+ let trustLevel;
653
+ for (let i = 0; i < rest.length; i++) {
654
+ if (rest[i] === "--name" && rest[i + 1]) {
655
+ name = rest[i + 1];
656
+ i += 1;
657
+ }
658
+ else if (rest[i] === "--trust" && rest[i + 1]) {
659
+ trustLevel = rest[i + 1];
660
+ i += 1;
661
+ }
662
+ }
663
+ if (!name)
664
+ throw new Error(`Usage\n${usage()}`);
665
+ return {
666
+ kind: "friend.create",
667
+ name,
668
+ ...(trustLevel ? { trustLevel } : {}),
669
+ ...(agent ? { agent } : {}),
670
+ };
671
+ }
672
+ if (sub === "link")
673
+ return parseLinkCommand(rest, "friend.link");
674
+ if (sub === "unlink")
675
+ return parseLinkCommand(rest, "friend.unlink");
676
+ throw new Error(`Usage\n${usage()}`);
677
+ }
239
678
  function parseOuroCommand(args) {
240
679
  const [head, second] = args;
241
680
  if (!head)
242
681
  return { kind: "daemon.up" };
243
682
  if (head === "up")
244
683
  return { kind: "daemon.up" };
245
- if (head === "stop")
684
+ if (head === "stop" || head === "down")
246
685
  return { kind: "daemon.stop" };
247
686
  if (head === "status")
248
687
  return { kind: "daemon.status" };
@@ -250,6 +689,22 @@ function parseOuroCommand(args) {
250
689
  return { kind: "daemon.logs" };
251
690
  if (head === "hatch")
252
691
  return parseHatchCommand(args.slice(1));
692
+ if (head === "auth")
693
+ return parseAuthCommand(args.slice(1));
694
+ if (head === "task")
695
+ return parseTaskCommand(args.slice(1));
696
+ if (head === "reminder")
697
+ return parseReminderCommand(args.slice(1));
698
+ if (head === "friend")
699
+ return parseFriendCommand(args.slice(1));
700
+ if (head === "whoami") {
701
+ const { agent } = extractAgentFlag(args.slice(1));
702
+ return { kind: "whoami", ...(agent ? { agent } : {}) };
703
+ }
704
+ if (head === "session")
705
+ return parseSessionCommand(args.slice(1));
706
+ if (head === "thoughts")
707
+ return parseThoughtsCommand(args.slice(1));
253
708
  if (head === "chat") {
254
709
  if (!second)
255
710
  throw new Error(`Usage\n${usage()}`);
@@ -263,38 +718,6 @@ function parseOuroCommand(args) {
263
718
  return parseLinkCommand(args.slice(1));
264
719
  throw new Error(`Unknown command '${args.join(" ")}'.\n${usage()}`);
265
720
  }
266
- function defaultSendCommand(socketPath, command) {
267
- return new Promise((resolve, reject) => {
268
- const client = net.createConnection(socketPath);
269
- let raw = "";
270
- client.on("connect", () => {
271
- client.write(JSON.stringify(command));
272
- client.end();
273
- });
274
- client.on("data", (chunk) => {
275
- raw += chunk.toString("utf-8");
276
- });
277
- client.on("error", reject);
278
- client.on("end", () => {
279
- const trimmed = raw.trim();
280
- if (trimmed.length === 0 && command.kind === "daemon.stop") {
281
- resolve({ ok: true, message: "daemon stopped" });
282
- return;
283
- }
284
- if (trimmed.length === 0) {
285
- reject(new Error("Daemon returned empty response."));
286
- return;
287
- }
288
- try {
289
- const parsed = JSON.parse(trimmed);
290
- resolve(parsed);
291
- }
292
- catch (error) {
293
- reject(error);
294
- }
295
- });
296
- });
297
- }
298
721
  function defaultStartDaemonProcess(socketPath) {
299
722
  const entry = path.join((0, identity_1.getRepoRoot)(), "dist", "heart", "daemon", "daemon-entry.js");
300
723
  const child = (0, child_process_1.spawn)("node", [entry, "--socket", socketPath], {
@@ -308,46 +731,6 @@ function defaultWriteStdout(text) {
308
731
  // eslint-disable-next-line no-console -- terminal UX: CLI command output
309
732
  console.log(text);
310
733
  }
311
- function defaultCheckSocketAlive(socketPath) {
312
- return new Promise((resolve) => {
313
- const client = net.createConnection(socketPath);
314
- let raw = "";
315
- let done = false;
316
- const finalize = (alive) => {
317
- if (done)
318
- return;
319
- done = true;
320
- resolve(alive);
321
- };
322
- if ("setTimeout" in client && typeof client.setTimeout === "function") {
323
- client.setTimeout(800, () => {
324
- client.destroy();
325
- finalize(false);
326
- });
327
- }
328
- client.on("connect", () => {
329
- client.write(JSON.stringify({ kind: "daemon.status" }));
330
- client.end();
331
- });
332
- client.on("data", (chunk) => {
333
- raw += chunk.toString("utf-8");
334
- });
335
- client.on("error", () => finalize(false));
336
- client.on("end", () => {
337
- if (raw.trim().length === 0) {
338
- finalize(false);
339
- return;
340
- }
341
- try {
342
- JSON.parse(raw);
343
- finalize(true);
344
- }
345
- catch {
346
- finalize(false);
347
- }
348
- });
349
- });
350
- }
351
734
  function defaultCleanupStaleSocket(socketPath) {
352
735
  if (fs.existsSync(socketPath)) {
353
736
  fs.unlinkSync(socketPath);
@@ -382,9 +765,38 @@ function defaultFallbackPendingMessage(command) {
382
765
  });
383
766
  return pendingPath;
384
767
  }
385
- async function defaultInstallSubagents() {
386
- return (0, subagent_installer_1.installSubagentsForAvailableCli)({
387
- repoRoot: (0, identity_1.getRepoRoot)(),
768
+ function defaultEnsureDaemonBootPersistence(socketPath) {
769
+ if (process.platform !== "darwin") {
770
+ return;
771
+ }
772
+ const homeDir = os.homedir();
773
+ const launchdDeps = {
774
+ exec: (cmd) => { (0, child_process_1.execSync)(cmd, { stdio: "ignore" }); },
775
+ writeFile: (filePath, content) => fs.writeFileSync(filePath, content, "utf-8"),
776
+ removeFile: (filePath) => fs.rmSync(filePath, { force: true }),
777
+ existsFile: (filePath) => fs.existsSync(filePath),
778
+ mkdirp: (dir) => fs.mkdirSync(dir, { recursive: true }),
779
+ homeDir,
780
+ userUid: process.getuid?.() ?? 0,
781
+ };
782
+ const entryPath = path.join((0, identity_1.getRepoRoot)(), "dist", "heart", "daemon", "daemon-entry.js");
783
+ /* v8 ignore next -- covered via mock in daemon-cli-defaults.test.ts; v8 on CI attributes the real fs.existsSync branch to the non-mock load @preserve */
784
+ if (!fs.existsSync(entryPath)) {
785
+ (0, runtime_1.emitNervesEvent)({
786
+ level: "warn",
787
+ component: "daemon",
788
+ event: "daemon.entry_path_missing",
789
+ message: "entryPath does not exist on disk — plist may point to a stale location. Run 'ouro daemon install' from the correct location.",
790
+ meta: { entryPath },
791
+ });
792
+ }
793
+ const logDir = (0, identity_1.getAgentDaemonLogsDir)();
794
+ (0, launchd_1.installLaunchAgent)(launchdDeps, {
795
+ nodePath: process.execPath,
796
+ entryPath,
797
+ socketPath,
798
+ logDir,
799
+ envPath: process.env.PATH,
388
800
  });
389
801
  }
390
802
  async function defaultPromptInput(question) {
@@ -402,142 +814,291 @@ async function defaultPromptInput(question) {
402
814
  }
403
815
  }
404
816
  function defaultListDiscoveredAgents() {
405
- const bundlesRoot = (0, identity_1.getAgentBundlesRoot)();
817
+ return (0, agent_discovery_1.listEnabledBundleAgents)({
818
+ bundlesRoot: (0, identity_1.getAgentBundlesRoot)(),
819
+ readdirSync: fs.readdirSync,
820
+ readFileSync: fs.readFileSync,
821
+ });
822
+ }
823
+ function discoverExistingCredentials(secretsRoot) {
824
+ const found = [];
406
825
  let entries;
407
826
  try {
408
- entries = fs.readdirSync(bundlesRoot, { withFileTypes: true });
827
+ entries = fs.readdirSync(secretsRoot, { withFileTypes: true });
409
828
  }
410
829
  catch {
411
- return [];
830
+ return found;
412
831
  }
413
- const discovered = [];
414
832
  for (const entry of entries) {
415
- if (!entry.isDirectory() || !entry.name.endsWith(".ouro"))
833
+ if (!entry.isDirectory())
416
834
  continue;
417
- const agentName = entry.name.slice(0, -5);
418
- const configPath = path.join(bundlesRoot, entry.name, "agent.json");
419
- let enabled = true;
835
+ const secretsPath = path.join(secretsRoot, entry.name, "secrets.json");
836
+ let raw;
420
837
  try {
421
- const raw = fs.readFileSync(configPath, "utf-8");
422
- const parsed = JSON.parse(raw);
423
- if (typeof parsed.enabled === "boolean") {
424
- enabled = parsed.enabled;
425
- }
838
+ raw = fs.readFileSync(secretsPath, "utf-8");
426
839
  }
427
840
  catch {
428
841
  continue;
429
842
  }
430
- if (enabled) {
431
- discovered.push(agentName);
843
+ let parsed;
844
+ try {
845
+ parsed = JSON.parse(raw);
846
+ }
847
+ catch {
848
+ continue;
849
+ }
850
+ if (!parsed.providers)
851
+ continue;
852
+ for (const [provName, provConfig] of Object.entries(parsed.providers)) {
853
+ if (provName === "anthropic" && provConfig.setupToken) {
854
+ found.push({ agentName: entry.name, provider: "anthropic", credentials: { setupToken: provConfig.setupToken }, providerConfig: { ...provConfig } });
855
+ }
856
+ else if (provName === "openai-codex" && provConfig.oauthAccessToken) {
857
+ found.push({ agentName: entry.name, provider: "openai-codex", credentials: { oauthAccessToken: provConfig.oauthAccessToken }, providerConfig: { ...provConfig } });
858
+ }
859
+ else if (provName === "minimax" && provConfig.apiKey) {
860
+ found.push({ agentName: entry.name, provider: "minimax", credentials: { apiKey: provConfig.apiKey }, providerConfig: { ...provConfig } });
861
+ }
862
+ else if (provName === "azure" && provConfig.apiKey && provConfig.endpoint && provConfig.deployment) {
863
+ found.push({ agentName: entry.name, provider: "azure", credentials: { apiKey: provConfig.apiKey, endpoint: provConfig.endpoint, deployment: provConfig.deployment }, providerConfig: { ...provConfig } });
864
+ }
432
865
  }
433
866
  }
434
- return discovered.sort((left, right) => left.localeCompare(right));
435
- }
436
- async function defaultLinkFriendIdentity(command) {
437
- const friendStore = new store_file_1.FileFriendStore(path.join((0, identity_1.getAgentBundlesRoot)(), `${command.agent}.ouro`, "friends"));
438
- const current = await friendStore.get(command.friendId);
439
- if (!current) {
440
- return `friend not found: ${command.friendId}`;
441
- }
442
- const alreadyLinked = current.externalIds.some((ext) => ext.provider === command.provider && ext.externalId === command.externalId);
443
- if (alreadyLinked) {
444
- return `identity already linked: ${command.provider}:${command.externalId}`;
445
- }
446
- const now = new Date().toISOString();
447
- await friendStore.put(command.friendId, {
448
- ...current,
449
- externalIds: [
450
- ...current.externalIds,
451
- {
452
- provider: command.provider,
453
- externalId: command.externalId,
454
- linkedAt: now,
455
- },
456
- ],
457
- updatedAt: now,
867
+ // Deduplicate by provider+credential value (keep first seen)
868
+ const seen = new Set();
869
+ return found.filter((cred) => {
870
+ const key = `${cred.provider}:${JSON.stringify(cred.credentials)}`;
871
+ if (seen.has(key))
872
+ return false;
873
+ seen.add(key);
874
+ return true;
458
875
  });
459
- return `linked ${command.provider}:${command.externalId} to ${command.friendId}`;
460
876
  }
461
- /* v8 ignore next 49 -- integration: interactive terminal specialist session @preserve */
877
+ /* v8 ignore start -- integration: interactive terminal specialist session @preserve */
462
878
  async function defaultRunAdoptionSpecialist() {
463
- const readline = await Promise.resolve().then(() => __importStar(require("readline/promises")));
464
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
465
- const prompt = async (q) => {
466
- const answer = await rl.question(q);
879
+ const { runCliSession } = await Promise.resolve().then(() => __importStar(require("../../senses/cli")));
880
+ const { patchRuntimeConfig } = await Promise.resolve().then(() => __importStar(require("../config")));
881
+ const { setAgentName, setAgentConfigOverride } = await Promise.resolve().then(() => __importStar(require("../identity")));
882
+ const readlinePromises = await Promise.resolve().then(() => __importStar(require("readline/promises")));
883
+ const crypto = await Promise.resolve().then(() => __importStar(require("crypto")));
884
+ // Phase 1: cold CLI — collect provider/credentials with a simple readline
885
+ const coldRl = readlinePromises.createInterface({ input: process.stdin, output: process.stdout });
886
+ const coldPrompt = async (q) => {
887
+ const answer = await coldRl.question(q);
467
888
  return answer.trim();
468
889
  };
890
+ let providerRaw;
891
+ let credentials = {};
892
+ let providerConfig = {};
893
+ const tempDir = path.join(os.tmpdir(), `ouro-hatch-${crypto.randomUUID()}`);
469
894
  try {
470
- const humanName = await prompt("Your name: ");
471
- const providerRaw = await prompt("Provider (azure|anthropic|minimax|openai-codex): ");
472
- if (!humanName || !isAgentProvider(providerRaw)) {
473
- process.stdout.write("Invalid input. Run `ouro hatch` to try again.\n");
474
- return null;
895
+ const secretsRoot = path.join(os.homedir(), ".agentsecrets");
896
+ const discovered = discoverExistingCredentials(secretsRoot);
897
+ const existingBundleCount = (0, specialist_orchestrator_1.listExistingBundles)((0, identity_1.getAgentBundlesRoot)()).length;
898
+ const hatchVerb = existingBundleCount > 0 ? "let's hatch a new agent." : "let's hatch your first agent.";
899
+ // Default models per provider (used when entering new credentials)
900
+ const defaultModels = {
901
+ anthropic: "claude-opus-4-6",
902
+ minimax: "MiniMax-Text-01",
903
+ "openai-codex": "gpt-5.4",
904
+ azure: "",
905
+ };
906
+ if (discovered.length > 0) {
907
+ process.stdout.write(`\n\ud83d\udc0d welcome to ouroboros! ${hatchVerb}\n`);
908
+ process.stdout.write("i found existing API credentials:\n\n");
909
+ const unique = [...new Map(discovered.map((d) => [`${d.provider}`, d])).values()];
910
+ for (let i = 0; i < unique.length; i++) {
911
+ const model = unique[i].providerConfig.model || unique[i].providerConfig.deployment || "";
912
+ const modelLabel = model ? `, ${model}` : "";
913
+ process.stdout.write(` ${i + 1}. ${unique[i].provider}${modelLabel} (from ${unique[i].agentName})\n`);
914
+ }
915
+ process.stdout.write("\n");
916
+ const choice = await coldPrompt("use one of these? enter number, or 'new' for a different key: ");
917
+ const idx = parseInt(choice, 10) - 1;
918
+ if (idx >= 0 && idx < unique.length) {
919
+ providerRaw = unique[idx].provider;
920
+ credentials = unique[idx].credentials;
921
+ providerConfig = unique[idx].providerConfig;
922
+ }
923
+ else {
924
+ const pRaw = await coldPrompt("provider (anthropic/azure/minimax/openai-codex): ");
925
+ if (!isAgentProvider(pRaw)) {
926
+ process.stdout.write("unknown provider. run `ouro hatch` to try again.\n");
927
+ coldRl.close();
928
+ return null;
929
+ }
930
+ providerRaw = pRaw;
931
+ providerConfig = { model: defaultModels[providerRaw] };
932
+ if (providerRaw === "anthropic")
933
+ credentials.setupToken = await coldPrompt("API key: ");
934
+ if (providerRaw === "openai-codex")
935
+ credentials.oauthAccessToken = await coldPrompt("OAuth token: ");
936
+ if (providerRaw === "minimax")
937
+ credentials.apiKey = await coldPrompt("API key: ");
938
+ if (providerRaw === "azure") {
939
+ credentials.apiKey = await coldPrompt("API key: ");
940
+ credentials.endpoint = await coldPrompt("endpoint: ");
941
+ credentials.deployment = await coldPrompt("deployment: ");
942
+ }
943
+ }
475
944
  }
476
- const credentials = {};
477
- if (providerRaw === "anthropic")
478
- credentials.setupToken = await prompt("Anthropic API key: ");
479
- if (providerRaw === "openai-codex")
480
- credentials.oauthAccessToken = await prompt("OpenAI Codex OAuth token: ");
481
- if (providerRaw === "minimax")
482
- credentials.apiKey = await prompt("MiniMax API key: ");
483
- if (providerRaw === "azure") {
484
- credentials.apiKey = await prompt("Azure API key: ");
485
- credentials.endpoint = await prompt("Azure endpoint: ");
486
- credentials.deployment = await prompt("Azure deployment: ");
945
+ else {
946
+ process.stdout.write(`\n\ud83d\udc0d welcome to ouroboros! ${hatchVerb}\n`);
947
+ process.stdout.write("i need an API key to power our conversation.\n\n");
948
+ const pRaw = await coldPrompt("provider (anthropic/azure/minimax/openai-codex): ");
949
+ if (!isAgentProvider(pRaw)) {
950
+ process.stdout.write("unknown provider. run `ouro hatch` to try again.\n");
951
+ coldRl.close();
952
+ return null;
953
+ }
954
+ providerRaw = pRaw;
955
+ providerConfig = { model: defaultModels[providerRaw] };
956
+ if (providerRaw === "anthropic")
957
+ credentials.setupToken = await coldPrompt("API key: ");
958
+ if (providerRaw === "openai-codex")
959
+ credentials.oauthAccessToken = await coldPrompt("OAuth token: ");
960
+ if (providerRaw === "minimax")
961
+ credentials.apiKey = await coldPrompt("API key: ");
962
+ if (providerRaw === "azure") {
963
+ credentials.apiKey = await coldPrompt("API key: ");
964
+ credentials.endpoint = await coldPrompt("endpoint: ");
965
+ credentials.deployment = await coldPrompt("deployment: ");
966
+ }
487
967
  }
488
- rl.close();
489
- // Locate the bundled AdoptionSpecialist.ouro shipped with the npm package
968
+ coldRl.close();
969
+ process.stdout.write("\n");
970
+ // Phase 2: configure runtime for adoption specialist
490
971
  const bundleSourceDir = path.resolve(__dirname, "..", "..", "..", "AdoptionSpecialist.ouro");
491
972
  const bundlesRoot = (0, identity_1.getAgentBundlesRoot)();
492
- const secretsRoot = path.join(os.homedir(), ".agentsecrets");
493
- return await (0, specialist_orchestrator_1.runAdoptionSpecialist)({
494
- bundleSourceDir,
495
- bundlesRoot,
496
- secretsRoot,
973
+ const secretsRoot2 = path.join(os.homedir(), ".agentsecrets");
974
+ // Suppress non-critical log noise during adoption (no secrets.json, etc.)
975
+ const { setRuntimeLogger } = await Promise.resolve().then(() => __importStar(require("../../nerves/runtime")));
976
+ const { createLogger } = await Promise.resolve().then(() => __importStar(require("../../nerves")));
977
+ setRuntimeLogger(createLogger({ level: "error" }));
978
+ // Configure runtime: set agent identity + config override so runAgent
979
+ // doesn't try to read from ~/AgentBundles/AdoptionSpecialist.ouro/
980
+ setAgentName("AdoptionSpecialist");
981
+ // Build specialist system prompt
982
+ const soulText = (0, specialist_orchestrator_1.loadSoulText)(bundleSourceDir);
983
+ const identitiesDir = path.join(bundleSourceDir, "psyche", "identities");
984
+ const identity = (0, specialist_orchestrator_1.pickRandomIdentity)(identitiesDir);
985
+ // Load identity-specific spinner phrases (falls back to DEFAULT_AGENT_PHRASES)
986
+ const { loadIdentityPhrases } = await Promise.resolve().then(() => __importStar(require("./specialist-orchestrator")));
987
+ const phrases = loadIdentityPhrases(bundleSourceDir, identity.fileName);
988
+ setAgentConfigOverride({
989
+ version: 1,
990
+ enabled: true,
497
991
  provider: providerRaw,
498
- credentials,
499
- humanName,
500
- createReadline: () => {
501
- const rl2 = readline.createInterface({ input: process.stdin, output: process.stdout });
502
- return { question: (q) => rl2.question(q), close: () => rl2.close() };
503
- },
504
- callbacks: {
505
- onModelStart: () => { },
506
- onModelStreamStart: () => { },
507
- onTextChunk: (text) => process.stdout.write(text),
508
- onReasoningChunk: () => { },
509
- onToolStart: () => { },
510
- onToolEnd: () => { },
511
- onError: (err) => process.stderr.write(`error: ${err.message}\n`),
992
+ phrases,
993
+ });
994
+ patchRuntimeConfig({
995
+ providers: {
996
+ [providerRaw]: { ...providerConfig, ...credentials },
512
997
  },
513
998
  });
999
+ const existingBundles = (0, specialist_orchestrator_1.listExistingBundles)(bundlesRoot);
1000
+ const systemPrompt = (0, specialist_prompt_1.buildSpecialistSystemPrompt)(soulText, identity.content, existingBundles, {
1001
+ tempDir,
1002
+ provider: providerRaw,
1003
+ });
1004
+ // Build specialist tools
1005
+ const specialistTools = (0, specialist_tools_1.getSpecialistTools)();
1006
+ const specialistExecTool = (0, specialist_tools_1.createSpecialistExecTool)({
1007
+ tempDir,
1008
+ credentials,
1009
+ provider: providerRaw,
1010
+ bundlesRoot,
1011
+ secretsRoot: secretsRoot2,
1012
+ animationWriter: (text) => process.stdout.write(text),
1013
+ });
1014
+ // Run the adoption specialist session via runCliSession
1015
+ const result = await runCliSession({
1016
+ agentName: "AdoptionSpecialist",
1017
+ tools: specialistTools,
1018
+ execTool: specialistExecTool,
1019
+ exitOnToolCall: "complete_adoption",
1020
+ autoFirstTurn: true,
1021
+ banner: false,
1022
+ disableCommands: true,
1023
+ skipSystemPromptRefresh: true,
1024
+ messages: [
1025
+ { role: "system", content: systemPrompt },
1026
+ { role: "user", content: "hi" },
1027
+ ],
1028
+ });
1029
+ if (result.exitReason === "tool_exit" && result.toolResult) {
1030
+ const parsed = typeof result.toolResult === "string" ? JSON.parse(result.toolResult) : result.toolResult;
1031
+ if (parsed.success && parsed.agentName) {
1032
+ return parsed.agentName;
1033
+ }
1034
+ }
1035
+ return null;
514
1036
  }
515
- catch {
516
- rl.close();
1037
+ catch (err) {
1038
+ process.stderr.write(`\nouro adoption error: ${err instanceof Error ? err.stack ?? err.message : String(err)}\n`);
1039
+ coldRl.close();
517
1040
  return null;
518
1041
  }
1042
+ finally {
1043
+ // Clear specialist config/identity so the hatched agent gets its own
1044
+ setAgentConfigOverride(null);
1045
+ const { resetProviderRuntime } = await Promise.resolve().then(() => __importStar(require("../core")));
1046
+ resetProviderRuntime();
1047
+ const { resetConfigCache } = await Promise.resolve().then(() => __importStar(require("../config")));
1048
+ resetConfigCache();
1049
+ // Restore default logging
1050
+ const { setRuntimeLogger: restoreLogger } = await Promise.resolve().then(() => __importStar(require("../../nerves/runtime")));
1051
+ restoreLogger(null);
1052
+ // Clean up temp dir if it still exists
1053
+ try {
1054
+ if (fs.existsSync(tempDir)) {
1055
+ fs.rmSync(tempDir, { recursive: true, force: true });
1056
+ }
1057
+ }
1058
+ catch {
1059
+ // Best effort cleanup
1060
+ }
1061
+ }
519
1062
  }
520
- function createDefaultOuroCliDeps(socketPath = "/tmp/ouroboros-daemon.sock") {
1063
+ /* v8 ignore stop */
1064
+ function createDefaultOuroCliDeps(socketPath = socket_client_1.DEFAULT_DAEMON_SOCKET_PATH) {
521
1065
  return {
522
1066
  socketPath,
523
- sendCommand: defaultSendCommand,
1067
+ sendCommand: socket_client_1.sendDaemonCommand,
524
1068
  startDaemonProcess: defaultStartDaemonProcess,
525
1069
  writeStdout: defaultWriteStdout,
526
- checkSocketAlive: defaultCheckSocketAlive,
1070
+ checkSocketAlive: socket_client_1.checkDaemonSocketAlive,
527
1071
  cleanupStaleSocket: defaultCleanupStaleSocket,
528
1072
  fallbackPendingMessage: defaultFallbackPendingMessage,
529
- installSubagents: defaultInstallSubagents,
530
- linkFriendIdentity: defaultLinkFriendIdentity,
531
1073
  listDiscoveredAgents: defaultListDiscoveredAgents,
532
1074
  runHatchFlow: hatch_flow_1.runHatchFlow,
533
1075
  promptInput: defaultPromptInput,
534
1076
  runAdoptionSpecialist: defaultRunAdoptionSpecialist,
1077
+ runAuthFlow: auth_flow_1.runRuntimeAuthFlow,
535
1078
  registerOuroBundleType: ouro_uti_1.registerOuroBundleUti,
1079
+ installOuroCommand: ouro_path_installer_1.installOuroCommand,
1080
+ syncGlobalOuroBotWrapper: ouro_bot_global_installer_1.syncGlobalOuroBotWrapper,
1081
+ ensureSkillManagement: skill_management_installer_1.ensureSkillManagement,
1082
+ ensureDaemonBootPersistence: defaultEnsureDaemonBootPersistence,
536
1083
  /* v8 ignore next 3 -- integration: launches interactive CLI session @preserve */
537
1084
  startChat: async (agentName) => {
538
1085
  const { main } = await Promise.resolve().then(() => __importStar(require("../../senses/cli")));
539
1086
  await main(agentName);
540
1087
  },
1088
+ scanSessions: async () => {
1089
+ const agentName = (0, identity_1.getAgentName)();
1090
+ const agentRoot = (0, identity_1.getAgentRoot)(agentName);
1091
+ return (0, session_activity_1.listSessionActivity)({
1092
+ sessionsDir: path.join(agentRoot, "state", "sessions"),
1093
+ friendsDir: path.join(agentRoot, "friends"),
1094
+ agentName,
1095
+ }).map((entry) => ({
1096
+ friendId: entry.friendId,
1097
+ friendName: entry.friendName,
1098
+ channel: entry.channel,
1099
+ lastActivity: entry.lastActivityAt,
1100
+ }));
1101
+ },
541
1102
  };
542
1103
  }
543
1104
  function toDaemonCommand(command) {
@@ -551,24 +1112,13 @@ async function resolveHatchInput(command, deps) {
551
1112
  if (!agentName || !humanName || !isAgentProvider(providerRaw)) {
552
1113
  throw new Error(`Usage\n${usage()}`);
553
1114
  }
554
- const credentials = { ...(command.credentials ?? {}) };
555
- if (providerRaw === "anthropic" && !credentials.setupToken && prompt) {
556
- credentials.setupToken = await prompt("Anthropic setup-token: ");
557
- }
558
- if (providerRaw === "openai-codex" && !credentials.oauthAccessToken && prompt) {
559
- credentials.oauthAccessToken = await prompt("OpenAI Codex OAuth token: ");
560
- }
561
- if (providerRaw === "minimax" && !credentials.apiKey && prompt) {
562
- credentials.apiKey = await prompt("MiniMax API key: ");
563
- }
564
- if (providerRaw === "azure") {
565
- if (!credentials.apiKey && prompt)
566
- credentials.apiKey = await prompt("Azure API key: ");
567
- if (!credentials.endpoint && prompt)
568
- credentials.endpoint = await prompt("Azure endpoint: ");
569
- if (!credentials.deployment && prompt)
570
- credentials.deployment = await prompt("Azure deployment: ");
571
- }
1115
+ const credentials = await (0, auth_flow_1.resolveHatchCredentials)({
1116
+ agentName,
1117
+ provider: providerRaw,
1118
+ credentials: command.credentials,
1119
+ promptInput: prompt,
1120
+ runAuthFlow: deps.runAuthFlow,
1121
+ });
572
1122
  return {
573
1123
  agentName,
574
1124
  humanName,
@@ -594,12 +1144,238 @@ async function registerOuroBundleTypeNonBlocking(deps) {
594
1144
  });
595
1145
  }
596
1146
  }
1147
+ async function performSystemSetup(deps) {
1148
+ // Install ouro command to PATH (non-blocking)
1149
+ if (deps.installOuroCommand) {
1150
+ try {
1151
+ deps.installOuroCommand();
1152
+ }
1153
+ catch (error) {
1154
+ (0, runtime_1.emitNervesEvent)({
1155
+ level: "warn",
1156
+ component: "daemon",
1157
+ event: "daemon.system_setup_ouro_cmd_error",
1158
+ message: "failed to install ouro command to PATH",
1159
+ meta: { error: error instanceof Error ? error.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(error) },
1160
+ });
1161
+ }
1162
+ }
1163
+ if (deps.syncGlobalOuroBotWrapper) {
1164
+ try {
1165
+ await Promise.resolve(deps.syncGlobalOuroBotWrapper());
1166
+ }
1167
+ catch (error) {
1168
+ (0, runtime_1.emitNervesEvent)({
1169
+ level: "warn",
1170
+ component: "daemon",
1171
+ event: "daemon.system_setup_ouro_bot_wrapper_error",
1172
+ message: "failed to sync global ouro.bot wrapper",
1173
+ meta: { error: error instanceof Error ? error.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(error) },
1174
+ });
1175
+ }
1176
+ }
1177
+ // Ensure skill-management skill is available
1178
+ if (deps.ensureSkillManagement) {
1179
+ try {
1180
+ await deps.ensureSkillManagement();
1181
+ }
1182
+ catch (error) {
1183
+ (0, runtime_1.emitNervesEvent)({
1184
+ level: "warn",
1185
+ component: "daemon",
1186
+ event: "daemon.system_setup_skill_management_error",
1187
+ message: "failed to ensure skill-management skill",
1188
+ meta: { error: error instanceof Error ? error.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(error) },
1189
+ });
1190
+ }
1191
+ }
1192
+ // Register .ouro bundle type (UTI on macOS)
1193
+ await registerOuroBundleTypeNonBlocking(deps);
1194
+ }
1195
+ function executeTaskCommand(command, taskMod) {
1196
+ if (command.kind === "task.board") {
1197
+ if (command.status) {
1198
+ const lines = taskMod.boardStatus(command.status);
1199
+ return lines.length > 0 ? lines.join("\n") : "no tasks in that status";
1200
+ }
1201
+ const board = taskMod.getBoard();
1202
+ return board.full || board.compact || "no tasks found";
1203
+ }
1204
+ if (command.kind === "task.create") {
1205
+ try {
1206
+ const created = taskMod.createTask({
1207
+ title: command.title,
1208
+ type: command.type ?? "one-shot",
1209
+ category: "general",
1210
+ body: "",
1211
+ });
1212
+ return `created: ${created}`;
1213
+ }
1214
+ catch (error) {
1215
+ return `error: ${error instanceof Error ? error.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(error)}`;
1216
+ }
1217
+ }
1218
+ if (command.kind === "task.update") {
1219
+ const result = taskMod.updateStatus(command.id, command.status);
1220
+ if (!result.ok) {
1221
+ return `error: ${result.reason ?? "status update failed"}`;
1222
+ }
1223
+ const archivedSuffix = result.archived && result.archived.length > 0
1224
+ ? ` | archived: ${result.archived.join(", ")}`
1225
+ : "";
1226
+ return `updated: ${command.id} -> ${result.to}${archivedSuffix}`;
1227
+ }
1228
+ if (command.kind === "task.show") {
1229
+ const task = taskMod.getTask(command.id);
1230
+ if (!task)
1231
+ return `task not found: ${command.id}`;
1232
+ return [
1233
+ `title: ${task.title}`,
1234
+ `type: ${task.type}`,
1235
+ `status: ${task.status}`,
1236
+ `category: ${task.category}`,
1237
+ `created: ${task.created}`,
1238
+ `updated: ${task.updated}`,
1239
+ `path: ${task.path}`,
1240
+ task.body ? `\n${task.body}` : "",
1241
+ ].filter(Boolean).join("\n");
1242
+ }
1243
+ if (command.kind === "task.actionable") {
1244
+ const lines = taskMod.boardAction();
1245
+ return lines.length > 0 ? lines.join("\n") : "no action required";
1246
+ }
1247
+ if (command.kind === "task.deps") {
1248
+ const lines = taskMod.boardDeps();
1249
+ return lines.length > 0 ? lines.join("\n") : "no unresolved dependencies";
1250
+ }
1251
+ // command.kind === "task.sessions"
1252
+ const lines = taskMod.boardSessions();
1253
+ return lines.length > 0 ? lines.join("\n") : "no active sessions";
1254
+ }
1255
+ const TRUST_RANK = { family: 4, friend: 3, acquaintance: 2, stranger: 1 };
1256
+ /* v8 ignore start -- defensive: ?? fallbacks are unreachable when inputs are valid TrustLevel values @preserve */
1257
+ function higherTrust(a, b) {
1258
+ const rankA = TRUST_RANK[a ?? "stranger"] ?? 1;
1259
+ const rankB = TRUST_RANK[b ?? "stranger"] ?? 1;
1260
+ return rankA >= rankB ? (a ?? "stranger") : (b ?? "stranger");
1261
+ }
1262
+ /* v8 ignore stop */
1263
+ async function executeFriendCommand(command, store) {
1264
+ if (command.kind === "friend.list") {
1265
+ const listAll = store.listAll;
1266
+ if (!listAll)
1267
+ return "friend store does not support listing";
1268
+ const friends = await listAll.call(store);
1269
+ if (friends.length === 0)
1270
+ return "no friends found";
1271
+ const lines = friends.map((f) => {
1272
+ const trust = f.trustLevel ?? "unknown";
1273
+ return `${f.id} ${f.name} ${trust}`;
1274
+ });
1275
+ return lines.join("\n");
1276
+ }
1277
+ if (command.kind === "friend.show") {
1278
+ const record = await store.get(command.friendId);
1279
+ if (!record)
1280
+ return `friend not found: ${command.friendId}`;
1281
+ return JSON.stringify(record, null, 2);
1282
+ }
1283
+ if (command.kind === "friend.create") {
1284
+ const now = new Date().toISOString();
1285
+ const id = (0, crypto_1.randomUUID)();
1286
+ const trustLevel = (command.trustLevel ?? "acquaintance");
1287
+ await store.put(id, {
1288
+ id,
1289
+ name: command.name,
1290
+ trustLevel,
1291
+ externalIds: [],
1292
+ tenantMemberships: [],
1293
+ toolPreferences: {},
1294
+ notes: {},
1295
+ totalTokens: 0,
1296
+ createdAt: now,
1297
+ updatedAt: now,
1298
+ schemaVersion: 1,
1299
+ });
1300
+ return `created: ${id} (${command.name}, ${trustLevel})`;
1301
+ }
1302
+ if (command.kind === "friend.link") {
1303
+ const current = await store.get(command.friendId);
1304
+ if (!current)
1305
+ return `friend not found: ${command.friendId}`;
1306
+ const alreadyLinked = current.externalIds.some((ext) => ext.provider === command.provider && ext.externalId === command.externalId);
1307
+ if (alreadyLinked)
1308
+ return `identity already linked: ${command.provider}:${command.externalId}`;
1309
+ const now = new Date().toISOString();
1310
+ const newExternalIds = [
1311
+ ...current.externalIds,
1312
+ { provider: command.provider, externalId: command.externalId, linkedAt: now },
1313
+ ];
1314
+ // Orphan cleanup: check if another friend has this externalId
1315
+ const orphan = await store.findByExternalId(command.provider, command.externalId);
1316
+ let mergeMessage = "";
1317
+ let mergedNotes = { ...current.notes };
1318
+ let mergedTrust = current.trustLevel;
1319
+ let orphanExternalIds = [];
1320
+ if (orphan && orphan.id !== command.friendId) {
1321
+ // Merge orphan's notes (target's notes take priority)
1322
+ mergedNotes = { ...orphan.notes, ...current.notes };
1323
+ // Keep higher trust level
1324
+ mergedTrust = higherTrust(current.trustLevel, orphan.trustLevel);
1325
+ // Collect orphan's other externalIds (excluding the one being linked)
1326
+ orphanExternalIds = orphan.externalIds.filter((ext) => !(ext.provider === command.provider && ext.externalId === command.externalId));
1327
+ await store.delete(orphan.id);
1328
+ mergeMessage = ` (merged orphan ${orphan.id})`;
1329
+ }
1330
+ await store.put(command.friendId, {
1331
+ ...current,
1332
+ externalIds: [...newExternalIds, ...orphanExternalIds],
1333
+ notes: mergedNotes,
1334
+ trustLevel: mergedTrust,
1335
+ updatedAt: now,
1336
+ });
1337
+ return `linked ${command.provider}:${command.externalId} to ${command.friendId}${mergeMessage}`;
1338
+ }
1339
+ // command.kind === "friend.unlink"
1340
+ const current = await store.get(command.friendId);
1341
+ if (!current)
1342
+ return `friend not found: ${command.friendId}`;
1343
+ const idx = current.externalIds.findIndex((ext) => ext.provider === command.provider && ext.externalId === command.externalId);
1344
+ if (idx === -1)
1345
+ return `identity not linked: ${command.provider}:${command.externalId}`;
1346
+ const now = new Date().toISOString();
1347
+ const filtered = current.externalIds.filter((_, i) => i !== idx);
1348
+ await store.put(command.friendId, { ...current, externalIds: filtered, updatedAt: now });
1349
+ return `unlinked ${command.provider}:${command.externalId} from ${command.friendId}`;
1350
+ }
1351
+ function executeReminderCommand(command, taskMod) {
1352
+ try {
1353
+ const created = taskMod.createTask({
1354
+ title: command.title,
1355
+ type: command.cadence ? "habit" : "one-shot",
1356
+ category: command.category ?? "reminder",
1357
+ body: command.body,
1358
+ scheduledAt: command.scheduledAt,
1359
+ cadence: command.cadence,
1360
+ requester: command.requester,
1361
+ });
1362
+ return `created: ${created}`;
1363
+ }
1364
+ catch (error) {
1365
+ return `error: ${error instanceof Error ? error.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(error)}`;
1366
+ }
1367
+ }
597
1368
  async function runOuroCli(args, deps = createDefaultOuroCliDeps()) {
598
1369
  if (args.includes("--help") || args.includes("-h")) {
599
1370
  const text = usage();
600
1371
  deps.writeStdout(text);
601
1372
  return text;
602
1373
  }
1374
+ if (args.length === 1 && (args[0] === "-v" || args[0] === "--version")) {
1375
+ const text = formatVersionOutput();
1376
+ deps.writeStdout(text);
1377
+ return text;
1378
+ }
603
1379
  let command;
604
1380
  try {
605
1381
  command = parseOuroCommand(args);
@@ -618,23 +1394,12 @@ async function runOuroCli(args, deps = createDefaultOuroCliDeps()) {
618
1394
  if (args.length === 0) {
619
1395
  const discovered = await Promise.resolve(deps.listDiscoveredAgents ? deps.listDiscoveredAgents() : defaultListDiscoveredAgents());
620
1396
  if (discovered.length === 0 && deps.runAdoptionSpecialist) {
1397
+ // System setup first — ouro command, subagents, UTI — before the interactive specialist
1398
+ await performSystemSetup(deps);
621
1399
  const hatchlingName = await deps.runAdoptionSpecialist();
622
1400
  if (!hatchlingName) {
623
1401
  return "";
624
1402
  }
625
- try {
626
- await deps.installSubagents();
627
- }
628
- catch (error) {
629
- (0, runtime_1.emitNervesEvent)({
630
- level: "warn",
631
- component: "daemon",
632
- event: "daemon.subagent_install_error",
633
- message: "subagent auto-install failed",
634
- meta: { error: error instanceof Error ? error.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(error) },
635
- });
636
- }
637
- await registerOuroBundleTypeNonBlocking(deps);
638
1403
  await ensureDaemonRunning(deps);
639
1404
  if (deps.startChat) {
640
1405
  await deps.startChat(hatchlingName);
@@ -681,19 +1446,34 @@ async function runOuroCli(args, deps = createDefaultOuroCliDeps()) {
681
1446
  meta: { kind: command.kind },
682
1447
  });
683
1448
  if (command.kind === "daemon.up") {
684
- try {
685
- await deps.installSubagents();
1449
+ await performSystemSetup(deps);
1450
+ if (deps.ensureDaemonBootPersistence) {
1451
+ try {
1452
+ await Promise.resolve(deps.ensureDaemonBootPersistence(deps.socketPath));
1453
+ }
1454
+ catch (error) {
1455
+ (0, runtime_1.emitNervesEvent)({
1456
+ level: "warn",
1457
+ component: "daemon",
1458
+ event: "daemon.system_setup_launchd_error",
1459
+ message: "failed to persist daemon boot startup",
1460
+ meta: { error: error instanceof Error ? error.message : String(error), socketPath: deps.socketPath },
1461
+ });
1462
+ }
686
1463
  }
687
- catch (error) {
688
- (0, runtime_1.emitNervesEvent)({
689
- level: "warn",
690
- component: "daemon",
691
- event: "daemon.subagent_install_error",
692
- message: "subagent auto-install failed",
693
- meta: { error: error instanceof Error ? error.message : String(error) },
694
- });
1464
+ // Run update hooks before starting daemon so user sees the output
1465
+ (0, update_hooks_1.registerUpdateHook)(bundle_meta_1.bundleMetaHook);
1466
+ const bundlesRoot = (0, identity_1.getAgentBundlesRoot)();
1467
+ const currentVersion = (0, bundle_manifest_1.getPackageVersion)();
1468
+ const updateSummary = await (0, update_hooks_1.applyPendingUpdates)(bundlesRoot, currentVersion);
1469
+ if (updateSummary.updated.length > 0) {
1470
+ const agents = updateSummary.updated.map((e) => e.agent);
1471
+ const from = updateSummary.updated[0].from;
1472
+ const to = updateSummary.updated[0].to;
1473
+ const fromStr = from ? ` (was ${from})` : "";
1474
+ const count = agents.length;
1475
+ deps.writeStdout(`updated ${count} agent${count === 1 ? "" : "s"} to runtime ${to}${fromStr}`);
695
1476
  }
696
- await registerOuroBundleTypeNonBlocking(deps);
697
1477
  const daemonResult = await ensureDaemonRunning(deps);
698
1478
  deps.writeStdout(daemonResult.message);
699
1479
  return daemonResult.message;
@@ -702,13 +1482,175 @@ async function runOuroCli(args, deps = createDefaultOuroCliDeps()) {
702
1482
  deps.tailLogs();
703
1483
  return "";
704
1484
  }
705
- if (command.kind === "friend.link") {
706
- const linker = deps.linkFriendIdentity ?? defaultLinkFriendIdentity;
707
- const message = await linker(command);
1485
+ // ── task subcommands (local, no daemon socket needed) ──
1486
+ if (command.kind === "task.board" || command.kind === "task.create" || command.kind === "task.update" ||
1487
+ command.kind === "task.show" || command.kind === "task.actionable" || command.kind === "task.deps" ||
1488
+ command.kind === "task.sessions") {
1489
+ /* v8 ignore start -- production default: requires full identity setup @preserve */
1490
+ const taskMod = deps.taskModule ?? (0, tasks_1.getTaskModule)();
1491
+ /* v8 ignore stop */
1492
+ const message = executeTaskCommand(command, taskMod);
708
1493
  deps.writeStdout(message);
709
1494
  return message;
710
1495
  }
1496
+ // ── reminder subcommands (local, no daemon socket needed) ──
1497
+ if (command.kind === "reminder.create") {
1498
+ /* v8 ignore start -- production default: requires full identity setup @preserve */
1499
+ const taskMod = deps.taskModule ?? (0, tasks_1.getTaskModule)();
1500
+ /* v8 ignore stop */
1501
+ const message = executeReminderCommand(command, taskMod);
1502
+ deps.writeStdout(message);
1503
+ return message;
1504
+ }
1505
+ // ── friend subcommands (local, no daemon socket needed) ──
1506
+ if (command.kind === "friend.list" || command.kind === "friend.show" || command.kind === "friend.create" ||
1507
+ command.kind === "friend.link" || command.kind === "friend.unlink") {
1508
+ /* v8 ignore start -- production default: requires full identity setup @preserve */
1509
+ let store = deps.friendStore;
1510
+ if (!store) {
1511
+ // Derive agent-scoped friends dir from --agent flag or link/unlink's agent field
1512
+ const agentName = ("agent" in command && command.agent) ? command.agent : undefined;
1513
+ const friendsDir = agentName
1514
+ ? path.join((0, identity_1.getAgentBundlesRoot)(), `${agentName}.ouro`, "friends")
1515
+ : path.join((0, identity_1.getAgentBundlesRoot)(), "friends");
1516
+ store = new store_file_1.FileFriendStore(friendsDir);
1517
+ }
1518
+ /* v8 ignore stop */
1519
+ const message = await executeFriendCommand(command, store);
1520
+ deps.writeStdout(message);
1521
+ return message;
1522
+ }
1523
+ // ── auth (local, no daemon socket needed) ──
1524
+ if (command.kind === "auth.run") {
1525
+ const provider = command.provider ?? (0, auth_flow_1.readAgentConfigForAgent)(command.agent).config.provider;
1526
+ const authRunner = deps.runAuthFlow ?? auth_flow_1.runRuntimeAuthFlow;
1527
+ const result = await authRunner({
1528
+ agentName: command.agent,
1529
+ provider,
1530
+ promptInput: deps.promptInput,
1531
+ });
1532
+ if (command.provider) {
1533
+ (0, auth_flow_1.writeAgentProviderSelection)(command.agent, command.provider);
1534
+ }
1535
+ deps.writeStdout(result.message);
1536
+ return result.message;
1537
+ }
1538
+ // ── whoami (local, no daemon socket needed) ──
1539
+ if (command.kind === "whoami") {
1540
+ if (command.agent) {
1541
+ const agentRoot = path.join((0, identity_1.getAgentBundlesRoot)(), `${command.agent}.ouro`);
1542
+ const message = [
1543
+ `agent: ${command.agent}`,
1544
+ `home: ${agentRoot}`,
1545
+ `bones: ${(0, runtime_metadata_1.getRuntimeMetadata)().version}`,
1546
+ ].join("\n");
1547
+ deps.writeStdout(message);
1548
+ return message;
1549
+ }
1550
+ /* v8 ignore start -- production default: requires full identity setup @preserve */
1551
+ try {
1552
+ const info = deps.whoamiInfo
1553
+ ? deps.whoamiInfo()
1554
+ : {
1555
+ agentName: (0, identity_1.getAgentName)(),
1556
+ homePath: path.join((0, identity_1.getAgentBundlesRoot)(), `${(0, identity_1.getAgentName)()}.ouro`),
1557
+ bonesVersion: (0, runtime_metadata_1.getRuntimeMetadata)().version,
1558
+ };
1559
+ const message = [
1560
+ `agent: ${info.agentName}`,
1561
+ `home: ${info.homePath}`,
1562
+ `bones: ${info.bonesVersion}`,
1563
+ ].join("\n");
1564
+ deps.writeStdout(message);
1565
+ return message;
1566
+ }
1567
+ catch {
1568
+ const message = "error: no agent context — use --agent <name> to specify";
1569
+ deps.writeStdout(message);
1570
+ return message;
1571
+ }
1572
+ /* v8 ignore stop */
1573
+ }
1574
+ // ── thoughts (local, no daemon socket needed) ──
1575
+ if (command.kind === "thoughts") {
1576
+ try {
1577
+ const agentName = command.agent ?? (0, identity_1.getAgentName)();
1578
+ const agentRoot = path.join((0, identity_1.getAgentBundlesRoot)(), `${agentName}.ouro`);
1579
+ const sessionFilePath = (0, thoughts_1.getInnerDialogSessionPath)(agentRoot);
1580
+ if (command.json) {
1581
+ try {
1582
+ const raw = fs.readFileSync(sessionFilePath, "utf-8");
1583
+ deps.writeStdout(raw);
1584
+ return raw;
1585
+ }
1586
+ catch {
1587
+ const message = "no inner dialog session found";
1588
+ deps.writeStdout(message);
1589
+ return message;
1590
+ }
1591
+ }
1592
+ const turns = (0, thoughts_1.parseInnerDialogSession)(sessionFilePath);
1593
+ const message = (0, thoughts_1.formatThoughtTurns)(turns, command.last ?? 10);
1594
+ deps.writeStdout(message);
1595
+ if (command.follow) {
1596
+ deps.writeStdout("\n\n--- following (ctrl+c to stop) ---\n");
1597
+ /* v8 ignore start -- callback tested via followThoughts unit tests @preserve */
1598
+ const stop = (0, thoughts_1.followThoughts)(sessionFilePath, (formatted) => {
1599
+ deps.writeStdout("\n" + formatted);
1600
+ });
1601
+ /* v8 ignore stop */
1602
+ // Block until process exit; cleanup watcher on SIGINT/SIGTERM
1603
+ return new Promise((resolve) => {
1604
+ const cleanup = () => { stop(); resolve(message); };
1605
+ process.once("SIGINT", cleanup);
1606
+ process.once("SIGTERM", cleanup);
1607
+ });
1608
+ }
1609
+ return message;
1610
+ }
1611
+ catch {
1612
+ const message = "error: no agent context — use --agent <name> to specify";
1613
+ deps.writeStdout(message);
1614
+ return message;
1615
+ }
1616
+ }
1617
+ // ── session list (local, no daemon socket needed) ──
1618
+ if (command.kind === "session.list") {
1619
+ /* v8 ignore start -- production default: requires full identity setup @preserve */
1620
+ const scanner = deps.scanSessions ?? (async () => []);
1621
+ /* v8 ignore stop */
1622
+ const sessions = await scanner();
1623
+ if (sessions.length === 0) {
1624
+ const message = "no active sessions";
1625
+ deps.writeStdout(message);
1626
+ return message;
1627
+ }
1628
+ const lines = sessions.map((s) => `${s.friendId} ${s.friendName} ${s.channel} ${s.lastActivity}`);
1629
+ const message = lines.join("\n");
1630
+ deps.writeStdout(message);
1631
+ return message;
1632
+ }
1633
+ if (command.kind === "chat.connect" && deps.startChat) {
1634
+ await ensureDaemonRunning(deps);
1635
+ await deps.startChat(command.agent);
1636
+ return "";
1637
+ }
711
1638
  if (command.kind === "hatch.start") {
1639
+ // Route through adoption specialist when no explicit hatch args were provided
1640
+ const hasExplicitHatchArgs = !!(command.agentName || command.humanName || command.provider || command.credentials);
1641
+ if (deps.runAdoptionSpecialist && !hasExplicitHatchArgs) {
1642
+ // System setup first — ouro command, subagents, UTI — before the interactive specialist
1643
+ await performSystemSetup(deps);
1644
+ const hatchlingName = await deps.runAdoptionSpecialist();
1645
+ if (!hatchlingName) {
1646
+ return "";
1647
+ }
1648
+ await ensureDaemonRunning(deps);
1649
+ if (deps.startChat) {
1650
+ await deps.startChat(hatchlingName);
1651
+ }
1652
+ return "";
1653
+ }
712
1654
  const hatchRunner = deps.runHatchFlow;
713
1655
  if (!hatchRunner) {
714
1656
  const response = await deps.sendCommand(deps.socketPath, { kind: "hatch.start" });
@@ -718,19 +1660,7 @@ async function runOuroCli(args, deps = createDefaultOuroCliDeps()) {
718
1660
  }
719
1661
  const hatchInput = await resolveHatchInput(command, deps);
720
1662
  const result = await hatchRunner(hatchInput);
721
- try {
722
- await deps.installSubagents();
723
- }
724
- catch (error) {
725
- (0, runtime_1.emitNervesEvent)({
726
- level: "warn",
727
- component: "daemon",
728
- event: "daemon.subagent_install_error",
729
- message: "subagent auto-install failed",
730
- meta: { error: error instanceof Error ? error.message : String(error) },
731
- });
732
- }
733
- await registerOuroBundleTypeNonBlocking(deps);
1663
+ await performSystemSetup(deps);
734
1664
  const daemonResult = await ensureDaemonRunning(deps);
735
1665
  if (deps.startChat) {
736
1666
  await deps.startChat(hatchInput.agentName);
@@ -752,9 +1682,22 @@ async function runOuroCli(args, deps = createDefaultOuroCliDeps()) {
752
1682
  deps.writeStdout(message);
753
1683
  return message;
754
1684
  }
1685
+ if (command.kind === "daemon.status" && isDaemonUnavailableError(error)) {
1686
+ const message = daemonUnavailableStatusOutput(deps.socketPath);
1687
+ deps.writeStdout(message);
1688
+ return message;
1689
+ }
1690
+ if (command.kind === "daemon.stop" && isDaemonUnavailableError(error)) {
1691
+ const message = "daemon not running";
1692
+ deps.writeStdout(message);
1693
+ return message;
1694
+ }
755
1695
  throw error;
756
1696
  }
757
- const message = response.summary ?? response.message ?? (response.ok ? "ok" : `error: ${response.error ?? "unknown error"}`);
1697
+ const fallbackMessage = response.summary ?? response.message ?? (response.ok ? "ok" : `error: ${response.error ?? "unknown error"}`);
1698
+ const message = command.kind === "daemon.status"
1699
+ ? formatDaemonStatusOutput(response, fallbackMessage)
1700
+ : fallbackMessage;
758
1701
  deps.writeStdout(message);
759
1702
  return message;
760
1703
  }