synomem 0.9.6 → 0.10.0

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 (118) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/README.md +5 -3
  3. package/dist/actor-directory.d.ts +34 -0
  4. package/dist/actor-directory.d.ts.map +1 -0
  5. package/dist/actor-directory.js +52 -0
  6. package/dist/actor-directory.js.map +1 -0
  7. package/dist/backend.d.ts +3 -0
  8. package/dist/backend.d.ts.map +1 -1
  9. package/dist/backend.js +26 -3
  10. package/dist/backend.js.map +1 -1
  11. package/dist/bookmarks.d.ts +30 -0
  12. package/dist/bookmarks.d.ts.map +1 -0
  13. package/dist/bookmarks.js +87 -0
  14. package/dist/bookmarks.js.map +1 -0
  15. package/dist/cli.d.ts.map +1 -1
  16. package/dist/cli.js +291 -61
  17. package/dist/cli.js.map +1 -1
  18. package/dist/client.d.ts +115 -4
  19. package/dist/client.d.ts.map +1 -1
  20. package/dist/client.js +854 -138
  21. package/dist/client.js.map +1 -1
  22. package/dist/cursors.d.ts +30 -0
  23. package/dist/cursors.d.ts.map +1 -0
  24. package/dist/cursors.js +101 -0
  25. package/dist/cursors.js.map +1 -0
  26. package/dist/errors.d.ts +1 -1
  27. package/dist/errors.d.ts.map +1 -1
  28. package/dist/errors.js +2 -0
  29. package/dist/errors.js.map +1 -1
  30. package/dist/import.d.ts +605 -39
  31. package/dist/import.d.ts.map +1 -1
  32. package/dist/index.d.ts +19 -1
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +11 -1
  35. package/dist/index.js.map +1 -1
  36. package/dist/mcp/index.d.ts +1 -1
  37. package/dist/mcp/index.d.ts.map +1 -1
  38. package/dist/mcp/index.js +329 -22
  39. package/dist/mcp/index.js.map +1 -1
  40. package/dist/mutation-receipts.d.ts +8 -0
  41. package/dist/mutation-receipts.d.ts.map +1 -0
  42. package/dist/mutation-receipts.js +2 -0
  43. package/dist/mutation-receipts.js.map +1 -0
  44. package/dist/notifications.d.ts +73 -0
  45. package/dist/notifications.d.ts.map +1 -0
  46. package/dist/notifications.js +309 -0
  47. package/dist/notifications.js.map +1 -0
  48. package/dist/participation-repository.d.ts +46 -0
  49. package/dist/participation-repository.d.ts.map +1 -0
  50. package/dist/participation-repository.js +296 -0
  51. package/dist/participation-repository.js.map +1 -0
  52. package/dist/participation.d.ts +97 -0
  53. package/dist/participation.d.ts.map +1 -0
  54. package/dist/participation.js +10 -0
  55. package/dist/participation.js.map +1 -0
  56. package/dist/policy.d.ts +31 -0
  57. package/dist/policy.d.ts.map +1 -0
  58. package/dist/policy.js +71 -0
  59. package/dist/policy.js.map +1 -0
  60. package/dist/ports/repository.d.ts +23 -0
  61. package/dist/ports/repository.d.ts.map +1 -1
  62. package/dist/profiles.d.ts +12 -0
  63. package/dist/profiles.d.ts.map +1 -1
  64. package/dist/profiles.js +2 -1
  65. package/dist/profiles.js.map +1 -1
  66. package/dist/projections.d.ts.map +1 -1
  67. package/dist/projections.js +29 -11
  68. package/dist/projections.js.map +1 -1
  69. package/dist/remote.d.ts +79 -0
  70. package/dist/remote.d.ts.map +1 -1
  71. package/dist/remote.js +65 -6
  72. package/dist/remote.js.map +1 -1
  73. package/dist/schemas.d.ts +746 -58
  74. package/dist/schemas.d.ts.map +1 -1
  75. package/dist/schemas.js +114 -16
  76. package/dist/schemas.js.map +1 -1
  77. package/dist/search.d.ts +25 -0
  78. package/dist/search.d.ts.map +1 -0
  79. package/dist/search.js +2 -0
  80. package/dist/search.js.map +1 -0
  81. package/dist/service.d.ts +89 -0
  82. package/dist/service.d.ts.map +1 -1
  83. package/dist/storage.d.ts +38 -3
  84. package/dist/storage.d.ts.map +1 -1
  85. package/dist/storage.js +494 -365
  86. package/dist/storage.js.map +1 -1
  87. package/dist/types.d.ts +65 -34
  88. package/dist/types.d.ts.map +1 -1
  89. package/docs/cli.md +6 -0
  90. package/docs/human-participation.md +20 -0
  91. package/docs/mcp.md +2 -0
  92. package/docs/storage-format.md +4 -15
  93. package/openapi/synomem-v1.yaml +187 -10
  94. package/package.json +2 -1
  95. package/skills/synomem/SKILL.md +2 -0
  96. package/src/actor-directory.ts +87 -0
  97. package/src/backend.ts +29 -3
  98. package/src/bookmarks.ts +127 -0
  99. package/src/cli.ts +941 -546
  100. package/src/client.ts +1143 -279
  101. package/src/cursors.ts +132 -0
  102. package/src/errors.ts +2 -0
  103. package/src/index.ts +50 -0
  104. package/src/mcp/index.ts +412 -96
  105. package/src/mutation-receipts.ts +7 -0
  106. package/src/notifications.ts +483 -0
  107. package/src/participation-repository.ts +406 -0
  108. package/src/participation.ts +103 -0
  109. package/src/policy.ts +109 -0
  110. package/src/ports/repository.ts +33 -0
  111. package/src/profiles.ts +2 -1
  112. package/src/projections.ts +49 -48
  113. package/src/remote.ts +227 -37
  114. package/src/schemas.ts +116 -16
  115. package/src/search.ts +24 -0
  116. package/src/service.ts +122 -8
  117. package/src/storage.ts +1039 -697
  118. package/src/types.ts +67 -32
package/src/cli.ts CHANGED
@@ -1,10 +1,15 @@
1
1
  #!/usr/bin/env node
2
+ import type { RecordKind as HpRecordKind } from './types.js';
3
+ import type { BookmarkInput as HpBookmarkInput } from './bookmarks.js';
4
+ import type { NotificationInput as HpNotificationInput } from './notifications.js';
5
+ import type { ReactionCode as HpReactionCode } from './participation.js';
6
+ import type { ActorRef } from './policy.js';
2
7
  import { existsSync, readFileSync, realpathSync, rmSync } from 'node:fs';
3
8
  import { spawn } from 'node:child_process';
4
9
  import { join, resolve } from 'node:path';
5
10
  import { pathToFileURL } from 'node:url';
6
11
  import { Command, CommanderError, Option } from 'commander';
7
- import { ensureLocalStore, LOCAL_OPERATOR, openLocalService } from './backend.js';
12
+ import { ensureLocalStore, openLocalOwnerService, exportRawLocalEvents } from './backend.js';
8
13
  import { cloudApiUrl } from './cloud.js';
9
14
  import { resolveHome } from './config.js';
10
15
  import {
@@ -78,12 +83,10 @@ import type {
78
83
  } from './types.js';
79
84
  import { packageVersion } from './version.js';
80
85
  import { listLocalWorkspaces, localWorkspaceHome } from './workspaces.js';
81
-
82
86
  export interface CliIo {
83
87
  stdout: (text: string) => void;
84
88
  stderr: (text: string) => void;
85
89
  }
86
-
87
90
  export interface CliDependencies {
88
91
  /** Injected so interactive steps can be driven by a test without a terminal. */
89
92
  promptIo?: PromptIo;
@@ -106,14 +109,11 @@ export interface CliDependencies {
106
109
  planId?: string;
107
110
  }) => Promise<ImportPreview | ImportResult>;
108
111
  }
109
-
110
112
  const defaultIo: CliIo = {
111
113
  stdout: (text) => process.stdout.write(text),
112
114
  stderr: (text) => process.stderr.write(text),
113
115
  };
114
-
115
116
  const cliExitCodes = new WeakMap<Command, number>();
116
-
117
117
  /**
118
118
  * Identity overrides the profile model replaced. Named here so using one fails
119
119
  * with a message that points at `--profile`, rather than a generic unknown
@@ -135,7 +135,6 @@ const OBSOLETE_ENV = [
135
135
  'SYNOMEM_AGENT_ID',
136
136
  'SYNOMEM_WORKSPACE',
137
137
  ];
138
-
139
138
  function obsoleteIdentityInput(argv: string[], env: NodeJS.ProcessEnv): string | undefined {
140
139
  const args = argv.slice(2);
141
140
  for (const argument of args) {
@@ -156,11 +155,9 @@ function obsoleteIdentityInput(argv: string[], env: NodeJS.ProcessEnv): string |
156
155
  }
157
156
  return OBSOLETE_ENV.find((name) => env[name]?.trim());
158
157
  }
159
-
160
158
  function collect(value: string, previous: string[]): string[] {
161
159
  return [...previous, value];
162
160
  }
163
-
164
161
  function skillRuntimes(values: string[]): SkillRuntime[] | undefined {
165
162
  const normalized = values.map((value) => (value === 'grokbot' ? 'grok' : value));
166
163
  const invalid = normalized.find(
@@ -172,9 +169,7 @@ function skillRuntimes(values: string[]): SkillRuntime[] | undefined {
172
169
  if (!normalized.length || normalized.includes('all')) return undefined;
173
170
  return normalized as SkillRuntime[];
174
171
  }
175
-
176
172
  const skillRuntimeHelp = `${skillRuntimeNames.join(', ')}, grokbot (alias for grok), or all`;
177
-
178
173
  function parseEvidence(value: string): EvidenceReference {
179
174
  const separator = value.indexOf(':');
180
175
  if (separator < 1 || separator === value.length - 1) {
@@ -185,7 +180,6 @@ function parseEvidence(value: string): EvidenceReference {
185
180
  value: value.slice(separator + 1),
186
181
  };
187
182
  }
188
-
189
183
  function taskDue(options: {
190
184
  dueDate?: string;
191
185
  dueAt?: string;
@@ -201,7 +195,6 @@ function taskDue(options: {
201
195
  }
202
196
  return undefined;
203
197
  }
204
-
205
198
  function exitCode(code: SynomemErrorCode): number {
206
199
  if (code.endsWith('_NOT_FOUND')) return 3;
207
200
  if (
@@ -218,7 +211,6 @@ function exitCode(code: SynomemErrorCode): number {
218
211
  if (code === 'INTERNAL_ERROR') return 1;
219
212
  return 2;
220
213
  }
221
-
222
214
  function lineForSummary(record: KudosSummary): string {
223
215
  const state =
224
216
  record.revocationStatus === 'revoked'
@@ -226,19 +218,19 @@ function lineForSummary(record: KudosSummary): string {
226
218
  : record.status === 'acknowledged'
227
219
  ? 'acknowledged'
228
220
  : 'new';
229
- return `${record.id} ${record.createdAt.slice(0, 10)} ${record.recipientAgentId} [${state}] ${record.title}`;
221
+ return `${record.id} ${record.createdAt.slice(0, 10)} ${record.recipient?.id} [${state}] ${record.title}`;
230
222
  }
231
-
232
223
  function lineForItem(item: ItemSummary): string {
233
224
  return `${item.id} ${item.createdAt.slice(0, 10)} ${item.kind.padEnd(5)} [${item.status}] ${item.title}`;
234
225
  }
235
-
236
226
  function itemListInput(options: Record<string, string | string[]>): ItemListInput {
237
227
  return {
238
228
  ...(Array.isArray(options.kind) && options.kind.length
239
229
  ? { kinds: options.kind as ItemListInput['kinds'] }
240
230
  : {}),
241
- ...(typeof options.participant === 'string' ? { participantAgentId: options.participant } : {}),
231
+ ...(typeof options.participant === 'string'
232
+ ? { participant: targetRef(options.participant) }
233
+ : {}),
242
234
  ...(typeof options.author === 'string' ? { actorId: options.author } : {}),
243
235
  ...(typeof options.status === 'string' ? { status: options.status } : {}),
244
236
  ...(typeof options.tag === 'string' ? { tag: options.tag } : {}),
@@ -251,14 +243,20 @@ function itemListInput(options: Record<string, string | string[]>): ItemListInpu
251
243
  offset: Number(options.offset ?? 0),
252
244
  };
253
245
  }
254
-
246
+ function targetRef(value: string): ActorRef {
247
+ const match = /^(human|agent):(.+)$/.exec(value);
248
+ if (match) return { kind: match[1] as 'human' | 'agent', id: match[2]! };
249
+ if (value.includes(':'))
250
+ throw new SynomemError('INVALID_INPUT', 'Use human:<handle> or agent:<handle>.');
251
+ return { kind: 'agent', id: value };
252
+ }
255
253
  function showRecord(record: KudosRecord): string {
256
254
  const event = record.event;
257
255
  const evidence = event.evidence?.map((item) => ` - ${item.kind}: ${item.value}`).join('\n');
258
256
  return [
259
257
  event.title,
260
258
  `ID: ${event.id}`,
261
- `Recipient: ${event.recipientDisplayName} (${event.recipientAgentId})`,
259
+ `Recipient: ${event.recipientDisplayName} (${event.recipient?.id})`,
262
260
  `From: ${event.actor.displayName ?? event.actor.id} (${event.actor.kind}:${event.actor.id})`,
263
261
  `Date: ${event.createdAt}`,
264
262
  `Visibility: ${event.visibility}`,
@@ -274,11 +272,9 @@ function showRecord(record: KudosRecord): string {
274
272
  .filter((value) => value !== undefined)
275
273
  .join('\n');
276
274
  }
277
-
278
275
  function output(io: CliIo, json: boolean, value: unknown, human: string): void {
279
276
  io.stdout(json ? `${JSON.stringify(value, null, 2)}\n` : `${human}\n`);
280
277
  }
281
-
282
278
  function addListOptions(command: Command): Command {
283
279
  return command
284
280
  .option('--recipient <agent>')
@@ -297,10 +293,9 @@ function addListOptions(command: Command): Command {
297
293
  .option('--cursor <cursor>', 'opaque cursor returned by the previous page')
298
294
  .option('--offset <number>', 'pagination offset', '0');
299
295
  }
300
-
301
296
  function listInput(options: Record<string, string>): KudosListInput {
302
297
  return {
303
- ...(options.recipient ? { recipientAgentId: options.recipient } : {}),
298
+ ...(options.recipient ? { recipient: targetRef(options.recipient) } : {}),
304
299
  ...(options.author ? { actorId: options.author } : {}),
305
300
  ...(options.authorKind ? { actorKind: options.authorKind as KudosListInput['actorKind'] } : {}),
306
301
  ...(options.tag ? { tag: options.tag } : {}),
@@ -318,11 +313,9 @@ function listInput(options: Record<string, string>): KudosListInput {
318
313
  offset: Number(options.offset),
319
314
  };
320
315
  }
321
-
322
316
  function describeEffective(context: EffectiveContext): string {
323
317
  return `${context.actor.displayName ?? context.actor.id} (${context.actor.kind}:${context.actor.id}) in ${context.workspaceId}`;
324
318
  }
325
-
326
319
  interface Globals {
327
320
  home: string;
328
321
  explicitHome?: string;
@@ -330,7 +323,6 @@ interface Globals {
330
323
  profile?: string;
331
324
  preset?: string;
332
325
  }
333
-
334
326
  export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies = {}): Command {
335
327
  const env = dependencies.env ?? process.env;
336
328
  const cwd = dependencies.cwd ?? process.cwd();
@@ -340,7 +332,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
340
332
  const oauthLogin = dependencies.oauthLogin ?? loginWithOAuth;
341
333
  const storesFor = dependencies.credentialStores ?? defaultCredentialStores;
342
334
  const profileStoreFor = dependencies.profileStore ?? ((home: string) => new ProfileStore(home));
343
-
344
335
  const globals = (command: Command): Globals => {
345
336
  const options = command.optsWithGlobals<{
346
337
  home?: string;
@@ -356,7 +347,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
356
347
  ...(options.preset ? { preset: options.preset } : {}),
357
348
  };
358
349
  };
359
-
360
350
  const resolverDeps = (home: string): ResolverDependencies => ({
361
351
  stores: storesFor(home),
362
352
  env,
@@ -368,7 +358,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
368
358
  ? { createLocalResolver: dependencies.createLocalResolver }
369
359
  : {}),
370
360
  });
371
-
372
361
  const selectionFor = (global: Globals, config: ProfilesConfig) =>
373
362
  resolveSelection(config, {
374
363
  ...(global.profile ? { profile: global.profile } : {}),
@@ -377,7 +366,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
377
366
  cwd,
378
367
  home: global.home,
379
368
  });
380
-
381
369
  /**
382
370
  * Runs a domain command as the selected profile's single context. Every
383
371
  * acting identity comes from here — there is no per-command actor flag.
@@ -413,7 +401,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
413
401
  await resolver.close?.();
414
402
  }
415
403
  };
416
-
417
404
  /**
418
405
  * Store administration: agent management, rebuild, backup, export, doctor.
419
406
  *
@@ -442,14 +429,13 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
442
429
  }
443
430
  const storeHome =
444
431
  profile && isLocalProfile(profile) ? (profile.home ?? global.home) : global.home;
445
- const client = await openLocalService(storeHome, LOCAL_OPERATOR);
432
+ const client = await openLocalOwnerService(storeHome);
446
433
  try {
447
434
  return await operation(client, undefined, storeHome);
448
435
  } finally {
449
436
  await client.close();
450
437
  }
451
438
  };
452
-
453
439
  const program = new Command();
454
440
  cliExitCodes.set(program, 0);
455
441
  program
@@ -462,9 +448,69 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
462
448
  .option('--json', 'emit stable machine-readable JSON', false)
463
449
  .showSuggestionAfterError()
464
450
  .configureOutput({ writeOut: io.stdout, writeErr: io.stderr });
465
-
451
+ const humanCommand = program.command('human').description('Local human identities');
452
+ humanCommand
453
+ .command('register <handle>')
454
+ .requiredOption('--name <display-name>')
455
+ .action(async (handle: string, options: { name: string }, command: Command) => {
456
+ await withManagement(command, async (service) => {
457
+ const human = await service.actors.registerHuman({
458
+ id: handle,
459
+ handle,
460
+ displayName: options.name,
461
+ });
462
+ output(
463
+ io,
464
+ globals(command).json,
465
+ human,
466
+ `Registered ${human.displayName} (human:${human.handle}).`,
467
+ );
468
+ });
469
+ });
470
+ program
471
+ .command('actors')
472
+ .description('Search addressable humans and agents')
473
+ .option('--query <text>', 'Search name, handle or ID')
474
+ .option('--kind <kind>', 'human or agent')
475
+ .option('--cursor <cursor>', 'Signed continuation cursor')
476
+ .option('--limit <number>', 'Maximum actors', Number, 20)
477
+ .action(
478
+ async (
479
+ options: { query?: string; kind?: 'human' | 'agent'; cursor?: string; limit: number },
480
+ command: Command,
481
+ ) => {
482
+ await withProfile(command, async (service) => {
483
+ const page = await service.actors.list(options);
484
+ output(
485
+ io,
486
+ globals(command).json,
487
+ page,
488
+ page.items
489
+ .map((actor) => `${actor.kind}:${actor.handle} — ${actor.displayName}`)
490
+ .join('\n') + (page.nextCursor ? `\nNext cursor: ${page.nextCursor}` : ''),
491
+ );
492
+ });
493
+ },
494
+ );
495
+ program
496
+ .command('actor-profile <target>')
497
+ .description('Visible authored records and separate kudos/useful counts')
498
+ .option('--after <cursor>', 'Continue authored record page')
499
+ .option('--limit <number>', 'Maximum records', Number, 20)
500
+ .action(
501
+ async (target: string, options: { after?: string; limit: number }, command: Command) => {
502
+ await withProfile(command, async (service) => {
503
+ const profile = await service.actors.profile({ target: targetRef(target), ...options });
504
+ output(
505
+ io,
506
+ globals(command).json,
507
+ profile,
508
+ `${profile.actor.displayName}: ${profile.counts.kudosReceived} kudos, ${profile.counts.usefulReceived} useful reactions`,
509
+ );
510
+ });
511
+ },
512
+ );
466
513
  /* ------------------------------------------------------------ setup */
