synomem 0.5.3 → 0.6.1

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 (77) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/README.md +30 -12
  3. package/dist/cli.d.ts +5 -3
  4. package/dist/cli.d.ts.map +1 -1
  5. package/dist/cli.js +245 -47
  6. package/dist/cli.js.map +1 -1
  7. package/dist/client.d.ts.map +1 -1
  8. package/dist/client.js +10 -0
  9. package/dist/client.js.map +1 -1
  10. package/dist/config.d.ts +1 -0
  11. package/dist/config.d.ts.map +1 -1
  12. package/dist/config.js +4 -0
  13. package/dist/config.js.map +1 -1
  14. package/dist/configure.d.ts.map +1 -1
  15. package/dist/configure.js +5 -4
  16. package/dist/configure.js.map +1 -1
  17. package/dist/discover.d.ts +9 -11
  18. package/dist/discover.d.ts.map +1 -1
  19. package/dist/discover.js +14 -15
  20. package/dist/discover.js.map +1 -1
  21. package/dist/import.d.ts +4 -4
  22. package/dist/index.d.ts +5 -1
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +3 -1
  25. package/dist/index.js.map +1 -1
  26. package/dist/mcp/index.d.ts.map +1 -1
  27. package/dist/mcp/index.js +46 -0
  28. package/dist/mcp/index.js.map +1 -1
  29. package/dist/mcp-server.js +26 -3
  30. package/dist/mcp-server.js.map +1 -1
  31. package/dist/ports/projections.d.ts +8 -0
  32. package/dist/ports/projections.d.ts.map +1 -1
  33. package/dist/project.d.ts +51 -0
  34. package/dist/project.d.ts.map +1 -0
  35. package/dist/project.js +143 -0
  36. package/dist/project.js.map +1 -0
  37. package/dist/projections.d.ts +14 -0
  38. package/dist/projections.d.ts.map +1 -1
  39. package/dist/projections.js +36 -1
  40. package/dist/projections.js.map +1 -1
  41. package/dist/remote.d.ts.map +1 -1
  42. package/dist/remote.js +9 -0
  43. package/dist/remote.js.map +1 -1
  44. package/dist/schemas.d.ts +18 -4
  45. package/dist/schemas.d.ts.map +1 -1
  46. package/dist/schemas.js +29 -2
  47. package/dist/schemas.js.map +1 -1
  48. package/dist/service.d.ts +1 -0
  49. package/dist/service.d.ts.map +1 -1
  50. package/dist/storage.d.ts +13 -0
  51. package/dist/storage.d.ts.map +1 -1
  52. package/dist/storage.js +24 -0
  53. package/dist/storage.js.map +1 -1
  54. package/dist/types.d.ts +1 -0
  55. package/dist/types.d.ts.map +1 -1
  56. package/dist/workspaces.d.ts +41 -0
  57. package/dist/workspaces.d.ts.map +1 -0
  58. package/dist/workspaces.js +96 -0
  59. package/dist/workspaces.js.map +1 -0
  60. package/package.json +1 -1
  61. package/src/cli.ts +305 -50
  62. package/src/client.ts +10 -0
  63. package/src/config.ts +4 -0
  64. package/src/configure.ts +5 -4
  65. package/src/discover.ts +18 -19
  66. package/src/index.ts +22 -1
  67. package/src/mcp/index.ts +62 -0
  68. package/src/mcp-server.ts +32 -5
  69. package/src/ports/projections.ts +9 -0
  70. package/src/project.ts +168 -0
  71. package/src/projections.ts +38 -1
  72. package/src/remote.ts +9 -0
  73. package/src/schemas.ts +44 -12
  74. package/src/service.ts +1 -0
  75. package/src/storage.ts +28 -0
  76. package/src/types.ts +1 -0
  77. package/src/workspaces.ts +107 -0
package/dist/cli.js CHANGED
@@ -8,8 +8,10 @@ import { configuredServiceFactory, readSynomemConfig, writeSynomemBackend } from
8
8
  import { cloudApiUrl } from './cloud.js';
9
9
  import { resolveHome } from './config.js';
10
10
  import { assertInteractive, confirmPlan, credentialFingerprint, credentialStoreChoices, environmentInstructions, readAccessToken, runConfigWizard, writeCredentialFile, } from './configure.js';
