synomem 0.1.1 → 0.3.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 -0
- package/README.md +26 -13
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +379 -51
- package/dist/cli.js.map +1 -1
- package/dist/client.d.ts +131 -17
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +489 -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 +356 -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 +213 -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 +21 -1
- package/dist/ports/repository.d.ts.map +1 -1
- package/dist/projections.d.ts +26 -1
- package/dist/projections.d.ts.map +1 -1
- package/dist/projections.js +208 -44
- package/dist/projections.js.map +1 -1
- package/dist/remote.d.ts +82 -9
- package/dist/remote.d.ts.map +1 -1
- package/dist/remote.js +53 -2
- package/dist/remote.js.map +1 -1
- package/dist/schemas.d.ts +476 -21
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +204 -26
- package/dist/schemas.js.map +1 -1
- package/dist/service.d.ts +82 -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 +51 -1
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +366 -36
- package/dist/storage.js.map +1 -1
- package/dist/types.d.ts +310 -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 +610 -95
- package/src/client.ts +587 -79
- package/src/config.ts +8 -8
- package/src/index.ts +11 -1
- package/src/mcp/index.ts +265 -30
- package/src/mcp-server.ts +58 -8
- package/src/ports/repository.ts +21 -0
- package/src/projections.ts +209 -44
- package/src/remote.ts +166 -8
- package/src/schemas.ts +211 -26
- package/src/service.ts +85 -7
- package/src/skill-install.ts +14 -17
- package/src/storage.ts +472 -34
- package/src/types.ts +332 -39
package/dist/cli.js
CHANGED
|
@@ -62,7 +62,7 @@ function defaultActor(env, fallbackKind, fallbackId) {
|
|
|
62
62
|
return actor(fallbackKind, fallbackId);
|
|
63
63
|
return actor(env.SYNOMEM_ACTOR_KIND?.trim() || fallbackKind, id, env.SYNOMEM_ACTOR_NAME?.trim());
|
|
64
64
|
}
|
|
65
|
-
function
|
|
65
|
+
function taskDue(options) {
|
|
66
66
|
if (options.dueDate && options.dueAt)
|
|
67
67
|
throw new SynomemError('INVALID_INPUT', 'Use due-date or due-at, not both.');
|
|
68
68
|
if (options.dueDate)
|
|
@@ -444,32 +444,64 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
|
|
|
444
444
|
.option('--yes', 'apply the displayed plan', false)
|
|
445
445
|
.option('--force', 'replace a conflicting synomem directory', false)
|
|
446
446
|
.option('--link', 'symlink to the packaged skill instead of copying it', false)
|
|
447
|
-
.option('--
|
|
448
|
-
.
|
|
449
|
-
.action((options, command) => {
|
|
447
|
+
.option('--agent <id-or-alias>', 'bind this installation to an agent')
|
|
448
|
+
.action(async (options, command) => {
|
|
450
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
|
+
}
|
|
451
471
|
const result = installSkill({
|
|
452
|
-
runtimes:
|
|
472
|
+
...(runtimes ? { runtimes } : {}),
|
|
453
473
|
apply: options.yes,
|
|
454
474
|
force: options.force,
|
|
455
475
|
link: options.link,
|
|
456
|
-
|
|
457
|
-
actorName: options.actorName,
|
|
476
|
+
...(agentId ? { agentId } : {}),
|
|
458
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
|
+
}
|
|
459
493
|
output(io, global.json, result, formatSkillResult(result, 'install'));
|
|
460
494
|
});
|
|
461
495
|
skillCommand
|
|
462
496
|
.command('status')
|
|
463
497
|
.description('Show installed, stale, missing, or conflicting skill copies')
|
|
464
498
|
.option('--runtime <runtime>', `${skillRuntimeHelp} (repeatable)`, collect, [])
|
|
465
|
-
.option('--
|
|
466
|
-
.option('--actor-name <name>', 'display name used in MCP registration commands')
|
|
499
|
+
.option('--agent <id>', 'print the registration command for this agent')
|
|
467
500
|
.action((options, command) => {
|
|
468
501
|
const global = globals(command);
|
|
469
502
|
const result = skillStatus({
|
|
470
503
|
runtimes: skillRuntimes(options.runtime),
|
|
471
|
-
|
|
472
|
-
actorName: options.actorName,
|
|
504
|
+
...(options.agent ? { agentId: options.agent } : {}),
|
|
473
505
|
});
|
|
474
506
|
output(io, global.json, result, formatSkillResult(result, 'status'));
|
|
475
507
|
});
|
|
@@ -526,6 +558,183 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
|
|
|
526
558
|
}));
|
|
527
559
|
output(io, global.json, profile, `Updated ${profile.displayName} (${profile.id})`);
|
|
528
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
|
+
});
|
|
639
|
+
const postCommand = program.command('post').description('Publish to everyone in the workspace');
|
|
640
|
+
postCommand
|
|
641
|
+
.command('create')
|
|
642
|
+
.description('Publish a post the whole workspace can read')
|
|
643
|
+
.requiredOption('--as <actor-id>')
|
|
644
|
+
.option('--actor-kind <kind>', 'human, agent, or system', 'agent')
|
|
645
|
+
.requiredOption('--title <title>')
|
|
646
|
+
.requiredOption('--body <body>')
|
|
647
|
+
.option('--tag <tag>', 'repeatable', collect, [])
|
|
648
|
+
.option('--reply-to <post-id>')
|
|
649
|
+
.action(async (options, command) => {
|
|
650
|
+
const global = globals(command);
|
|
651
|
+
const result = await withClient(global.home, actor(options.actorKind, options.as), (client) => client.posts.create({
|
|
652
|
+
title: options.title,
|
|
653
|
+
body: options.body,
|
|
654
|
+
...(options.tag.length ? { tags: options.tag } : {}),
|
|
655
|
+
...(options.replyTo ? { replyTo: options.replyTo } : {}),
|
|
656
|
+
}));
|
|
657
|
+
output(io, global.json, result, `Published ${result.record.event.id}`);
|
|
658
|
+
});
|
|
659
|
+
postCommand
|
|
660
|
+
.command('list')
|
|
661
|
+
.description('List posts in this workspace')
|
|
662
|
+
.requiredOption('--as <actor-id>')
|
|
663
|
+
.option('--actor-kind <kind>', 'human, agent, or system', 'agent')
|
|
664
|
+
.option('--limit <n>', 'default 10, maximum 50')
|
|
665
|
+
.action(async (options, command) => {
|
|
666
|
+
const global = globals(command);
|
|
667
|
+
const page = await withClient(global.home, actor(options.actorKind, options.as), (client) => client.posts.list(options.limit ? { limit: Number(options.limit) } : {}));
|
|
668
|
+
const human = page.items.length
|
|
669
|
+
? page.items.map((item) => `${item.id} ${item.title}`).join('\n')
|
|
670
|
+
: 'No posts yet.';
|
|
671
|
+
output(io, global.json, page, human);
|
|
672
|
+
});
|
|
673
|
+
postCommand
|
|
674
|
+
.command('show <post-id>')
|
|
675
|
+
.description('Show one post with its acknowledgements')
|
|
676
|
+
.requiredOption('--as <actor-id>')
|
|
677
|
+
.option('--actor-kind <kind>', 'human, agent, or system', 'agent')
|
|
678
|
+
.action(async (postId, options, command) => {
|
|
679
|
+
const global = globals(command);
|
|
680
|
+
const record = await withClient(global.home, actor(options.actorKind, options.as), (client) => client.posts.get(postId));
|
|
681
|
+
const acks = record.acknowledgments.length
|
|
682
|
+
? record.acknowledgments
|
|
683
|
+
.map((entry) => ` ${entry.actor.id}${entry.note ? ` — ${entry.note}` : ''}`)
|
|
684
|
+
.join('\n')
|
|
685
|
+
: ' none yet';
|
|
686
|
+
output(io, global.json, record, `${record.title}\n\n${record.body}\n\nAcknowledged by:\n${acks}`);
|
|
687
|
+
});
|
|
688
|
+
postCommand
|
|
689
|
+
.command('acknowledge <post-id>')
|
|
690
|
+
.description('Say you have seen a post')
|
|
691
|
+
.requiredOption('--as <actor-id>')
|
|
692
|
+
.option('--actor-kind <kind>', 'human, agent, or system', 'agent')
|
|
693
|
+
.option('--note <text>', 'optional context for the author')
|
|
694
|
+
.action(async (postId, options, command) => {
|
|
695
|
+
const global = globals(command);
|
|
696
|
+
const record = await withClient(global.home, actor(options.actorKind, options.as), (client) => client.posts.acknowledge({
|
|
697
|
+
postId,
|
|
698
|
+
...(options.note ? { note: options.note } : {}),
|
|
699
|
+
}));
|
|
700
|
+
output(io, global.json, record, `Acknowledged ${postId}`);
|
|
701
|
+
});
|
|
702
|
+
postCommand
|
|
703
|
+
.command('roster <post-id>')
|
|
704
|
+
.description('Who has acknowledged a post, and who has not')
|
|
705
|
+
.requiredOption('--as <actor-id>')
|
|
706
|
+
.option('--actor-kind <kind>', 'human, agent, or system', 'agent')
|
|
707
|
+
.action(async (postId, options, command) => {
|
|
708
|
+
const global = globals(command);
|
|
709
|
+
const roster = await withClient(global.home, actor(options.actorKind, options.as), (client) => client.posts.roster(postId));
|
|
710
|
+
// "Outstanding" means no acknowledgement recorded — never that somebody
|
|
711
|
+
// has not read it, which this cannot know.
|
|
712
|
+
const lines = [
|
|
713
|
+
`Acknowledged (${roster.acknowledged.length}):`,
|
|
714
|
+
...(roster.acknowledged.length
|
|
715
|
+
? roster.acknowledged.map((entry) => ` ${entry.actor.id}`)
|
|
716
|
+
: [' none yet']),
|
|
717
|
+
`No acknowledgement recorded (${roster.outstanding.length}):`,
|
|
718
|
+
...(roster.outstanding.length
|
|
719
|
+
? roster.outstanding.map((entry) => ` ${entry.id}`)
|
|
720
|
+
: [' none']),
|
|
721
|
+
];
|
|
722
|
+
if (roster.joinedSince > 0) {
|
|
723
|
+
lines.push(`${roster.joinedSince} agent(s) joined after this was posted.`);
|
|
724
|
+
}
|
|
725
|
+
output(io, global.json, roster, lines.join('\n'));
|
|
726
|
+
});
|
|
727
|
+
postCommand
|
|
728
|
+
.command('archive <post-id>')
|
|
729
|
+
.description('Archive a post you wrote')
|
|
730
|
+
.requiredOption('--as <actor-id>')
|
|
731
|
+
.option('--actor-kind <kind>', 'human, agent, or system', 'agent')
|
|
732
|
+
.option('--reason <text>')
|
|
733
|
+
.action(async (postId, options, command) => {
|
|
734
|
+
const global = globals(command);
|
|
735
|
+
const record = await withClient(global.home, actor(options.actorKind, options.as), (client) => client.posts.archive({ postId, ...(options.reason ? { reason: options.reason } : {}) }));
|
|
736
|
+
output(io, global.json, record, `Archived ${postId}`);
|
|
737
|
+
});
|
|
529
738
|
const kudosCommand = program.command('kudos').description('Give and manage agent recognition');
|
|
530
739
|
kudosCommand
|
|
531
740
|
.command('give <recipient>')
|
|
@@ -555,7 +764,7 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
|
|
|
555
764
|
});
|
|
556
765
|
program
|
|
557
766
|
.command('inbox [agent]')
|
|
558
|
-
.description('Show pending kudos, memos, and
|
|
767
|
+
.description('Show pending kudos, memos, and tasks for an agent')
|
|
559
768
|
.option('--as <agent-id>', 'defaults to the positional agent')
|
|
560
769
|
.option('--limit <number>', 'maximum results (default 10, maximum 50)', '10')
|
|
561
770
|
.option('--cursor <cursor>', 'opaque cursor returned by the previous page')
|
|
@@ -584,7 +793,7 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
|
|
|
584
793
|
program
|
|
585
794
|
.command('list')
|
|
586
795
|
.description('List compact summaries across all record types')
|
|
587
|
-
.option('--kind <kind>', 'kudos, memo, note, or
|
|
796
|
+
.option('--kind <kind>', 'kudos, memo, note, or task (repeatable)', collect, [])
|
|
588
797
|
.option('--participant <agent>')
|
|
589
798
|
.option('--actor <id>')
|
|
590
799
|
.option('--tag <tag>')
|
|
@@ -755,8 +964,100 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
|
|
|
755
964
|
}));
|
|
756
965
|
output(io, global.json, record, `Archived note ${id}.`);
|
|
757
966
|
});
|
|
758
|
-
const todoCommand = program
|
|
967
|
+
const todoCommand = program
|
|
968
|
+
.command('todo')
|
|
969
|
+
.description('Create and manage your own private reminders');
|
|
759
970
|
todoCommand
|
|
971
|
+
.command('create')
|
|
972
|
+
.requiredOption('--as <actor-id>')
|
|
973
|
+
.option('--actor-kind <kind>', 'human, agent, or system', 'agent')
|
|
974
|
+
.requiredOption('--title <title>')
|
|
975
|
+
.option('--details <text>', 'private working detail')
|
|
976
|
+
.option('--priority <number>', '1 highest, 4 lowest', '3')
|
|
977
|
+
.option('--due-date <date>')
|
|
978
|
+
.option('--due-at <datetime>')
|
|
979
|
+
.option('--time-zone <iana-zone>')
|
|
980
|
+
.option('--tag <tag>', 'tag (repeatable)', collect, [])
|
|
981
|
+
.option('--idempotency-key <key>')
|
|
982
|
+
.action(async (options, command) => {
|
|
983
|
+
const global = globals(command);
|
|
984
|
+
const result = await withClient(global.home, actor(options.actorKind, options.as), (client) => client.todos.create({
|
|
985
|
+
title: options.title,
|
|
986
|
+
...(options.details ? { details: options.details } : {}),
|
|
987
|
+
priority: Number(options.priority),
|
|
988
|
+
...((due) => (due ? { due } : {}))(taskDue(options)),
|
|
989
|
+
...(options.tag.length ? { tags: options.tag } : {}),
|
|
990
|
+
...(options.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : {}),
|
|
991
|
+
}));
|
|
992
|
+
output(io, global.json, result, `${result.deduplicated ? 'Found existing' : 'Created'} private todo\nTitle: ${result.record.current.title}\nID: ${result.record.event.id}`);
|
|
993
|
+
});
|
|
994
|
+
todoCommand
|
|
995
|
+
.command('list')
|
|
996
|
+
.requiredOption('--as <actor-id>')
|
|
997
|
+
.option('--actor-kind <kind>', 'human, agent, or system', 'agent')
|
|
998
|
+
.option('--status <status>')
|
|
999
|
+
.option('--limit <number>', 'maximum results', '10')
|
|
1000
|
+
.action(async (options, command) => {
|
|
1001
|
+
const global = globals(command);
|
|
1002
|
+
const page = await withClient(global.home, actor(options.actorKind, options.as), (client) => client.todos.list({
|
|
1003
|
+
...(options.status ? { status: options.status } : {}),
|
|
1004
|
+
limit: Number(options.limit),
|
|
1005
|
+
}));
|
|
1006
|
+
output(io, global.json, page, page.items.length
|
|
1007
|
+
? page.items
|
|
1008
|
+
.map((item) => `${item.id} ${item.status.padEnd(9)} ${item.title}`)
|
|
1009
|
+
.join('\n')
|
|
1010
|
+
: 'No todos.');
|
|
1011
|
+
});
|
|
1012
|
+
todoCommand
|
|
1013
|
+
.command('show <todo-id>')
|
|
1014
|
+
.requiredOption('--as <actor-id>')
|
|
1015
|
+
.option('--actor-kind <kind>', 'human, agent, or system', 'agent')
|
|
1016
|
+
.action(async (id, options, command) => {
|
|
1017
|
+
const global = globals(command);
|
|
1018
|
+
const record = await withClient(global.home, actor(options.actorKind, options.as), (client) => client.todos.get(id));
|
|
1019
|
+
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}` : ''}`);
|
|
1020
|
+
});
|
|
1021
|
+
for (const operation of ['complete', 'reopen', 'cancel', 'archive']) {
|
|
1022
|
+
todoCommand
|
|
1023
|
+
.command(`${operation} <todo-id>`)
|
|
1024
|
+
.requiredOption('--as <actor-id>')
|
|
1025
|
+
.option('--actor-kind <kind>', 'human, agent, or system', 'agent')
|
|
1026
|
+
.option('--note <text>')
|
|
1027
|
+
.option('--reason <text>')
|
|
1028
|
+
.option('--idempotency-key <key>')
|
|
1029
|
+
.action(async (id, options, command) => {
|
|
1030
|
+
const global = globals(command);
|
|
1031
|
+
const record = await withClient(global.home, actor(options.actorKind, options.as), (client) => operation === 'complete'
|
|
1032
|
+
? client.todos.complete({
|
|
1033
|
+
todoId: id,
|
|
1034
|
+
...(options.note ? { note: options.note } : {}),
|
|
1035
|
+
...(options.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : {}),
|
|
1036
|
+
})
|
|
1037
|
+
: operation === 'cancel'
|
|
1038
|
+
? client.todos.cancel({
|
|
1039
|
+
todoId: id,
|
|
1040
|
+
...(options.reason ? { reason: options.reason } : {}),
|
|
1041
|
+
...(options.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : {}),
|
|
1042
|
+
})
|
|
1043
|
+
: operation === 'archive'
|
|
1044
|
+
? client.todos.archive({
|
|
1045
|
+
todoId: id,
|
|
1046
|
+
...(options.idempotencyKey
|
|
1047
|
+
? { idempotencyKey: options.idempotencyKey }
|
|
1048
|
+
: {}),
|
|
1049
|
+
})
|
|
1050
|
+
: client.todos.reopen({
|
|
1051
|
+
todoId: id,
|
|
1052
|
+
...(options.idempotencyKey
|
|
1053
|
+
? { idempotencyKey: options.idempotencyKey }
|
|
1054
|
+
: {}),
|
|
1055
|
+
}));
|
|
1056
|
+
output(io, global.json, record, `Todo ${id} is now ${record.status}.`);
|
|
1057
|
+
});
|
|
1058
|
+
}
|
|
1059
|
+
const taskCommand = program.command('task').description('Create and manage agent tasks');
|
|
1060
|
+
taskCommand
|
|
760
1061
|
.command('create <assignee>')
|
|
761
1062
|
.requiredOption('--from <actor-id>')
|
|
762
1063
|
.option('--actor-kind <kind>', 'human, agent, or system', 'agent')
|
|
@@ -771,39 +1072,39 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
|
|
|
771
1072
|
.option('--idempotency-key <key>')
|
|
772
1073
|
.action(async (assignee, options, command) => {
|
|
773
1074
|
const global = globals(command);
|
|
774
|
-
const result = await withClient(global.home, actor(options.actorKind, options.from), (client) => client.
|
|
1075
|
+
const result = await withClient(global.home, actor(options.actorKind, options.from), (client) => client.tasks.create({
|
|
775
1076
|
assigneeAgentId: assignee,
|
|
776
1077
|
title: options.title,
|
|
777
1078
|
...(options.description ? { description: options.description } : {}),
|
|
778
1079
|
priority: Number(options.priority),
|
|
779
|
-
...((due) => (due ? { due } : {}))(
|
|
1080
|
+
...((due) => (due ? { due } : {}))(taskDue(options)),
|
|
780
1081
|
...(options.tag.length ? { tags: options.tag } : {}),
|
|
781
1082
|
visibility: options.visibility,
|
|
782
1083
|
...(options.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : {}),
|
|
783
1084
|
}));
|
|
784
|
-
output(io, global.json, result, `${result.deduplicated ? 'Found existing' : 'Created'}
|
|
1085
|
+
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}`);
|
|
785
1086
|
});
|
|
786
|
-
|
|
1087
|
+
taskCommand
|
|
787
1088
|
.command('list')
|
|
788
1089
|
.option('--assignee <agent>')
|
|
789
1090
|
.option('--status <status>')
|
|
790
1091
|
.option('--limit <number>', 'maximum results', '10')
|
|
791
1092
|
.action(async (options, command) => {
|
|
792
1093
|
const global = globals(command);
|
|
793
|
-
const page = await withClient(global.home, defaultActor(env, 'human', 'local-cli'), (client) => client.
|
|
1094
|
+
const page = await withClient(global.home, defaultActor(env, 'human', 'local-cli'), (client) => client.tasks.list({
|
|
794
1095
|
...(options.assignee ? { participantAgentId: options.assignee } : {}),
|
|
795
1096
|
...(options.status ? { status: options.status } : {}),
|
|
796
1097
|
limit: Number(options.limit),
|
|
797
1098
|
}));
|
|
798
|
-
output(io, global.json, page, page.items.map(lineForItem).join('\n') || 'No
|
|
1099
|
+
output(io, global.json, page, page.items.map(lineForItem).join('\n') || 'No tasks found.');
|
|
799
1100
|
});
|
|
800
|
-
|
|
1101
|
+
taskCommand.command('show <task-id>').action(async (id, _options, command) => {
|
|
801
1102
|
const global = globals(command);
|
|
802
|
-
const record = await withClient(global.home, defaultActor(env, 'human', 'local-cli'), (client) => client.
|
|
1103
|
+
const record = await withClient(global.home, defaultActor(env, 'human', 'local-cli'), (client) => client.tasks.get(id));
|
|
803
1104
|
output(io, global.json, record, `${record.current.title}\nID: ${record.event.id}\nStatus: ${record.status}\nVersion: ${record.current.version}`);
|
|
804
1105
|
});
|
|
805
|
-
|
|
806
|
-
.command('update <
|
|
1106
|
+
taskCommand
|
|
1107
|
+
.command('update <task-id>')
|
|
807
1108
|
.requiredOption('--as <actor-id>')
|
|
808
1109
|
.option('--actor-kind <kind>', 'agent or human', 'agent')
|
|
809
1110
|
.requiredOption('--expected-version <number>')
|
|
@@ -818,9 +1119,9 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
|
|
|
818
1119
|
.option('--idempotency-key <key>')
|
|
819
1120
|
.action(async (id, options, command) => {
|
|
820
1121
|
const global = globals(command);
|
|
821
|
-
const parsedDue =
|
|
822
|
-
const record = await withClient(global.home, actor(options.actorKind, options.as), (client) => client.
|
|
823
|
-
|
|
1122
|
+
const parsedDue = taskDue(options);
|
|
1123
|
+
const record = await withClient(global.home, actor(options.actorKind, options.as), (client) => client.tasks.update({
|
|
1124
|
+
taskId: id,
|
|
824
1125
|
expectedVersion: Number(options.expectedVersion),
|
|
825
1126
|
...(options.title ? { title: options.title } : {}),
|
|
826
1127
|
...(options.description !== undefined ? { description: options.description } : {}),
|
|
@@ -829,52 +1130,59 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
|
|
|
829
1130
|
...(options.visibility ? { visibility: options.visibility } : {}),
|
|
830
1131
|
...(options.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : {}),
|
|
831
1132
|
}));
|
|
832
|
-
output(io, global.json, record, `Updated
|
|
1133
|
+
output(io, global.json, record, `Updated task ${id} to version ${record.current.version}.`);
|
|
833
1134
|
});
|
|
834
1135
|
for (const operation of ['accept', 'reject', 'complete', 'reopen', 'cancel']) {
|
|
835
|
-
|
|
836
|
-
.command(`${operation} <
|
|
1136
|
+
const command_ = taskCommand
|
|
1137
|
+
.command(`${operation} <task-id>`)
|
|
837
1138
|
.requiredOption('--as <actor-id>')
|
|
838
1139
|
.option('--actor-kind <kind>', 'agent or human', 'agent')
|
|
839
1140
|
.option('--note <text>')
|
|
840
1141
|
.option('--reason <text>')
|
|
841
|
-
.option('--idempotency-key <key>')
|
|
842
|
-
|
|
1142
|
+
.option('--idempotency-key <key>');
|
|
1143
|
+
// Rejecting requires saying why; accepting may. Marked required at the
|
|
1144
|
+
// parser so the CLI refuses before touching the store.
|
|
1145
|
+
if (operation === 'reject') {
|
|
1146
|
+
command_.requiredOption('--response <text>', 'why the task is being refused');
|
|
1147
|
+
}
|
|
1148
|
+
else if (operation === 'accept') {
|
|
1149
|
+
command_.option('--response <text>', 'conditions, timing, or partial capability');
|
|
1150
|
+
}
|
|
1151
|
+
command_.action(async (id, options, command) => {
|
|
843
1152
|
const global = globals(command);
|
|
844
1153
|
const record = await withClient(global.home, actor(options.actorKind, options.as), (client) => operation === 'accept'
|
|
845
|
-
? client.
|
|
846
|
-
|
|
1154
|
+
? client.tasks.accept({
|
|
1155
|
+
taskId: id,
|
|
1156
|
+
...(options.response ? { response: options.response } : {}),
|
|
847
1157
|
...(options.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : {}),
|
|
848
1158
|
})
|
|
849
1159
|
: operation === 'reject'
|
|
850
|
-
? client.
|
|
851
|
-
|
|
852
|
-
|
|
1160
|
+
? client.tasks.reject({
|
|
1161
|
+
taskId: id,
|
|
1162
|
+
response: options.response ?? options.reason ?? '',
|
|
853
1163
|
...(options.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : {}),
|
|
854
1164
|
})
|
|
855
1165
|
: operation === 'complete'
|
|
856
|
-
? client.
|
|
857
|
-
|
|
1166
|
+
? client.tasks.complete({
|
|
1167
|
+
taskId: id,
|
|
858
1168
|
...(options.note ? { note: options.note } : {}),
|
|
859
|
-
...(options.idempotencyKey
|
|
860
|
-
? { idempotencyKey: options.idempotencyKey }
|
|
861
|
-
: {}),
|
|
1169
|
+
...(options.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : {}),
|
|
862
1170
|
})
|
|
863
1171
|
: operation === 'cancel'
|
|
864
|
-
? client.
|
|
865
|
-
|
|
1172
|
+
? client.tasks.cancel({
|
|
1173
|
+
taskId: id,
|
|
866
1174
|
...(options.reason ? { reason: options.reason } : {}),
|
|
867
1175
|
...(options.idempotencyKey
|
|
868
1176
|
? { idempotencyKey: options.idempotencyKey }
|
|
869
1177
|
: {}),
|
|
870
1178
|
})
|
|
871
|
-
: client.
|
|
872
|
-
|
|
1179
|
+
: client.tasks.reopen({
|
|
1180
|
+
taskId: id,
|
|
873
1181
|
...(options.idempotencyKey
|
|
874
1182
|
? { idempotencyKey: options.idempotencyKey }
|
|
875
1183
|
: {}),
|
|
876
1184
|
}));
|
|
877
|
-
output(io, global.json, record, `
|
|
1185
|
+
output(io, global.json, record, `Task ${id} is ${record.status}.`);
|
|
878
1186
|
});
|
|
879
1187
|
}
|
|
880
1188
|
kudosCommand
|
|
@@ -1011,14 +1319,34 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
|
|
|
1011
1319
|
program
|
|
1012
1320
|
.command('mcp')
|
|
1013
1321
|
.description('Run the actor-bound MCP server over stdio')
|
|
1014
|
-
.
|
|
1015
|
-
.
|
|
1016
|
-
.option('--actor-
|
|
1322
|
+
.option('--agent-id <id>', 'bound agent, whose identity is read from Synomem')
|
|
1323
|
+
.option('--actor-id <id>', 'bound non-agent actor ID')
|
|
1324
|
+
.option('--actor-kind <kind>', 'human or system')
|
|
1325
|
+
.option('--actor-name <display-name>', 'display name for a non-agent actor')
|
|
1017
1326
|
.action(async (options, command) => {
|
|
1018
1327
|
const global = globals(command);
|
|
1328
|
+
// An agent's name comes from its profile, never from the command line:
|
|
1329
|
+
// the name is written into every event the session appends, and a
|
|
1330
|
+
// harness must not be able to sign another agent's name to work.
|
|
1331
|
+
const bound = options.agentId
|
|
1332
|
+
? await withClient(global.home, defaultActor(env, 'system', 'cli'), async (client) => {
|
|
1333
|
+
const resolution = await client.agents.resolve(options.agentId);
|
|
1334
|
+
if (!resolution.match) {
|
|
1335
|
+
throw new SynomemError('AGENT_NOT_FOUND', resolution.candidates.length
|
|
1336
|
+
? `"${options.agentId}" matches ${resolution.candidates.length} agents: ${resolution.candidates
|
|
1337
|
+
.map((candidate) => candidate.id)
|
|
1338
|
+
.join(', ')}. Name one of them.`
|
|
1339
|
+
: `Unknown agent: ${options.agentId}`);
|
|
1340
|
+
}
|
|
1341
|
+
return actor('agent', resolution.match.id, resolution.match.displayName);
|
|
1342
|
+
})
|
|
1343
|
+
: undefined;
|
|
1344
|
+
if (!bound && !(options.actorId && options.actorKind)) {
|
|
1345
|
+
throw new SynomemError('INVALID_INPUT', 'Specify --agent-id, or --actor-id with --actor-kind for a non-agent actor.');
|
|
1346
|
+
}
|
|
1019
1347
|
await startMcpServer({
|
|
1020
1348
|
...(global.home ? { home: global.home } : {}),
|
|
1021
|
-
actor: actor(options.actorKind, options.actorId, options.actorName),
|
|
1349
|
+
actor: bound ?? actor(options.actorKind, options.actorId, options.actorName),
|
|
1022
1350
|
});
|
|
1023
1351
|
});
|
|
1024
1352
|
return program;
|