467
-
468
514
  program
469
515
  .command('setup')
470
516
  .description('Set up a local store with its first agent and a matching fixed profile')
@@ -506,9 +552,8 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
506
552
  const store = profileStoreFor(global.home);
507
553
  ensureLocalStore(global.home);
508
554
  const config = store.read();
509
-
510
555
  const existing = config.profiles[profileName];
511
- const client = await openLocalService(global.home, LOCAL_OPERATOR);
556
+ const client = await openLocalOwnerService(global.home);
512
557
  let agent;
513
558
  let resumed = false;
514
559
  try {
@@ -556,7 +601,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
556
601
  } finally {
557
602
  await client.close();
558
603
  }
559
-
560
604
  const actor = { kind: 'agent' as const, id: agent.id };
561
605
  const profile: LocalProfile = {
562
606
  backend: 'local',
@@ -597,13 +641,10 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
597
641
  );
598
642
  },
599
643
  );
600
-
601
644
  /* ------------------------------------------------------------ connections */
602
-
603
645
  const connectionCommand = program
604
646
  .command('connection')
605
647
  .description('Hosted credentials: one per harness installation, shared by its profiles');
606
-
607
648
  const saveCredential = async (
608
649
  global: Globals,
609
650
  name: string,
@@ -619,11 +660,13 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
619
660
  store.write(next);
620
661
  return next;
621
662
  };
622
-
623
663
  const verifyConnection = async (
624
664
  apiUrl: string,
625
665
  bearer: string,
626
- ): Promise<{ identity: IdentityDescription; contexts: ContextSummary[] }> => {
666
+ ): Promise<{
667
+ identity: IdentityDescription;
668
+ contexts: ContextSummary[];
669
+ }> => {
627
670
  const identity = await describeIdentity({
628
671
  baseUrl: apiUrl,
629
672
  accessToken: bearer,
@@ -636,7 +679,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
636
679
  });
637
680
  return { identity, contexts: listing.contexts };
638
681
  };
639
-
640
682
  const connectionSummary = (
641
683
  name: string,
642
684
  identity: IdentityDescription,
@@ -654,7 +696,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
654
696
  ? `Next: synomem profile create <name> --connection ${name} --context <context-id>`
655
697
  : 'Authorize an agent for it on the consent screen or in the portal.',
656
698
  ].join('\n');
657
-
658
699
  connectionCommand
659
700
  .command('login')
660
701
  .description('Sign in through the browser (OAuth 2.1 + PKCE) and store the credential')
@@ -695,7 +736,7 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
695
736
  );
696
737
  }
697
738
  const callbackPort = Number(options.callbackPort);
698
- if (!Number.isSafeInteger(callbackPort) || callbackPort < 1 || callbackPort > 65_535) {
739
+ if (!Number.isSafeInteger(callbackPort) || callbackPort < 1 || callbackPort > 65535) {
699
740
  throw new SynomemError('INVALID_INPUT', '--callback-port must be from 1 through 65535.');
700
741
  }
701
742
  const credential = await oauthLogin({
@@ -729,7 +770,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
729
770
  );
730
771
  },
731
772
  );
732
-
733
773
  connectionCommand
734
774
  .command('add-key')
735
775
  .description('Store a member-owned access key (read from stdin, never an argument)')
@@ -737,7 +777,14 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
737
777
  .option('--api-url <url>', 'internal: alternate API origin')
738
778
  .option('--store <where>', 'keychain (default), file, or environment')
739
779
  .action(
740
- async (options: { name: string; apiUrl?: string; store?: string }, command: Command) => {
780
+ async (
781
+ options: {
782
+ name: string;
783
+ apiUrl?: string;
784
+ store?: string;
785
+ },
786
+ command: Command,
787
+ ) => {
741
788
  const global = globals(command);
742
789
  const name = assertName(options.name, 'connection');
743
790
  const apiUrl = secureUrl(options.apiUrl ?? cloudApiUrl(env), 'API URL').origin;
@@ -793,7 +840,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
793
840
  );
794
841
  },
795
842
  );
796
-
797
843
  connectionCommand
798
844
  .command('list')
799
845
  .description('List connections and the profiles using each, without secrets')
@@ -819,15 +865,12 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
819
865
  ? connections
820
866
  .map(
821
867
  (connection) =>
822
- `${connection.name} ${connection.kind} ${connection.store} ${connection.label ?? ''}\n profiles: ${
823
- connection.profiles.join(', ') || 'none'
824
- }`,
868
+ `${connection.name} ${connection.kind} ${connection.store} ${connection.label ?? ''}\n profiles: ${connection.profiles.join(', ') || 'none'}`,
825
869
  )
826
870
  .join('\n')
827
871
  : 'No connections. Run `synomem connection login --name <name>`.',
828
872
  );
829
873
  });
830
-
831
874
  connectionCommand
832
875
  .command('status [name]')
833
876
  .description('Check that a connection (or every connection) authenticates')
@@ -875,80 +918,93 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
875
918
  .map((result) =>
876
919
  result.ok
877
920
  ? `${String(result.name)} ok (${String(result.store)}) — ${String(result.contexts)} context(s)`
878
- : `${String(result.name)} FAILED ${(result.error as { message: string }).message}`,
921
+ : `${String(result.name)} FAILED ${
922
+ (
923
+ result.error as {
924
+ message: string;
925
+ }
926
+ ).message
927
+ }`,
879
928
  )
880
929
  .join('\n')
881
930
  : 'No connections.',
882
931
  );
883
932
  if (failed) cliExitCodes.set(program, 4);
884
933
  });
885
-
886
934
  connectionCommand
887
935
  .command('remove')
888
936
  .description('Forget a connection and delete its locally stored secret')
889
937
  .requiredOption('--name <name>')
890
938
  .option('--force', 'also remove the profiles that use it', false)