11
- import { discoverBoundWorkspace, discoverOrganizations, workspaceChoices } from './discover.js';
12
- import { defaultPromptIo } from './prompt.js';
11
+ import { discoverAccessKeyWorkspaces, discoverOrganizations, workspaceChoices, } from './discover.js';
12
+ import { DEFAULT_WORKSPACE, listLocalWorkspaces, localWorkspaceHome } from './workspaces.js';
13
+ import { findProjectSelection, resolveWorkspaceSelection, writeProjectSelection, } from './project.js';
14
+ import { defaultPromptIo, select } from './prompt.js';
13
15
  import { credentialReference, OsCredentialStore } from './credentials.js';
14
16
  import { asSynomemError, SynomemError } from './errors.js';
15
17
  import { atomicWriteFile } from './fs-utils.js';
@@ -61,11 +63,19 @@ function actor(kind, id, displayName) {
61
63
  * backend binds the credential to one actor, so SYNOMEM_ACTOR_ID/KIND/NAME must be able to
62
64
  * override them or those commands cannot authenticate.
63
65
  */
64
- function defaultActor(env, fallbackKind, fallbackId) {
65
- const id = env.SYNOMEM_ACTOR_ID?.trim();
66
+ function defaultActor(env, fallbackKind, fallbackId,
67
+ /** `--actor`, which outranks the environment: it is said on this invocation. */
68
+ override) {
69
+ const id = override?.trim() || env.SYNOMEM_ACTOR_ID?.trim();
66
70
  if (!id)
67
71
  return actor(fallbackKind, fallbackId);
68
- return actor(env.SYNOMEM_ACTOR_KIND?.trim() || fallbackKind, id, env.SYNOMEM_ACTOR_NAME?.trim());
72
+ const kind = override?.trim()
73
+ ? // An explicit --actor names an agent unless told otherwise; the historical
74
+ // fallbacks here are `system`/`cli`, which is not what somebody means when
75
+ // they name one.
76
+ env.SYNOMEM_ACTOR_KIND?.trim() || 'agent'
77
+ : env.SYNOMEM_ACTOR_KIND?.trim() || fallbackKind;
78
+ return actor(kind, id, env.SYNOMEM_ACTOR_NAME?.trim());
69
79
  }
70
80
  function taskDue(options) {
71
81
  if (options.dueDate && options.dueAt)
@@ -148,9 +158,28 @@ function showRecord(record) {
148
158
  function output(io, json, value, human) {
149
159
  io.stdout(json ? `${JSON.stringify(value, null, 2)}\n` : `${human}\n`);
150
160
  }
151
- function globals(command) {
152
- return command.optsWithGlobals();
161
+ /**
162
+ * The resolved global options for a command.
163
+ *
164
+ * `--workspace` is turned into a home HERE, before any service exists, which is
165
+ * the whole reason it costs nothing downstream: a local workspace is a separate
166
+ * database in its own home, and choosing one is choosing a home. Nothing in the
167
+ * domain, the commands, or the MCP tools learns that a workspace was selected.
168
+ *
169
+ * On a remote backend the name means a hosted workspace instead, which
170
+ * `backend use remote --workspace` already handles; passing both here would be
171
+ * two different answers to the same question, so it is refused.
172
+ */
173
+ /** `parent child`, so a subcommand name cannot be confused with another's. */
174
+ function commandPath(command) {
175
+ const parent = command.parent?.name();
176
+ return parent && parent !== 'synomem' ? `${parent} ${command.name()}` : command.name();
153
177
  }
178
+ /**
179
+ * Commands where `--workspace` names a HOSTED workspace being configured,
180
+ * rather than a local one to act in.
181
+ */
182
+ const CONFIGURES_BACKEND = new Set(['config init', 'backend use', 'remote import']);
154
183
  async function withService(serviceFactory, home, configuredActor, operation) {
155
184
  const client = serviceFactory({ ...(home ? { home } : {}), actor: configuredActor });
156
185
  await client.init();
@@ -195,11 +224,80 @@ function listInput(options) {
195
224
  offset: Number(options.offset),
196
225
  };
197
226
  }
198
- export function createCli(io = defaultIo, serviceFactory = configuredServiceFactory, dependencies = {}) {
227
+ /**
228
+ * The value `--actor` should supply to commands that name an actor.
229
+ *
230
+ * Read from argv directly, before the commands are built, because Commander
231
+ * evaluates option defaults at DECLARATION time: a `--as` declared without one
232
+ * is required, and a `--as` declared with one is already satisfied. Supplying
233
+ * it here is a single change point instead of a fallback threaded through
234
+ * twenty action bodies, and `--as` still wins when both are given because an
235
+ * explicitly passed option overrides its default.
236
+ */
237
+ function actorDefault(argv, env) {
238
+ for (let index = 0; index < argv.length; index += 1) {
239
+ const argument = argv[index];
240
+ if (argument === '--actor')
241
+ return argv[index + 1]?.trim() || undefined;
242
+ if (argument.startsWith('--actor='))
243
+ return argument.slice('--actor='.length).trim() || undefined;
244
+ }
245
+ if (env.SYNOMEM_ACTOR_ID?.trim())
246
+ return env.SYNOMEM_ACTOR_ID.trim();
247
+ /*
248
+ * Last, the project's own binding. `workspace use --as` exists so a
249
+ * repository can settle both questions once — which workspace, and as whom —
250
+ * and a command run there needs neither flag afterwards.
251
+ */
252
+ try {
253
+ return findProjectSelection()?.actor;
254
+ }
255
+ catch {
256
+ // A malformed project file is reported by the resolver when the command
257
+ // actually runs, with the path in the message. Failing here would turn it
258
+ // into an error before any command had been parsed.
259
+ return undefined;
260
+ }
261
+ }
262
+ export function createCli(io = defaultIo, serviceFactory = configuredServiceFactory, dependencies = {}, argv = process.argv) {
199
263
  const env = dependencies.env ?? process.env;
264
+ const actingDefault = actorDefault(argv, env);
265
+ const globals = (command) => {
266
+ const options = command.optsWithGlobals();
267
+ if (CONFIGURES_BACKEND.has(commandPath(command)))
268
+ return options;
269
+ /*
270
+ * Resolution runs even with no `--workspace`, because a project's
271
+ * `.synomem/config.json` selects one without anybody passing a flag — that is
272
+ * the whole point of it. `--home` still wins outright: it names a home
273
+ * directly rather than a workspace within one.
274
+ */
275
+ if (!options.home) {
276
+ const selection = resolveWorkspaceSelection({
277
+ ...(options.workspace ? { flag: options.workspace } : {}),
278
+ ...(options.actor ? { actorFlag: options.actor } : {}),
279
+ env,
280
+ });
281
+ return { ...options, home: selection.home, actor: selection.actor ?? options.actor };
282
+ }
283
+ if (!options.workspace)
284
+ return options;
285
+ /*
286
+ * One flag, one meaning — "which workspace" — resolved differently by the
287
+ * handful of commands that CONFIGURE a backend rather than act inside one.
288
+ * For those, the value is a hosted workspace ID to be written to the config,
289
+ * so it is passed through raw and they read `workspace`. Everywhere else it
290
+ * names a local workspace, which is a home.
291
+ *
292
+ * These commands used to declare their own `--workspace`, which does not
293
+ * work: Commander gives a duplicated long flag to the parent, so the
294
+ * subcommand never received it at all.
295
+ */
296
+ return { ...options, home: localWorkspaceHome(options.workspace, options.home) };
297
+ };
200
298
  const credentialStore = dependencies.credentialStore ?? new OsCredentialStore();
201
299
  const oauthLogin = dependencies.oauthLogin ?? loginWithOAuth;
202
- const discoverWorkspace = dependencies.discoverBoundWorkspace ?? discoverBoundWorkspace;
300
+ const discoverWorkspaces = dependencies.discoverAccessKeyWorkspaces ?? discoverAccessKeyWorkspaces;
203
301
  const promptIo = dependencies.promptIo ?? defaultPromptIo();
204
302
  const verifyRemoteCredential = dependencies.verifyRemoteCredential ??
205
303
  (async (options) => {
@@ -224,9 +322,86 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
224
322
  .description('Local-first communication, memory, recognition, and task infrastructure for agents')
225
323
  .version(packageVersion())
226
324
  .option('--home <path>', 'storage root (defaults to SYNOMEM_HOME or ~/.synomem)')
325
+ // A local workspace is its own database under the root, so this selects a
326
+ // home. On a remote backend the hosted workspace is chosen by
327
+ // `backend use remote --workspace` instead.
328
+ .option('--workspace <name>', 'local workspace to act in (see `synomem workspace list`)')
329
+ .option('--actor <id>', 'act as this agent (overrides SYNOMEM_ACTOR_ID)')
227
330
  .option('--json', 'emit stable machine-readable JSON', false)
228
331
  .showSuggestionAfterError()
229
332
  .configureOutput({ writeOut: io.stdout, writeErr: io.stderr });
333
+ /*
334
+ * Local workspaces.
335
+ *
336
+ * Each is a separate database in its own home, which is what makes the
337
+ * isolation real: SQLite has no row-level security, so a shared file would
338
+ * rest on every query remembering to filter, with nothing to catch a miss.
339
+ * Separate files mean cross-workspace leakage is not something anybody can
340
+ * write by accident.
341
+ */
342
+ const workspaceCommand = program
343
+ .command('workspace')
344
+ .description('Work in a separate local store, isolated from the others');
345
+ workspaceCommand
346
+ .command('list')
347
+ .description('List the local workspaces on this machine')
348
+ .action((_options, command) => {
349
+ const global = globals(command);
350
+ // Read from disk, so nothing is listed that does not exist.
351
+ const workspaces = listLocalWorkspaces(global.home);
352
+ // Which one is in effect here, and what decided it — a flag, the
353
+ // environment, a project file, or nothing.
354
+ const selection = resolveWorkspaceSelection({ env });
355
+ const human = workspaces
356
+ .map((workspace) => `${workspace.name === DEFAULT_WORKSPACE ? '*' : ' '} ${workspace.name.padEnd(24)} ${workspace.initialized ? workspace.home : `${workspace.home} (not initialized)`}`)
357
+ .join('\n');
358
+ output(io, global.json, { workspaces, active: selection.workspace ?? DEFAULT_WORKSPACE, source: selection.source }, [
359
+ human,
360
+ '',
361
+ `Acting in: ${selection.workspace ?? DEFAULT_WORKSPACE} (from ${selection.source})`,
362
+ '',
363
+ 'Bind a directory with `synomem workspace use <name>`, or pass --workspace once.',
364
+ 'A local workspace is a separate store on this machine; a hosted workspace is shared,',
365
+ 'and is selected with `backend use remote --workspace`.',
366
+ ].join('\n'));
367
+ });
368
+ workspaceCommand
369
+ .command('use <name>')
370
+ .description('Bind this directory to a workspace, for every session opened here')
371
+ .option('--as <actor-id>', 'also always write as this agent', actingDefault)
372
+ .action((name, options, command) => {
373
+ const global = globals(command);
374
+ // Validated by resolving it, so a name that could never work is refused
375
+ // before a file claiming it is written.
376
+ const home = localWorkspaceHome(name, undefined);
377
+ const path = writeProjectSelection(process.cwd(), {
378
+ workspace: name,
379
+ ...(options.as ? { actor: options.as } : {}),
380
+ });
381
+ output(io, global.json, { path, workspace: name, home, ...(options.as ? { actor: options.as } : {}) }, [
382
+ `Wrote ${path}`,
383
+ '',
384
+ `Every Synomem command and MCP server started in this directory now acts in ${name}${options.as ? ` as ${options.as}` : ''}, with no flag.`,
385
+ `Records live in ${home} — nothing is stored in this directory.`,
386
+ '',
387
+ 'Commit it to share the choice with the repository, or ignore it to keep it yours.',
388
+ ].join('\n'));
389
+ });
390
+ workspaceCommand
391
+ .command('create <name>')
392
+ .description('Create a local workspace and initialize its store')
393
+ .action(async (name, _options, command) => {
394
+ const global = globals(command);
395
+ const home = localWorkspaceHome(name, global.home);
396
+ if (readSynomemConfig(home)) {
397
+ throw new SynomemError('INVALID_INPUT', `Workspace already exists: ${name}`);
398
+ }
399
+ writeSynomemBackend({ kind: 'local' }, home);
400
+ // Opening it once creates the database, so `list` does not report a
401
+ // workspace that exists in name only.
402
+ await withClient(home, defaultActor(env, 'system', 'cli'), async () => undefined);
403
+ output(io, global.json, { name, home }, `Created workspace ${name} at ${home}.\nAct in it with --workspace ${name}.`);
404
+ });
230
405
  const remoteCommand = program.command('remote').description('Administer a remote workspace');
231
406
  /*
232
407
  * The browser counterpart to an access key naming its own workspace.
@@ -347,18 +522,43 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
347
522
  const home = plan.home;
348
523
  const serviceUrl = plan.serviceUrl ?? cloudApiUrl(env);
349
524
  /*
350
- * The workspace is discovered, not typed.
351
- *
352
- * An installation access key is bound to exactly one workspace, so the
353
- * service can be asked which one rather than the person. An explicit
354
- * --workspace still wins, because automation should not depend on a
355
- * network round trip to configure a machine.
525
+ * A member-owned access key reaches every workspace in its organization,
526
+ * never just one, so the workspace is discovered and then chosen — not
527
+ * typed from memory, and not assumed. An explicit --workspace still wins
528
+ * regardless: automation should not depend on a network round trip, and a
529
+ * person who already knows which one they want should not be asked again.
356
530
  */
357
531
  let workspaceId = plan.workspaceId;
358
532
  if (plan.backend === 'remote' && !workspaceId && token) {
359
- const bound = await discoverWorkspace({ baseUrl: serviceUrl, accessToken: token });
360
- workspaceId = bound.workspaceId;
361
- io.stdout(`Access key is bound to workspace ${workspaceId}.\n`);
533
+ const discovered = await discoverWorkspaces({ baseUrl: serviceUrl, accessToken: token });
534
+ if (discovered.workspaces.length === 0) {
535
+ throw new SynomemError('INVALID_INPUT', [
536
+ "This access key's organization has no workspaces yet.",
537
+ 'Create one in the Synomem portal, then re-run this command — or pass',
538
+ '--workspace <workspace-id> once one exists.',
539
+ ].join('\n'));
540
+ }
541
+ else if (discovered.workspaces.length === 1) {
542
+ workspaceId = discovered.workspaces[0].id;
543
+ io.stdout(`Using this key's only workspace: ${discovered.workspaces[0].displayName} (${workspaceId}).\n`);
544
+ }
545
+ else if (promptIo.interactive) {
546
+ workspaceId = await select(promptIo, 'Which workspace should this machine use?', discovered.workspaces.map((workspace) => ({
547
+ value: workspace.id,
548
+ label: workspace.displayName,
549
+ detail: workspace.id,
550
+ })));
551
+ }
552
+ else {
553
+ throw new SynomemError('INVALID_INPUT', [
554
+ 'This access key can reach more than one workspace, so a non-interactive',
555
+ 'setup needs to be told which one:',
556
+ '',
557
+ ...discovered.workspaces.map((workspace) => ` ${workspace.id} ${workspace.displayName}`),
558
+ '',
559
+ 'Re-run with --workspace <workspace-id>.',
560
+ ].join('\n'));
561
+ }
362
562
  }
363
563
  if (plan.backend === 'remote' && !workspaceId) {
364
564
  /*
@@ -449,7 +649,6 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
449
649
  .description('Configure Synomem without prompting')
450
650
  .option('--backend <kind>', 'local or remote')
451
651
  .option('--auth <method>', 'browser or access-key')
452
- .option('--workspace <id>', 'remote workspace ID')
453
652
  .option('--credential-store <where>', 'auto, keychain, file, or environment', 'auto')
454
653
  // The token is read from stdin, never taken as an argument: an argument is
455
654
  // kept by the shell history and visible in the process list.
@@ -461,10 +660,10 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
461
660
  throw new SynomemError('INVALID_INPUT', 'Pass --backend local or --backend remote.');
462
661
  }
463
662
  const backend = options.backend;
464
- // An access key names its own workspace, so --workspace is only
465
- // required when there is no key to ask.
466
- if (backend === 'remote' && !options.workspace && !options.accessTokenStdin) {
467
- throw new SynomemError('INVALID_INPUT', 'Remote setup requires --workspace, or --access-token-stdin so the key can name its own.');
663
+ // An access key can be asked which workspaces it reaches, so
664
+ // --workspace is only required when there is no key to ask.
665
+ if (backend === 'remote' && !global.workspace && !options.accessTokenStdin) {
666
+ throw new SynomemError('INVALID_INPUT', 'Remote setup requires --workspace, or --access-token-stdin so the key can be asked.');
468
667
  }
469
668
  const token = options.accessTokenStdin ? await readAccessToken(promptIo) : undefined;
470
669
  if (backend === 'remote' && options.auth === 'access-key' && !token) {
@@ -477,7 +676,7 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
477
676
  ? {
478
677
  serviceUrl: cloudApiUrl(env),
479
678
  auth: options.auth ?? 'access-key',
480
- workspaceId: options.workspace,
679
+ workspaceId: global.workspace,
481
680
  credentialStore: options.credentialStore,
482
681
  }
483
682
  : {}),
@@ -594,13 +793,12 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
594
793
  // never ask for a service address, because a person has no way to tell a
595
794
  // real one from a phished one.
596
795
  .option('--url <url>', 'internal: alternate HTTPS origin')
597
- .option('--workspace <id>', 'remote workspace ID')
598
796
  .action((kind, options, command) => {
599
797
  const global = globals(command);
600
798
  if (kind !== 'local' && kind !== 'remote') {
601
799
  throw new SynomemError('INVALID_INPUT', 'Backend kind must be local or remote.');
602
800
  }
603
- if (kind === 'remote' && !options.workspace) {
801
+ if (kind === 'remote' && !global.workspace) {
604
802
  throw new SynomemError('INVALID_INPUT', 'Remote backend selection requires --workspace.');
605
803
  }
606
804
  const config = writeSynomemBackend(kind === 'local'
@@ -608,7 +806,7 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
608
806
  : {
609
807
  kind: 'remote',
610
808
  baseUrl: options.url ?? cloudApiUrl(env),
611
- workspaceId: options.workspace,
809
+ workspaceId: global.workspace,
612
810
  }, global.home);
613
811
  output(io, global.json, { backend: config.backend }, `Selected ${config.backend.kind} Synomem backend.`);
614
812
  });
@@ -1066,7 +1264,7 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
1066
1264
  postCommand
1067
1265
  .command('create')
1068
1266
  .description('Publish a post the whole workspace can read')
1069
- .requiredOption('--as <actor-id>')
1267
+ .requiredOption('--as <actor-id>', 'actor to act as (defaults to --actor)', actingDefault)
1070
1268
  .option('--actor-kind <kind>', 'human, agent, or system', 'agent')
1071
1269
  .requiredOption('--title <title>')
1072
1270
  .requiredOption('--body <body>')
@@ -1085,7 +1283,7 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
1085
1283
  postCommand
1086
1284
  .command('list')
1087
1285
  .description('List posts in this workspace')
1088
- .requiredOption('--as <actor-id>')
1286
+ .requiredOption('--as <actor-id>', 'actor to act as (defaults to --actor)', actingDefault)
1089
1287
  .option('--actor-kind <kind>', 'human, agent, or system', 'agent')
1090
1288
  .option('--limit <n>', 'default 10, maximum 50')
1091
1289
  .action(async (options, command) => {
@@ -1099,7 +1297,7 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
1099
1297
  postCommand
1100
1298
  .command('show <post-id>')
1101
1299
  .description('Show one post with its acknowledgements')
1102
- .requiredOption('--as <actor-id>')
1300
+ .requiredOption('--as <actor-id>', 'actor to act as (defaults to --actor)', actingDefault)
1103
1301
  .option('--actor-kind <kind>', 'human, agent, or system', 'agent')
1104
1302
  .action(async (postId, options, command) => {
1105
1303
  const global = globals(command);
@@ -1114,7 +1312,7 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
1114
1312
  postCommand
1115
1313
  .command('acknowledge <post-id>')
1116
1314
  .description('Say you have seen a post')
1117
- .requiredOption('--as <actor-id>')
1315
+ .requiredOption('--as <actor-id>', 'actor to act as (defaults to --actor)', actingDefault)
1118
1316
  .option('--actor-kind <kind>', 'human, agent, or system', 'agent')
1119
1317
  .option('--note <text>', 'optional context for the author')
1120
1318
  .action(async (postId, options, command) => {
@@ -1128,7 +1326,7 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
1128
1326
  postCommand
1129
1327
  .command('roster <post-id>')
1130
1328
  .description('Who has acknowledged a post, and who has not')
1131
- .requiredOption('--as <actor-id>')
1329
+ .requiredOption('--as <actor-id>', 'actor to act as (defaults to --actor)', actingDefault)
1132
1330
  .option('--actor-kind <kind>', 'human, agent, or system', 'agent')
1133
1331
  .action(async (postId, options, command) => {
1134
1332
  const global = globals(command);
@@ -1153,7 +1351,7 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
1153
1351
  postCommand
1154
1352
  .command('archive <post-id>')
1155
1353
  .description('Archive a post you wrote')
1156
- .requiredOption('--as <actor-id>')
1354
+ .requiredOption('--as <actor-id>', 'actor to act as (defaults to --actor)', actingDefault)
1157
1355
  .option('--actor-kind <kind>', 'human, agent, or system', 'agent')
1158
1356
  .option('--reason <text>')
1159
1357
  .action(async (postId, options, command) => {
@@ -1165,7 +1363,7 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
1165
1363
  kudosCommand
1166
1364
  .command('give <recipient>')
1167
1365
  .description('Give specific, evidence-based kudos to an agent')
1168
- .requiredOption('--from <actor-id>', 'stable ID of the giver')
1366
+ .requiredOption('--from <actor-id>', 'stable ID of the giver (defaults to --actor)', actingDefault)
1169
1367
  .requiredOption('--actor-kind <kind>', 'human, agent, or system')
1170
1368
  .option('--actor-name <display-name>')
1171
1369
  .requiredOption('--title <title>')
@@ -1258,7 +1456,7 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
1258
1456
  const memoCommand = program.command('memo').description('Send and manage durable messages');
1259
1457
  memoCommand
1260
1458
  .command('send <recipient>')
1261
- .requiredOption('--from <actor-id>')
1459
+ .requiredOption('--from <actor-id>', 'actor to act as (defaults to --actor)', actingDefault)
1262
1460
  .option('--actor-kind <kind>', 'human, agent, or system', 'agent')
1263
1461
  .option('--actor-name <name>')
1264
1462
  .requiredOption('--subject <subject>')
@@ -1319,7 +1517,7 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
1319
1517
  .description('Retain and revise agent-owned knowledge');
1320
1518
  noteCommand
1321
1519
  .command('create')
1322
- .requiredOption('--as <actor-id>')
1520
+ .requiredOption('--as <actor-id>', 'actor to act as (defaults to --actor)', actingDefault)
1323
1521
  .option('--actor-kind <kind>', 'agent or human', 'agent')
1324
1522
  .option('--owner <agent-id>')
1325
1523
  .requiredOption('--title <title>')
@@ -1358,7 +1556,7 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
1358
1556
  });
1359
1557
  noteCommand
1360
1558
  .command('revise <note-id>')
1361
- .requiredOption('--as <actor-id>')
1559
+ .requiredOption('--as <actor-id>', 'actor to act as (defaults to --actor)', actingDefault)
1362
1560
  .option('--actor-kind <kind>', 'agent or human', 'agent')
1363
1561
  .requiredOption('--expected-version <number>')
1364
1562
  .option('--title <title>')
@@ -1379,7 +1577,7 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
1379
1577
  });
1380
1578
  noteCommand
1381
1579
  .command('archive <note-id>')
1382
- .requiredOption('--as <actor-id>')
1580
+ .requiredOption('--as <actor-id>', 'actor to act as (defaults to --actor)', actingDefault)
1383
1581
  .option('--actor-kind <kind>', 'agent or human', 'agent')
1384
1582
  .option('--idempotency-key <key>')
1385
1583
  .action(async (id, options, command) => {
@@ -1395,7 +1593,7 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
1395
1593
  .description('Create and manage your own private reminders');
1396
1594
  todoCommand
1397
1595
  .command('create')
1398
- .requiredOption('--as <actor-id>')
1596
+ .requiredOption('--as <actor-id>', 'actor to act as (defaults to --actor)', actingDefault)
1399
1597
  .option('--actor-kind <kind>', 'human, agent, or system', 'agent')
1400
1598
  .requiredOption('--title <title>')
1401
1599
  .option('--details <text>', 'private working detail')
@@ -1419,7 +1617,7 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
1419
1617
  });
1420
1618
  todoCommand
1421
1619
  .command('list')
1422
- .requiredOption('--as <actor-id>')
1620
+ .requiredOption('--as <actor-id>', 'actor to act as (defaults to --actor)', actingDefault)
1423
1621
  .option('--actor-kind <kind>', 'human, agent, or system', 'agent')
1424
1622
  .option('--status <status>')
1425
1623
  .option('--limit <number>', 'maximum results', '10')
@@ -1437,7 +1635,7 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
1437
1635
  });
1438
1636
  todoCommand
1439
1637
  .command('show <todo-id>')
1440
- .requiredOption('--as <actor-id>')
1638
+ .requiredOption('--as <actor-id>', 'actor to act as (defaults to --actor)', actingDefault)
1441
1639
  .option('--actor-kind <kind>', 'human, agent, or system', 'agent')
1442
1640
  .action(async (id, options, command) => {
1443
1641
  const global = globals(command);
@@ -1447,7 +1645,7 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
1447
1645
  for (const operation of ['complete', 'reopen', 'cancel', 'archive']) {
1448
1646
  todoCommand
1449
1647
  .command(`${operation} <todo-id>`)
1450
- .requiredOption('--as <actor-id>')
1648
+ .requiredOption('--as <actor-id>', 'actor to act as (defaults to --actor)', actingDefault)
1451
1649
  .option('--actor-kind <kind>', 'human, agent, or system', 'agent')
1452
1650
  .option('--note <text>')
1453
1651
  .option('--reason <text>')
@@ -1485,7 +1683,7 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
1485
1683
  const taskCommand = program.command('task').description('Create and manage agent tasks');
1486
1684
  taskCommand
1487
1685
  .command('create <assignee>')
1488
- .requiredOption('--from <actor-id>')
1686
+ .requiredOption('--from <actor-id>', 'actor to act as (defaults to --actor)', actingDefault)
1489
1687
  .option('--actor-kind <kind>', 'human, agent, or system', 'agent')
1490
1688
  .requiredOption('--title <title>')
1491
1689
  .option('--description <text>')
@@ -1531,7 +1729,7 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
1531
1729
  });
1532
1730
  taskCommand
1533
1731
  .command('update <task-id>')
1534
- .requiredOption('--as <actor-id>')
1732
+ .requiredOption('--as <actor-id>', 'actor to act as (defaults to --actor)', actingDefault)
1535
1733
  .option('--actor-kind <kind>', 'agent or human', 'agent')
1536
1734
  .requiredOption('--expected-version <number>')
1537
1735
  .option('--title <title>')
@@ -1561,7 +1759,7 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
1561
1759
  for (const operation of ['accept', 'reject', 'complete', 'reopen', 'cancel']) {
1562
1760
  const command_ = taskCommand
1563
1761
  .command(`${operation} <task-id>`)
1564
- .requiredOption('--as <actor-id>')
1762
+ .requiredOption('--as <actor-id>', 'actor to act as (defaults to --actor)', actingDefault)
1565
1763
  .option('--actor-kind <kind>', 'agent or human', 'agent')
1566
1764
  .option('--note <text>')
1567
1765
  .option('--reason <text>')
@@ -1637,7 +1835,7 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
1637
1835
  kudosCommand
1638
1836
  .command('revoke <kudos-id>')
1639
1837
  .description('Record a revocation while preserving history')
1640
- .requiredOption('--as <actor-id>')
1838
+ .requiredOption('--as <actor-id>', 'actor to act as (defaults to --actor)', actingDefault)
1641
1839
  .option('--actor-kind <kind>', 'human, agent, or system', 'human')
1642
1840
  .requiredOption('--reason <reason>')
1643
1841
  .option('--administrative', 'mark as an administrative revocation', false)
@@ -1779,7 +1977,7 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
1779
1977
  return program;
1780
1978
  }
1781
1979
  export async function runCli(argv = process.argv, io = defaultIo, serviceFactory = configuredServiceFactory, dependencies = {}) {
1782
- const program = createCli(io, serviceFactory, dependencies);
1980
+ const program = createCli(io, serviceFactory, dependencies, argv);
1783
1981
  program.exitOverride();
1784
1982
  try {
1785
1983
  await program.parseAsync(argv);