synomem 0.1.0 → 0.2.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.
- package/ARCHITECTURE.md +2 -2
- package/CHANGELOG.md +37 -1
- package/README.md +28 -15
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +311 -70
- package/dist/cli.js.map +1 -1
- package/dist/client.d.ts +96 -17
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +333 -76
- package/dist/client.js.map +1 -1
- package/dist/config.d.ts +2 -2
- package/dist/config.js +8 -8
- package/dist/import.d.ts +207 -13
- package/dist/import.d.ts.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +156 -31
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp-server.js +54 -8
- package/dist/mcp-server.js.map +1 -1
- package/dist/ports/repository.d.ts +18 -1
- package/dist/ports/repository.d.ts.map +1 -1
- package/dist/projections.d.ts +18 -1
- package/dist/projections.d.ts.map +1 -1
- package/dist/projections.js +137 -44
- package/dist/projections.js.map +1 -1
- package/dist/remote.d.ts +57 -9
- package/dist/remote.d.ts.map +1 -1
- package/dist/remote.js +43 -2
- package/dist/remote.js.map +1 -1
- package/dist/schemas.d.ts +302 -21
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +142 -26
- package/dist/schemas.js.map +1 -1
- package/dist/service.d.ts +57 -10
- package/dist/service.d.ts.map +1 -1
- package/dist/skill-install.d.ts +8 -2
- package/dist/skill-install.d.ts.map +1 -1
- package/dist/skill-install.js +6 -11
- package/dist/skill-install.js.map +1 -1
- package/dist/storage.d.ts +41 -1
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +254 -36
- package/dist/storage.js.map +1 -1
- package/dist/types.d.ts +213 -38
- package/dist/types.d.ts.map +1 -1
- package/docs/cli.md +53 -20
- package/docs/examples.md +4 -4
- package/docs/mcp.md +12 -4
- package/docs/skill.md +10 -7
- package/docs/storage-format.md +4 -4
- package/openapi/synomem-v1.yaml +24 -24
- package/package.json +1 -1
- package/skills/synomem/SKILL.md +24 -8
- package/skills/synomem/agents/openai.yaml +1 -1
- package/skills/synomem/references/examples.md +1 -1
- package/src/cli.ts +572 -173
- package/src/client.ts +391 -79
- package/src/config.ts +8 -8
- package/src/index.ts +11 -1
- package/src/mcp/index.ts +189 -30
- package/src/mcp-server.ts +58 -8
- package/src/ports/repository.ts +16 -0
- package/src/projections.ts +139 -44
- package/src/remote.ts +118 -8
- package/src/schemas.ts +147 -26
- package/src/service.ts +59 -7
- package/src/skill-install.ts +14 -17
- package/src/storage.ts +326 -34
- package/src/types.ts +228 -39
package/dist/cli.js
CHANGED
|
@@ -50,7 +50,19 @@ function actor(kind, id, displayName) {
|
|
|
50
50
|
...(displayName ? { displayName } : {}),
|
|
51
51
|
};
|
|
52
52
|
}
|
|
53
|
-
|
|
53
|
+
/**
|
|
54
|
+
* The acting identity for commands that do not take an explicit actor.
|
|
55
|
+
* Local storage accepts any actor, so these fall back to the historical CLI defaults; a remote
|
|
56
|
+
* backend binds the credential to one actor, so SYNOMEM_ACTOR_ID/KIND/NAME must be able to
|
|
57
|
+
* override them or those commands cannot authenticate.
|
|
58
|
+
*/
|
|
59
|
+
function defaultActor(env, fallbackKind, fallbackId) {
|
|
60
|
+
const id = env.SYNOMEM_ACTOR_ID?.trim();
|
|
61
|
+
if (!id)
|
|
62
|
+
return actor(fallbackKind, fallbackId);
|
|
63
|
+
return actor(env.SYNOMEM_ACTOR_KIND?.trim() || fallbackKind, id, env.SYNOMEM_ACTOR_NAME?.trim());
|
|
64
|
+
}
|
|
65
|
+
function taskDue(options) {
|
|
54
66
|
if (options.dueDate && options.dueAt)
|
|
55
67
|
throw new SynomemError('INVALID_INPUT', 'Use due-date or due-at, not both.');
|
|
56
68
|
if (options.dueDate)
|
|
@@ -272,7 +284,7 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
|
|
|
272
284
|
if (persisted?.backend.kind === 'remote') {
|
|
273
285
|
throw new SynomemError('INVALID_INPUT', 'The init command requires a local backend.');
|
|
274
286
|
}
|
|
275
|
-
await withClient(options.home,
|
|
287
|
+
await withClient(options.home, defaultActor(env, 'system', 'cli'), async (client) => {
|
|
276
288
|
const info = await client.info();
|
|
277
289
|
if (info.backend !== 'local') {
|
|
278
290
|
throw new SynomemError('INVALID_INPUT', 'The init command requires a local backend.');
|
|
@@ -414,7 +426,7 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
|
|
|
414
426
|
.option('--description <text>')
|
|
415
427
|
.action(async (id, options, command) => {
|
|
416
428
|
const global = globals(command);
|
|
417
|
-
const profile = await withClient(global.home,
|
|
429
|
+
const profile = await withClient(global.home, defaultActor(env, 'system', 'cli'), (client) => client.agents.create({
|
|
418
430
|
id,
|
|
419
431
|
displayName: options.name,
|
|
420
432
|
...(options.alias.length ? { aliases: options.alias } : {}),
|
|
@@ -432,32 +444,64 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
|
|
|
432
444
|
.option('--yes', 'apply the displayed plan', false)
|
|
433
445
|
.option('--force', 'replace a conflicting synomem directory', false)
|
|
434
446
|
.option('--link', 'symlink to the packaged skill instead of copying it', false)
|
|
435
|
-
.option('--
|
|
436
|
-
.
|
|
437
|
-
.action((options, command) => {
|
|
447
|
+
.option('--agent <id-or-alias>', 'bind this installation to an agent')
|
|
448
|
+
.action(async (options, command) => {
|
|
438
449
|
const global = globals(command);
|
|
450
|
+
const runtimes = skillRuntimes(options.runtime);
|
|
451
|
+
/*
|
|
452
|
+
* The agent is resolved BEFORE anything is written. An ambiguous or
|
|
453
|
+
* unknown name then stops the command with a name to fix, rather than
|
|
454
|
+
* leaving a skill installed and pointed at an agent that does not
|
|
455
|
+
* exist.
|
|
456
|
+
*/
|
|
457
|
+
let agentId;
|
|
458
|
+
if (options.agent) {
|
|
459
|
+
agentId = await withClient(global.home, defaultActor(env, 'system', 'cli'), async (client) => {
|
|
460
|
+
const resolution = await client.agents.resolve(options.agent);
|
|
461
|
+
if (!resolution.match) {
|
|
462
|
+
throw new SynomemError('AGENT_NOT_FOUND', resolution.candidates.length
|
|
463
|
+
? `"${options.agent}" matches ${resolution.candidates.length} agents: ${resolution.candidates
|
|
464
|
+
.map((candidate) => candidate.id)
|
|
465
|
+
.join(', ')}. Name one of them.`
|
|
466
|
+
: `Unknown agent: ${options.agent}`);
|
|
467
|
+
}
|
|
468
|
+
return resolution.match.id;
|
|
469
|
+
});
|
|
470
|
+
}
|
|
439
471
|
const result = installSkill({
|
|
440
|
-
runtimes:
|
|
472
|
+
...(runtimes ? { runtimes } : {}),
|
|
441
473
|
apply: options.yes,
|
|
442
474
|
force: options.force,
|
|
443
475
|
link: options.link,
|
|
444
|
-
|
|
445
|
-
actorName: options.actorName,
|
|
476
|
+
...(agentId ? { agentId } : {}),
|
|
446
477
|
});
|
|
478
|
+
// Bindings follow what was actually installed, and only on a real run:
|
|
479
|
+
// a dry run must not claim a binding it did not make, and a runtime
|
|
480
|
+
// whose harness is not present here is not somewhere this agent runs.
|
|
481
|
+
if (agentId && options.yes) {
|
|
482
|
+
const installed = result.locations
|
|
483
|
+
.filter((location) => location.state !== 'unavailable')
|
|
484
|
+
.map((location) => location.runtime);
|
|
485
|
+
if (installed.length) {
|
|
486
|
+
await withClient(global.home, defaultActor(env, 'system', 'cli'), async (client) => {
|
|
487
|
+
for (const runtime of installed) {
|
|
488
|
+
await client.agents.bindRuntime({ agentId, runtime });
|
|
489
|
+
}
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
}
|
|
447
493
|
output(io, global.json, result, formatSkillResult(result, 'install'));
|
|
448
494
|
});
|
|
449
495
|
skillCommand
|
|
450
496
|
.command('status')
|
|
451
497
|
.description('Show installed, stale, missing, or conflicting skill copies')
|
|
452
498
|
.option('--runtime <runtime>', `${skillRuntimeHelp} (repeatable)`, collect, [])
|
|
453
|
-
.option('--
|
|
454
|
-
.option('--actor-name <name>', 'display name used in MCP registration commands')
|
|
499
|
+
.option('--agent <id>', 'print the registration command for this agent')
|
|
455
500
|
.action((options, command) => {
|
|
456
501
|
const global = globals(command);
|
|
457
502
|
const result = skillStatus({
|
|
458
503
|
runtimes: skillRuntimes(options.runtime),
|
|
459
|
-
|
|
460
|
-
actorName: options.actorName,
|
|
504
|
+
...(options.agent ? { agentId: options.agent } : {}),
|
|
461
505
|
});
|
|
462
506
|
output(io, global.json, result, formatSkillResult(result, 'status'));
|
|
463
507
|
});
|
|
@@ -481,7 +525,7 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
|
|
|
481
525
|
.description('List known agent identities')
|
|
482
526
|
.action(async (_options, command) => {
|
|
483
527
|
const global = globals(command);
|
|
484
|
-
const agents = await withClient(global.home,
|
|
528
|
+
const agents = await withClient(global.home, defaultActor(env, 'system', 'cli'), (client) => client.agents.list());
|
|
485
529
|
const human = agents.length
|
|
486
530
|
? agents
|
|
487
531
|
.map((profile) => `${profile.id} ${profile.displayName}${profile.aliases?.length ? ` aliases: ${profile.aliases.join(', ')}` : ''}`)
|
|
@@ -494,7 +538,7 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
|
|
|
494
538
|
.description('Show one agent profile, resolving aliases')
|
|
495
539
|
.action(async (id, _options, command) => {
|
|
496
540
|
const global = globals(command);
|
|
497
|
-
const profile = await withClient(global.home,
|
|
541
|
+
const profile = await withClient(global.home, defaultActor(env, 'system', 'cli'), (client) => client.agents.get(id));
|
|
498
542
|
output(io, global.json, profile, `${profile.displayName} (${profile.id})\n${profile.description ?? 'No description.'}`);
|
|
499
543
|
});
|
|
500
544
|
agentCommand
|
|
@@ -507,13 +551,91 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
|
|
|
507
551
|
.action(async (id, options, command) => {
|
|
508
552
|
const global = globals(command);
|
|
509
553
|
const hasAliases = options.clearAliases || options.alias.length > 0;
|
|
510
|
-
const profile = await withClient(global.home,
|
|
554
|
+
const profile = await withClient(global.home, defaultActor(env, 'system', 'cli'), (client) => client.agents.update(id, {
|
|
511
555
|
...(options.name ? { displayName: options.name } : {}),
|
|
512
556
|
...(hasAliases ? { aliases: options.clearAliases ? [] : options.alias } : {}),
|
|
513
557
|
...(options.description !== undefined ? { description: options.description } : {}),
|
|
514
558
|
}));
|
|
515
559
|
output(io, global.json, profile, `Updated ${profile.displayName} (${profile.id})`);
|
|
516
560
|
});
|
|
561
|
+
agentCommand
|
|
562
|
+
.command('resolve <name>')
|
|
563
|
+
.description('Resolve a name or alias to one agent, or list the candidates')
|
|
564
|
+
.action(async (name, _options, command) => {
|
|
565
|
+
const global = globals(command);
|
|
566
|
+
const resolution = await withClient(global.home, defaultActor(env, 'system', 'cli'), (client) => client.agents.resolve(name));
|
|
567
|
+
// An ambiguous name is a question, not a failure: exit zero and show the
|
|
568
|
+
// candidates so the caller can pick one.
|
|
569
|
+
const human = resolution.match
|
|
570
|
+
? `${resolution.match.displayName} (${resolution.match.id})`
|
|
571
|
+
: resolution.candidates.length
|
|
572
|
+
? `"${resolution.query}" is ambiguous. Candidates:\n${resolution.candidates
|
|
573
|
+
.map((profile) => ` ${profile.id} ${profile.displayName}`)
|
|
574
|
+
.join('\n')}`
|
|
575
|
+
: `No agent answers to "${resolution.query}".`;
|
|
576
|
+
output(io, global.json, resolution, human);
|
|
577
|
+
});
|
|
578
|
+
agentCommand
|
|
579
|
+
.command('directory')
|
|
580
|
+
.description('List agents with their runtime bindings')
|
|
581
|
+
.action(async (_options, command) => {
|
|
582
|
+
const global = globals(command);
|
|
583
|
+
const entries = await withClient(global.home, defaultActor(env, 'system', 'cli'), (client) => client.agents.directory());
|
|
584
|
+
const human = entries.length
|
|
585
|
+
? entries
|
|
586
|
+
.map((entry) => {
|
|
587
|
+
const runtimes = entry.runtimeBindings.length
|
|
588
|
+
? entry.runtimeBindings
|
|
589
|
+
.map((binding) => ` ${binding.runtime}${binding.profile ? `/${binding.profile}` : ''}` +
|
|
590
|
+
// Last-seen is advisory, so it is labelled as an
|
|
591
|
+
// observation rather than a status.
|
|
592
|
+
`${binding.lastSeenAt ? ` last seen ${binding.lastSeenAt}` : ' not yet seen'}`)
|
|
593
|
+
.join('\n')
|
|
594
|
+
: ' no runtime bindings';
|
|
595
|
+
return `${entry.profile.id} ${entry.profile.displayName}\n${runtimes}`;
|
|
596
|
+
})
|
|
597
|
+
.join('\n')
|
|
598
|
+
: 'No agents configured.';
|
|
599
|
+
output(io, global.json, { entries }, human);
|
|
600
|
+
});
|
|
601
|
+
const runtimeCommand = agentCommand.command('runtime').description('Record where an agent runs');
|
|
602
|
+
runtimeCommand
|
|
603
|
+
.command('bind <agent>')
|
|
604
|
+
.description('Bind an agent to a runtime')
|
|
605
|
+
.requiredOption('--runtime <name>', 'runtime family, e.g. claude-code')
|
|
606
|
+
.option('--profile <name>', 'named configuration within the runtime')
|
|
607
|
+
.option('--installation <id>', 'hosted installation this binding belongs to')
|
|
608
|
+
.action(async (agent, options, command) => {
|
|
609
|
+
const global = globals(command);
|
|
610
|
+
const binding = await withClient(global.home, defaultActor(env, 'system', 'cli'), (client) => client.agents.bindRuntime({
|
|
611
|
+
agentId: agent,
|
|
612
|
+
runtime: options.runtime,
|
|
613
|
+
...(options.profile ? { profile: options.profile } : {}),
|
|
614
|
+
...(options.installation ? { installationId: options.installation } : {}),
|
|
615
|
+
}));
|
|
616
|
+
output(io, global.json, binding, `Bound ${binding.agentId} to ${binding.runtime}${binding.profile ? `/${binding.profile}` : ''} (${binding.id})`);
|
|
617
|
+
});
|
|
618
|
+
runtimeCommand
|
|
619
|
+
.command('list <agent>')
|
|
620
|
+
.description('List an agent runtime bindings')
|
|
621
|
+
.action(async (agent, _options, command) => {
|
|
622
|
+
const global = globals(command);
|
|
623
|
+
const bindings = await withClient(global.home, defaultActor(env, 'system', 'cli'), (client) => client.agents.bindings(agent));
|
|
624
|
+
const human = bindings.length
|
|
625
|
+
? bindings
|
|
626
|
+
.map((binding) => `${binding.id} ${binding.runtime}${binding.profile ? `/${binding.profile}` : ''} bound ${binding.boundAt}`)
|
|
627
|
+
.join('\n')
|
|
628
|
+
: 'No runtime bindings.';
|
|
629
|
+
output(io, global.json, { bindings }, human);
|
|
630
|
+
});
|
|
631
|
+
runtimeCommand
|
|
632
|
+
.command('unbind <binding-id>')
|
|
633
|
+
.description('Remove a runtime binding')
|
|
634
|
+
.action(async (bindingId, _options, command) => {
|
|
635
|
+
const global = globals(command);
|
|
636
|
+
const removed = await withClient(global.home, defaultActor(env, 'system', 'cli'), (client) => client.agents.unbindRuntime(bindingId));
|
|
637
|
+
output(io, global.json, { removed }, removed ? `Removed binding ${bindingId}.` : `No binding ${bindingId}.`);
|
|
638
|
+
});
|
|
517
639
|
const kudosCommand = program.command('kudos').description('Give and manage agent recognition');
|
|
518
640
|
kudosCommand
|
|
519
641
|
.command('give <recipient>')
|
|
@@ -543,7 +665,7 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
|
|
|
543
665
|
});
|
|
544
666
|
program
|
|
545
667
|
.command('inbox [agent]')
|
|
546
|
-
.description('Show pending kudos, memos, and
|
|
668
|
+
.description('Show pending kudos, memos, and tasks for an agent')
|
|
547
669
|
.option('--as <agent-id>', 'defaults to the positional agent')
|
|
548
670
|
.option('--limit <number>', 'maximum results (default 10, maximum 50)', '10')
|
|
549
671
|
.option('--cursor <cursor>', 'opaque cursor returned by the previous page')
|
|
@@ -564,7 +686,7 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
|
|
|
564
686
|
});
|
|
565
687
|
addListOptions(kudosCommand.command('list').description('List and filter kudos')).action(async (options, command) => {
|
|
566
688
|
const global = globals(command);
|
|
567
|
-
const page = await withClient(global.home,
|
|
689
|
+
const page = await withClient(global.home, defaultActor(env, 'human', 'local-cli'), (client) => client.kudos.list(listInput(options)));
|
|
568
690
|
output(io, global.json, page, page.items.length
|
|
569
691
|
? `${page.items.map(lineForSummary).join('\n')}${page.hasMore ? `\nNext cursor: ${page.nextCursor}` : ''}`
|
|
570
692
|
: 'No kudos found.');
|
|
@@ -572,7 +694,7 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
|
|
|
572
694
|
program
|
|
573
695
|
.command('list')
|
|
574
696
|
.description('List compact summaries across all record types')
|
|
575
|
-
.option('--kind <kind>', 'kudos, memo, note, or
|
|
697
|
+
.option('--kind <kind>', 'kudos, memo, note, or task (repeatable)', collect, [])
|
|
576
698
|
.option('--participant <agent>')
|
|
577
699
|
.option('--actor <id>')
|
|
578
700
|
.option('--tag <tag>')
|
|
@@ -583,7 +705,7 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
|
|
|
583
705
|
.option('--offset <number>', 'deprecated offset', '0')
|
|
584
706
|
.action(async (options, command) => {
|
|
585
707
|
const global = globals(command);
|
|
586
|
-
const page = await withClient(global.home,
|
|
708
|
+
const page = await withClient(global.home, defaultActor(env, 'human', 'local-cli'), (client) => client.items.list(itemListInput(options)));
|
|
587
709
|
output(io, global.json, page, page.items.length
|
|
588
710
|
? `${page.items.map(lineForItem).join('\n')}${page.hasMore ? `\nNext cursor: ${page.nextCursor}` : ''}`
|
|
589
711
|
: 'No items found.');
|
|
@@ -596,7 +718,7 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
|
|
|
596
718
|
.option('--limit <number>', 'maximum changes (default 20, maximum 100)', '20')
|
|
597
719
|
.action(async (options, command) => {
|
|
598
720
|
const global = globals(command);
|
|
599
|
-
const page = await withClient(global.home,
|
|
721
|
+
const page = await withClient(global.home, defaultActor(env, 'human', 'local-cli'), (client) => client.items.changes({
|
|
600
722
|
limit: Number(options.limit),
|
|
601
723
|
...(options.kind.length ? { kinds: options.kind } : {}),
|
|
602
724
|
...(options.after ? { after: options.after } : {}),
|
|
@@ -639,7 +761,7 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
|
|
|
639
761
|
.option('--cursor <cursor>')
|
|
640
762
|
.action(async (options, command) => {
|
|
641
763
|
const global = globals(command);
|
|
642
|
-
const page = await withClient(global.home,
|
|
764
|
+
const page = await withClient(global.home, defaultActor(env, 'human', 'local-cli'), (client) => client.memos.list({
|
|
643
765
|
...(options.participant ? { participantAgentId: options.participant } : {}),
|
|
644
766
|
...(options.status ? { status: options.status } : {}),
|
|
645
767
|
limit: Number(options.limit),
|
|
@@ -649,7 +771,7 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
|
|
|
649
771
|
});
|
|
650
772
|
memoCommand.command('show <memo-id>').action(async (id, _options, command) => {
|
|
651
773
|
const global = globals(command);
|
|
652
|
-
const record = await withClient(global.home,
|
|
774
|
+
const record = await withClient(global.home, defaultActor(env, 'human', 'local-cli'), (client) => client.memos.get(id));
|
|
653
775
|
output(io, global.json, record, `${record.event.subject}\nID: ${record.event.id}\nStatus: ${record.status}\n\n${record.event.body}`);
|
|
654
776
|
});
|
|
655
777
|
for (const operation of ['read', 'archive']) {
|
|
@@ -697,7 +819,7 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
|
|
|
697
819
|
.option('--limit <number>', 'maximum results', '10')
|
|
698
820
|
.action(async (options, command) => {
|
|
699
821
|
const global = globals(command);
|
|
700
|
-
const page = await withClient(global.home,
|
|
822
|
+
const page = await withClient(global.home, defaultActor(env, 'human', 'local-cli'), (client) => client.notes.list({
|
|
701
823
|
...(options.owner ? { participantAgentId: options.owner } : {}),
|
|
702
824
|
...(options.status ? { status: options.status } : {}),
|
|
703
825
|
limit: Number(options.limit),
|
|
@@ -706,7 +828,7 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
|
|
|
706
828
|
});
|
|
707
829
|
noteCommand.command('show <note-id>').action(async (id, _options, command) => {
|
|
708
830
|
const global = globals(command);
|
|
709
|
-
const record = await withClient(global.home,
|
|
831
|
+
const record = await withClient(global.home, defaultActor(env, 'human', 'local-cli'), (client) => client.notes.get(id));
|
|
710
832
|
output(io, global.json, record, `${record.current.title}\nID: ${record.event.id}\nVersion: ${record.current.version}\n\n${record.current.body}`);
|
|
711
833
|
});
|
|
712
834
|
noteCommand
|
|
@@ -743,8 +865,100 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
|
|
|
743
865
|
}));
|
|
744
866
|
output(io, global.json, record, `Archived note ${id}.`);
|
|
745
867
|
});
|
|
746
|
-
const todoCommand = program
|
|
868
|
+
const todoCommand = program
|
|
869
|
+
.command('todo')
|
|
870
|
+
.description('Create and manage your own private reminders');
|
|
871
|
+
todoCommand
|
|
872
|
+
.command('create')
|
|
873
|
+
.requiredOption('--as <actor-id>')
|
|
874
|
+
.option('--actor-kind <kind>', 'human, agent, or system', 'agent')
|
|
875
|
+
.requiredOption('--title <title>')
|
|
876
|
+
.option('--details <text>', 'private working detail')
|
|
877
|
+
.option('--priority <number>', '1 highest, 4 lowest', '3')
|
|
878
|
+
.option('--due-date <date>')
|
|
879
|
+
.option('--due-at <datetime>')
|
|
880
|
+
.option('--time-zone <iana-zone>')
|
|
881
|
+
.option('--tag <tag>', 'tag (repeatable)', collect, [])
|
|
882
|
+
.option('--idempotency-key <key>')
|
|
883
|
+
.action(async (options, command) => {
|
|
884
|
+
const global = globals(command);
|
|
885
|
+
const result = await withClient(global.home, actor(options.actorKind, options.as), (client) => client.todos.create({
|
|
886
|
+
title: options.title,
|
|
887
|
+
...(options.details ? { details: options.details } : {}),
|
|
888
|
+
priority: Number(options.priority),
|
|
889
|
+
...((due) => (due ? { due } : {}))(taskDue(options)),
|
|
890
|
+
...(options.tag.length ? { tags: options.tag } : {}),
|
|
891
|
+
...(options.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : {}),
|
|
892
|
+
}));
|
|
893
|
+
output(io, global.json, result, `${result.deduplicated ? 'Found existing' : 'Created'} private todo\nTitle: ${result.record.current.title}\nID: ${result.record.event.id}`);
|
|
894
|
+
});
|
|
895
|
+
todoCommand
|
|
896
|
+
.command('list')
|
|
897
|
+
.requiredOption('--as <actor-id>')
|
|
898
|
+
.option('--actor-kind <kind>', 'human, agent, or system', 'agent')
|
|
899
|
+
.option('--status <status>')
|
|
900
|
+
.option('--limit <number>', 'maximum results', '10')
|
|
901
|
+
.action(async (options, command) => {
|
|
902
|
+
const global = globals(command);
|
|
903
|
+
const page = await withClient(global.home, actor(options.actorKind, options.as), (client) => client.todos.list({
|
|
904
|
+
...(options.status ? { status: options.status } : {}),
|
|
905
|
+
limit: Number(options.limit),
|
|
906
|
+
}));
|
|
907
|
+
output(io, global.json, page, page.items.length
|
|
908
|
+
? page.items
|
|
909
|
+
.map((item) => `${item.id} ${item.status.padEnd(9)} ${item.title}`)
|
|
910
|
+
.join('\n')
|
|
911
|
+
: 'No todos.');
|
|
912
|
+
});
|
|
747
913
|
todoCommand
|
|
914
|
+
.command('show <todo-id>')
|
|
915
|
+
.requiredOption('--as <actor-id>')
|
|
916
|
+
.option('--actor-kind <kind>', 'human, agent, or system', 'agent')
|
|
917
|
+
.action(async (id, options, command) => {
|
|
918
|
+
const global = globals(command);
|
|
919
|
+
const record = await withClient(global.home, actor(options.actorKind, options.as), (client) => client.todos.get(id));
|
|
920
|
+
output(io, global.json, record, `${record.current.title}\nStatus: ${record.status}\nPriority: ${record.current.priority}\nVersion: ${record.current.version}${record.current.details ? `\n\n${record.current.details}` : ''}`);
|
|
921
|
+
});
|
|
922
|
+
for (const operation of ['complete', 'reopen', 'cancel', 'archive']) {
|
|
923
|
+
todoCommand
|
|
924
|
+
.command(`${operation} <todo-id>`)
|
|
925
|
+
.requiredOption('--as <actor-id>')
|
|
926
|
+
.option('--actor-kind <kind>', 'human, agent, or system', 'agent')
|
|
927
|
+
.option('--note <text>')
|
|
928
|
+
.option('--reason <text>')
|
|
929
|
+
.option('--idempotency-key <key>')
|
|
930
|
+
.action(async (id, options, command) => {
|
|
931
|
+
const global = globals(command);
|
|
932
|
+
const record = await withClient(global.home, actor(options.actorKind, options.as), (client) => operation === 'complete'
|
|
933
|
+
? client.todos.complete({
|
|
934
|
+
todoId: id,
|
|
935
|
+
...(options.note ? { note: options.note } : {}),
|
|
936
|
+
...(options.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : {}),
|
|
937
|
+
})
|
|
938
|
+
: operation === 'cancel'
|
|
939
|
+
? client.todos.cancel({
|
|
940
|
+
todoId: id,
|
|
941
|
+
...(options.reason ? { reason: options.reason } : {}),
|
|
942
|
+
...(options.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : {}),
|
|
943
|
+
})
|
|
944
|
+
: operation === 'archive'
|
|
945
|
+
? client.todos.archive({
|
|
946
|
+
todoId: id,
|
|
947
|
+
...(options.idempotencyKey
|
|
948
|
+
? { idempotencyKey: options.idempotencyKey }
|
|
949
|
+
: {}),
|
|
950
|
+
})
|
|
951
|
+
: client.todos.reopen({
|
|
952
|
+
todoId: id,
|
|
953
|
+
...(options.idempotencyKey
|
|
954
|
+
? { idempotencyKey: options.idempotencyKey }
|
|
955
|
+
: {}),
|
|
956
|
+
}));
|
|
957
|
+
output(io, global.json, record, `Todo ${id} is now ${record.status}.`);
|
|
958
|
+
});
|
|
959
|
+
}
|
|
960
|
+
const taskCommand = program.command('task').description('Create and manage agent tasks');
|
|
961
|
+
taskCommand
|
|
748
962
|
.command('create <assignee>')
|
|
749
963
|
.requiredOption('--from <actor-id>')
|
|
750
964
|
.option('--actor-kind <kind>', 'human, agent, or system', 'agent')
|
|
@@ -759,39 +973,39 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
|
|
|
759
973
|
.option('--idempotency-key <key>')
|
|
760
974
|
.action(async (assignee, options, command) => {
|
|
761
975
|
const global = globals(command);
|
|
762
|
-
const result = await withClient(global.home, actor(options.actorKind, options.from), (client) => client.
|
|
976
|
+
const result = await withClient(global.home, actor(options.actorKind, options.from), (client) => client.tasks.create({
|
|
763
977
|
assigneeAgentId: assignee,
|
|
764
978
|
title: options.title,
|
|
765
979
|
...(options.description ? { description: options.description } : {}),
|
|
766
980
|
priority: Number(options.priority),
|
|
767
|
-
...((due) => (due ? { due } : {}))(
|
|
981
|
+
...((due) => (due ? { due } : {}))(taskDue(options)),
|
|
768
982
|
...(options.tag.length ? { tags: options.tag } : {}),
|
|
769
983
|
visibility: options.visibility,
|
|
770
984
|
...(options.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : {}),
|
|
771
985
|
}));
|
|
772
|
-
output(io, global.json, result, `${result.deduplicated ? 'Found existing' : 'Created'}
|
|
986
|
+
output(io, global.json, result, `${result.deduplicated ? 'Found existing' : 'Created'} task for ${result.record.event.assigneeDisplayName}\nTitle: ${result.record.current.title}\nID: ${result.record.event.id}`);
|
|
773
987
|
});
|
|
774
|
-
|
|
988
|
+
taskCommand
|
|
775
989
|
.command('list')
|
|
776
990
|
.option('--assignee <agent>')
|
|
777
991
|
.option('--status <status>')
|
|
778
992
|
.option('--limit <number>', 'maximum results', '10')
|
|
779
993
|
.action(async (options, command) => {
|
|
780
994
|
const global = globals(command);
|
|
781
|
-
const page = await withClient(global.home,
|
|
995
|
+
const page = await withClient(global.home, defaultActor(env, 'human', 'local-cli'), (client) => client.tasks.list({
|
|
782
996
|
...(options.assignee ? { participantAgentId: options.assignee } : {}),
|
|
783
997
|
...(options.status ? { status: options.status } : {}),
|
|
784
998
|
limit: Number(options.limit),
|
|
785
999
|
}));
|
|
786
|
-
output(io, global.json, page, page.items.map(lineForItem).join('\n') || 'No
|
|
1000
|
+
output(io, global.json, page, page.items.map(lineForItem).join('\n') || 'No tasks found.');
|
|
787
1001
|
});
|
|
788
|
-
|
|
1002
|
+
taskCommand.command('show <task-id>').action(async (id, _options, command) => {
|
|
789
1003
|
const global = globals(command);
|
|
790
|
-
const record = await withClient(global.home,
|
|
1004
|
+
const record = await withClient(global.home, defaultActor(env, 'human', 'local-cli'), (client) => client.tasks.get(id));
|
|
791
1005
|
output(io, global.json, record, `${record.current.title}\nID: ${record.event.id}\nStatus: ${record.status}\nVersion: ${record.current.version}`);
|
|
792
1006
|
});
|
|
793
|
-
|
|
794
|
-
.command('update <
|
|
1007
|
+
taskCommand
|
|
1008
|
+
.command('update <task-id>')
|
|
795
1009
|
.requiredOption('--as <actor-id>')
|
|
796
1010
|
.option('--actor-kind <kind>', 'agent or human', 'agent')
|
|
797
1011
|
.requiredOption('--expected-version <number>')
|
|
@@ -806,9 +1020,9 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
|
|
|
806
1020
|
.option('--idempotency-key <key>')
|
|
807
1021
|
.action(async (id, options, command) => {
|
|
808
1022
|
const global = globals(command);
|
|
809
|
-
const parsedDue =
|
|
810
|
-
const record = await withClient(global.home, actor(options.actorKind, options.as), (client) => client.
|
|
811
|
-
|
|
1023
|
+
const parsedDue = taskDue(options);
|
|
1024
|
+
const record = await withClient(global.home, actor(options.actorKind, options.as), (client) => client.tasks.update({
|
|
1025
|
+
taskId: id,
|
|
812
1026
|
expectedVersion: Number(options.expectedVersion),
|
|
813
1027
|
...(options.title ? { title: options.title } : {}),
|
|
814
1028
|
...(options.description !== undefined ? { description: options.description } : {}),
|
|
@@ -817,52 +1031,59 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
|
|
|
817
1031
|
...(options.visibility ? { visibility: options.visibility } : {}),
|
|
818
1032
|
...(options.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : {}),
|
|
819
1033
|
}));
|
|
820
|
-
output(io, global.json, record, `Updated
|
|
1034
|
+
output(io, global.json, record, `Updated task ${id} to version ${record.current.version}.`);
|
|
821
1035
|
});
|
|
822
1036
|
for (const operation of ['accept', 'reject', 'complete', 'reopen', 'cancel']) {
|
|
823
|
-
|
|
824
|
-
.command(`${operation} <
|
|
1037
|
+
const command_ = taskCommand
|
|
1038
|
+
.command(`${operation} <task-id>`)
|
|
825
1039
|
.requiredOption('--as <actor-id>')
|
|
826
1040
|
.option('--actor-kind <kind>', 'agent or human', 'agent')
|
|
827
1041
|
.option('--note <text>')
|
|
828
1042
|
.option('--reason <text>')
|
|
829
|
-
.option('--idempotency-key <key>')
|
|
830
|
-
|
|
1043
|
+
.option('--idempotency-key <key>');
|
|
1044
|
+
// Rejecting requires saying why; accepting may. Marked required at the
|
|
1045
|
+
// parser so the CLI refuses before touching the store.
|
|
1046
|
+
if (operation === 'reject') {
|
|
1047
|
+
command_.requiredOption('--response <text>', 'why the task is being refused');
|
|
1048
|
+
}
|
|
1049
|
+
else if (operation === 'accept') {
|
|
1050
|
+
command_.option('--response <text>', 'conditions, timing, or partial capability');
|
|
1051
|
+
}
|
|
1052
|
+
command_.action(async (id, options, command) => {
|
|
831
1053
|
const global = globals(command);
|
|
832
1054
|
const record = await withClient(global.home, actor(options.actorKind, options.as), (client) => operation === 'accept'
|
|
833
|
-
? client.
|
|
834
|
-
|
|
1055
|
+
? client.tasks.accept({
|
|
1056
|
+
taskId: id,
|
|
1057
|
+
...(options.response ? { response: options.response } : {}),
|
|
835
1058
|
...(options.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : {}),
|
|
836
1059
|
})
|
|
837
1060
|
: operation === 'reject'
|
|
838
|
-
? client.
|
|
839
|
-
|
|
840
|
-
|
|
1061
|
+
? client.tasks.reject({
|
|
1062
|
+
taskId: id,
|
|
1063
|
+
response: options.response ?? options.reason ?? '',
|
|
841
1064
|
...(options.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : {}),
|
|
842
1065
|
})
|
|
843
1066
|
: operation === 'complete'
|
|
844
|
-
? client.
|
|
845
|
-
|
|
1067
|
+
? client.tasks.complete({
|
|
1068
|
+
taskId: id,
|
|
846
1069
|
...(options.note ? { note: options.note } : {}),
|
|
847
|
-
...(options.idempotencyKey
|
|
848
|
-
? { idempotencyKey: options.idempotencyKey }
|
|
849
|
-
: {}),
|
|
1070
|
+
...(options.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : {}),
|
|
850
1071
|
})
|
|
851
1072
|
: operation === 'cancel'
|
|
852
|
-
? client.
|
|
853
|
-
|
|
1073
|
+
? client.tasks.cancel({
|
|
1074
|
+
taskId: id,
|
|
854
1075
|
...(options.reason ? { reason: options.reason } : {}),
|
|
855
1076
|
...(options.idempotencyKey
|
|
856
1077
|
? { idempotencyKey: options.idempotencyKey }
|
|
857
1078
|
: {}),
|
|
858
1079
|
})
|
|
859
|
-
: client.
|
|
860
|
-
|
|
1080
|
+
: client.tasks.reopen({
|
|
1081
|
+
taskId: id,
|
|
861
1082
|
...(options.idempotencyKey
|
|
862
1083
|
? { idempotencyKey: options.idempotencyKey }
|
|
863
1084
|
: {}),
|
|
864
1085
|
}));
|
|
865
|
-
output(io, global.json, record, `
|
|
1086
|
+
output(io, global.json, record, `Task ${id} is ${record.status}.`);
|
|
866
1087
|
});
|
|
867
1088
|
}
|
|
868
1089
|
kudosCommand
|
|
@@ -870,7 +1091,7 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
|
|
|
870
1091
|
.description('Show one kudos item and its current state')
|
|
871
1092
|
.action(async (id, _options, command) => {
|
|
872
1093
|
const global = globals(command);
|
|
873
|
-
const record = await withClient(global.home,
|
|
1094
|
+
const record = await withClient(global.home, defaultActor(env, 'system', 'cli'), (client) => client.kudos.get(id));
|
|
874
1095
|
output(io, global.json, record, showRecord(record));
|
|
875
1096
|
});
|
|
876
1097
|
kudosCommand
|
|
@@ -913,7 +1134,7 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
|
|
|
913
1134
|
const global = globals(command);
|
|
914
1135
|
if (!agentId)
|
|
915
1136
|
throw new SynomemError('INVALID_INPUT', 'Specify an agent.');
|
|
916
|
-
const details = await withClient(global.home,
|
|
1137
|
+
const details = await withClient(global.home, defaultActor(env, 'system', 'cli'), async (client) => {
|
|
917
1138
|
const profile = await client.agents.get(agentId);
|
|
918
1139
|
const info = await client.info();
|
|
919
1140
|
if (info.backend !== 'local') {
|
|
@@ -942,7 +1163,7 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
|
|
|
942
1163
|
});
|
|
943
1164
|
addListOptions(kudosCommand.command('stats').description('Show aggregate kudos statistics')).action(async (options, command) => {
|
|
944
1165
|
const global = globals(command);
|
|
945
|
-
const stats = await withClient(global.home,
|
|
1166
|
+
const stats = await withClient(global.home, defaultActor(env, 'system', 'cli'), (client) => client.stats(listInput(options)));
|
|
946
1167
|
output(io, global.json, stats, `Total: ${stats.total}\nActive: ${stats.active}\nAcknowledged: ${stats.acknowledged}\nRevoked: ${stats.revoked}`);
|
|
947
1168
|
});
|
|
948
1169
|
program
|
|
@@ -950,7 +1171,7 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
|
|
|
950
1171
|
.description('Regenerate current-state and filesystem projections from canonical events')
|
|
951
1172
|
.action(async (_options, command) => {
|
|
952
1173
|
const global = globals(command);
|
|
953
|
-
const result = await withClient(global.home,
|
|
1174
|
+
const result = await withClient(global.home, defaultActor(env, 'system', 'cli'), (client) => client.rebuild());
|
|
954
1175
|
output(io, global.json, result, `Rebuilt ${result.generated.length} file(s); removed ${result.removed.length} stale file(s).`);
|
|
955
1176
|
});
|
|
956
1177
|
program
|
|
@@ -958,7 +1179,7 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
|
|
|
958
1179
|
.description('Create a transactionally consistent SQLite backup')
|
|
959
1180
|
.action(async (destination, _options, command) => {
|
|
960
1181
|
const global = globals(command);
|
|
961
|
-
const path = await withClient(global.home,
|
|
1182
|
+
const path = await withClient(global.home, defaultActor(env, 'system', 'cli'), (client) => {
|
|
962
1183
|
if (!client.backup) {
|
|
963
1184
|
throw new SynomemError('INVALID_INPUT', 'Filesystem backup is available only with the local backend.');
|
|
964
1185
|
}
|
|
@@ -973,7 +1194,7 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
|
|
|
973
1194
|
.option('--output <path>', 'write to an explicit destination instead of stdout')
|
|
974
1195
|
.action(async (options, command) => {
|
|
975
1196
|
const global = globals(command);
|
|
976
|
-
const content = await withClient(global.home,
|
|
1197
|
+
const content = await withClient(global.home, defaultActor(env, 'system', 'cli'), (client) => client.export(options.format));
|
|
977
1198
|
if (options.output) {
|
|
978
1199
|
const destination = resolve(options.output);
|
|
979
1200
|
atomicWriteFile(destination, content, 0o600);
|
|
@@ -988,7 +1209,7 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
|
|
|
988
1209
|
.description('Run safe diagnostics')
|
|
989
1210
|
.action(async (_options, command) => {
|
|
990
1211
|
const global = globals(command);
|
|
991
|
-
const result = await withClient(global.home,
|
|
1212
|
+
const result = await withClient(global.home, defaultActor(env, 'system', 'cli'), (client) => client.doctor());
|
|
992
1213
|
const human = result.diagnostics
|
|
993
1214
|
.map((item) => `${item.level.toUpperCase().padEnd(7)} ${item.code}: ${item.message}`)
|
|
994
1215
|
.join('\n');
|
|
@@ -999,14 +1220,34 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
|
|
|
999
1220
|
program
|
|
1000
1221
|
.command('mcp')
|
|
1001
1222
|
.description('Run the actor-bound MCP server over stdio')
|
|
1002
|
-
.
|
|
1003
|
-
.
|
|
1004
|
-
.option('--actor-
|
|
1223
|
+
.option('--agent-id <id>', 'bound agent, whose identity is read from Synomem')
|
|
1224
|
+
.option('--actor-id <id>', 'bound non-agent actor ID')
|
|
1225
|
+
.option('--actor-kind <kind>', 'human or system')
|
|
1226
|
+
.option('--actor-name <display-name>', 'display name for a non-agent actor')
|
|
1005
1227
|
.action(async (options, command) => {
|
|
1006
1228
|
const global = globals(command);
|
|
1229
|
+
// An agent's name comes from its profile, never from the command line:
|
|
1230
|
+
// the name is written into every event the session appends, and a
|
|
1231
|
+
// harness must not be able to sign another agent's name to work.
|
|
1232
|
+
const bound = options.agentId
|
|
1233
|
+
? await withClient(global.home, defaultActor(env, 'system', 'cli'), async (client) => {
|
|
1234
|
+
const resolution = await client.agents.resolve(options.agentId);
|
|
1235
|
+
if (!resolution.match) {
|
|
1236
|
+
throw new SynomemError('AGENT_NOT_FOUND', resolution.candidates.length
|
|
1237
|
+
? `"${options.agentId}" matches ${resolution.candidates.length} agents: ${resolution.candidates
|
|
1238
|
+
.map((candidate) => candidate.id)
|
|
1239
|
+
.join(', ')}. Name one of them.`
|
|
1240
|
+
: `Unknown agent: ${options.agentId}`);
|
|
1241
|
+
}
|
|
1242
|
+
return actor('agent', resolution.match.id, resolution.match.displayName);
|
|
1243
|
+
})
|
|
1244
|
+
: undefined;
|
|
1245
|
+
if (!bound && !(options.actorId && options.actorKind)) {
|
|
1246
|
+
throw new SynomemError('INVALID_INPUT', 'Specify --agent-id, or --actor-id with --actor-kind for a non-agent actor.');
|
|
1247
|
+
}
|
|
1007
1248
|
await startMcpServer({
|
|
1008
1249
|
...(global.home ? { home: global.home } : {}),
|
|
1009
|
-
actor: actor(options.actorKind, options.actorId, options.actorName),
|
|
1250
|
+
actor: bound ?? actor(options.actorKind, options.actorId, options.actorName),
|
|
1010
1251
|
});
|
|
1011
1252
|
});
|
|
1012
1253
|
return program;
|