891
- .action(async (options: { name: string; force: boolean }, command: Command) => {
892
- const global = globals(command);
893
- const store = profileStoreFor(global.home);
894
- const config = store.read();
895
- const entry = config.credentials[options.name];
896
- if (!entry) throw new SynomemError('CONFIG_INVALID', `Unknown connection "${options.name}".`);
897
- const dependents = Object.entries(config.profiles)
898
- .filter(([, profile]) => !isLocalProfile(profile) && profile.credentialRef === options.name)
899
- .map(([profileName]) => profileName);
900
- if (dependents.length && !options.force) {
901
- throw new SynomemError(
902
- 'INVALID_INPUT',
903
- `Profiles ${dependents.join(', ')} use connection ${options.name}. Remove them first, or pass --force to remove them too.`,
904
- );
905
- }
906
- if (entry.store !== 'environment') {
907
- await storeFor(storesFor(global.home), entry).delete(entry.secretRef!);
908
- }
909
- const profiles = Object.fromEntries(
910
- Object.entries(config.profiles).filter(
911
- ([profileName]) => !dependents.includes(profileName),
912
- ),
913
- );
914
- const harnessPresets = Object.fromEntries(
915
- Object.entries(config.harnessPresets)
916
- .map(
917
- ([preset, members]) =>
918
- [preset, members.filter((member) => !dependents.includes(member))] as const,
939
+ .action(
940
+ async (
941
+ options: {
942
+ name: string;
943
+ force: boolean;
944
+ },
945
+ command: Command,
946
+ ) => {
947
+ const global = globals(command);
948
+ const store = profileStoreFor(global.home);
949
+ const config = store.read();
950
+ const entry = config.credentials[options.name];
951
+ if (!entry)
952
+ throw new SynomemError('CONFIG_INVALID', `Unknown connection "${options.name}".`);
953
+ const dependents = Object.entries(config.profiles)
954
+ .filter(
955
+ ([, profile]) => !isLocalProfile(profile) && profile.credentialRef === options.name,
919
956
  )
920
- .filter(([, members]) => members.length > 0),
921
- );
922
- const credentials = Object.fromEntries(
923
- Object.entries(config.credentials).filter(([name]) => name !== options.name),
924
- );
925
- store.write({
926
- ...config,
927
- credentials,
928
- profiles,
929
- harnessPresets,
930
- ...(config.defaultProfile && dependents.includes(config.defaultProfile)
931
- ? { defaultProfile: undefined }
932
- : {}),
933
- });
934
- output(
935
- io,
936
- global.json,
937
- { removed: options.name, profilesRemoved: dependents },
938
- [
939
- `Removed connection ${options.name} and its local secret.`,
940
- ...(dependents.length ? [`Also removed profiles: ${dependents.join(', ')}.`] : []),
941
- 'The server-side authorization still exists: revoke it in the portal (Connections) to stop it everywhere.',
942
- ].join('\n'),
943
- );
944
- });
945
-
957
+ .map(([profileName]) => profileName);
958
+ if (dependents.length && !options.force) {
959
+ throw new SynomemError(
960
+ 'INVALID_INPUT',
961
+ `Profiles ${dependents.join(', ')} use connection ${options.name}. Remove them first, or pass --force to remove them too.`,
962
+ );
963
+ }
964
+ if (entry.store !== 'environment') {
965
+ await storeFor(storesFor(global.home), entry).delete(entry.secretRef!);
966
+ }
967
+ const profiles = Object.fromEntries(
968
+ Object.entries(config.profiles).filter(
969
+ ([profileName]) => !dependents.includes(profileName),
970
+ ),
971
+ );
972
+ const harnessPresets = Object.fromEntries(
973
+ Object.entries(config.harnessPresets)
974
+ .map(
975
+ ([preset, members]) =>
976
+ [preset, members.filter((member) => !dependents.includes(member))] as const,
977
+ )
978
+ .filter(([, members]) => members.length > 0),
979
+ );
980
+ const credentials = Object.fromEntries(
981
+ Object.entries(config.credentials).filter(([name]) => name !== options.name),
982
+ );
983
+ store.write({
984
+ ...config,
985
+ credentials,
986
+ profiles,
987
+ harnessPresets,
988
+ ...(config.defaultProfile && dependents.includes(config.defaultProfile)
989
+ ? { defaultProfile: undefined }
990
+ : {}),
991
+ });
992
+ output(
993
+ io,
994
+ global.json,
995
+ { removed: options.name, profilesRemoved: dependents },
996
+ [
997
+ `Removed connection ${options.name} and its local secret.`,
998
+ ...(dependents.length ? [`Also removed profiles: ${dependents.join(', ')}.`] : []),
999
+ 'The server-side authorization still exists: revoke it in the portal (Connections) to stop it everywhere.',
1000
+ ].join('\n'),
1001
+ );
1002
+ },
1003
+ );
946
1004
  /* ------------------------------------------------------------ profiles */
947
-
948
1005
  const profileCommand = program
949
1006
  .command('profile')
950
1007
  .description('Named identities: one stable context through one connection or local store');
951
-
952
1008
  profileCommand
953
1009
  .command('create <name>')
954
1010
  .description('Create a profile for a context this connection may already use')
@@ -956,7 +1012,8 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
956
1012
  .option('--context <context-id>', 'exact context id (see `connection status`)')
957
1013
  .option('--agent <handle-or-id>', 'narrow by agent handle, id or display name')
958
1014
  .option('--workspace <name-or-id>', 'narrow by workspace name or id')
959
- .option('--local', 'a local-store profile for an existing local agent', false)
1015
+ .option('--human <handle-or-id>', 'local registered human identity')
1016
+ .option('--local', 'a local-store profile for an existing human or agent', false)
960
1017
  .option('--store-home <path>', 'local store home (default: the Synomem home)')
961
1018
  .option('--default', 'make this the default profile', false)
962
1019
  .action(
@@ -966,6 +1023,7 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
966
1023
  connection?: string;
967
1024
  context?: string;
968
1025
  agent?: string;
1026
+ human?: string;
969
1027
  workspace?: string;
970
1028
  local: boolean;
971
1029
  storeHome?: string;
@@ -983,37 +1041,41 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
983
1041
  `Profile ${name} already exists. Remove it first to change what it points at.`,
984
1042
  );
985
1043
  }
986
-
987
1044
  if (options.local) {
988
- if (!options.agent)
1045
+ if ((!options.agent && !options.human) || (options.agent && options.human))
989
1046
  throw new SynomemError(
990
1047
  'INVALID_INPUT',
991
- 'A local profile needs --agent <handle-or-id>.',
1048
+ 'A local profile needs exactly one of --human or --agent.',
992
1049
  );
993
1050
  const storeHome = options.storeHome ? resolve(options.storeHome) : global.home;
994
- const client = await openLocalService(storeHome, LOCAL_OPERATOR);
1051
+ const client = await openLocalOwnerService(storeHome);
995
1052
  let agent;
996
1053
  try {
997
- const resolution = await client.agents.resolve(options.agent);
998
- if (!resolution.match) {
999
- throw new SynomemError(
1000
- resolution.candidates.length ? 'CONTEXT_AMBIGUOUS' : 'AGENT_NOT_FOUND',
1001
- resolution.candidates.length
1002
- ? `"${options.agent}" matches several agents: ${resolution.candidates.map((c) => `${c.handle} (${c.id})`).join(', ')}.`
1003
- : `No local agent answers to "${options.agent}". Profiles never create agents: run \`synomem agent create\` first.`,
1004
- );
1054
+ if (options.human) {
1055
+ agent = await client.actors.get({ kind: 'human', id: options.human });
1056
+ } else {
1057
+ const resolution = await client.agents.resolve(options.agent!);
1058
+ if (!resolution.match) {
1059
+ throw new SynomemError(
1060
+ resolution.candidates.length ? 'CONTEXT_AMBIGUOUS' : 'AGENT_NOT_FOUND',
1061
+ resolution.candidates.length
1062
+ ? `"${options.agent}" matches several agents: ${resolution.candidates.map((c) => `${c.handle} (${c.id})`).join(', ')}.`
1063
+ : `No local agent answers to "${options.agent}". Profiles never create agents: run \`synomem agent create\` first.`,
1064
+ );
1065
+ }
1066
+ agent = resolution.match;
1005
1067
  }
1006
- agent = resolution.match;
1007
1068
  } finally {
1008
1069
  await client.close();
1009
1070
  }
1010
1071
  const profile: LocalProfile = {
1011
1072
  backend: 'local',
1073
+ actorKind: options.human ? 'human' : 'agent',
1012
1074
  ...(storeHome !== global.home ? { home: storeHome } : {}),
1013
1075
  actorId: agent.id,
1014
1076
  actorName: agent.displayName,
1015
1077
  contextId: localContextId(storeHome, {
1016
- kind: 'agent',
1078
+ kind: options.human ? 'human' : 'agent',
1017
1079
  id: agent.id,
1018
1080
  }),
1019
1081
  };
@@ -1030,7 +1092,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
1030
1092
  );
1031
1093
  return;
1032
1094
  }
1033
-
1034
1095
  if (!options.connection) {
1035
1096
  throw new SynomemError(
1036
1097
  'INVALID_INPUT',
@@ -1128,7 +1189,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
1128
1189
  );
1129
1190
  },
1130
1191
  );
1131
-
1132
1192
  profileCommand
1133
1193
  .command('list')
1134
1194
  .description('List profiles and presets')
@@ -1163,7 +1223,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
1163
1223
  : 'No profiles. Run `synomem setup --backend local`, or `synomem connection login` then `synomem profile create`.',
1164
1224
  );
1165
1225
  });
1166
-
1167
1226
  profileCommand
1168
1227
  .command('show <name>')
1169
1228
  .description('Show one profile')
@@ -1179,49 +1238,56 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
1179
1238
  describeProfile(name, profile),
1180
1239
  );
1181
1240
  });
1182
-
1183
1241
  profileCommand
1184
1242
  .command('remove <name>')
1185
1243
  .description('Remove a profile (its connection and credential are kept)')
1186
1244
  .option('--force', 'also remove it from presets that use it', false)
1187
- .action((name: string, options: { force: boolean }, command: Command) => {
1188
- const global = globals(command);
1189
- const store = profileStoreFor(global.home);
1190
- const config = store.read();
1191
- if (!config.profiles[name])
1192
- throw new SynomemError('CONFIG_INVALID', `Unknown profile "${name}".`);
1193
- const presets = Object.entries(config.harnessPresets)
1194
- .filter(([, members]) => members.includes(name))
1195
- .map(([preset]) => preset);
1196
- if (presets.length && !options.force) {
1197
- throw new SynomemError(
1198
- 'INVALID_INPUT',
1199
- `Presets ${presets.join(', ')} use profile ${name}. Pass --force to remove it from them too.`,
1245
+ .action(
1246
+ (
1247
+ name: string,
1248
+ options: {
1249
+ force: boolean;
1250
+ },
1251
+ command: Command,
1252
+ ) => {
1253
+ const global = globals(command);
1254
+ const store = profileStoreFor(global.home);
1255
+ const config = store.read();
1256
+ if (!config.profiles[name])
1257
+ throw new SynomemError('CONFIG_INVALID', `Unknown profile "${name}".`);
1258
+ const presets = Object.entries(config.harnessPresets)
1259
+ .filter(([, members]) => members.includes(name))
1260
+ .map(([preset]) => preset);
1261
+ if (presets.length && !options.force) {
1262
+ throw new SynomemError(
1263
+ 'INVALID_INPUT',
1264
+ `Presets ${presets.join(', ')} use profile ${name}. Pass --force to remove it from them too.`,
1265
+ );
1266
+ }
1267
+ const profiles = Object.fromEntries(
1268
+ Object.entries(config.profiles).filter(([profileName]) => profileName !== name),
1200
1269
  );
1201
- }
1202
- const profiles = Object.fromEntries(
1203
- Object.entries(config.profiles).filter(([profileName]) => profileName !== name),
1204
- );
1205
- store.write({
1206
- ...config,
1207
- profiles,
1208
- harnessPresets: Object.fromEntries(
1209
- Object.entries(config.harnessPresets)
1210
- .map(
1211
- ([preset, members]) => [preset, members.filter((member) => member !== name)] as const,
1212
- )
1213
- .filter(([, members]) => members.length > 0),
1214
- ),
1215
- ...(config.defaultProfile === name ? { defaultProfile: undefined } : {}),
1216
- });
1217
- output(
1218
- io,
1219
- global.json,
1220
- { removed: name },
1221
- `Removed profile ${name}. Its connection and credential were kept.`,
1222
- );
1223
- });
1224
-
1270
+ store.write({
1271
+ ...config,
1272
+ profiles,
1273
+ harnessPresets: Object.fromEntries(
1274
+ Object.entries(config.harnessPresets)
1275
+ .map(
1276
+ ([preset, members]) =>
1277
+ [preset, members.filter((member) => member !== name)] as const,
1278
+ )
1279
+ .filter(([, members]) => members.length > 0),
1280
+ ),
1281
+ ...(config.defaultProfile === name ? { defaultProfile: undefined } : {}),
1282
+ });
1283
+ output(
1284
+ io,
1285
+ global.json,
1286
+ { removed: name },
1287
+ `Removed profile ${name}. Its connection and credential were kept.`,
1288
+ );
1289
+ },
1290
+ );
1225
1291
  profileCommand
1226
1292
  .command('default <name>')
1227
1293
  .description('Make a profile the default when nothing else selects one')
@@ -1234,7 +1300,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
1234
1300
  store.write({ ...config, defaultProfile: name });
1235
1301
  output(io, global.json, { defaultProfile: name }, `Default profile: ${name}.`);
1236
1302
  });
1237
-
1238
1303
  profileCommand
1239
1304
  .command('use <name>')
1240
1305
  .description('Bind this directory to a profile (writes .synomem/project.json)')
@@ -1251,11 +1316,9 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
1251
1316
  `Wrote ${path}\n\nCommands and MCP servers started here now act as profile ${name} unless --profile or SYNOMEM_PROFILE says otherwise.`,
1252
1317
  );
1253
1318
  });
1254
-
1255
1319
  const presetCommand = program
1256
1320
  .command('preset')
1257
1321
  .description('Several profiles served by one explicit-context MCP server');
1258
-
1259
1322
  presetCommand
1260
1323
  .command('create <name> <profiles...>')
1261
1324
  .description('Create a preset from existing profiles')
@@ -1281,7 +1344,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
1281
1344
  `Created preset ${name}: ${members.join(', ')}.\n\nStart it with:\n synomem mcp --preset ${name} --contexts explicit`,
1282
1345
  );
1283
1346
  });
1284
-
1285
1347
  presetCommand.command('list').action((_options, command: Command) => {
1286
1348
  const global = globals(command);
1287
1349
  const config = profileStoreFor(global.home).read();
@@ -1294,7 +1356,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
1294
1356
  .join('\n') || 'No presets.',
1295
1357
  );
1296
1358
  });
1297
-
1298
1359
  presetCommand.command('remove <name>').action((name: string, _options, command: Command) => {
1299
1360
  const global = globals(command);
1300
1361
  const store = profileStoreFor(global.home);
@@ -1307,9 +1368,7 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
1307
1368
  store.write({ ...config, harnessPresets });
1308
1369
  output(io, global.json, { removed: name }, `Removed preset ${name}.`);
1309
1370
  });
1310
-
1311
1371
  /* ------------------------------------------------------------ identity */
1312
-
1313
1372
  program
1314
1373
  .command('whoami')
1315
1374
  .description('Show the selected profile, its effective context, and its credential source')
@@ -1373,13 +1432,10 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
1373
1432
  await resolver.close?.();
1374
1433
  }
1375
1434
  });
1376
-
1377
1435
  /* ------------------------------------------------------------ local stores */
1378
-
1379
1436
  const workspaceCommand = program
1380
1437
  .command('workspace')
1381
1438
  .description('Local stores on this machine, each a separate SQLite database');
1382
-
1383
1439
  workspaceCommand
1384
1440
  .command('list')
1385
1441
  .description('List the local stores on this machine')
@@ -1398,7 +1454,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
1398
1454
  .join('\n') || 'No local stores.',
1399
1455
  );
1400
1456
  });
1401
-
1402
1457
  workspaceCommand
1403
1458
  .command('create <name>')
1404
1459
  .description('Create a local store (then `profile create --local --store-home <path>`)')
@@ -1411,11 +1466,8 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
1411
1466
  ensureLocalStore(home);
1412
1467
  output(io, global.json, { name, home }, `Created local store ${name} at ${home}.`);
1413
1468
  });
1414
-
1415
1469
  /* ------------------------------------------------------------ remote import */
1416
-
1417
1470
  const remoteCommand = program.command('remote').description('Hosted workspace administration');
1418
-
1419
1471
  remoteCommand
1420
1472
  .command('import')
