synomem 0.1.1 → 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 +28 -0
- package/README.md +26 -13
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +280 -51
- 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 +444 -95
- 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/src/cli.ts
CHANGED
|
@@ -34,7 +34,7 @@ import type {
|
|
|
34
34
|
KudosSummary,
|
|
35
35
|
ItemListInput,
|
|
36
36
|
ItemSummary,
|
|
37
|
-
|
|
37
|
+
TaskDue,
|
|
38
38
|
} from './types.js';
|
|
39
39
|
import { packageVersion } from './version.js';
|
|
40
40
|
import type { SynomemService, SynomemServiceFactory } from './service.js';
|
|
@@ -125,11 +125,11 @@ function defaultActor(
|
|
|
125
125
|
return actor(env.SYNOMEM_ACTOR_KIND?.trim() || fallbackKind, id, env.SYNOMEM_ACTOR_NAME?.trim());
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
-
function
|
|
128
|
+
function taskDue(options: {
|
|
129
129
|
dueDate?: string;
|
|
130
130
|
dueAt?: string;
|
|
131
131
|
timeZone?: string;
|
|
132
|
-
}):
|
|
132
|
+
}): TaskDue | undefined {
|
|
133
133
|
if (options.dueDate && options.dueAt)
|
|
134
134
|
throw new SynomemError('INVALID_INPUT', 'Use due-date or due-at, not both.');
|
|
135
135
|
if (options.dueDate) return { kind: 'date', date: options.dueDate };
|
|
@@ -637,29 +637,73 @@ export function createCli(
|
|
|
637
637
|
.option('--yes', 'apply the displayed plan', false)
|
|
638
638
|
.option('--force', 'replace a conflicting synomem directory', false)
|
|
639
639
|
.option('--link', 'symlink to the packaged skill instead of copying it', false)
|
|
640
|
-
.option('--
|
|
641
|
-
.option('--actor-name <name>', 'display name used in MCP registration commands')
|
|
640
|
+
.option('--agent <id-or-alias>', 'bind this installation to an agent')
|
|
642
641
|
.action(
|
|
643
|
-
(
|
|
642
|
+
async (
|
|
644
643
|
options: {
|
|
645
644
|
runtime: string[];
|
|
646
645
|
yes: boolean;
|
|
647
646
|
force: boolean;
|
|
648
647
|
link: boolean;
|
|
649
|
-
|
|
650
|
-
actorName?: string;
|
|
648
|
+
agent?: string;
|
|
651
649
|
},
|
|
652
650
|
command: Command,
|
|
653
651
|
) => {
|
|
654
652
|
const global = globals(command);
|
|
653
|
+
const runtimes = skillRuntimes(options.runtime);
|
|
654
|
+
|
|
655
|
+
/*
|
|
656
|
+
* The agent is resolved BEFORE anything is written. An ambiguous or
|
|
657
|
+
* unknown name then stops the command with a name to fix, rather than
|
|
658
|
+
* leaving a skill installed and pointed at an agent that does not
|
|
659
|
+
* exist.
|
|
660
|
+
*/
|
|
661
|
+
let agentId: string | undefined;
|
|
662
|
+
if (options.agent) {
|
|
663
|
+
agentId = await withClient(
|
|
664
|
+
global.home,
|
|
665
|
+
defaultActor(env, 'system', 'cli'),
|
|
666
|
+
async (client) => {
|
|
667
|
+
const resolution = await client.agents.resolve(options.agent!);
|
|
668
|
+
if (!resolution.match) {
|
|
669
|
+
throw new SynomemError(
|
|
670
|
+
'AGENT_NOT_FOUND',
|
|
671
|
+
resolution.candidates.length
|
|
672
|
+
? `"${options.agent}" matches ${resolution.candidates.length} agents: ${resolution.candidates
|
|
673
|
+
.map((candidate) => candidate.id)
|
|
674
|
+
.join(', ')}. Name one of them.`
|
|
675
|
+
: `Unknown agent: ${options.agent}`,
|
|
676
|
+
);
|
|
677
|
+
}
|
|
678
|
+
return resolution.match.id;
|
|
679
|
+
},
|
|
680
|
+
);
|
|
681
|
+
}
|
|
682
|
+
|
|
655
683
|
const result = installSkill({
|
|
656
|
-
runtimes:
|
|
684
|
+
...(runtimes ? { runtimes } : {}),
|
|
657
685
|
apply: options.yes,
|
|
658
686
|
force: options.force,
|
|
659
687
|
link: options.link,
|
|
660
|
-
|
|
661
|
-
actorName: options.actorName,
|
|
688
|
+
...(agentId ? { agentId } : {}),
|
|
662
689
|
});
|
|
690
|
+
|
|
691
|
+
// Bindings follow what was actually installed, and only on a real run:
|
|
692
|
+
// a dry run must not claim a binding it did not make, and a runtime
|
|
693
|
+
// whose harness is not present here is not somewhere this agent runs.
|
|
694
|
+
if (agentId && options.yes) {
|
|
695
|
+
const installed = result.locations
|
|
696
|
+
.filter((location) => location.state !== 'unavailable')
|
|
697
|
+
.map((location) => location.runtime);
|
|
698
|
+
if (installed.length) {
|
|
699
|
+
await withClient(global.home, defaultActor(env, 'system', 'cli'), async (client) => {
|
|
700
|
+
for (const runtime of installed) {
|
|
701
|
+
await client.agents.bindRuntime({ agentId, runtime });
|
|
702
|
+
}
|
|
703
|
+
});
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
|
|
663
707
|
output(io, global.json, result, formatSkillResult(result, 'install'));
|
|
664
708
|
},
|
|
665
709
|
);
|
|
@@ -668,19 +712,15 @@ export function createCli(
|
|
|
668
712
|
.command('status')
|
|
669
713
|
.description('Show installed, stale, missing, or conflicting skill copies')
|
|
670
714
|
.option('--runtime <runtime>', `${skillRuntimeHelp} (repeatable)`, collect, [])
|
|
671
|
-
.option('--
|
|
672
|
-
.
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
});
|
|
681
|
-
output(io, global.json, result, formatSkillResult(result, 'status'));
|
|
682
|
-
},
|
|
683
|
-
);
|
|
715
|
+
.option('--agent <id>', 'print the registration command for this agent')
|
|
716
|
+
.action((options: { runtime: string[]; agent?: string }, command: Command) => {
|
|
717
|
+
const global = globals(command);
|
|
718
|
+
const result = skillStatus({
|
|
719
|
+
runtimes: skillRuntimes(options.runtime),
|
|
720
|
+
...(options.agent ? { agentId: options.agent } : {}),
|
|
721
|
+
});
|
|
722
|
+
output(io, global.json, result, formatSkillResult(result, 'status'));
|
|
723
|
+
});
|
|
684
724
|
|
|
685
725
|
skillCommand
|
|
686
726
|
.command('uninstall')
|
|
@@ -762,6 +802,127 @@ export function createCli(
|
|
|
762
802
|
},
|
|
763
803
|
);
|
|
764
804
|
|
|
805
|
+
agentCommand
|
|
806
|
+
.command('resolve <name>')
|
|
807
|
+
.description('Resolve a name or alias to one agent, or list the candidates')
|
|
808
|
+
.action(async (name: string, _options, command: Command) => {
|
|
809
|
+
const global = globals(command);
|
|
810
|
+
const resolution = await withClient(
|
|
811
|
+
global.home,
|
|
812
|
+
defaultActor(env, 'system', 'cli'),
|
|
813
|
+
(client) => client.agents.resolve(name),
|
|
814
|
+
);
|
|
815
|
+
// An ambiguous name is a question, not a failure: exit zero and show the
|
|
816
|
+
// candidates so the caller can pick one.
|
|
817
|
+
const human = resolution.match
|
|
818
|
+
? `${resolution.match.displayName} (${resolution.match.id})`
|
|
819
|
+
: resolution.candidates.length
|
|
820
|
+
? `"${resolution.query}" is ambiguous. Candidates:\n${resolution.candidates
|
|
821
|
+
.map((profile) => ` ${profile.id} ${profile.displayName}`)
|
|
822
|
+
.join('\n')}`
|
|
823
|
+
: `No agent answers to "${resolution.query}".`;
|
|
824
|
+
output(io, global.json, resolution, human);
|
|
825
|
+
});
|
|
826
|
+
|
|
827
|
+
agentCommand
|
|
828
|
+
.command('directory')
|
|
829
|
+
.description('List agents with their runtime bindings')
|
|
830
|
+
.action(async (_options, command: Command) => {
|
|
831
|
+
const global = globals(command);
|
|
832
|
+
const entries = await withClient(global.home, defaultActor(env, 'system', 'cli'), (client) =>
|
|
833
|
+
client.agents.directory(),
|
|
834
|
+
);
|
|
835
|
+
const human = entries.length
|
|
836
|
+
? entries
|
|
837
|
+
.map((entry) => {
|
|
838
|
+
const runtimes = entry.runtimeBindings.length
|
|
839
|
+
? entry.runtimeBindings
|
|
840
|
+
.map(
|
|
841
|
+
(binding) =>
|
|
842
|
+
` ${binding.runtime}${binding.profile ? `/${binding.profile}` : ''}` +
|
|
843
|
+
// Last-seen is advisory, so it is labelled as an
|
|
844
|
+
// observation rather than a status.
|
|
845
|
+
`${binding.lastSeenAt ? ` last seen ${binding.lastSeenAt}` : ' not yet seen'}`,
|
|
846
|
+
)
|
|
847
|
+
.join('\n')
|
|
848
|
+
: ' no runtime bindings';
|
|
849
|
+
return `${entry.profile.id} ${entry.profile.displayName}\n${runtimes}`;
|
|
850
|
+
})
|
|
851
|
+
.join('\n')
|
|
852
|
+
: 'No agents configured.';
|
|
853
|
+
output(io, global.json, { entries }, human);
|
|
854
|
+
});
|
|
855
|
+
|
|
856
|
+
const runtimeCommand = agentCommand.command('runtime').description('Record where an agent runs');
|
|
857
|
+
|
|
858
|
+
runtimeCommand
|
|
859
|
+
.command('bind <agent>')
|
|
860
|
+
.description('Bind an agent to a runtime')
|
|
861
|
+
.requiredOption('--runtime <name>', 'runtime family, e.g. claude-code')
|
|
862
|
+
.option('--profile <name>', 'named configuration within the runtime')
|
|
863
|
+
.option('--installation <id>', 'hosted installation this binding belongs to')
|
|
864
|
+
.action(
|
|
865
|
+
async (
|
|
866
|
+
agent: string,
|
|
867
|
+
options: { runtime: string; profile?: string; installation?: string },
|
|
868
|
+
command: Command,
|
|
869
|
+
) => {
|
|
870
|
+
const global = globals(command);
|
|
871
|
+
const binding = await withClient(
|
|
872
|
+
global.home,
|
|
873
|
+
defaultActor(env, 'system', 'cli'),
|
|
874
|
+
(client) =>
|
|
875
|
+
client.agents.bindRuntime({
|
|
876
|
+
agentId: agent,
|
|
877
|
+
runtime: options.runtime,
|
|
878
|
+
...(options.profile ? { profile: options.profile } : {}),
|
|
879
|
+
...(options.installation ? { installationId: options.installation } : {}),
|
|
880
|
+
}),
|
|
881
|
+
);
|
|
882
|
+
output(
|
|
883
|
+
io,
|
|
884
|
+
global.json,
|
|
885
|
+
binding,
|
|
886
|
+
`Bound ${binding.agentId} to ${binding.runtime}${binding.profile ? `/${binding.profile}` : ''} (${binding.id})`,
|
|
887
|
+
);
|
|
888
|
+
},
|
|
889
|
+
);
|
|
890
|
+
|
|
891
|
+
runtimeCommand
|
|
892
|
+
.command('list <agent>')
|
|
893
|
+
.description('List an agent runtime bindings')
|
|
894
|
+
.action(async (agent: string, _options, command: Command) => {
|
|
895
|
+
const global = globals(command);
|
|
896
|
+
const bindings = await withClient(global.home, defaultActor(env, 'system', 'cli'), (client) =>
|
|
897
|
+
client.agents.bindings(agent),
|
|
898
|
+
);
|
|
899
|
+
const human = bindings.length
|
|
900
|
+
? bindings
|
|
901
|
+
.map(
|
|
902
|
+
(binding) =>
|
|
903
|
+
`${binding.id} ${binding.runtime}${binding.profile ? `/${binding.profile}` : ''} bound ${binding.boundAt}`,
|
|
904
|
+
)
|
|
905
|
+
.join('\n')
|
|
906
|
+
: 'No runtime bindings.';
|
|
907
|
+
output(io, global.json, { bindings }, human);
|
|
908
|
+
});
|
|
909
|
+
|
|
910
|
+
runtimeCommand
|
|
911
|
+
.command('unbind <binding-id>')
|
|
912
|
+
.description('Remove a runtime binding')
|
|
913
|
+
.action(async (bindingId: string, _options, command: Command) => {
|
|
914
|
+
const global = globals(command);
|
|
915
|
+
const removed = await withClient(global.home, defaultActor(env, 'system', 'cli'), (client) =>
|
|
916
|
+
client.agents.unbindRuntime(bindingId),
|
|
917
|
+
);
|
|
918
|
+
output(
|
|
919
|
+
io,
|
|
920
|
+
global.json,
|
|
921
|
+
{ removed },
|
|
922
|
+
removed ? `Removed binding ${bindingId}.` : `No binding ${bindingId}.`,
|
|
923
|
+
);
|
|
924
|
+
});
|
|
925
|
+
|
|
765
926
|
const kudosCommand = program.command('kudos').description('Give and manage agent recognition');
|
|
766
927
|
|
|
767
928
|
kudosCommand
|
|
@@ -819,7 +980,7 @@ export function createCli(
|
|
|
819
980
|
|
|
820
981
|
program
|
|
821
982
|
.command('inbox [agent]')
|
|
822
|
-
.description('Show pending kudos, memos, and
|
|
983
|
+
.description('Show pending kudos, memos, and tasks for an agent')
|
|
823
984
|
.option('--as <agent-id>', 'defaults to the positional agent')
|
|
824
985
|
.option('--limit <number>', 'maximum results (default 10, maximum 50)', '10')
|
|
825
986
|
.option('--cursor <cursor>', 'opaque cursor returned by the previous page')
|
|
@@ -876,7 +1037,7 @@ export function createCli(
|
|
|
876
1037
|
program
|
|
877
1038
|
.command('list')
|
|
878
1039
|
.description('List compact summaries across all record types')
|
|
879
|
-
.option('--kind <kind>', 'kudos, memo, note, or
|
|
1040
|
+
.option('--kind <kind>', 'kudos, memo, note, or task (repeatable)', collect, [])
|
|
880
1041
|
.option('--participant <agent>')
|
|
881
1042
|
.option('--actor <id>')
|
|
882
1043
|
.option('--tag <tag>')
|
|
@@ -1194,8 +1355,163 @@ export function createCli(
|
|
|
1194
1355
|
},
|
|
1195
1356
|
);
|
|
1196
1357
|
|
|
1197
|
-
const todoCommand = program
|
|
1358
|
+
const todoCommand = program
|
|
1359
|
+
.command('todo')
|
|
1360
|
+
.description('Create and manage your own private reminders');
|
|
1198
1361
|
todoCommand
|
|
1362
|
+
.command('create')
|
|
1363
|
+
.requiredOption('--as <actor-id>')
|
|
1364
|
+
.option('--actor-kind <kind>', 'human, agent, or system', 'agent')
|
|
1365
|
+
.requiredOption('--title <title>')
|
|
1366
|
+
.option('--details <text>', 'private working detail')
|
|
1367
|
+
.option('--priority <number>', '1 highest, 4 lowest', '3')
|
|
1368
|
+
.option('--due-date <date>')
|
|
1369
|
+
.option('--due-at <datetime>')
|
|
1370
|
+
.option('--time-zone <iana-zone>')
|
|
1371
|
+
.option('--tag <tag>', 'tag (repeatable)', collect, [])
|
|
1372
|
+
.option('--idempotency-key <key>')
|
|
1373
|
+
.action(
|
|
1374
|
+
async (
|
|
1375
|
+
options: {
|
|
1376
|
+
as: string;
|
|
1377
|
+
actorKind: string;
|
|
1378
|
+
title: string;
|
|
1379
|
+
details?: string;
|
|
1380
|
+
priority: string;
|
|
1381
|
+
dueDate?: string;
|
|
1382
|
+
dueAt?: string;
|
|
1383
|
+
timeZone?: string;
|
|
1384
|
+
tag: string[];
|
|
1385
|
+
idempotencyKey?: string;
|
|
1386
|
+
},
|
|
1387
|
+
command: Command,
|
|
1388
|
+
) => {
|
|
1389
|
+
const global = globals(command);
|
|
1390
|
+
const result = await withClient(
|
|
1391
|
+
global.home,
|
|
1392
|
+
actor(options.actorKind, options.as),
|
|
1393
|
+
(client) =>
|
|
1394
|
+
client.todos.create({
|
|
1395
|
+
title: options.title,
|
|
1396
|
+
...(options.details ? { details: options.details } : {}),
|
|
1397
|
+
priority: Number(options.priority) as 1 | 2 | 3 | 4,
|
|
1398
|
+
...((due) => (due ? { due } : {}))(taskDue(options)),
|
|
1399
|
+
...(options.tag.length ? { tags: options.tag } : {}),
|
|
1400
|
+
...(options.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : {}),
|
|
1401
|
+
}),
|
|
1402
|
+
);
|
|
1403
|
+
output(
|
|
1404
|
+
io,
|
|
1405
|
+
global.json,
|
|
1406
|
+
result,
|
|
1407
|
+
`${result.deduplicated ? 'Found existing' : 'Created'} private todo\nTitle: ${result.record.current.title}\nID: ${result.record.event.id}`,
|
|
1408
|
+
);
|
|
1409
|
+
},
|
|
1410
|
+
);
|
|
1411
|
+
todoCommand
|
|
1412
|
+
.command('list')
|
|
1413
|
+
.requiredOption('--as <actor-id>')
|
|
1414
|
+
.option('--actor-kind <kind>', 'human, agent, or system', 'agent')
|
|
1415
|
+
.option('--status <status>')
|
|
1416
|
+
.option('--limit <number>', 'maximum results', '10')
|
|
1417
|
+
.action(
|
|
1418
|
+
async (
|
|
1419
|
+
options: { as: string; actorKind: string; status?: string; limit: string },
|
|
1420
|
+
command: Command,
|
|
1421
|
+
) => {
|
|
1422
|
+
const global = globals(command);
|
|
1423
|
+
const page = await withClient(global.home, actor(options.actorKind, options.as), (client) =>
|
|
1424
|
+
client.todos.list({
|
|
1425
|
+
...(options.status ? { status: options.status } : {}),
|
|
1426
|
+
limit: Number(options.limit),
|
|
1427
|
+
}),
|
|
1428
|
+
);
|
|
1429
|
+
output(
|
|
1430
|
+
io,
|
|
1431
|
+
global.json,
|
|
1432
|
+
page,
|
|
1433
|
+
page.items.length
|
|
1434
|
+
? page.items
|
|
1435
|
+
.map((item) => `${item.id} ${item.status.padEnd(9)} ${item.title}`)
|
|
1436
|
+
.join('\n')
|
|
1437
|
+
: 'No todos.',
|
|
1438
|
+
);
|
|
1439
|
+
},
|
|
1440
|
+
);
|
|
1441
|
+
todoCommand
|
|
1442
|
+
.command('show <todo-id>')
|
|
1443
|
+
.requiredOption('--as <actor-id>')
|
|
1444
|
+
.option('--actor-kind <kind>', 'human, agent, or system', 'agent')
|
|
1445
|
+
.action(async (id: string, options: { as: string; actorKind: string }, command: Command) => {
|
|
1446
|
+
const global = globals(command);
|
|
1447
|
+
const record = await withClient(global.home, actor(options.actorKind, options.as), (client) =>
|
|
1448
|
+
client.todos.get(id),
|
|
1449
|
+
);
|
|
1450
|
+
output(
|
|
1451
|
+
io,
|
|
1452
|
+
global.json,
|
|
1453
|
+
record,
|
|
1454
|
+
`${record.current.title}\nStatus: ${record.status}\nPriority: ${record.current.priority}\nVersion: ${record.current.version}${record.current.details ? `\n\n${record.current.details}` : ''}`,
|
|
1455
|
+
);
|
|
1456
|
+
});
|
|
1457
|
+
for (const operation of ['complete', 'reopen', 'cancel', 'archive'] as const) {
|
|
1458
|
+
todoCommand
|
|
1459
|
+
.command(`${operation} <todo-id>`)
|
|
1460
|
+
.requiredOption('--as <actor-id>')
|
|
1461
|
+
.option('--actor-kind <kind>', 'human, agent, or system', 'agent')
|
|
1462
|
+
.option('--note <text>')
|
|
1463
|
+
.option('--reason <text>')
|
|
1464
|
+
.option('--idempotency-key <key>')
|
|
1465
|
+
.action(
|
|
1466
|
+
async (
|
|
1467
|
+
id: string,
|
|
1468
|
+
options: {
|
|
1469
|
+
as: string;
|
|
1470
|
+
actorKind: string;
|
|
1471
|
+
note?: string;
|
|
1472
|
+
reason?: string;
|
|
1473
|
+
idempotencyKey?: string;
|
|
1474
|
+
},
|
|
1475
|
+
command: Command,
|
|
1476
|
+
) => {
|
|
1477
|
+
const global = globals(command);
|
|
1478
|
+
const record = await withClient(
|
|
1479
|
+
global.home,
|
|
1480
|
+
actor(options.actorKind, options.as),
|
|
1481
|
+
(client) =>
|
|
1482
|
+
operation === 'complete'
|
|
1483
|
+
? client.todos.complete({
|
|
1484
|
+
todoId: id,
|
|
1485
|
+
...(options.note ? { note: options.note } : {}),
|
|
1486
|
+
...(options.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : {}),
|
|
1487
|
+
})
|
|
1488
|
+
: operation === 'cancel'
|
|
1489
|
+
? client.todos.cancel({
|
|
1490
|
+
todoId: id,
|
|
1491
|
+
...(options.reason ? { reason: options.reason } : {}),
|
|
1492
|
+
...(options.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : {}),
|
|
1493
|
+
})
|
|
1494
|
+
: operation === 'archive'
|
|
1495
|
+
? client.todos.archive({
|
|
1496
|
+
todoId: id,
|
|
1497
|
+
...(options.idempotencyKey
|
|
1498
|
+
? { idempotencyKey: options.idempotencyKey }
|
|
1499
|
+
: {}),
|
|
1500
|
+
})
|
|
1501
|
+
: client.todos.reopen({
|
|
1502
|
+
todoId: id,
|
|
1503
|
+
...(options.idempotencyKey
|
|
1504
|
+
? { idempotencyKey: options.idempotencyKey }
|
|
1505
|
+
: {}),
|
|
1506
|
+
}),
|
|
1507
|
+
);
|
|
1508
|
+
output(io, global.json, record, `Todo ${id} is now ${record.status}.`);
|
|
1509
|
+
},
|
|
1510
|
+
);
|
|
1511
|
+
}
|
|
1512
|
+
|
|
1513
|
+
const taskCommand = program.command('task').description('Create and manage agent tasks');
|
|
1514
|
+
taskCommand
|
|
1199
1515
|
.command('create <assignee>')
|
|
1200
1516
|
.requiredOption('--from <actor-id>')
|
|
1201
1517
|
.option('--actor-kind <kind>', 'human, agent, or system', 'agent')
|
|
@@ -1231,12 +1547,12 @@ export function createCli(
|
|
|
1231
1547
|
global.home,
|
|
1232
1548
|
actor(options.actorKind, options.from),
|
|
1233
1549
|
(client) =>
|
|
1234
|
-
client.
|
|
1550
|
+
client.tasks.create({
|
|
1235
1551
|
assigneeAgentId: assignee,
|
|
1236
1552
|
title: options.title,
|
|
1237
1553
|
...(options.description ? { description: options.description } : {}),
|
|
1238
1554
|
priority: Number(options.priority) as 1 | 2 | 3 | 4,
|
|
1239
|
-
...((due) => (due ? { due } : {}))(
|
|
1555
|
+
...((due) => (due ? { due } : {}))(taskDue(options)),
|
|
1240
1556
|
...(options.tag.length ? { tags: options.tag } : {}),
|
|
1241
1557
|
visibility: options.visibility,
|
|
1242
1558
|
...(options.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : {}),
|
|
@@ -1246,11 +1562,11 @@ export function createCli(
|
|
|
1246
1562
|
io,
|
|
1247
1563
|
global.json,
|
|
1248
1564
|
result,
|
|
1249
|
-
`${result.deduplicated ? 'Found existing' : 'Created'}
|
|
1565
|
+
`${result.deduplicated ? 'Found existing' : 'Created'} task for ${result.record.event.assigneeDisplayName}\nTitle: ${result.record.current.title}\nID: ${result.record.event.id}`,
|
|
1250
1566
|
);
|
|
1251
1567
|
},
|
|
1252
1568
|
);
|
|
1253
|
-
|
|
1569
|
+
taskCommand
|
|
1254
1570
|
.command('list')
|
|
1255
1571
|
.option('--assignee <agent>')
|
|
1256
1572
|
.option('--status <status>')
|
|
@@ -1262,21 +1578,21 @@ export function createCli(
|
|
|
1262
1578
|
global.home,
|
|
1263
1579
|
defaultActor(env, 'human', 'local-cli'),
|
|
1264
1580
|
(client) =>
|
|
1265
|
-
client.
|
|
1581
|
+
client.tasks.list({
|
|
1266
1582
|
...(options.assignee ? { participantAgentId: options.assignee } : {}),
|
|
1267
1583
|
...(options.status ? { status: options.status } : {}),
|
|
1268
1584
|
limit: Number(options.limit),
|
|
1269
1585
|
}),
|
|
1270
1586
|
);
|
|
1271
|
-
output(io, global.json, page, page.items.map(lineForItem).join('\n') || 'No
|
|
1587
|
+
output(io, global.json, page, page.items.map(lineForItem).join('\n') || 'No tasks found.');
|
|
1272
1588
|
},
|
|
1273
1589
|
);
|
|
1274
|
-
|
|
1590
|
+
taskCommand.command('show <task-id>').action(async (id: string, _options, command: Command) => {
|
|
1275
1591
|
const global = globals(command);
|
|
1276
1592
|
const record = await withClient(
|
|
1277
1593
|
global.home,
|
|
1278
1594
|
defaultActor(env, 'human', 'local-cli'),
|
|
1279
|
-
(client) => client.
|
|
1595
|
+
(client) => client.tasks.get(id),
|
|
1280
1596
|
);
|
|
1281
1597
|
output(
|
|
1282
1598
|
io,
|
|
@@ -1285,8 +1601,8 @@ export function createCli(
|
|
|
1285
1601
|
`${record.current.title}\nID: ${record.event.id}\nStatus: ${record.status}\nVersion: ${record.current.version}`,
|
|
1286
1602
|
);
|
|
1287
1603
|
});
|
|
1288
|
-
|
|
1289
|
-
.command('update <
|
|
1604
|
+
taskCommand
|
|
1605
|
+
.command('update <task-id>')
|
|
1290
1606
|
.requiredOption('--as <actor-id>')
|
|
1291
1607
|
.option('--actor-kind <kind>', 'agent or human', 'agent')
|
|
1292
1608
|
.requiredOption('--expected-version <number>')
|
|
@@ -1319,13 +1635,13 @@ export function createCli(
|
|
|
1319
1635
|
command: Command,
|
|
1320
1636
|
) => {
|
|
1321
1637
|
const global = globals(command);
|
|
1322
|
-
const parsedDue =
|
|
1638
|
+
const parsedDue = taskDue(options);
|
|
1323
1639
|
const record = await withClient(
|
|
1324
1640
|
global.home,
|
|
1325
1641
|
actor(options.actorKind, options.as),
|
|
1326
1642
|
(client) =>
|
|
1327
|
-
client.
|
|
1328
|
-
|
|
1643
|
+
client.tasks.update({
|
|
1644
|
+
taskId: id,
|
|
1329
1645
|
expectedVersion: Number(options.expectedVersion),
|
|
1330
1646
|
...(options.title ? { title: options.title } : {}),
|
|
1331
1647
|
...(options.description !== undefined ? { description: options.description } : {}),
|
|
@@ -1335,71 +1651,78 @@ export function createCli(
|
|
|
1335
1651
|
...(options.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : {}),
|
|
1336
1652
|
}),
|
|
1337
1653
|
);
|
|
1338
|
-
output(io, global.json, record, `Updated
|
|
1654
|
+
output(io, global.json, record, `Updated task ${id} to version ${record.current.version}.`);
|
|
1339
1655
|
},
|
|
1340
1656
|
);
|
|
1341
1657
|
for (const operation of ['accept', 'reject', 'complete', 'reopen', 'cancel'] as const) {
|
|
1342
|
-
|
|
1343
|
-
.command(`${operation} <
|
|
1658
|
+
const command_ = taskCommand
|
|
1659
|
+
.command(`${operation} <task-id>`)
|
|
1344
1660
|
.requiredOption('--as <actor-id>')
|
|
1345
1661
|
.option('--actor-kind <kind>', 'agent or human', 'agent')
|
|
1346
1662
|
.option('--note <text>')
|
|
1347
1663
|
.option('--reason <text>')
|
|
1348
|
-
.option('--idempotency-key <key>')
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1664
|
+
.option('--idempotency-key <key>');
|
|
1665
|
+
// Rejecting requires saying why; accepting may. Marked required at the
|
|
1666
|
+
// parser so the CLI refuses before touching the store.
|
|
1667
|
+
if (operation === 'reject') {
|
|
1668
|
+
command_.requiredOption('--response <text>', 'why the task is being refused');
|
|
1669
|
+
} else if (operation === 'accept') {
|
|
1670
|
+
command_.option('--response <text>', 'conditions, timing, or partial capability');
|
|
1671
|
+
}
|
|
1672
|
+
command_.action(
|
|
1673
|
+
async (
|
|
1674
|
+
id: string,
|
|
1675
|
+
options: {
|
|
1676
|
+
as: string;
|
|
1677
|
+
actorKind: string;
|
|
1678
|
+
note?: string;
|
|
1679
|
+
reason?: string;
|
|
1680
|
+
response?: string;
|
|
1681
|
+
idempotencyKey?: string;
|
|
1682
|
+
},
|
|
1683
|
+
command: Command,
|
|
1684
|
+
) => {
|
|
1685
|
+
const global = globals(command);
|
|
1686
|
+
const record = await withClient(
|
|
1687
|
+
global.home,
|
|
1688
|
+
actor(options.actorKind, options.as),
|
|
1689
|
+
(client) =>
|
|
1690
|
+
operation === 'accept'
|
|
1691
|
+
? client.tasks.accept({
|
|
1692
|
+
taskId: id,
|
|
1693
|
+
...(options.response ? { response: options.response } : {}),
|
|
1694
|
+
...(options.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : {}),
|
|
1695
|
+
})
|
|
1696
|
+
: operation === 'reject'
|
|
1697
|
+
? client.tasks.reject({
|
|
1698
|
+
taskId: id,
|
|
1699
|
+
response: options.response ?? options.reason ?? '',
|
|
1369
1700
|
...(options.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : {}),
|
|
1370
1701
|
})
|
|
1371
|
-
: operation === '
|
|
1372
|
-
? client.
|
|
1373
|
-
|
|
1374
|
-
...(options.
|
|
1702
|
+
: operation === 'complete'
|
|
1703
|
+
? client.tasks.complete({
|
|
1704
|
+
taskId: id,
|
|
1705
|
+
...(options.note ? { note: options.note } : {}),
|
|
1375
1706
|
...(options.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : {}),
|
|
1376
1707
|
})
|
|
1377
|
-
: operation === '
|
|
1378
|
-
? client.
|
|
1379
|
-
|
|
1380
|
-
...(options.
|
|
1708
|
+
: operation === 'cancel'
|
|
1709
|
+
? client.tasks.cancel({
|
|
1710
|
+
taskId: id,
|
|
1711
|
+
...(options.reason ? { reason: options.reason } : {}),
|
|
1381
1712
|
...(options.idempotencyKey
|
|
1382
1713
|
? { idempotencyKey: options.idempotencyKey }
|
|
1383
1714
|
: {}),
|
|
1384
1715
|
})
|
|
1385
|
-
:
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
...(options.idempotencyKey
|
|
1396
|
-
? { idempotencyKey: options.idempotencyKey }
|
|
1397
|
-
: {}),
|
|
1398
|
-
}),
|
|
1399
|
-
);
|
|
1400
|
-
output(io, global.json, record, `Todo ${id} is ${record.status}.`);
|
|
1401
|
-
},
|
|
1402
|
-
);
|
|
1716
|
+
: client.tasks.reopen({
|
|
1717
|
+
taskId: id,
|
|
1718
|
+
...(options.idempotencyKey
|
|
1719
|
+
? { idempotencyKey: options.idempotencyKey }
|
|
1720
|
+
: {}),
|
|
1721
|
+
}),
|
|
1722
|
+
);
|
|
1723
|
+
output(io, global.json, record, `Task ${id} is ${record.status}.`);
|
|
1724
|
+
},
|
|
1725
|
+
);
|
|
1403
1726
|
}
|
|
1404
1727
|
|
|
1405
1728
|
kudosCommand
|
|
@@ -1621,18 +1944,44 @@ export function createCli(
|
|
|
1621
1944
|
program
|
|
1622
1945
|
.command('mcp')
|
|
1623
1946
|
.description('Run the actor-bound MCP server over stdio')
|
|
1624
|
-
.
|
|
1625
|
-
.
|
|
1626
|
-
.option('--actor-
|
|
1947
|
+
.option('--agent-id <id>', 'bound agent, whose identity is read from Synomem')
|
|
1948
|
+
.option('--actor-id <id>', 'bound non-agent actor ID')
|
|
1949
|
+
.option('--actor-kind <kind>', 'human or system')
|
|
1950
|
+
.option('--actor-name <display-name>', 'display name for a non-agent actor')
|
|
1627
1951
|
.action(
|
|
1628
1952
|
async (
|
|
1629
|
-
options: { actorId
|
|
1953
|
+
options: { agentId?: string; actorId?: string; actorKind?: string; actorName?: string },
|
|
1630
1954
|
command: Command,
|
|
1631
1955
|
) => {
|
|
1632
1956
|
const global = globals(command);
|
|
1957
|
+
// An agent's name comes from its profile, never from the command line:
|
|
1958
|
+
// the name is written into every event the session appends, and a
|
|
1959
|
+
// harness must not be able to sign another agent's name to work.
|
|
1960
|
+
const bound = options.agentId
|
|
1961
|
+
? await withClient(global.home, defaultActor(env, 'system', 'cli'), async (client) => {
|
|
1962
|
+
const resolution = await client.agents.resolve(options.agentId!);
|
|
1963
|
+
if (!resolution.match) {
|
|
1964
|
+
throw new SynomemError(
|
|
1965
|
+
'AGENT_NOT_FOUND',
|
|
1966
|
+
resolution.candidates.length
|
|
1967
|
+
? `"${options.agentId}" matches ${resolution.candidates.length} agents: ${resolution.candidates
|
|
1968
|
+
.map((candidate) => candidate.id)
|
|
1969
|
+
.join(', ')}. Name one of them.`
|
|
1970
|
+
: `Unknown agent: ${options.agentId}`,
|
|
1971
|
+
);
|
|
1972
|
+
}
|
|
1973
|
+
return actor('agent', resolution.match.id, resolution.match.displayName);
|
|
1974
|
+
})
|
|
1975
|
+
: undefined;
|
|
1976
|
+
if (!bound && !(options.actorId && options.actorKind)) {
|
|
1977
|
+
throw new SynomemError(
|
|
1978
|
+
'INVALID_INPUT',
|
|
1979
|
+
'Specify --agent-id, or --actor-id with --actor-kind for a non-agent actor.',
|
|
1980
|
+
);
|
|
1981
|
+
}
|
|
1633
1982
|
await startMcpServer({
|
|
1634
1983
|
...(global.home ? { home: global.home } : {}),
|
|
1635
|
-
actor: actor(options.actorKind
|
|
1984
|
+
actor: bound ?? actor(options.actorKind!, options.actorId!, options.actorName),
|
|
1636
1985
|
});
|
|
1637
1986
|
},
|
|
1638
1987
|
);
|