@ouro.bot/cli 0.1.0-alpha.37 → 0.1.0-alpha.39

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/changelog.json CHANGED
@@ -1,6 +1,28 @@
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.39",
6
+ "changes": [
7
+ "All senses now route through a shared per-turn pipeline — friend resolution, trust gate, session load, pending drain, agent turn, post-turn, and token accumulation happen in one place instead of four.",
8
+ "Trust gate is now channel-aware: open senses (iMessage) enforce stranger/acquaintance rules, closed senses (Teams) trust the org, local and internal always pass through.",
9
+ "Tool access and prompt restrictions use a single shared isTrustedLevel check — no more scattered family/friend comparisons that could drift apart.",
10
+ "Pending messages now inject correctly into multimodal content (image attachments no longer silently drop pending messages).",
11
+ "ouro reminder create supports --agent flag, matching every other identity-scoped CLI command."
12
+ ]
13
+ },
14
+ {
15
+ "version": "0.1.0-alpha.38",
16
+ "changes": [
17
+ "You now have a proper body map — understanding of your home (bundle) and bones (harness), what each directory is for, and how to modify your own configuration.",
18
+ "Inner dialog is now genuine internal monologue with metacognitive framing, not a second CLI session. Heartbeat and bootstrap messages read as first-person awareness.",
19
+ "Cross-session communication works end-to-end: inner dialog thoughts surface as [inner thought: ...] in conversations, messages to yourself route to inner dialog, and you can proactively reach out to friends via iMessage and Teams.",
20
+ "Tool audit: removed wrapper tools (git_commit, gh_cli, get_current_time, list_directory), added surgical tools (edit_file, glob, grep, read_file with offset/limit), consolidated 7 task tools + schedule_reminder + friend tools into ouro CLI commands.",
21
+ "You now understand why certain tools are restricted in certain contexts — trust level and shared channels each have independent, explained gates.",
22
+ "ouro friend link/unlink commands handle orphan cleanup when linking external identities, merging duplicate friend records intelligently.",
23
+ "During onboarding, the adoption specialist can collect phone number and Teams handle to create an initial friend record with contact info."
24
+ ]
25
+ },
4
26
  {
5
27
  "version": "0.1.0-alpha.37",
6
28
  "changes": [
@@ -39,6 +39,7 @@ exports.discoverExistingCredentials = discoverExistingCredentials;
39
39
  exports.createDefaultOuroCliDeps = createDefaultOuroCliDeps;
40
40
  exports.runOuroCli = runOuroCli;
41
41
  const child_process_1 = require("child_process");
42
+ const crypto_1 = require("crypto");
42
43
  const fs = __importStar(require("fs"));
43
44
  const net = __importStar(require("net"));
44
45
  const os = __importStar(require("os"));
@@ -60,6 +61,7 @@ const agent_discovery_1 = require("./agent-discovery");
60
61
  const update_hooks_1 = require("./update-hooks");
61
62
  const bundle_meta_1 = require("./hooks/bundle-meta");
62
63
  const bundle_manifest_1 = require("../../mind/bundle-manifest");
64
+ const tasks_1 = require("../../repertoire/tasks");
63
65
  const ouro_bot_global_installer_1 = require("./ouro-bot-global-installer");
64
66
  function stringField(value) {
65
67
  return typeof value === "string" ? value : null;
@@ -224,6 +226,18 @@ async function ensureDaemonRunning(deps) {
224
226
  message: `daemon started (pid ${started.pid ?? "unknown"})`,
225
227
  };
226
228
  }
229
+ /**
230
+ * Extract `--agent <name>` from an args array, returning the agent name and
231
+ * the remaining args with the flag pair removed.
232
+ */
233
+ function extractAgentFlag(args) {
234
+ const idx = args.indexOf("--agent");
235
+ if (idx === -1 || idx + 1 >= args.length)
236
+ return { rest: args };
237
+ const agent = args[idx + 1];
238
+ const rest = [...args.slice(0, idx), ...args.slice(idx + 2)];
239
+ return { agent, rest };
240
+ }
227
241
  function usage() {
228
242
  return [
229
243
  "Usage:",
@@ -234,6 +248,19 @@ function usage() {
234
248
  " ouro msg --to <agent> [--session <id>] [--task <ref>] <message>",
235
249
  " ouro poke <agent> --task <task-id>",
236
250
  " ouro link <agent> --friend <id> --provider <provider> --external-id <external-id>",
251
+ " ouro task board [<status>] [--agent <name>]",
252
+ " ouro task create <title> [--type <type>] [--agent <name>]",
253
+ " ouro task update <id> <status> [--agent <name>]",
254
+ " ouro task show <id> [--agent <name>]",
255
+ " ouro task actionable|deps|sessions [--agent <name>]",
256
+ " ouro reminder create <title> --body <body> [--at <iso>] [--cadence <interval>] [--category <category>] [--agent <name>]",
257
+ " ouro friend list [--agent <name>]",
258
+ " ouro friend show <id> [--agent <name>]",
259
+ " ouro friend create --name <name> [--trust <level>] [--agent <name>]",
260
+ " ouro friend link <agent> --friend <id> --provider <p> --external-id <eid>",
261
+ " ouro friend unlink <agent> --friend <id> --provider <p> --external-id <eid>",
262
+ " ouro whoami [--agent <name>]",
263
+ " ouro session list [--agent <name>]",
237
264
  ].join("\n");
238
265
  }
239
266
  function formatVersionOutput() {
@@ -323,7 +350,7 @@ function parsePokeCommand(args) {
323
350
  throw new Error(`Usage\n${usage()}`);
324
351
  return { kind: "task.poke", agent, taskId };
325
352
  }
326
- function parseLinkCommand(args) {
353
+ function parseLinkCommand(args, kind = "friend.link") {
327
354
  const agent = args[0];
328
355
  if (!agent)
329
356
  throw new Error(`Usage\n${usage()}`);
@@ -355,7 +382,7 @@ function parseLinkCommand(args) {
355
382
  throw new Error(`Unknown identity provider '${providerRaw}'. Use aad|local|teams-conversation.`);
356
383
  }
357
384
  return {
358
- kind: "friend.link",
385
+ kind,
359
386
  agent,
360
387
  friendId,
361
388
  provider: providerRaw,
@@ -432,6 +459,150 @@ function parseHatchCommand(args) {
432
459
  migrationPath,
433
460
  };
434
461
  }
462
+ function parseTaskCommand(args) {
463
+ const { agent, rest: cleaned } = extractAgentFlag(args);
464
+ const [sub, ...rest] = cleaned;
465
+ if (!sub)
466
+ throw new Error(`Usage\n${usage()}`);
467
+ if (sub === "board") {
468
+ const status = rest[0];
469
+ return status
470
+ ? { kind: "task.board", status, ...(agent ? { agent } : {}) }
471
+ : { kind: "task.board", ...(agent ? { agent } : {}) };
472
+ }
473
+ if (sub === "create") {
474
+ const title = rest[0];
475
+ if (!title)
476
+ throw new Error(`Usage\n${usage()}`);
477
+ let type;
478
+ for (let i = 1; i < rest.length; i++) {
479
+ if (rest[i] === "--type" && rest[i + 1]) {
480
+ type = rest[i + 1];
481
+ i += 1;
482
+ }
483
+ }
484
+ return type
485
+ ? { kind: "task.create", title, type, ...(agent ? { agent } : {}) }
486
+ : { kind: "task.create", title, ...(agent ? { agent } : {}) };
487
+ }
488
+ if (sub === "update") {
489
+ const id = rest[0];
490
+ const status = rest[1];
491
+ if (!id || !status)
492
+ throw new Error(`Usage\n${usage()}`);
493
+ return { kind: "task.update", id, status, ...(agent ? { agent } : {}) };
494
+ }
495
+ if (sub === "show") {
496
+ const id = rest[0];
497
+ if (!id)
498
+ throw new Error(`Usage\n${usage()}`);
499
+ return { kind: "task.show", id, ...(agent ? { agent } : {}) };
500
+ }
501
+ if (sub === "actionable")
502
+ return { kind: "task.actionable", ...(agent ? { agent } : {}) };
503
+ if (sub === "deps")
504
+ return { kind: "task.deps", ...(agent ? { agent } : {}) };
505
+ if (sub === "sessions")
506
+ return { kind: "task.sessions", ...(agent ? { agent } : {}) };
507
+ throw new Error(`Usage\n${usage()}`);
508
+ }
509
+ function parseReminderCommand(args) {
510
+ const { agent, rest: cleaned } = extractAgentFlag(args);
511
+ const [sub, ...rest] = cleaned;
512
+ if (!sub)
513
+ throw new Error(`Usage\n${usage()}`);
514
+ if (sub === "create") {
515
+ const title = rest[0];
516
+ if (!title)
517
+ throw new Error(`Usage\n${usage()}`);
518
+ let body;
519
+ let scheduledAt;
520
+ let cadence;
521
+ let category;
522
+ for (let i = 1; i < rest.length; i++) {
523
+ if (rest[i] === "--body" && rest[i + 1]) {
524
+ body = rest[i + 1];
525
+ i += 1;
526
+ }
527
+ else if (rest[i] === "--at" && rest[i + 1]) {
528
+ scheduledAt = rest[i + 1];
529
+ i += 1;
530
+ }
531
+ else if (rest[i] === "--cadence" && rest[i + 1]) {
532
+ cadence = rest[i + 1];
533
+ i += 1;
534
+ }
535
+ else if (rest[i] === "--category" && rest[i + 1]) {
536
+ category = rest[i + 1];
537
+ i += 1;
538
+ }
539
+ }
540
+ if (!body)
541
+ throw new Error(`Usage\n${usage()}`);
542
+ if (!scheduledAt && !cadence)
543
+ throw new Error(`Usage\n${usage()}`);
544
+ return {
545
+ kind: "reminder.create",
546
+ title,
547
+ body,
548
+ ...(scheduledAt ? { scheduledAt } : {}),
549
+ ...(cadence ? { cadence } : {}),
550
+ ...(category ? { category } : {}),
551
+ ...(agent ? { agent } : {}),
552
+ };
553
+ }
554
+ throw new Error(`Usage\n${usage()}`);
555
+ }
556
+ function parseSessionCommand(args) {
557
+ const { agent, rest: cleaned } = extractAgentFlag(args);
558
+ const [sub] = cleaned;
559
+ if (!sub)
560
+ throw new Error(`Usage\n${usage()}`);
561
+ if (sub === "list")
562
+ return { kind: "session.list", ...(agent ? { agent } : {}) };
563
+ throw new Error(`Usage\n${usage()}`);
564
+ }
565
+ function parseFriendCommand(args) {
566
+ const { agent, rest: cleaned } = extractAgentFlag(args);
567
+ const [sub, ...rest] = cleaned;
568
+ if (!sub)
569
+ throw new Error(`Usage\n${usage()}`);
570
+ if (sub === "list")
571
+ return { kind: "friend.list", ...(agent ? { agent } : {}) };
572
+ if (sub === "show") {
573
+ const friendId = rest[0];
574
+ if (!friendId)
575
+ throw new Error(`Usage\n${usage()}`);
576
+ return { kind: "friend.show", friendId, ...(agent ? { agent } : {}) };
577
+ }
578
+ if (sub === "create") {
579
+ let name;
580
+ let trustLevel;
581
+ for (let i = 0; i < rest.length; i++) {
582
+ if (rest[i] === "--name" && rest[i + 1]) {
583
+ name = rest[i + 1];
584
+ i += 1;
585
+ }
586
+ else if (rest[i] === "--trust" && rest[i + 1]) {
587
+ trustLevel = rest[i + 1];
588
+ i += 1;
589
+ }
590
+ }
591
+ if (!name)
592
+ throw new Error(`Usage\n${usage()}`);
593
+ return {
594
+ kind: "friend.create",
595
+ name,
596
+ ...(trustLevel ? { trustLevel } : {}),
597
+ ...(agent ? { agent } : {}),
598
+ };
599
+ }
600
+ if (sub === "link")
601
+ return parseLinkCommand(rest, "friend.link");
602
+ if (sub === "unlink")
603
+ return parseLinkCommand(rest, "friend.unlink");
604
+ throw new Error(`Usage\n${usage()}`);
605
+ }
435
606
  function parseOuroCommand(args) {
436
607
  const [head, second] = args;
437
608
  if (!head)
@@ -446,6 +617,18 @@ function parseOuroCommand(args) {
446
617
  return { kind: "daemon.logs" };
447
618
  if (head === "hatch")
448
619
  return parseHatchCommand(args.slice(1));
620
+ if (head === "task")
621
+ return parseTaskCommand(args.slice(1));
622
+ if (head === "reminder")
623
+ return parseReminderCommand(args.slice(1));
624
+ if (head === "friend")
625
+ return parseFriendCommand(args.slice(1));
626
+ if (head === "whoami") {
627
+ const { agent } = extractAgentFlag(args.slice(1));
628
+ return { kind: "whoami", ...(agent ? { agent } : {}) };
629
+ }
630
+ if (head === "session")
631
+ return parseSessionCommand(args.slice(1));
449
632
  if (head === "chat") {
450
633
  if (!second)
451
634
  throw new Error(`Usage\n${usage()}`);
@@ -604,32 +787,6 @@ function defaultListDiscoveredAgents() {
604
787
  readFileSync: fs.readFileSync,
605
788
  });
606
789
  }
607
- async function defaultLinkFriendIdentity(command) {
608
- const fp = path.join((0, identity_1.getAgentBundlesRoot)(), `${command.agent}.ouro`, "friends");
609
- const friendStore = new store_file_1.FileFriendStore(fp);
610
- const current = await friendStore.get(command.friendId);
611
- if (!current) {
612
- return `friend not found: ${command.friendId}`;
613
- }
614
- const alreadyLinked = current.externalIds.some((ext) => ext.provider === command.provider && ext.externalId === command.externalId);
615
- if (alreadyLinked) {
616
- return `identity already linked: ${command.provider}:${command.externalId}`;
617
- }
618
- const now = new Date().toISOString();
619
- await friendStore.put(command.friendId, {
620
- ...current,
621
- externalIds: [
622
- ...current.externalIds,
623
- {
624
- provider: command.provider,
625
- externalId: command.externalId,
626
- linkedAt: now,
627
- },
628
- ],
629
- updatedAt: now,
630
- });
631
- return `linked ${command.provider}:${command.externalId} to ${command.friendId}`;
632
- }
633
790
  function discoverExistingCredentials(secretsRoot) {
634
791
  const found = [];
635
792
  let entries;
@@ -881,7 +1038,6 @@ function createDefaultOuroCliDeps(socketPath = "/tmp/ouroboros-daemon.sock") {
881
1038
  cleanupStaleSocket: defaultCleanupStaleSocket,
882
1039
  fallbackPendingMessage: defaultFallbackPendingMessage,
883
1040
  installSubagents: defaultInstallSubagents,
884
- linkFriendIdentity: defaultLinkFriendIdentity,
885
1041
  listDiscoveredAgents: defaultListDiscoveredAgents,
886
1042
  runHatchFlow: hatch_flow_1.runHatchFlow,
887
1043
  promptInput: defaultPromptInput,
@@ -996,6 +1152,178 @@ async function performSystemSetup(deps) {
996
1152
  // Register .ouro bundle type (UTI on macOS)
997
1153
  await registerOuroBundleTypeNonBlocking(deps);
998
1154
  }
1155
+ function executeTaskCommand(command, taskMod) {
1156
+ if (command.kind === "task.board") {
1157
+ if (command.status) {
1158
+ const lines = taskMod.boardStatus(command.status);
1159
+ return lines.length > 0 ? lines.join("\n") : "no tasks in that status";
1160
+ }
1161
+ const board = taskMod.getBoard();
1162
+ return board.full || board.compact || "no tasks found";
1163
+ }
1164
+ if (command.kind === "task.create") {
1165
+ try {
1166
+ const created = taskMod.createTask({
1167
+ title: command.title,
1168
+ type: command.type ?? "one-shot",
1169
+ category: "general",
1170
+ body: "",
1171
+ });
1172
+ return `created: ${created}`;
1173
+ }
1174
+ catch (error) {
1175
+ return `error: ${error instanceof Error ? error.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(error)}`;
1176
+ }
1177
+ }
1178
+ if (command.kind === "task.update") {
1179
+ const result = taskMod.updateStatus(command.id, command.status);
1180
+ if (!result.ok) {
1181
+ return `error: ${result.reason ?? "status update failed"}`;
1182
+ }
1183
+ const archivedSuffix = result.archived && result.archived.length > 0
1184
+ ? ` | archived: ${result.archived.join(", ")}`
1185
+ : "";
1186
+ return `updated: ${command.id} -> ${result.to}${archivedSuffix}`;
1187
+ }
1188
+ if (command.kind === "task.show") {
1189
+ const task = taskMod.getTask(command.id);
1190
+ if (!task)
1191
+ return `task not found: ${command.id}`;
1192
+ return [
1193
+ `title: ${task.title}`,
1194
+ `type: ${task.type}`,
1195
+ `status: ${task.status}`,
1196
+ `category: ${task.category}`,
1197
+ `created: ${task.created}`,
1198
+ `updated: ${task.updated}`,
1199
+ `path: ${task.path}`,
1200
+ task.body ? `\n${task.body}` : "",
1201
+ ].filter(Boolean).join("\n");
1202
+ }
1203
+ if (command.kind === "task.actionable") {
1204
+ const lines = taskMod.boardAction();
1205
+ return lines.length > 0 ? lines.join("\n") : "no action required";
1206
+ }
1207
+ if (command.kind === "task.deps") {
1208
+ const lines = taskMod.boardDeps();
1209
+ return lines.length > 0 ? lines.join("\n") : "no unresolved dependencies";
1210
+ }
1211
+ // command.kind === "task.sessions"
1212
+ const lines = taskMod.boardSessions();
1213
+ return lines.length > 0 ? lines.join("\n") : "no active sessions";
1214
+ }
1215
+ const TRUST_RANK = { family: 4, friend: 3, acquaintance: 2, stranger: 1 };
1216
+ /* v8 ignore start -- defensive: ?? fallbacks are unreachable when inputs are valid TrustLevel values @preserve */
1217
+ function higherTrust(a, b) {
1218
+ const rankA = TRUST_RANK[a ?? "stranger"] ?? 1;
1219
+ const rankB = TRUST_RANK[b ?? "stranger"] ?? 1;
1220
+ return rankA >= rankB ? (a ?? "stranger") : (b ?? "stranger");
1221
+ }
1222
+ /* v8 ignore stop */
1223
+ async function executeFriendCommand(command, store) {
1224
+ if (command.kind === "friend.list") {
1225
+ const listAll = store.listAll;
1226
+ if (!listAll)
1227
+ return "friend store does not support listing";
1228
+ const friends = await listAll.call(store);
1229
+ if (friends.length === 0)
1230
+ return "no friends found";
1231
+ const lines = friends.map((f) => {
1232
+ const trust = f.trustLevel ?? "unknown";
1233
+ return `${f.id} ${f.name} ${trust}`;
1234
+ });
1235
+ return lines.join("\n");
1236
+ }
1237
+ if (command.kind === "friend.show") {
1238
+ const record = await store.get(command.friendId);
1239
+ if (!record)
1240
+ return `friend not found: ${command.friendId}`;
1241
+ return JSON.stringify(record, null, 2);
1242
+ }
1243
+ if (command.kind === "friend.create") {
1244
+ const now = new Date().toISOString();
1245
+ const id = (0, crypto_1.randomUUID)();
1246
+ const trustLevel = (command.trustLevel ?? "acquaintance");
1247
+ await store.put(id, {
1248
+ id,
1249
+ name: command.name,
1250
+ trustLevel,
1251
+ externalIds: [],
1252
+ tenantMemberships: [],
1253
+ toolPreferences: {},
1254
+ notes: {},
1255
+ totalTokens: 0,
1256
+ createdAt: now,
1257
+ updatedAt: now,
1258
+ schemaVersion: 1,
1259
+ });
1260
+ return `created: ${id} (${command.name}, ${trustLevel})`;
1261
+ }
1262
+ if (command.kind === "friend.link") {
1263
+ const current = await store.get(command.friendId);
1264
+ if (!current)
1265
+ return `friend not found: ${command.friendId}`;
1266
+ const alreadyLinked = current.externalIds.some((ext) => ext.provider === command.provider && ext.externalId === command.externalId);
1267
+ if (alreadyLinked)
1268
+ return `identity already linked: ${command.provider}:${command.externalId}`;
1269
+ const now = new Date().toISOString();
1270
+ const newExternalIds = [
1271
+ ...current.externalIds,
1272
+ { provider: command.provider, externalId: command.externalId, linkedAt: now },
1273
+ ];
1274
+ // Orphan cleanup: check if another friend has this externalId
1275
+ const orphan = await store.findByExternalId(command.provider, command.externalId);
1276
+ let mergeMessage = "";
1277
+ let mergedNotes = { ...current.notes };
1278
+ let mergedTrust = current.trustLevel;
1279
+ let orphanExternalIds = [];
1280
+ if (orphan && orphan.id !== command.friendId) {
1281
+ // Merge orphan's notes (target's notes take priority)
1282
+ mergedNotes = { ...orphan.notes, ...current.notes };
1283
+ // Keep higher trust level
1284
+ mergedTrust = higherTrust(current.trustLevel, orphan.trustLevel);
1285
+ // Collect orphan's other externalIds (excluding the one being linked)
1286
+ orphanExternalIds = orphan.externalIds.filter((ext) => !(ext.provider === command.provider && ext.externalId === command.externalId));
1287
+ await store.delete(orphan.id);
1288
+ mergeMessage = ` (merged orphan ${orphan.id})`;
1289
+ }
1290
+ await store.put(command.friendId, {
1291
+ ...current,
1292
+ externalIds: [...newExternalIds, ...orphanExternalIds],
1293
+ notes: mergedNotes,
1294
+ trustLevel: mergedTrust,
1295
+ updatedAt: now,
1296
+ });
1297
+ return `linked ${command.provider}:${command.externalId} to ${command.friendId}${mergeMessage}`;
1298
+ }
1299
+ // command.kind === "friend.unlink"
1300
+ const current = await store.get(command.friendId);
1301
+ if (!current)
1302
+ return `friend not found: ${command.friendId}`;
1303
+ const idx = current.externalIds.findIndex((ext) => ext.provider === command.provider && ext.externalId === command.externalId);
1304
+ if (idx === -1)
1305
+ return `identity not linked: ${command.provider}:${command.externalId}`;
1306
+ const now = new Date().toISOString();
1307
+ const filtered = current.externalIds.filter((_, i) => i !== idx);
1308
+ await store.put(command.friendId, { ...current, externalIds: filtered, updatedAt: now });
1309
+ return `unlinked ${command.provider}:${command.externalId} from ${command.friendId}`;
1310
+ }
1311
+ function executeReminderCommand(command, taskMod) {
1312
+ try {
1313
+ const created = taskMod.createTask({
1314
+ title: command.title,
1315
+ type: command.cadence ? "habit" : "one-shot",
1316
+ category: command.category ?? "reminder",
1317
+ body: command.body,
1318
+ scheduledAt: command.scheduledAt,
1319
+ cadence: command.cadence,
1320
+ });
1321
+ return `created: ${created}`;
1322
+ }
1323
+ catch (error) {
1324
+ return `error: ${error instanceof Error ? error.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(error)}`;
1325
+ }
1326
+ }
999
1327
  async function runOuroCli(args, deps = createDefaultOuroCliDeps()) {
1000
1328
  if (args.includes("--help") || args.includes("-h")) {
1001
1329
  const text = usage();
@@ -1099,9 +1427,93 @@ async function runOuroCli(args, deps = createDefaultOuroCliDeps()) {
1099
1427
  deps.tailLogs();
1100
1428
  return "";
1101
1429
  }
1102
- if (command.kind === "friend.link") {
1103
- const linker = deps.linkFriendIdentity ?? defaultLinkFriendIdentity;
1104
- const message = await linker(command);
1430
+ // ── task subcommands (local, no daemon socket needed) ──
1431
+ if (command.kind === "task.board" || command.kind === "task.create" || command.kind === "task.update" ||
1432
+ command.kind === "task.show" || command.kind === "task.actionable" || command.kind === "task.deps" ||
1433
+ command.kind === "task.sessions") {
1434
+ /* v8 ignore start -- production default: requires full identity setup @preserve */
1435
+ const taskMod = deps.taskModule ?? (0, tasks_1.getTaskModule)();
1436
+ /* v8 ignore stop */
1437
+ const message = executeTaskCommand(command, taskMod);
1438
+ deps.writeStdout(message);
1439
+ return message;
1440
+ }
1441
+ // ── reminder subcommands (local, no daemon socket needed) ──
1442
+ if (command.kind === "reminder.create") {
1443
+ /* v8 ignore start -- production default: requires full identity setup @preserve */
1444
+ const taskMod = deps.taskModule ?? (0, tasks_1.getTaskModule)();
1445
+ /* v8 ignore stop */
1446
+ const message = executeReminderCommand(command, taskMod);
1447
+ deps.writeStdout(message);
1448
+ return message;
1449
+ }
1450
+ // ── friend subcommands (local, no daemon socket needed) ──
1451
+ if (command.kind === "friend.list" || command.kind === "friend.show" || command.kind === "friend.create" ||
1452
+ command.kind === "friend.link" || command.kind === "friend.unlink") {
1453
+ /* v8 ignore start -- production default: requires full identity setup @preserve */
1454
+ let store = deps.friendStore;
1455
+ if (!store) {
1456
+ // Derive agent-scoped friends dir from --agent flag or link/unlink's agent field
1457
+ const agentName = ("agent" in command && command.agent) ? command.agent : undefined;
1458
+ const friendsDir = agentName
1459
+ ? path.join((0, identity_1.getAgentBundlesRoot)(), `${agentName}.ouro`, "friends")
1460
+ : path.join((0, identity_1.getAgentBundlesRoot)(), "friends");
1461
+ store = new store_file_1.FileFriendStore(friendsDir);
1462
+ }
1463
+ /* v8 ignore stop */
1464
+ const message = await executeFriendCommand(command, store);
1465
+ deps.writeStdout(message);
1466
+ return message;
1467
+ }
1468
+ // ── whoami (local, no daemon socket needed) ──
1469
+ if (command.kind === "whoami") {
1470
+ if (command.agent) {
1471
+ const agentRoot = path.join((0, identity_1.getAgentBundlesRoot)(), `${command.agent}.ouro`);
1472
+ const message = [
1473
+ `agent: ${command.agent}`,
1474
+ `home: ${agentRoot}`,
1475
+ `bones: ${(0, runtime_metadata_1.getRuntimeMetadata)().version}`,
1476
+ ].join("\n");
1477
+ deps.writeStdout(message);
1478
+ return message;
1479
+ }
1480
+ /* v8 ignore start -- production default: requires full identity setup @preserve */
1481
+ try {
1482
+ const info = deps.whoamiInfo
1483
+ ? deps.whoamiInfo()
1484
+ : {
1485
+ agentName: (0, identity_1.getAgentName)(),
1486
+ homePath: path.join((0, identity_1.getAgentBundlesRoot)(), `${(0, identity_1.getAgentName)()}.ouro`),
1487
+ bonesVersion: (0, runtime_metadata_1.getRuntimeMetadata)().version,
1488
+ };
1489
+ const message = [
1490
+ `agent: ${info.agentName}`,
1491
+ `home: ${info.homePath}`,
1492
+ `bones: ${info.bonesVersion}`,
1493
+ ].join("\n");
1494
+ deps.writeStdout(message);
1495
+ return message;
1496
+ }
1497
+ catch {
1498
+ const message = "error: no agent context — use --agent <name> to specify";
1499
+ deps.writeStdout(message);
1500
+ return message;
1501
+ }
1502
+ /* v8 ignore stop */
1503
+ }
1504
+ // ── session list (local, no daemon socket needed) ──
1505
+ if (command.kind === "session.list") {
1506
+ /* v8 ignore start -- production default: requires full identity setup @preserve */
1507
+ const scanner = deps.scanSessions ?? (async () => []);
1508
+ /* v8 ignore stop */
1509
+ const sessions = await scanner();
1510
+ if (sessions.length === 0) {
1511
+ const message = "no active sessions";
1512
+ deps.writeStdout(message);
1513
+ return message;
1514
+ }
1515
+ const lines = sessions.map((s) => `${s.friendId} ${s.friendName} ${s.channel} ${s.lastActivity}`);
1516
+ const message = lines.join("\n");
1105
1517
  deps.writeStdout(message);
1106
1518
  return message;
1107
1519
  }