1421
1473
  .description(
@@ -1426,7 +1478,11 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
1426
1478
  .option('--confirm <plan-id>', 'commit the exact bundle authorized by a preview')
1427
1479
  .action(
1428
1480
  async (
1429
- options: { fromHome: string; preview?: boolean; confirm?: string },
1481
+ options: {
1482
+ fromHome: string;
1483
+ preview?: boolean;
1484
+ confirm?: string;
1485
+ },
1430
1486
  command: Command,
1431
1487
  ) => {
1432
1488
  const global = globals(command);
@@ -1492,91 +1548,97 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
1492
1548
  output(io, global.json, result, human);
1493
1549
  },
1494
1550
  );
1495
-
1496
1551
  /* ------------------------------------------------------------ reset */
1497
-
1498
1552
  program
1499
1553
  .command('reset')
1500
1554
  .description('Remove Synomem configuration, the local database, profiles and stored secrets')
1501
1555
  .option('--integrations', 'also remove installed skills', false)
1502
1556
  .option('--yes', 'apply the displayed plan', false)
1503
- .action(async (options: { integrations: boolean; yes: boolean }, command: Command) => {
1504
- const global = globals(command);
1505
- const home = global.home;
1506
- const store = profileStoreFor(home);
1507
- const config = existsSync(store.path) ? store.read() : undefined;
1508
- const secrets = Object.entries(config?.credentials ?? {})
1509
- .filter(([, entry]) => entry.store !== 'environment')
1510
- .map(([name, entry]) => ({ name, entry }));
1511
- // Every target is an exact path; no recursive delete is derived from a
1512
- // variable that might be empty.
1513
- const targets = [
1514
- join(home, 'config.json'),
1515
- join(home, 'profiles.json'),
1516
- join(home, 'synomem.sqlite3'),
1517
- join(home, 'synomem.sqlite3-wal'),
1518
- join(home, 'synomem.sqlite3-shm'),
1519
- ...secrets
1520
- .filter(({ entry }) => entry.store === 'file')
1521
- .map(({ entry }) => join(home, 'credentials', `${entry.secretRef}.json`)),
1522
- ].filter((path) => existsSync(path));
1523
- const keychain = secrets
1524
- .filter(({ entry }) => entry.store === 'keychain')
1525
- .map(({ name }) => name);
1526
- const skillPlan = options.integrations ? uninstallSkill({ apply: false }) : undefined;
1527
- const skillTargets =
1528
- skillPlan?.locations
1529
- .filter((location) => location.state === 'current' || location.state === 'stale')
1530
- .map((location) => location.target) ?? [];
1531
-
1532
- if (!options.yes) {
1557
+ .action(
1558
+ async (
1559
+ options: {
1560
+ integrations: boolean;
1561
+ yes: boolean;
1562
+ },
1563
+ command: Command,
1564
+ ) => {
1565
+ const global = globals(command);
1566
+ const home = global.home;
1567
+ const store = profileStoreFor(home);
1568
+ const config = existsSync(store.path) ? store.read() : undefined;
1569
+ const secrets = Object.entries(config?.credentials ?? {})
1570
+ .filter(([, entry]) => entry.store !== 'environment')
1571
+ .map(([name, entry]) => ({ name, entry }));
1572
+ // Every target is an exact path; no recursive delete is derived from a
1573
+ // variable that might be empty.
1574
+ const targets = [
1575
+ join(home, 'config.json'),
1576
+ join(home, 'profiles.json'),
1577
+ join(home, 'synomem.sqlite3'),
1578
+ join(home, 'synomem.sqlite3-wal'),
1579
+ join(home, 'synomem.sqlite3-shm'),
1580
+ ...secrets
1581
+ .filter(({ entry }) => entry.store === 'file')
1582
+ .map(({ entry }) => join(home, 'credentials', `${entry.secretRef}.json`)),
1583
+ ].filter((path) => existsSync(path));
1584
+ const keychain = secrets
1585
+ .filter(({ entry }) => entry.store === 'keychain')
1586
+ .map(({ name }) => name);
1587
+ const skillPlan = options.integrations ? uninstallSkill({ apply: false }) : undefined;
1588
+ const skillTargets =
1589
+ skillPlan?.locations
1590
+ .filter((location) => location.state === 'current' || location.state === 'stale')
1591
+ .map((location) => location.target) ?? [];
1592
+ if (!options.yes) {
1593
+ output(
1594
+ io,
1595
+ global.json,
1596
+ { targets, keychain, skillTargets, applied: false },
1597
+ [
1598
+ 'This will remove:',
1599
+ ...(targets.length ? targets.map((path) => ` ${path}`) : [' (no files found)']),
1600
+ ...(keychain.length
1601
+ ? [
1602
+ '',
1603
+ 'And the keychain secrets of connections:',
1604
+ ...keychain.map((name) => ` ${name}`),
1605
+ ]
1606
+ : []),
1607
+ ...(skillTargets.length
1608
+ ? [
1609
+ '',
1610
+ 'And these Synomem-owned skills:',
1611
+ ...skillTargets.map((path) => ` ${path}`),
1612
+ ]
1613
+ : []),
1614
+ '',
1615
+ 'Server-side authorizations are not revoked; do that in the portal.',
1616
+ 'Run with --yes to continue.',
1617
+ ].join('\n'),
1618
+ );
1619
+ return;
1620
+ }
1621
+ const stores = storesFor(home);
1622
+ for (const { entry } of secrets.filter(({ entry }) => entry.store === 'keychain')) {
1623
+ await stores.keychain.delete(entry.secretRef!).catch(() => false);
1624
+ }
1625
+ for (const path of targets) rmSync(path, { force: true });
1626
+ const skillResult = options.integrations ? uninstallSkill({ apply: true }) : undefined;
1533
1627
  output(
1534
1628
  io,
1535
1629
  global.json,
1536
- { targets, keychain, skillTargets, applied: false },
1630
+ { removed: targets, keychain, skills: skillResult?.locations ?? [] },
1537
1631
  [
1538
- 'This will remove:',
1539
- ...(targets.length ? targets.map((path) => ` ${path}`) : [' (no files found)']),
1540
- ...(keychain.length
1541
- ? [
1542
- '',
1543
- 'And the keychain secrets of connections:',
1544
- ...keychain.map((name) => ` ${name}`),
1545
- ]
1546
- : []),
1547
- ...(skillTargets.length
1548
- ? ['', 'And these Synomem-owned skills:', ...skillTargets.map((path) => ` ${path}`)]
1549
- : []),
1550
- '',
1551
- 'Server-side authorizations are not revoked; do that in the portal.',
1552
- 'Run with --yes to continue.',
1632
+ `Removed ${targets.length} file(s)${keychain.length ? ` and ${keychain.length} keychain secret(s)` : ''}.`,
1633
+ ...(skillResult ? [`Skill locations processed: ${skillResult.locations.length}.`] : []),
1553
1634
  ].join('\n'),
1554
1635
  );
1555
- return;
1556
- }
1557
- const stores = storesFor(home);
1558
- for (const { entry } of secrets.filter(({ entry }) => entry.store === 'keychain')) {
1559
- await stores.keychain.delete(entry.secretRef!).catch(() => false);
1560
- }
1561
- for (const path of targets) rmSync(path, { force: true });
1562
- const skillResult = options.integrations ? uninstallSkill({ apply: true }) : undefined;
1563
- output(
1564
- io,
1565
- global.json,
1566
- { removed: targets, keychain, skills: skillResult?.locations ?? [] },
1567
- [
1568
- `Removed ${targets.length} file(s)${keychain.length ? ` and ${keychain.length} keychain secret(s)` : ''}.`,
1569
- ...(skillResult ? [`Skill locations processed: ${skillResult.locations.length}.`] : []),
1570
- ].join('\n'),
1571
- );
1572
- });
1573
-
1636
+ },
1637
+ );
1574
1638
  /* ------------------------------------------------------------ agents */
1575
-
1576
1639
  const agentCommand = program
1577
1640
  .command('agent')
1578
1641
  .description('Create and inspect stable agent identities');
1579
-
1580
1642
  agentCommand
1581
1643
  .command('create <handle>')
1582
1644
  .description('Create an agent. The canonical ID is generated, not chosen.')
@@ -1587,7 +1649,12 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
1587
1649
  .action(
1588
1650
  async (
1589
1651
  handle: string,
1590
- options: { name: string; alias: string[]; description?: string; createProfile: boolean },
1652
+ options: {
1653
+ name: string;
1654
+ alias: string[];
1655
+ description?: string;
1656
+ createProfile: boolean;
1657
+ },
1591
1658
  command: Command,
1592
1659
  ) => {
1593
1660
  const global = globals(command);
@@ -1635,17 +1702,13 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
1635
1702
  io,
1636
1703
  global.json,
1637
1704
  { ...agent, ...(profileName ? { profileCreated: profileName } : {}) },
1638
- `Created ${agent.displayName}\n\nHandle: ${agent.handle}\nAgent ID: ${agent.id}${
1639
- profileName ? `\nProfile: ${profileName} (synomem mcp --profile ${profileName})` : ''
1640
- }`,
1705
+ `Created ${agent.displayName}\n\nHandle: ${agent.handle}\nAgent ID: ${agent.id}${profileName ? `\nProfile: ${profileName} (synomem mcp --profile ${profileName})` : ''}`,
1641
1706
  );
1642
1707
  },
1643
1708
  );
1644
-
1645
1709
  const aliasCommand = agentCommand
1646
1710
  .command('alias')
1647
1711
  .description('Add or remove discovery aliases without replacing the set');
1648
-
1649
1712
  aliasCommand
1650
1713
  .command('add <agent> <alias...>')
1651
1714
  .description('Add aliases, keeping the ones already there')
@@ -1656,7 +1719,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
1656
1719
  );
1657
1720
  output(io, global.json, profile, `Aliases: ${(profile.aliases ?? []).join(', ') || 'none'}`);
1658
1721
  });
1659
-
1660
1722
  aliasCommand
1661
1723
  .command('remove <agent> <alias...>')
1662
1724
  .description('Remove aliases, keeping the rest')
@@ -1667,7 +1729,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
1667
1729
  );
1668
1730
  output(io, global.json, profile, `Aliases: ${(profile.aliases ?? []).join(', ') || 'none'}`);
1669
1731
  });
1670
-
1671
1732
  agentCommand
1672
1733
  .command('rename <agent> <handle>')
1673
1734
  .description('Change an agent handle. Its canonical ID never changes.')
@@ -1683,7 +1744,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
1683
1744
  `Handle: ${profile.handle}\nAgent ID: ${profile.id} (unchanged)`,
1684
1745
  );
1685
1746
  });
1686
-
1687
1747
  agentCommand
1688
1748
  .command('archive <agent>')
1689
1749
  .description('Stop an agent acting, keeping its records and history')
@@ -1692,7 +1752,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
1692
1752
  const profile = await withManagement(command, (service) => service.agents.archive(agent));
1693
1753
  output(io, global.json, profile, `Archived ${profile.handle} (${profile.id})`);
1694
1754
  });
1695
-
1696
1755
  agentCommand
1697
1756
  .command('restore <agent>')
1698
1757
  .description('Let an archived agent act again')
@@ -1701,7 +1760,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
1701
1760
  const profile = await withManagement(command, (service) => service.agents.restore(agent));
1702
1761
  output(io, global.json, profile, `Restored ${profile.handle} (${profile.id})`);
1703
1762
  });
1704
-
1705
1763
  agentCommand
1706
1764
  .command('list')
1707
1765
  .description('List known agent identities')
@@ -1712,15 +1770,12 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
1712
1770
  ? agents
1713
1771
  .map(
1714
1772
  (profile) =>
1715
- `${profile.handle} ${profile.displayName}${profile.status === 'archived' ? ' [archived]' : ''}${
1716
- profile.aliases?.length ? ` aliases: ${profile.aliases.join(', ')}` : ''
1717
- }\n ${profile.id}`,
1773
+ `${profile.handle} ${profile.displayName}${profile.status === 'archived' ? ' [archived]' : ''}${profile.aliases?.length ? ` aliases: ${profile.aliases.join(', ')}` : ''}\n ${profile.id}`,
1718
1774
  )
1719
1775
  .join('\n')
1720
1776
  : 'No agents configured.';
1721
1777
  output(io, global.json, { agents }, human);
1722
1778
  });
1723
-
1724
1779
  agentCommand
1725
1780
  .command('show <id>')
1726
1781
  .description('Show one agent profile, resolving aliases')
@@ -1734,7 +1789,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
1734
1789
  `${profile.displayName}\n\nHandle: ${profile.handle}\nAgent ID: ${profile.id}\nStatus: ${profile.status}\n\n${profile.description ?? 'No description.'}`,
1735
1790
  );
1736
1791
  });
1737
-
1738
1792
  agentCommand
1739
1793
  .command('update <id>')
1740
1794
  .description('Update an agent profile without rewriting history')
@@ -1745,7 +1799,12 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
1745
1799
  .action(
1746
1800
  async (
1747
1801
  id: string,
1748
- options: { name?: string; alias: string[]; clearAliases: boolean; description?: string },
1802
+ options: {
1803
+ name?: string;
1804
+ alias: string[];
1805
+ clearAliases: boolean;
1806
+ description?: string;
1807
+ },
1749
1808
  command: Command,
1750
1809
  ) => {
1751
1810
  const global = globals(command);
@@ -1760,7 +1819,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
1760
1819
  output(io, global.json, profile, `Updated ${profile.displayName} (${profile.id})`);
1761
1820
  },
1762
1821
  );
1763
-
1764
1822
  agentCommand
1765
1823
  .command('resolve <name>')
1766
1824
  .description('Resolve a name or alias to one agent, or list the candidates')
@@ -1776,7 +1834,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
1776
1834
  : `No agent answers to "${resolution.query}".`;
1777
1835
  output(io, global.json, resolution, human);
1778
1836
  });
1779
-
1780
1837
  agentCommand
1781
1838
  .command('directory')
1782
1839
  .description('List agents with their runtime bindings')
@@ -1801,9 +1858,7 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
1801
1858
  : 'No agents configured.';
1802
1859
  output(io, global.json, { entries }, human);
1803
1860
  });
1804
-
1805
1861
  const runtimeCommand = agentCommand.command('runtime').description('Record where an agent runs');
1806
-
1807
1862
  runtimeCommand
1808
1863
  .command('bind <agent>')
1809
1864
  .description('Bind an agent to a runtime')
@@ -1812,7 +1867,10 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
1812
1867
  .action(
1813
1868
  async (
1814
1869
  agent: string,
1815
- options: { runtime: string; runtimeProfile?: string },
1870
+ options: {
1871
+ runtime: string;
1872
+ runtimeProfile?: string;
1873
+ },
1816
1874
  command: Command,
1817
1875
  ) => {
1818
1876
  const global = globals(command);
@@ -1831,7 +1889,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
1831
1889
  );
1832
1890
  },
1833
1891
  );
1834
-
1835
1892
  runtimeCommand
1836
1893
  .command('list [agent]')
1837
1894
  .description('List runtime bindings for one agent, or for every agent')
@@ -1862,7 +1919,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
1862
1919
  : 'No agent in this workspace has a runtime binding.';
1863
1920
  output(io, global.json, { agents: result }, human);
1864
1921
  });
1865
-
1866
1922
  runtimeCommand
1867
1923
  .command('unbind <binding-id>')
1868
1924
  .description('Remove a runtime binding')
@@ -1878,52 +1934,60 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
1878
1934
  removed ? `Removed binding ${bindingId}.` : `No binding ${bindingId}.`,
1879
1935
  );
1880
1936
  });
1881
-
1882
1937
  /* ------------------------------------------------------------ topics */
1883
-
1884
1938
  const topicCommand = program
1885
1939
  .command('topic')
1886
1940
  .description('Create and manage topics — a stable, reusable subject any record can carry');
1887
-
1888
1941
  topicCommand
1889
1942
  .command('create <display-name>')
1890
1943
  .description('Create a topic. Any actor may create one.')
1891
1944
  .option('--alias <name>', 'alias (repeatable)', collect, [])
1892
- .action(async (displayName: string, options: { alias: string[] }, command: Command) => {
1893
- const global = globals(command);
1894
- const topic = await withProfile(command, (service) =>
1895
- service.topics.create({
1896
- displayName,
1897
- ...(options.alias.length ? { aliases: options.alias } : {}),
1898
- }),
1899
- );
1900
- output(io, global.json, topic, `Created ${topic.displayName}\n\nTopic ID: ${topic.id}`);
1901
- });
1902
-
1945
+ .action(
1946
+ async (
1947
+ displayName: string,
1948
+ options: {
1949
+ alias: string[];
1950
+ },
1951
+ command: Command,
1952
+ ) => {
1953
+ const global = globals(command);
1954
+ const topic = await withProfile(command, (service) =>
1955
+ service.topics.create({
1956
+ displayName,
1957
+ ...(options.alias.length ? { aliases: options.alias } : {}),
1958
+ }),
1959
+ );
1960
+ output(io, global.json, topic, `Created ${topic.displayName}\n\nTopic ID: ${topic.id}`);
1961
+ },
1962
+ );
1903
1963
  topicCommand
1904
1964
  .command('list')
1905
1965
  .description('List known topics')
1906
1966
  .option('--status <status>', 'active or archived')
1907
- .action(async (options: { status?: string }, command: Command) => {
1908
- const global = globals(command);
1909
- const topics = await withProfile(command, (service) =>
1910
- service.topics.list(
1911
- options.status ? { status: options.status as 'active' | 'archived' } : {},
1912
- ),
1913
- );
1914
- const human = topics.length
1915
- ? topics
1916
- .map(
1917
- (topic) =>
1918
- `${topic.displayName}${topic.status === 'archived' ? ' [archived]' : ''}${
1919
- topic.aliases?.length ? ` aliases: ${topic.aliases.join(', ')}` : ''
1920
- }\n ${topic.id}`,
1921
- )
1922
- .join('\n')
1923
- : 'No topics yet.';
1924
- output(io, global.json, { topics }, human);
1925
- });
1926
-
1967
+ .action(
1968
+ async (
1969
+ options: {
1970
+ status?: string;
1971
+ },
1972
+ command: Command,
1973
+ ) => {
1974
+ const global = globals(command);
1975
+ const topics = await withProfile(command, (service) =>
1976
+ service.topics.list(
1977
+ options.status ? { status: options.status as 'active' | 'archived' } : {},
1978
+ ),
1979
+ );
1980
+ const human = topics.length
1981
+ ? topics
1982
+ .map(
1983
+ (topic) =>
1984
+ `${topic.displayName}${topic.status === 'archived' ? ' [archived]' : ''}${topic.aliases?.length ? ` aliases: ${topic.aliases.join(', ')}` : ''}\n ${topic.id}`,
1985
+ )
1986
+ .join('\n')
1987
+ : 'No topics yet.';
1988
+ output(io, global.json, { topics }, human);
1989
+ },
1990
+ );
1927
1991
  topicCommand
1928
1992
  .command('show <id>')
1929
1993
  .description('Show one topic, resolving aliases')
@@ -1937,7 +2001,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
1937
2001
  `${topic.displayName}\n\nTopic ID: ${topic.id}\nStatus: ${topic.status}\nAliases: ${topic.aliases?.join(', ') ?? 'none'}`,
1938
2002
  );
1939
2003
  });
1940
-
1941
2004
  topicCommand
1942
2005
  .command('resolve <name>')
1943
2006
  .description('Resolve a name or alias to one topic, or list the candidates')
@@ -1953,7 +2016,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
1953
2016
  : `No topic answers to "${resolution.query}".`;
1954
2017
  output(io, global.json, resolution, human);
1955
2018
  });
1956
-
1957
2019
  topicCommand
1958
2020
  .command('rename <id> <display-name>')
1959
2021
  .description("Change a topic's display name. Its ID never changes.")
@@ -1969,7 +2031,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
1969
2031
  `Renamed to ${topic.displayName}\nTopic ID: ${topic.id} (unchanged)`,
1970
2032
  );
1971
2033
  });
1972
-
1973
2034
  topicCommand
1974
2035
  .command('archive <id>')
1975
2036
  .description('Stop a topic being attached to new records, keeping the ones it already has')
@@ -1978,7 +2039,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
1978
2039
  const topic = await withProfile(command, (service) => service.topics.archive(id));
1979
2040
  output(io, global.json, topic, `Archived ${topic.displayName} (${topic.id})`);
1980
2041
  });
1981
-
1982
2042
  topicCommand
1983
2043
  .command('restore <id>')
1984
2044
  .description('Let an archived topic be attached to new records again')
@@ -1987,13 +2047,10 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
1987
2047
  const topic = await withProfile(command, (service) => service.topics.restore(id));
1988
2048
  output(io, global.json, topic, `Restored ${topic.displayName} (${topic.id})`);
1989
2049
  });
1990
-
1991
2050
  /* ------------------------------------------------------------ skills */
1992
-
1993
2051
  const skillCommand = program
1994
2052
  .command('skill')
1995
2053
  .description('Install and maintain the packaged agent skill');
1996
-
1997
2054
  skillCommand
1998
2055
  .command('install')
1999
2056
  .description('Plan or install the skill for detected agent runtimes')
@@ -2003,7 +2060,12 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
2003
2060
  .option('--link', 'symlink to the packaged skill instead of copying it', false)
2004
2061
  .action(
2005
2062
  async (
2006
- options: { runtime: string[]; yes: boolean; force: boolean; link: boolean },
2063
+ options: {
2064
+ runtime: string[];
2065
+ yes: boolean;
2066
+ force: boolean;
2067
+ link: boolean;
2068
+ },
2007
2069
  command: Command,
2008
2070
  ) => {
2009
2071
  const global = globals(command);
@@ -2049,40 +2111,51 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
2049
2111
  );
2050
2112
  },
2051
2113
  );
2052
-
2053
2114
  skillCommand
2054
2115
  .command('status')
2055
2116
  .description('Show installed, stale, missing, or conflicting skill copies')
2056
2117
  .option('--runtime <runtime>', `${skillRuntimeHelp} (repeatable)`, collect, [])
2057
- .action((options: { runtime: string[] }, command: Command) => {
2058
- const global = globals(command);
2059
- const result = skillStatus({
2060
- runtimes: skillRuntimes(options.runtime),
2061
- ...(global.profile ? { profile: global.profile } : {}),
2062
- });
2063
- output(io, global.json, result, formatSkillResult(result, 'status'));
2064
- });
2065
-
2118
+ .action(
2119
+ (
2120
+ options: {
2121
+ runtime: string[];
2122
+ },
2123
+ command: Command,
2124
+ ) => {
2125
+ const global = globals(command);
2126
+ const result = skillStatus({
2127
+ runtimes: skillRuntimes(options.runtime),
2128
+ ...(global.profile ? { profile: global.profile } : {}),
2129
+ });
2130
+ output(io, global.json, result, formatSkillResult(result, 'status'));
2131
+ },
2132
+ );
2066
2133
  skillCommand
2067
2134
  .command('uninstall')
2068
2135
  .description('Plan or remove Synomem-owned skill installations')
2069
2136
  .option('--runtime <runtime>', `${skillRuntimeHelp} (repeatable)`, collect, [])
2070
2137
  .option('--yes', 'apply the displayed plan', false)
2071
2138
  .option('--force', 'remove a conflicting synomem directory', false)
2072
- .action((options: { runtime: string[]; yes: boolean; force: boolean }, command: Command) => {
2073
- const global = globals(command);
2074
- const result = uninstallSkill({
2075
- runtimes: skillRuntimes(options.runtime),
2076
- apply: options.yes,
2077
- force: options.force,
2078
- });
2079
- output(io, global.json, result, formatSkillResult(result, 'uninstall'));
2080
- });
2081
-
2139
+ .action(
2140
+ (
2141
+ options: {
2142
+ runtime: string[];
2143
+ yes: boolean;
2144
+ force: boolean;
2145
+ },
2146
+ command: Command,
2147
+ ) => {
2148
+ const global = globals(command);
2149
+ const result = uninstallSkill({
2150
+ runtimes: skillRuntimes(options.runtime),
2151
+ apply: options.yes,
2152
+ force: options.force,
2153
+ });
2154
+ output(io, global.json, result, formatSkillResult(result, 'uninstall'));
2155
+ },
2156
+ );
2082
2157
  /* ------------------------------------------------------------ posts */
2083
-
2084
2158
  const postCommand = program.command('post').description('Publish to everyone in the workspace');
2085
-
2086
2159
  postCommand
2087
2160
  .command('create')
2088
2161
  .description('Publish a post the whole workspace can read')
@@ -2090,10 +2163,14 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
2090
2163
  .requiredOption('--body <body>')
2091
2164
  .option('--tag <tag>', 'repeatable', collect, [])
2092
2165
  .option('--topic <id>', 'topic ID (repeatable)', collect, [])
2093
- .option('--reply-to <post-id>')
2094
2166
  .action(
2095
2167
  async (
2096
- options: { title: string; body: string; tag: string[]; topic: string[]; replyTo?: string },
2168
+ options: {
2169
+ title: string;
2170
+ body: string;
2171
+ tag: string[];
2172
+ topic: string[];
2173
+ },
2097
2174
  command: Command,
2098
2175
  ) => {
2099
2176
  const global = globals(command);
@@ -2103,33 +2180,37 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
2103
2180
  body: options.body,
2104
2181
  ...(options.tag.length ? { tags: options.tag } : {}),
2105
2182
  ...(options.topic.length ? { topicIds: options.topic } : {}),
2106
- ...(options.replyTo ? { replyTo: options.replyTo } : {}),
2107
2183
  }),
2108
2184
  );
2109
2185
  output(io, global.json, result, `Published ${result.record.event.id}`);
2110
2186
  },
2111
2187
  );
2112
-
2113
2188
  postCommand
2114
2189
  .command('list')
2115
2190
  .description('List posts in this workspace')
2116
2191
  .option('--limit <n>', 'default 10, maximum 50')
2117
- .action(async (options: { limit?: string }, command: Command) => {
2118
- const global = globals(command);
2119
- const page = await withProfile(command, (service) =>
2120
- service.posts.list(options.limit ? { limit: Number(options.limit) } : {}),
2121
- );
2122
- const human = page.items.length
2123
- ? page.items.map((item) => `${item.id} ${item.title}`).join('\n')
2124
- : 'No posts yet.';
2125
- output(io, global.json, page, human);
2126
- });
2127
-
2128
- postCommand
2129
- .command('show <post-id>')
2130
- .description('Show one post with its acknowledgements')
2131
- .action(async (postId: string, _options, command: Command) => {
2132
- const global = globals(command);
2192
+ .action(
2193
+ async (
2194
+ options: {
2195
+ limit?: string;
2196
+ },
2197
+ command: Command,
2198
+ ) => {
2199
+ const global = globals(command);
2200
+ const page = await withProfile(command, (service) =>
2201
+ service.posts.list(options.limit ? { limit: Number(options.limit) } : {}),
2202
+ );
2203
+ const human = page.items.length
2204
+ ? page.items.map((item) => `${item.id} ${item.title}`).join('\n')
2205
+ : 'No posts yet.';
2206
+ output(io, global.json, page, human);
2207
+ },
2208
+ );
2209
+ postCommand
2210
+ .command('show <post-id>')
2211
+ .description('Show one post with its acknowledgements')
2212
+ .action(async (postId: string, _options, command: Command) => {
2213
+ const global = globals(command);
2133
2214
  const record = await withProfile(command, (service) => service.posts.get(postId));
2134
2215
  const acks = record.acknowledgments.length
2135
2216
  ? record.acknowledgments
@@ -2143,19 +2224,29 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
2143
2224
  `${record.title}\n\n${record.body}\n\nAcknowledged by:\n${acks}`,
2144
2225
  );
2145
2226
  });
2146
-
2147
2227
  postCommand
2148
2228
  .command('acknowledge <post-id>')
2149
2229
  .description('Say you have seen a post')
2150
2230
  .option('--note <text>', 'optional context for the author')
2151
- .action(async (postId: string, options: { note?: string }, command: Command) => {
2152
- const global = globals(command);
2153
- const record = await withProfile(command, (service) =>
2154
- service.posts.acknowledge({ postId, ...(options.note ? { note: options.note } : {}) }),
2155
- );
2156
- output(io, global.json, record, `Acknowledged ${postId}`);
2157
- });
2158
-
2231
+ .action(
2232
+ async (
2233
+ postId: string,
2234
+ options: {
2235
+ note?: string;
2236
+ },
2237
+ command: Command,
2238
+ ) => {
2239
+ const global = globals(command);
2240
+ const record = await withProfile(command, async (service) =>
2241
+ service.posts.acknowledge({
2242
+ expectedVersion: (await service.posts.get(postId)).lifecycleVersion!,
2243
+ postId,
2244
+ ...(options.note ? { note: options.note } : {}),
2245
+ }),
2246
+ );
2247
+ output(io, global.json, record, `Acknowledged ${postId}`);
2248
+ },
2249
+ );
2159
2250
  postCommand
2160
2251
  .command('roster <post-id>')
2161
2252
  .description('Who has acknowledged a post, and who has not')
@@ -2177,23 +2268,31 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
2177
2268
  }
2178
2269
  output(io, global.json, roster, lines.join('\n'));
2179
2270
  });
2180
-
2181
2271
  postCommand
2182
2272
  .command('archive <post-id>')
2183
2273
  .description('Archive a post you wrote')
2184
2274
  .option('--reason <text>')
2185
- .action(async (postId: string, options: { reason?: string }, command: Command) => {
2186
- const global = globals(command);
2187
- const record = await withProfile(command, (service) =>
2188
- service.posts.archive({ postId, ...(options.reason ? { reason: options.reason } : {}) }),
2189
- );
2190
- output(io, global.json, record, `Archived ${postId}`);
2191
- });
2192
-
2275
+ .action(
2276
+ async (
2277
+ postId: string,
2278
+ options: {
2279
+ reason?: string;
2280
+ },
2281
+ command: Command,
2282
+ ) => {
2283
+ const global = globals(command);
2284
+ const record = await withProfile(command, async (service) =>
2285
+ service.posts.archive({
2286
+ expectedVersion: (await service.posts.get(postId)).lifecycleVersion!,
2287
+ postId,
2288
+ ...(options.reason ? { reason: options.reason } : {}),
2289
+ }),
2290
+ );
2291
+ output(io, global.json, record, `Archived ${postId}`);
2292
+ },
2293
+ );
2193
2294
  /* ------------------------------------------------------------ kudos */
2194
-
2195
2295
  const kudosCommand = program.command('kudos').description('Give and manage agent recognition');
2196
-
2197
2296
  kudosCommand
2198
2297
  .command('give <recipient>')
2199
2298
  .description('Give specific, evidence-based kudos to an agent')
@@ -2221,7 +2320,7 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
2221
2320
  const global = globals(command);
2222
2321
  const result = await withProfile(command, (service) =>
2223
2322
  service.kudos.give({
2224
- recipientAgentId: recipient,
2323
+ recipient: targetRef(recipient),
2225
2324
  title: options.title,
2226
2325
  reason: options.reason,
2227
2326
  visibility: options.visibility,
@@ -2240,7 +2339,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
2240
2339
  );
2241
2340
  },
2242
2341
  );
2243
-
2244
2342
  addListOptions(kudosCommand.command('list').description('List and filter kudos')).action(
2245
2343
  async (options: Record<string, string>, command: Command) => {
2246
2344
  const global = globals(command);
@@ -2255,7 +2353,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
2255
2353
  );
2256
2354
  },
2257
2355
  );
2258
-
2259
2356
  kudosCommand
2260
2357
  .command('show <kudos-id>')
2261
2358
  .description('Show one kudos item and its current state')
@@ -2264,19 +2361,29 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
2264
2361
  const record = await withProfile(command, (service) => service.kudos.get(id));
2265
2362
  output(io, global.json, record, showRecord(record));
2266
2363
  });
2267
-
2268
2364
  kudosCommand
2269
2365
  .command('acknowledge <kudos-id>')
2270
2366
  .description('Record that you (the recipient) reviewed kudos')
2271
2367
  .option('--note <text>')
2272
- .action(async (id: string, options: { note?: string }, command: Command) => {
2273
- const global = globals(command);
2274
- const record = await withProfile(command, (service) =>
2275
- service.kudos.acknowledge({ kudosId: id, ...(options.note ? { note: options.note } : {}) }),
2276
- );
2277
- output(io, global.json, record, `Acknowledged ${id}.`);
2278
- });
2279
-
2368
+ .action(
2369
+ async (
2370
+ id: string,
2371
+ options: {
2372
+ note?: string;
2373
+ },
2374
+ command: Command,
2375
+ ) => {
2376
+ const global = globals(command);
2377
+ const record = await withProfile(command, async (service) =>
2378
+ service.kudos.acknowledge({
2379
+ expectedVersion: (await service.kudos.get(id)).lifecycleVersion!,
2380
+ kudosId: id,
2381
+ ...(options.note ? { note: options.note } : {}),
2382
+ }),
2383
+ );
2384
+ output(io, global.json, record, `Acknowledged ${id}.`);
2385
+ },
2386
+ );
2280
2387
  kudosCommand
2281
2388
  .command('revoke <kudos-id>')
2282
2389
  .description('Record a revocation while preserving history')
@@ -2285,12 +2392,16 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
2285
2392
  .action(
2286
2393
  async (
2287
2394
  id: string,
2288
- options: { reason: string; administrative: boolean },
2395
+ options: {
2396
+ reason: string;
2397
+ administrative: boolean;
2398
+ },
2289
2399
  command: Command,
2290
2400
  ) => {
2291
2401
  const global = globals(command);
2292
- const record = await withProfile(command, (service) =>
2402
+ const record = await withProfile(command, async (service) =>
2293
2403
  service.kudos.revoke({
2404
+ expectedVersion: (await service.kudos.get(id)).lifecycleVersion!,
2294
2405
  kudosId: id,
2295
2406
  reason: options.reason,
2296
2407
  administrative: options.administrative,
@@ -2299,7 +2410,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
2299
2410
  output(io, global.json, record, `Revoked ${id}; the audit trail was preserved.`);
2300
2411
  },
2301
2412
  );
2302
-
2303
2413
  kudosCommand
2304
2414
  .command('wins [agent]')
2305
2415
  .description('Print the generated WINS.md path or content (local stores)')
@@ -2308,7 +2418,10 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
2308
2418
  .action(
2309
2419
  async (
2310
2420
  agentId: string | undefined,
2311
- options: { open: boolean; print: boolean },
2421
+ options: {
2422
+ open: boolean;
2423
+ print: boolean;
2424
+ },
2312
2425
  command: Command,
2313
2426
  ) => {
2314
2427
  const global = globals(command);
@@ -2355,7 +2468,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
2355
2468
  output(io, global.json, details, options.print ? details.content.trimEnd() : details.path);
2356
2469
  },
2357
2470
  );
2358
-
2359
2471
  addListOptions(
2360
2472
  kudosCommand.command('stats').description('Show aggregate kudos statistics'),
2361
2473
  ).action(async (options: Record<string, string>, command: Command) => {
@@ -2368,48 +2480,51 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
2368
2480
  `Total: ${stats.total}\nActive: ${stats.active}\nAcknowledged: ${stats.acknowledged}\nRevoked: ${stats.revoked}`,
2369
2481
  );
2370
2482
  });
2371
-
2372
2483
  /* ------------------------------------------------------------ cross-kind */
2373
-
2374
2484
  program
2375
- .command('inbox [agent]')
2376
- .description("Show pending kudos, memos, and tasks (the profile's own agent by default)")
2377
- .option('--limit <number>', 'maximum results (default 10, maximum 50)', '10')
2378
- .option('--cursor <cursor>', 'opaque cursor returned by the previous page')
2485
+ .command('inbox')
2486
+ .description('Read notifications belonging to the profile effective actor')
2487
+ .option('--limit <number>', 'Maximum results', '20')
2488
+ .option('--after <cursor>', 'Signed continuation cursor')
2489
+ .option('--view <view>', 'all, unread, action_required or default', 'default')
2379
2490
  .action(
2380
2491
  async (
2381
- agentId: string | undefined,
2382
- options: { limit: string; cursor?: string },
2492
+ options: {
2493
+ limit: string;
2494
+ after?: string;
2495
+ view: HpNotificationInput['view'];
2496
+ },
2383
2497
  command: Command,
2384
2498
  ) => {
2385
- const global = globals(command);
2386
- const page = await withProfile(command, (service, context) => {
2387
- const recipient =
2388
- agentId ?? (context.actor.kind === 'agent' ? context.actor.id : undefined);
2389
- if (!recipient) {
2390
- throw new SynomemError(
2391
- 'INVALID_INPUT',
2392
- 'This profile is not an agent; name the agent whose inbox to show.',
2393
- );
2394
- }
2395
- return service.items.list({
2396
- participantAgentId: recipient,
2397
- pending: true,
2499
+ const page = await withProfile(command, (service) =>
2500
+ service.notifications.list({
2398
2501
  limit: Number(options.limit),
2399
- ...(options.cursor ? { cursor: options.cursor } : {}),
2400
- });
2401
- });
2502
+ view: options.view,
2503
+ ...(options.after ? { after: options.after } : {}),
2504
+ }),
2505
+ );
2402
2506
  output(
2403
2507
  io,
2404
- global.json,
2508
+ globals(command).json,
2405
2509
  page,
2406
2510
  page.items.length
2407
- ? `${page.items.map(lineForItem).join('\n')}${page.hasMore ? `\nNext cursor: ${page.nextCursor}` : ''}`
2511
+ ? page.items
2512
+ .map(
2513
+ (item) =>
2514
+ `${item.read ? 'Read' : 'Unread'} ${item.root?.kind ?? 'record'}: ${item.root?.title ?? item.rootId} [${item.id}]`,
2515
+ )
2516
+ .join('\n')
2408
2517
  : 'Inbox is clear.',
2409
2518
  );
2410
2519
  },
2411
2520
  );
2412
-
2521
+ program
2522
+ .command('notification-read <notification-id>')
2523
+ .description('Mark your notification read without performing a lifecycle action')
2524
+ .action(async (id: string, _options: object, command: Command) => {
2525
+ await withProfile(command, (service) => service.notifications.read(id));
2526
+ output(io, globals(command).json, { updated: true }, 'Notification marked read.');
2527
+ });
2413
2528
  program
2414
2529
  .command('list')
2415
2530
  .description('List compact summaries across all record types')
@@ -2437,7 +2552,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
2437
2552
  : 'No items found.',
2438
2553
  );
2439
2554
  });
2440
-
2441
2555
  program
2442
2556
  .command('changes')
2443
2557
  .description('List compact changes across all record types after an opaque watermark')
@@ -2445,7 +2559,14 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
2445
2559
  .option('--after <watermark>', 'watermark or change cursor from a previous response')
2446
2560
  .option('--limit <number>', 'maximum changes (default 20, maximum 100)', '20')
2447
2561
  .action(
2448
- async (options: { after?: string; limit: string; kind: string[] }, command: Command) => {
2562
+ async (
2563
+ options: {
2564
+ after?: string;
2565
+ limit: string;
2566
+ kind: string[];
2567
+ },
2568
+ command: Command,
2569
+ ) => {
2449
2570
  const global = globals(command);
2450
2571
  const page = await withProfile(command, (service) =>
2451
2572
  service.items.changes({
@@ -2465,11 +2586,8 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
2465
2586
  output(io, global.json, page, human);
2466
2587
  },
2467
2588
  );
2468
-
2469
2589
  /* ------------------------------------------------------------ memos */
2470
-
2471
2590
  const memoCommand = program.command('memo').description('Send and manage durable messages');
2472
-
2473
2591
  memoCommand
2474
2592
  .command('send <recipient>')
2475
2593
  .description('Send a durable one-to-one memo')
@@ -2495,7 +2613,7 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
2495
2613
  const global = globals(command);
2496
2614
  const result = await withProfile(command, (service) =>
2497
2615
  service.memos.send({
2498
- recipientAgentId: recipient,
2616
+ recipient: targetRef(recipient),
2499
2617
  subject: options.subject,
2500
2618
  body: options.body,
2501
2619
  visibility: options.visibility,
@@ -2512,7 +2630,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
2512
2630
  );
2513
2631
  },
2514
2632
  );
2515
-
2516
2633
  memoCommand
2517
2634
  .command('list')
2518
2635
  .description('List memos')
@@ -2522,13 +2639,18 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
2522
2639
  .option('--cursor <cursor>')
2523
2640
  .action(
2524
2641
  async (
2525
- options: { participant?: string; status?: string; limit: string; cursor?: string },
2642
+ options: {
2643
+ participant?: string;
2644
+ status?: string;
2645
+ limit: string;
2646
+ cursor?: string;
2647
+ },
2526
2648
  command: Command,
2527
2649
  ) => {
2528
2650
  const global = globals(command);
2529
2651
  const page = await withProfile(command, (service) =>
2530
2652
  service.memos.list({
2531
- ...(options.participant ? { participantAgentId: options.participant } : {}),
2653
+ ...(options.participant ? { participant: targetRef(options.participant) } : {}),
2532
2654
  ...(options.status ? { status: options.status } : {}),
2533
2655
  limit: Number(options.limit),
2534
2656
  ...(options.cursor ? { cursor: options.cursor } : {}),
@@ -2537,7 +2659,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
2537
2659
  output(io, global.json, page, page.items.map(lineForItem).join('\n') || 'No memos found.');
2538
2660
  },
2539
2661
  );
2540
-
2541
2662
  memoCommand
2542
2663
  .command('show <memo-id>')
2543
2664
  .description('Show one memo')
@@ -2551,7 +2672,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
2551
2672
  `${record.event.subject}\nID: ${record.event.id}\nStatus: ${record.status}\n\n${record.event.body}`,
2552
2673
  );
2553
2674
  });
2554
-
2555
2675
  for (const operation of ['read', 'archive'] as const) {
2556
2676
  memoCommand
2557
2677
  .command(`${operation} <memo-id>`)
@@ -2561,24 +2681,30 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
2561
2681
  : 'Archive a memo addressed to you',
2562
2682
  )
2563
2683
  .option('--idempotency-key <key>')
2564
- .action(async (id: string, options: { idempotencyKey?: string }, command: Command) => {
2565
- const global = globals(command);
2566
- const record = await withProfile(command, (service) =>
2567
- service.memos[operation]({
2568
- memoId: id,
2569
- ...(options.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : {}),
2570
- }),
2571
- );
2572
- output(io, global.json, record, `Memo ${id} is ${record.status}.`);
2573
- });
2684
+ .action(
2685
+ async (
2686
+ id: string,
2687
+ options: {
2688
+ idempotencyKey?: string;
2689
+ },
2690
+ command: Command,
2691
+ ) => {
2692
+ const global = globals(command);
2693
+ const record = await withProfile(command, async (service) =>
2694
+ service.memos[operation]({
2695
+ expectedVersion: (await service.memos.get(id)).lifecycleVersion!,
2696
+ memoId: id,
2697
+ ...(options.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : {}),
2698
+ }),
2699
+ );
2700
+ output(io, global.json, record, `Memo ${id} is ${record.status}.`);
2701
+ },
2702
+ );
2574
2703
  }
2575
-
2576
2704
  /* ------------------------------------------------------------ notes */
2577
-
2578
2705
  const noteCommand = program
2579
2706
  .command('note')
2580
2707
  .description('Retain and revise agent-owned knowledge');
2581
-
2582
2708
  noteCommand
2583
2709
  .command('create')
2584
2710
  .description('Create an owner-private note')
@@ -2603,7 +2729,7 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
2603
2729
  const global = globals(command);
2604
2730
  const result = await withProfile(command, (service) =>
2605
2731
  service.notes.create({
2606
- ...(options.owner ? { ownerAgentId: options.owner } : {}),
2732
+ ...(options.owner ? { owner: targetRef(options.owner) } : {}),
2607
2733
  title: options.title,
2608
2734
  body: options.body,
2609
2735
  ...(options.tag.length ? { tags: options.tag } : {}),
@@ -2619,7 +2745,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
2619
2745
  );
2620
2746
  },
2621
2747
  );
2622
-
2623
2748
  noteCommand
2624
2749
  .command('list')
2625
2750
  .description('List notes')
@@ -2627,11 +2752,18 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
2627
2752
  .option('--status <status>')
2628
2753
  .option('--limit <number>', 'maximum results', '10')
2629
2754
  .action(
2630
- async (options: { owner?: string; status?: string; limit: string }, command: Command) => {
2755
+ async (
2756
+ options: {
2757
+ owner?: string;
2758
+ status?: string;
2759
+ limit: string;
2760
+ },
2761
+ command: Command,
2762
+ ) => {
2631
2763
  const global = globals(command);
2632
2764
  const page = await withProfile(command, (service) =>
2633
2765
  service.notes.list({
2634
- ...(options.owner ? { participantAgentId: options.owner } : {}),
2766
+ ...(options.owner ? { participant: targetRef(options.owner) } : {}),
2635
2767
  ...(options.status ? { status: options.status } : {}),
2636
2768
  limit: Number(options.limit),
2637
2769
  }),
@@ -2639,7 +2771,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
2639
2771
  output(io, global.json, page, page.items.map(lineForItem).join('\n') || 'No notes found.');
2640
2772
  },
2641
2773
  );
2642
-
2643
2774
  noteCommand
2644
2775
  .command('show <note-id>')
2645
2776
  .description('Show one note')
@@ -2653,7 +2784,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
2653
2784
  `${record.current.title}\nID: ${record.event.id}\nVersion: ${record.current.version}\n\n${record.current.body}`,
2654
2785
  );
2655
2786
  });
2656
-
2657
2787
  noteCommand
2658
2788
  .command('revise <note-id>')
2659
2789
  .description('Revise a note (optimistic concurrency)')
@@ -2688,28 +2818,33 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
2688
2818
  output(io, global.json, record, `Revised note ${id} to version ${record.current.version}.`);
2689
2819
  },
2690
2820
  );
2691
-
2692
2821
  noteCommand
2693
2822
  .command('archive <note-id>')
2694
2823
  .description('Archive a note')
2695
2824
  .option('--idempotency-key <key>')
2696
- .action(async (id: string, options: { idempotencyKey?: string }, command: Command) => {
2697
- const global = globals(command);
2698
- const record = await withProfile(command, (service) =>
2699
- service.notes.archive({
2700
- noteId: id,
2701
- ...(options.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : {}),
2702
- }),
2703
- );
2704
- output(io, global.json, record, `Archived note ${id}.`);
2705
- });
2706
-
2825
+ .action(
2826
+ async (
2827
+ id: string,
2828
+ options: {
2829
+ idempotencyKey?: string;
2830
+ },
2831
+ command: Command,
2832
+ ) => {
2833
+ const global = globals(command);
2834
+ const record = await withProfile(command, async (service) =>
2835
+ service.notes.archive({
2836
+ expectedVersion: (await service.notes.get(id)).lifecycleVersion!,
2837
+ noteId: id,
2838
+ ...(options.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : {}),
2839
+ }),
2840
+ );
2841
+ output(io, global.json, record, `Archived note ${id}.`);
2842
+ },
2843
+ );
2707
2844
  /* ------------------------------------------------------------ todos */
2708
-
2709
2845
  const todoCommand = program
2710
2846
  .command('todo')
2711
2847
  .description('Create and manage your own private reminders');
2712
-
2713
2848
  todoCommand
2714
2849
  .command('create')
2715
2850
  .description('Create a private todo')
@@ -2757,32 +2892,38 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
2757
2892
  );
2758
2893
  },
2759
2894
  );
2760
-
2761
2895
  todoCommand
2762
2896
  .command('list')
2763
2897
  .description('List your todos')
2764
2898
  .option('--status <status>')
2765
2899
  .option('--limit <number>', 'maximum results', '10')
2766
- .action(async (options: { status?: string; limit: string }, command: Command) => {
2767
- const global = globals(command);
2768
- const page = await withProfile(command, (service) =>
2769
- service.todos.list({
2770
- ...(options.status ? { status: options.status } : {}),
2771
- limit: Number(options.limit),
2772
- }),
2773
- );
2774
- output(
2775
- io,
2776
- global.json,
2777
- page,
2778
- page.items.length
2779
- ? page.items
2780
- .map((item) => `${item.id} ${item.status.padEnd(9)} ${item.title}`)
2781
- .join('\n')
2782
- : 'No todos.',
2783
- );
2784
- });
2785
-
2900
+ .action(
2901
+ async (
2902
+ options: {
2903
+ status?: string;
2904
+ limit: string;
2905
+ },
2906
+ command: Command,
2907
+ ) => {
2908
+ const global = globals(command);
2909
+ const page = await withProfile(command, (service) =>
2910
+ service.todos.list({
2911
+ ...(options.status ? { status: options.status } : {}),
2912
+ limit: Number(options.limit),
2913
+ }),
2914
+ );
2915
+ output(
2916
+ io,
2917
+ global.json,
2918
+ page,
2919
+ page.items.length
2920
+ ? page.items
2921
+ .map((item) => `${item.id} ${item.status.padEnd(9)} ${item.title}`)
2922
+ .join('\n')
2923
+ : 'No todos.',
2924
+ );
2925
+ },
2926
+ );
2786
2927
  todoCommand
2787
2928
  .command('show <todo-id>')
2788
2929
  .description('Show one of your todos')
@@ -2796,7 +2937,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
2796
2937
  `${record.current.title}\nStatus: ${record.status}\nPriority: ${record.current.priority}\nVersion: ${record.current.version}${record.current.details ? `\n\n${record.current.details}` : ''}`,
2797
2938
  );
2798
2939
  });
2799
-
2800
2940
  for (const operation of ['complete', 'reopen', 'cancel', 'archive'] as const) {
2801
2941
  todoCommand
2802
2942
  .command(`${operation} <todo-id>`)
@@ -2807,37 +2947,48 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
2807
2947
  .action(
2808
2948
  async (
2809
2949
  id: string,
2810
- options: { note?: string; reason?: string; idempotencyKey?: string },
2950
+ options: {
2951
+ note?: string;
2952
+ reason?: string;
2953
+ idempotencyKey?: string;
2954
+ },
2811
2955
  command: Command,
2812
2956
  ) => {
2813
2957
  const global = globals(command);
2814
2958
  const key = options.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : {};
2815
- const record = await withProfile(command, (service) =>
2959
+ const record = await withProfile(command, async (service) =>
2816
2960
  operation === 'complete'
2817
2961
  ? service.todos.complete({
2962
+ expectedVersion: (await service.todos.get(id)).lifecycleVersion!,
2818
2963
  todoId: id,
2819
2964
  ...(options.note ? { note: options.note } : {}),
2820
2965
  ...key,
2821
2966
  })
2822
2967
  : operation === 'cancel'
2823
2968
  ? service.todos.cancel({
2969
+ expectedVersion: (await service.todos.get(id)).lifecycleVersion!,
2824
2970
  todoId: id,
2825
2971
  ...(options.reason ? { reason: options.reason } : {}),
2826
2972
  ...key,
2827
2973
  })
2828
2974
  : operation === 'archive'
2829
- ? service.todos.archive({ todoId: id, ...key })
2830
- : service.todos.reopen({ todoId: id, ...key }),
2975
+ ? service.todos.archive({
2976
+ expectedVersion: (await service.todos.get(id)).lifecycleVersion!,
2977
+ todoId: id,
2978
+ ...key,
2979
+ })
2980
+ : service.todos.reopen({
2981
+ expectedVersion: (await service.todos.get(id)).lifecycleVersion!,
2982
+ todoId: id,
2983
+ ...key,
2984
+ }),
2831
2985
  );
2832
2986
  output(io, global.json, record, `Todo ${id} is now ${record.status}.`);
2833
2987
  },
2834
2988
  );
2835
2989
  }
2836
-
2837
2990
  /* ------------------------------------------------------------ tasks */
2838
-
2839
2991
  const taskCommand = program.command('task').description('Create and manage agent tasks');
2840
-
2841
2992
  taskCommand
2842
2993
  .command('create <assignee>')
2843
2994
  .description('Assign a task to an agent')
@@ -2871,7 +3022,7 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
2871
3022
  const global = globals(command);
2872
3023
  const result = await withProfile(command, (service) =>
2873
3024
  service.tasks.create({
2874
- assigneeAgentId: assignee,
3025
+ assignee: targetRef(assignee),
2875
3026
  title: options.title,
2876
3027
  ...(options.description ? { description: options.description } : {}),
2877
3028
  priority: Number(options.priority) as 1 | 2 | 3 | 4,
@@ -2890,7 +3041,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
2890
3041
  );
2891
3042
  },
2892
3043
  );
2893
-
2894
3044
  taskCommand
2895
3045
  .command('list')
2896
3046
  .description('List tasks')
@@ -2898,11 +3048,18 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
2898
3048
  .option('--status <status>')
2899
3049
  .option('--limit <number>', 'maximum results', '10')
2900
3050
  .action(
2901
- async (options: { assignee?: string; status?: string; limit: string }, command: Command) => {
3051
+ async (
3052
+ options: {
3053
+ assignee?: string;
3054
+ status?: string;
3055
+ limit: string;
3056
+ },
3057
+ command: Command,
3058
+ ) => {
2902
3059
  const global = globals(command);
2903
3060
  const page = await withProfile(command, (service) =>
2904
3061
  service.tasks.list({
2905
- ...(options.assignee ? { participantAgentId: options.assignee } : {}),
3062
+ ...(options.assignee ? { participant: targetRef(options.assignee) } : {}),
2906
3063
  ...(options.status ? { status: options.status } : {}),
2907
3064
  limit: Number(options.limit),
2908
3065
  }),
@@ -2910,7 +3067,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
2910
3067
  output(io, global.json, page, page.items.map(lineForItem).join('\n') || 'No tasks found.');
2911
3068
  },
2912
3069
  );
2913
-
2914
3070
  taskCommand
2915
3071
  .command('show <task-id>')
2916
3072
  .description('Show one task')
@@ -2924,7 +3080,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
2924
3080
  `${record.current.title}\nID: ${record.event.id}\nStatus: ${record.status}\nVersion: ${record.current.version}`,
2925
3081
  );
2926
3082
  });
2927
-
2928
3083
  taskCommand
2929
3084
  .command('update <task-id>')
2930
3085
  .description('Update a task (optimistic concurrency)')
@@ -2972,7 +3127,35 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
2972
3127
  output(io, global.json, record, `Updated task ${id} to version ${record.current.version}.`);
2973
3128
  },
2974
3129
  );
2975
-
3130
+ taskCommand
3131
+ .command('override <task-id>')
3132
+ .requiredOption('--decision <status>', 'open or rejected')
3133
+ .requiredOption('--reason <text>')
3134
+ .requiredOption('--expected-version <number>')
3135
+ .option('--idempotency-key <key>')
3136
+ .action(
3137
+ async (
3138
+ id: string,
3139
+ options: {
3140
+ decision: 'open' | 'rejected';
3141
+ reason: string;
3142
+ expectedVersion: string;
3143
+ idempotencyKey?: string;
3144
+ },
3145
+ command: Command,
3146
+ ) => {
3147
+ const record = await withProfile(command, (service) =>
3148
+ service.tasks.overrideDecision({
3149
+ taskId: id,
3150
+ nextStatus: options.decision,
3151
+ reason: options.reason,
3152
+ expectedVersion: Number(options.expectedVersion),
3153
+ ...(options.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : {}),
3154
+ }),
3155
+ );
3156
+ output(io, globals(command).json, record, `Task decision overridden: ${record.status}.`);
3157
+ },
3158
+ );
2976
3159
  for (const operation of ['accept', 'reject', 'complete', 'reopen', 'cancel'] as const) {
2977
3160
  const command_ = taskCommand
2978
3161
  .command(`${operation} <task-id>`)
@@ -2989,49 +3172,251 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
2989
3172
  command_.action(
2990
3173
  async (
2991
3174
  id: string,
2992
- options: { note?: string; reason?: string; response?: string; idempotencyKey?: string },
3175
+ options: {
3176
+ note?: string;
3177
+ reason?: string;
3178
+ response?: string;
3179
+ idempotencyKey?: string;
3180
+ },
2993
3181
  command: Command,
2994
3182
  ) => {
2995
3183
  const global = globals(command);
2996
3184
  const key = options.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : {};
2997
- const record = await withProfile(command, (service) =>
3185
+ const record = await withProfile(command, async (service) =>
2998
3186
  operation === 'accept'
2999
3187
  ? service.tasks.accept({
3188
+ expectedVersion: (await service.tasks.get(id)).lifecycleVersion!,
3000
3189
  taskId: id,
3001
3190
  ...(options.response ? { response: options.response } : {}),
3002
3191
  ...key,
3003
3192
  })
3004
3193
  : operation === 'reject'
3005
3194
  ? service.tasks.reject({
3195
+ expectedVersion: (await service.tasks.get(id)).lifecycleVersion!,
3006
3196
  taskId: id,
3007
3197
  response: options.response ?? options.reason ?? '',
3008
3198
  ...key,
3009
3199
  })
3010
3200
  : operation === 'complete'
3011
3201
  ? service.tasks.complete({
3202
+ expectedVersion: (await service.tasks.get(id)).lifecycleVersion!,
3012
3203
  taskId: id,
3013
3204
  ...(options.note ? { note: options.note } : {}),
3014
3205
  ...key,
3015
3206
  })
3016
3207
  : operation === 'cancel'
3017
3208
  ? service.tasks.cancel({
3209
+ expectedVersion: (await service.tasks.get(id)).lifecycleVersion!,
3018
3210
  taskId: id,
3019
3211
  ...(options.reason ? { reason: options.reason } : {}),
3020
3212
  ...key,
3021
3213
  })
3022
- : service.tasks.reopen({ taskId: id, ...key }),
3214
+ : service.tasks.reopen({
3215
+ expectedVersion: (await service.tasks.get(id)).lifecycleVersion!,
3216
+ taskId: id,
3217
+ ...key,
3218
+ }),
3023
3219
  );
3024
3220
  output(io, global.json, record, `Task ${id} is ${record.status}.`);
3025
3221
  },
3026
3222
  );
3027
3223
  }
3028
-
3224
+ program
3225
+ .command('search <query>')
3226
+ .option('--kind <kind>')
3227
+ .option('--after <cursor>')
3228
+ .option('--limit <number>')
3229
+ .action(
3230
+ async (
3231
+ q: string,
3232
+ options: { kind?: string; after?: string; limit?: string },
3233
+ command: Command,
3234
+ ) => {
3235
+ const page = await withProfile(command, (service) =>
3236
+ service.search({
3237
+ q,
3238
+ ...(options.kind ? { kinds: [options.kind as HpRecordKind] } : {}),
3239
+ ...(options.after ? { after: options.after } : {}),
3240
+ ...(options.limit ? { limit: Number(options.limit) } : {}),
3241
+ }),
3242
+ );
3243
+ output(
3244
+ io,
3245
+ globals(command).json,
3246
+ page,
3247
+ page.items
3248
+ .map(
3249
+ (hit) => `${hit.kind}: ${hit.title} [${hit.rootId}]
3250
+ ${hit.snippet}`,
3251
+ )
3252
+ .join('\n') || 'No visible results.',
3253
+ );
3254
+ },
3255
+ );
3256
+ const bookmarkCommand = program.command('bookmark').description('Your own saved records');
3257
+ bookmarkCommand
3258
+ .command('list')
3259
+ .option('--after <cursor>')
3260
+ .option('--limit <number>')
3261
+ .action(async (options: { after?: string; limit?: string }, command: Command) => {
3262
+ const page = await withProfile(command, (service) =>
3263
+ service.bookmarks.list({
3264
+ ...options,
3265
+ ...(options.limit ? { limit: Number(options.limit) } : {}),
3266
+ } as HpBookmarkInput),
3267
+ );
3268
+ output(
3269
+ io,
3270
+ globals(command).json,
3271
+ page,
3272
+ page.items
3273
+ .map((entry) => `${entry.root.kind}: ${entry.root.title} [${entry.root.id}]`)
3274
+ .join('\n') || 'No visible bookmarks.',
3275
+ );
3276
+ });
3277
+ for (const operation of ['set', 'remove'] as const)
3278
+ bookmarkCommand
3279
+ .command(`${operation} <root-id>`)
3280
+ .action(async (rootId: string, _options: object, command: Command) => {
3281
+ const result = await withProfile(command, (service) =>
3282
+ service.bookmarks.set({ rootId, present: operation === 'set' }),
3283
+ );
3284
+ output(
3285
+ io,
3286
+ globals(command).json,
3287
+ result,
3288
+ operation === 'set' ? 'Bookmark saved.' : 'Bookmark removed.',
3289
+ );
3290
+ });
3291
+ const replyCommand = program
3292
+ .command('reply')
3293
+ .description('Discuss any visible record without changing its lifecycle');
3294
+ replyCommand
3295
+ .command('create <root-id>')
3296
+ .requiredOption('--body <text>', 'Immutable reply body')
3297
+ .option('--parent <reply-id>', 'Reply to a parent in this thread')
3298
+ .option(
3299
+ '--mention <actor>',
3300
+ 'Mention a typed human/agent',
3301
+ (value: string, previous: string[]) => [...previous, value],
3302
+ [],
3303
+ )
3304
+ .option('--idempotency-key <key>', 'Stable retry key')
3305
+ .action(
3306
+ async (
3307
+ rootId: string,
3308
+ options: { body: string; parent?: string; mention: string[]; idempotencyKey?: string },
3309
+ command: Command,
3310
+ ) => {
3311
+ const result = await withProfile(command, (service) =>
3312
+ service.replies.create({
3313
+ rootId,
3314
+ body: options.body,
3315
+ ...(options.parent ? { parentId: options.parent } : {}),
3316
+ mentions: options.mention.map(targetRef),
3317
+ ...(options.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : {}),
3318
+ }),
3319
+ );
3320
+ output(
3321
+ io,
3322
+ globals(command).json,
3323
+ result,
3324
+ `Reply ${result.id} created by ${result.author.kind}:${result.author.id}.`,
3325
+ );
3326
+ },
3327
+ );
3328
+ replyCommand
3329
+ .command('show <reply-id>')
3330
+ .action(async (id: string, _options: object, command: Command) => {
3331
+ const result = await withProfile(command, (service) => service.replies.get(id));
3332
+ output(
3333
+ io,
3334
+ globals(command).json,
3335
+ result,
3336
+ result.deleted ? 'Deleted reply.' : (result.body ?? ''),
3337
+ );
3338
+ });
3339
+ replyCommand
3340
+ .command('delete <reply-id>')
3341
+ .requiredOption('--expected-version <number>', 'Current reply version')
3342
+ .option('--reason <text>', 'Moderation reason')
3343
+ .option('--idempotency-key <key>', 'Stable retry key')
3344
+ .action(
3345
+ async (
3346
+ replyId: string,
3347
+ options: { expectedVersion: string; reason?: string; idempotencyKey?: string },
3348
+ command: Command,
3349
+ ) => {
3350
+ const result = await withProfile(command, (service) =>
3351
+ service.replies.delete({
3352
+ replyId,
3353
+ expectedVersion: Number(options.expectedVersion),
3354
+ ...(options.reason ? { reason: options.reason } : {}),
3355
+ ...(options.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : {}),
3356
+ }),
3357
+ );
3358
+ output(io, globals(command).json, result, 'Visible reply removed; audit history remains.');
3359
+ },
3360
+ );
3361
+ const threadCommand = program.command('thread').description('Read and follow record timelines');
3362
+ threadCommand
3363
+ .command('show <root-id>')
3364
+ .option('--after <cursor>', 'Continue a signed snapshot')
3365
+ .option('--limit <number>', 'Page size')
3366
+ .action(
3367
+ async (rootId: string, options: { after?: string; limit?: string }, command: Command) => {
3368
+ const result = await withProfile(command, (service) =>
3369
+ service.threads.get({
3370
+ rootId,
3371
+ ...(options.after ? { after: options.after } : {}),
3372
+ ...(options.limit ? { limit: Number(options.limit) } : {}),
3373
+ }),
3374
+ );
3375
+ output(io, globals(command).json, result, JSON.stringify(result, null, 2));
3376
+ },
3377
+ );
3378
+ threadCommand
3379
+ .command('subscription <root-id>')
3380
+ .option('--follow', 'Follow new replies')
3381
+ .option('--mute', 'Mute notifications')
3382
+ .action(
3383
+ async (rootId: string, options: { follow?: boolean; mute?: boolean }, command: Command) => {
3384
+ await withProfile(command, (service) =>
3385
+ service.threads.subscription({
3386
+ rootId,
3387
+ following: !!options.follow,
3388
+ muted: !!options.mute,
3389
+ }),
3390
+ );
3391
+ output(io, globals(command).json, { updated: true }, 'Thread subscription updated.');
3392
+ },
3393
+ );
3394
+ program
3395
+ .command('react <target-id> <code>')
3396
+ .option('--remove', 'Set absent instead of present')
3397
+ .option('--idempotency-key <key>', 'Stable retry key')
3398
+ .action(
3399
+ async (
3400
+ targetId: string,
3401
+ code: HpReactionCode,
3402
+ options: { remove?: boolean; idempotencyKey?: string },
3403
+ command: Command,
3404
+ ) => {
3405
+ const result = await withProfile(command, (service) =>
3406
+ service.reactions.set({
3407
+ targetId,
3408
+ code,
3409
+ present: !options.remove,
3410
+ ...(options.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : {}),
3411
+ }),
3412
+ );
3413
+ output(io, globals(command).json, result, 'Reaction updated.');
3414
+ },
3415
+ );
3029
3416
  /* ------------------------------------------------------------ maintenance */
3030
-
3031
3417
  const projectionCommand = program
3032
3418
  .command('projection')
3033
3419
  .description('Inspect the generated files Synomem derives from events');
3034
-
3035
3420
  projectionCommand
3036
3421
  .command('status')
3037
3422
  .description('Report whether the generated files match the canonical events (local stores)')
@@ -3061,7 +3446,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
3061
3446
  for (const path of status.unexpected) lines.push(` unexpected ${path}`);
3062
3447
  output(io, global.json, status, lines.join('\n'));
3063
3448
  });
3064
-
3065
3449
  program
3066
3450
  .command('rebuild')
3067
3451
  .description('Regenerate current-state and filesystem projections from canonical events')
@@ -3075,7 +3459,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
3075
3459
  `Rebuilt ${result.generated.length} file(s); removed ${result.removed.length} stale file(s).`,
3076
3460
  );
3077
3461
  });
3078
-
3079
3462
  program
3080
3463
  .command('backup <destination>')
3081
3464
  .description('Create a transactionally consistent SQLite backup (local stores)')
@@ -3092,21 +3475,33 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
3092
3475
  });
3093
3476
  output(io, global.json, { path }, `Created backup at ${path}`);
3094
3477
  });
3095
-
3096
3478
  program
3097
3479
  .command('export')
3098
3480
  .description('Export canonical events for portability')
3481
+ .option(
3482
+ '--raw',
3483
+ 'filesystem-owner recovery: export unchanged JSON events without opening or migrating the store',
3484
+ false,
3485
+ )
3099
3486
  .addOption(
3100
3487
  new Option('--format <format>').choices(['json', 'jsonl', 'markdown']).default('json'),
3101
3488
  )
3102
3489
  .option('--output <path>', 'write to an explicit destination instead of stdout')
3103
3490
  .action(
3104
3491
  async (
3105
- options: { format: 'json' | 'jsonl' | 'markdown'; output?: string },
3492
+ options: {
3493
+ format: 'json' | 'jsonl' | 'markdown';
3494
+ raw: boolean;
3495
+ output?: string;
3496
+ },
3106
3497
  command: Command,
3107
3498
  ) => {
3108
3499
  const global = globals(command);
3109
- const content = await withManagement(command, (service) => service.export(options.format));
3500
+ if (options.raw && options.format === 'markdown')
3501
+ throw new SynomemError('INVALID_INPUT', 'Raw recovery supports json or jsonl only.');
3502
+ const content = options.raw
3503
+ ? exportRawLocalEvents(global.home, options.format as 'json' | 'jsonl')
3504
+ : await withManagement(command, (service) => service.export(options.format));
3110
3505
  if (options.output) {
3111
3506
  const destination = resolve(options.output);
3112
3507
  atomicWriteFile(destination, content, 0o600);
@@ -3121,7 +3516,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
3121
3516
  }
3122
3517
  },
3123
3518
  );
3124
-
3125
3519
  program
3126
3520
  .command('doctor')
3127
3521
  .description('Run safe diagnostics')
@@ -3134,9 +3528,7 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
3134
3528
  output(io, global.json, result, human);
3135
3529
  if (!result.healthy) cliExitCodes.set(program, 5);
3136
3530
  });
3137
-
3138
3531
  /* ------------------------------------------------------------ mcp */
3139
-
3140
3532
  program
3141
3533
  .command('mcp')
3142
3534
  .description('Run the MCP server over stdio for a profile (fixed) or a preset (explicit)')
@@ -3146,34 +3538,39 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
3146
3538
  'explicit',
3147
3539
  ]),
3148
3540
  )
3149
- .action(async (options: { contexts?: 'fixed' | 'explicit' }, command: Command) => {
3150
- const global = globals(command);
3151
- const config = profileStoreFor(global.home).read();
3152
- const selection = selectionFor(global, config);
3153
- if (!selection) throw noSelectionError();
3154
- if (selection.kind === 'preset' && options.contexts !== 'explicit') {
3155
- throw new SynomemError(
3156
- 'INVALID_INPUT',
3157
- `Preset ${selection.name} serves several identities; start it with --contexts explicit so every tool call names its context.`,
3158
- );
3159
- }
3160
- if (selection.kind === 'profile' && options.contexts === 'explicit') {
3161
- throw new SynomemError(
3162
- 'INVALID_INPUT',
3163
- 'A profile is one fixed identity. Use --preset <name> --contexts explicit for several.',
3164
- );
3165
- }
3166
- const deps = resolverDeps(global.home);
3167
- const resolver: ContextResolver =
3168
- selection.kind === 'profile'
3169
- ? profileResolver(global.home, config, selection.name, deps)
3170
- : presetResolver(global.home, config, selection.name, deps);
3171
- await (dependencies.startMcpServer ?? runStdioServer)({ resolver });
3172
- });
3173
-
3541
+ .action(
3542
+ async (
3543
+ options: {
3544
+ contexts?: 'fixed' | 'explicit';
3545
+ },
3546
+ command: Command,
3547
+ ) => {
3548
+ const global = globals(command);
3549
+ const config = profileStoreFor(global.home).read();
3550
+ const selection = selectionFor(global, config);
3551
+ if (!selection) throw noSelectionError();
3552
+ if (selection.kind === 'preset' && options.contexts !== 'explicit') {
3553
+ throw new SynomemError(
3554
+ 'INVALID_INPUT',
3555
+ `Preset ${selection.name} serves several identities; start it with --contexts explicit so every tool call names its context.`,
3556
+ );
3557
+ }
3558
+ if (selection.kind === 'profile' && options.contexts === 'explicit') {
3559
+ throw new SynomemError(
3560
+ 'INVALID_INPUT',
3561
+ 'A profile is one fixed identity. Use --preset <name> --contexts explicit for several.',
3562
+ );
3563
+ }
3564
+ const deps = resolverDeps(global.home);
3565
+ const resolver: ContextResolver =
3566
+ selection.kind === 'profile'
3567
+ ? profileResolver(global.home, config, selection.name, deps)
3568
+ : presetResolver(global.home, config, selection.name, deps);
3569
+ await (dependencies.startMcpServer ?? runStdioServer)({ resolver });
3570
+ },
3571
+ );
3174
3572
  return program;
3175
3573
  }
3176
-
3177
3574
  /**
3178
3575
  * Serves one resolver over stdio until the client disconnects.
3179
3576
  *
@@ -3197,7 +3594,6 @@ async function runStdioServer(options: { resolver: ContextResolver }): Promise<v
3197
3594
  await runtime.close().catch(() => undefined);
3198
3595
  await options.resolver.close?.();
3199
3596
  }
3200
-
3201
3597
  export async function runCli(
3202
3598
  argv = process.argv,
3203
3599
  io: CliIo = defaultIo,
@@ -3235,7 +3631,6 @@ export async function runCli(
3235
3631
  return exitCode(synomemError.code);
3236
3632
  }
3237
3633
  }
3238
-
3239
3634
  if (process.argv[1] && import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href) {
3240
3635
  process.exitCode = await runCli();
3241
3636
  }