synomem 0.9.5 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/README.md +9 -7
- package/dist/actor-directory.d.ts +34 -0
- package/dist/actor-directory.d.ts.map +1 -0
- package/dist/actor-directory.js +52 -0
- package/dist/actor-directory.js.map +1 -0
- package/dist/backend.d.ts +3 -0
- package/dist/backend.d.ts.map +1 -1
- package/dist/backend.js +26 -3
- package/dist/backend.js.map +1 -1
- package/dist/bookmarks.d.ts +30 -0
- package/dist/bookmarks.d.ts.map +1 -0
- package/dist/bookmarks.js +87 -0
- package/dist/bookmarks.js.map +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +291 -61
- package/dist/cli.js.map +1 -1
- package/dist/client.d.ts +115 -4
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +854 -138
- package/dist/client.js.map +1 -1
- package/dist/cursors.d.ts +30 -0
- package/dist/cursors.d.ts.map +1 -0
- package/dist/cursors.js +101 -0
- package/dist/cursors.js.map +1 -0
- package/dist/errors.d.ts +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +2 -0
- package/dist/errors.js.map +1 -1
- package/dist/import.d.ts +605 -39
- package/dist/import.d.ts.map +1 -1
- package/dist/index.d.ts +19 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp/index.d.ts +1 -1
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +337 -30
- package/dist/mcp/index.js.map +1 -1
- package/dist/mutation-receipts.d.ts +8 -0
- package/dist/mutation-receipts.d.ts.map +1 -0
- package/dist/mutation-receipts.js +2 -0
- package/dist/mutation-receipts.js.map +1 -0
- package/dist/notifications.d.ts +73 -0
- package/dist/notifications.d.ts.map +1 -0
- package/dist/notifications.js +309 -0
- package/dist/notifications.js.map +1 -0
- package/dist/participation-repository.d.ts +46 -0
- package/dist/participation-repository.d.ts.map +1 -0
- package/dist/participation-repository.js +296 -0
- package/dist/participation-repository.js.map +1 -0
- package/dist/participation.d.ts +97 -0
- package/dist/participation.d.ts.map +1 -0
- package/dist/participation.js +10 -0
- package/dist/participation.js.map +1 -0
- package/dist/policy.d.ts +31 -0
- package/dist/policy.d.ts.map +1 -0
- package/dist/policy.js +71 -0
- package/dist/policy.js.map +1 -0
- package/dist/ports/repository.d.ts +23 -0
- package/dist/ports/repository.d.ts.map +1 -1
- package/dist/profiles.d.ts +12 -0
- package/dist/profiles.d.ts.map +1 -1
- package/dist/profiles.js +2 -1
- package/dist/profiles.js.map +1 -1
- package/dist/projections.d.ts.map +1 -1
- package/dist/projections.js +29 -11
- package/dist/projections.js.map +1 -1
- package/dist/remote.d.ts +79 -0
- package/dist/remote.d.ts.map +1 -1
- package/dist/remote.js +65 -6
- package/dist/remote.js.map +1 -1
- package/dist/schemas.d.ts +746 -58
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +114 -16
- package/dist/schemas.js.map +1 -1
- package/dist/search.d.ts +25 -0
- package/dist/search.d.ts.map +1 -0
- package/dist/search.js +2 -0
- package/dist/search.js.map +1 -0
- package/dist/service.d.ts +89 -0
- package/dist/service.d.ts.map +1 -1
- package/dist/storage.d.ts +38 -3
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +494 -367
- package/dist/storage.js.map +1 -1
- package/dist/types.d.ts +65 -34
- package/dist/types.d.ts.map +1 -1
- package/docs/cli.md +6 -0
- package/docs/human-participation.md +20 -0
- package/docs/mcp.md +3 -1
- package/docs/storage-format.md +4 -15
- package/openapi/synomem-v1.yaml +187 -10
- package/package.json +2 -1
- package/skills/synomem/SKILL.md +13 -13
- package/skills/synomem/agents/openai.yaml +1 -1
- package/skills/synomem/references/examples.md +2 -3
- package/src/actor-directory.ts +87 -0
- package/src/backend.ts +29 -3
- package/src/bookmarks.ts +127 -0
- package/src/cli.ts +941 -546
- package/src/client.ts +1143 -279
- package/src/cursors.ts +132 -0
- package/src/errors.ts +2 -0
- package/src/index.ts +50 -0
- package/src/mcp/index.ts +420 -104
- package/src/mutation-receipts.ts +7 -0
- package/src/notifications.ts +483 -0
- package/src/participation-repository.ts +406 -0
- package/src/participation.ts +103 -0
- package/src/policy.ts +109 -0
- package/src/ports/repository.ts +33 -0
- package/src/profiles.ts +2 -1
- package/src/projections.ts +49 -48
- package/src/remote.ts +227 -37
- package/src/schemas.ts +116 -16
- package/src/search.ts +24 -0
- package/src/service.ts +122 -8
- package/src/storage.ts +1039 -699
- package/src/types.ts +67 -32
package/dist/cli.js
CHANGED
|
@@ -4,7 +4,7 @@ import { spawn } from 'node:child_process';
|
|
|
4
4
|
import { join, resolve } from 'node:path';
|
|
5
5
|
import { pathToFileURL } from 'node:url';
|
|
6
6
|
import { Command, CommanderError, Option } from 'commander';
|
|
7
|
-
import { ensureLocalStore,
|
|
7
|
+
import { ensureLocalStore, openLocalOwnerService, exportRawLocalEvents } from './backend.js';
|
|
8
8
|
import { cloudApiUrl } from './cloud.js';
|
|
9
9
|
import { resolveHome } from './config.js';
|
|
10
10
|
import { assertInteractive, credentialFingerprint, parseCredentialBackend, readAccessKey, } from './configure.js';
|
|
@@ -126,7 +126,7 @@ function lineForSummary(record) {
|
|
|
126
126
|
: record.status === 'acknowledged'
|
|
127
127
|
? 'acknowledged'
|
|
128
128
|
: 'new';
|
|
129
|
-
return `${record.id} ${record.createdAt.slice(0, 10)} ${record.
|
|
129
|
+
return `${record.id} ${record.createdAt.slice(0, 10)} ${record.recipient?.id} [${state}] ${record.title}`;
|
|
130
130
|
}
|
|
131
131
|
function lineForItem(item) {
|
|
132
132
|
return `${item.id} ${item.createdAt.slice(0, 10)} ${item.kind.padEnd(5)} [${item.status}] ${item.title}`;
|
|
@@ -136,7 +136,9 @@ function itemListInput(options) {
|
|
|
136
136
|
...(Array.isArray(options.kind) && options.kind.length
|
|
137
137
|
? { kinds: options.kind }
|
|
138
138
|
: {}),
|
|
139
|
-
...(typeof options.participant === 'string'
|
|
139
|
+
...(typeof options.participant === 'string'
|
|
140
|
+
? { participant: targetRef(options.participant) }
|
|
141
|
+
: {}),
|
|
140
142
|
...(typeof options.author === 'string' ? { actorId: options.author } : {}),
|
|
141
143
|
...(typeof options.status === 'string' ? { status: options.status } : {}),
|
|
142
144
|
...(typeof options.tag === 'string' ? { tag: options.tag } : {}),
|
|
@@ -149,13 +151,21 @@ function itemListInput(options) {
|
|
|
149
151
|
offset: Number(options.offset ?? 0),
|
|
150
152
|
};
|
|
151
153
|
}
|
|
154
|
+
function targetRef(value) {
|
|
155
|
+
const match = /^(human|agent):(.+)$/.exec(value);
|
|
156
|
+
if (match)
|
|
157
|
+
return { kind: match[1], id: match[2] };
|
|
158
|
+
if (value.includes(':'))
|
|
159
|
+
throw new SynomemError('INVALID_INPUT', 'Use human:<handle> or agent:<handle>.');
|
|
160
|
+
return { kind: 'agent', id: value };
|
|
161
|
+
}
|
|
152
162
|
function showRecord(record) {
|
|
153
163
|
const event = record.event;
|
|
154
164
|
const evidence = event.evidence?.map((item) => ` - ${item.kind}: ${item.value}`).join('\n');
|
|
155
165
|
return [
|
|
156
166
|
event.title,
|
|
157
167
|
`ID: ${event.id}`,
|
|
158
|
-
`Recipient: ${event.recipientDisplayName} (${event.
|
|
168
|
+
`Recipient: ${event.recipientDisplayName} (${event.recipient?.id})`,
|
|
159
169
|
`From: ${event.actor.displayName ?? event.actor.id} (${event.actor.kind}:${event.actor.id})`,
|
|
160
170
|
`Date: ${event.createdAt}`,
|
|
161
171
|
`Visibility: ${event.visibility}`,
|
|
@@ -192,7 +202,7 @@ function addListOptions(command) {
|
|
|
192
202
|
}
|
|
193
203
|
function listInput(options) {
|
|
194
204
|
return {
|
|
195
|
-
...(options.recipient ? {
|
|
205
|
+
...(options.recipient ? { recipient: targetRef(options.recipient) } : {}),
|
|
196
206
|
...(options.author ? { actorId: options.author } : {}),
|
|
197
207
|
...(options.authorKind ? { actorKind: options.authorKind } : {}),
|
|
198
208
|
...(options.tag ? { tag: options.tag } : {}),
|
|
@@ -290,7 +300,7 @@ export function createCli(io = defaultIo, dependencies = {}) {
|
|
|
290
300
|
return await withProfile(command, (service, context) => operation(service, context, undefined));
|
|
291
301
|
}
|
|
292
302
|
const storeHome = profile && isLocalProfile(profile) ? (profile.home ?? global.home) : global.home;
|
|
293
|
-
const client = await
|
|
303
|
+
const client = await openLocalOwnerService(storeHome);
|
|
294
304
|
try {
|
|
295
305
|
return await operation(client, undefined, storeHome);
|
|
296
306
|
}
|
|
@@ -310,6 +320,46 @@ export function createCli(io = defaultIo, dependencies = {}) {
|
|
|
310
320
|
.option('--json', 'emit stable machine-readable JSON', false)
|
|
311
321
|
.showSuggestionAfterError()
|
|
312
322
|
.configureOutput({ writeOut: io.stdout, writeErr: io.stderr });
|
|
323
|
+
const humanCommand = program.command('human').description('Local human identities');
|
|
324
|
+
humanCommand
|
|
325
|
+
.command('register <handle>')
|
|
326
|
+
.requiredOption('--name <display-name>')
|
|
327
|
+
.action(async (handle, options, command) => {
|
|
328
|
+
await withManagement(command, async (service) => {
|
|
329
|
+
const human = await service.actors.registerHuman({
|
|
330
|
+
id: handle,
|
|
331
|
+
handle,
|
|
332
|
+
displayName: options.name,
|
|
333
|
+
});
|
|
334
|
+
output(io, globals(command).json, human, `Registered ${human.displayName} (human:${human.handle}).`);
|
|
335
|
+
});
|
|
336
|
+
});
|
|
337
|
+
program
|
|
338
|
+
.command('actors')
|
|
339
|
+
.description('Search addressable humans and agents')
|
|
340
|
+
.option('--query <text>', 'Search name, handle or ID')
|
|
341
|
+
.option('--kind <kind>', 'human or agent')
|
|
342
|
+
.option('--cursor <cursor>', 'Signed continuation cursor')
|
|
343
|
+
.option('--limit <number>', 'Maximum actors', Number, 20)
|
|
344
|
+
.action(async (options, command) => {
|
|
345
|
+
await withProfile(command, async (service) => {
|
|
346
|
+
const page = await service.actors.list(options);
|
|
347
|
+
output(io, globals(command).json, page, page.items
|
|
348
|
+
.map((actor) => `${actor.kind}:${actor.handle} — ${actor.displayName}`)
|
|
349
|
+
.join('\n') + (page.nextCursor ? `\nNext cursor: ${page.nextCursor}` : ''));
|
|
350
|
+
});
|
|
351
|
+
});
|
|
352
|
+
program
|
|
353
|
+
.command('actor-profile <target>')
|
|
354
|
+
.description('Visible authored records and separate kudos/useful counts')
|
|
355
|
+
.option('--after <cursor>', 'Continue authored record page')
|
|
356
|
+
.option('--limit <number>', 'Maximum records', Number, 20)
|
|
357
|
+
.action(async (target, options, command) => {
|
|
358
|
+
await withProfile(command, async (service) => {
|
|
359
|
+
const profile = await service.actors.profile({ target: targetRef(target), ...options });
|
|
360
|
+
output(io, globals(command).json, profile, `${profile.actor.displayName}: ${profile.counts.kudosReceived} kudos, ${profile.counts.usefulReceived} useful reactions`);
|
|
361
|
+
});
|
|
362
|
+
});
|
|
313
363
|
/* ------------------------------------------------------------ setup */
|
|
314
364
|
program
|
|
315
365
|
.command('setup')
|
|
@@ -338,7 +388,7 @@ export function createCli(io = defaultIo, dependencies = {}) {
|
|
|
338
388
|
ensureLocalStore(global.home);
|
|
339
389
|
const config = store.read();
|
|
340
390
|
const existing = config.profiles[profileName];
|
|
341
|
-
const client = await
|
|
391
|
+
const client = await openLocalOwnerService(global.home);
|
|
342
392
|
let agent;
|
|
343
393
|
let resumed = false;
|
|
344
394
|
try {
|
|
@@ -474,7 +524,7 @@ export function createCli(io = defaultIo, dependencies = {}) {
|
|
|
474
524
|
throw new SynomemError('INVALID_INPUT', 'A browser sign-in is stored in the keychain or a file, never the environment.');
|
|
475
525
|
}
|
|
476
526
|
const callbackPort = Number(options.callbackPort);
|
|
477
|
-
if (!Number.isSafeInteger(callbackPort) || callbackPort < 1 || callbackPort >
|
|
527
|
+
if (!Number.isSafeInteger(callbackPort) || callbackPort < 1 || callbackPort > 65535) {
|
|
478
528
|
throw new SynomemError('INVALID_INPUT', '--callback-port must be from 1 through 65535.');
|
|
479
529
|
}
|
|
480
530
|
const credential = await oauthLogin({
|
|
@@ -666,7 +716,8 @@ export function createCli(io = defaultIo, dependencies = {}) {
|
|
|
666
716
|
.option('--context <context-id>', 'exact context id (see `connection status`)')
|
|
667
717
|
.option('--agent <handle-or-id>', 'narrow by agent handle, id or display name')
|
|
668
718
|
.option('--workspace <name-or-id>', 'narrow by workspace name or id')
|
|
669
|
-
.option('--
|
|
719
|
+
.option('--human <handle-or-id>', 'local registered human identity')
|
|
720
|
+
.option('--local', 'a local-store profile for an existing human or agent', false)
|
|
670
721
|
.option('--store-home <path>', 'local store home (default: the Synomem home)')
|
|
671
722
|
.option('--default', 'make this the default profile', false)
|
|
672
723
|
.action(async (rawName, options, command) => {
|
|
@@ -678,30 +729,36 @@ export function createCli(io = defaultIo, dependencies = {}) {
|
|
|
678
729
|
throw new SynomemError('CONFIG_INVALID', `Profile ${name} already exists. Remove it first to change what it points at.`);
|
|
679
730
|
}
|
|
680
731
|
if (options.local) {
|
|
681
|
-
if (!options.agent)
|
|
682
|
-
throw new SynomemError('INVALID_INPUT', 'A local profile needs --
|
|
732
|
+
if ((!options.agent && !options.human) || (options.agent && options.human))
|
|
733
|
+
throw new SynomemError('INVALID_INPUT', 'A local profile needs exactly one of --human or --agent.');
|
|
683
734
|
const storeHome = options.storeHome ? resolve(options.storeHome) : global.home;
|
|
684
|
-
const client = await
|
|
735
|
+
const client = await openLocalOwnerService(storeHome);
|
|
685
736
|
let agent;
|
|
686
737
|
try {
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
738
|
+
if (options.human) {
|
|
739
|
+
agent = await client.actors.get({ kind: 'human', id: options.human });
|
|
740
|
+
}
|
|
741
|
+
else {
|
|
742
|
+
const resolution = await client.agents.resolve(options.agent);
|
|
743
|
+
if (!resolution.match) {
|
|
744
|
+
throw new SynomemError(resolution.candidates.length ? 'CONTEXT_AMBIGUOUS' : 'AGENT_NOT_FOUND', resolution.candidates.length
|
|
745
|
+
? `"${options.agent}" matches several agents: ${resolution.candidates.map((c) => `${c.handle} (${c.id})`).join(', ')}.`
|
|
746
|
+
: `No local agent answers to "${options.agent}". Profiles never create agents: run \`synomem agent create\` first.`);
|
|
747
|
+
}
|
|
748
|
+
agent = resolution.match;
|
|
692
749
|
}
|
|
693
|
-
agent = resolution.match;
|
|
694
750
|
}
|
|
695
751
|
finally {
|
|
696
752
|
await client.close();
|
|
697
753
|
}
|
|
698
754
|
const profile = {
|
|
699
755
|
backend: 'local',
|
|
756
|
+
actorKind: options.human ? 'human' : 'agent',
|
|
700
757
|
...(storeHome !== global.home ? { home: storeHome } : {}),
|
|
701
758
|
actorId: agent.id,
|
|
702
759
|
actorName: agent.displayName,
|
|
703
760
|
contextId: localContextId(storeHome, {
|
|
704
|
-
kind: 'agent',
|
|
761
|
+
kind: options.human ? 'human' : 'agent',
|
|
705
762
|
id: agent.id,
|
|
706
763
|
}),
|
|
707
764
|
};
|
|
@@ -1090,7 +1147,11 @@ export function createCli(io = defaultIo, dependencies = {}) {
|
|
|
1090
1147
|
]
|
|
1091
1148
|
: []),
|
|
1092
1149
|
...(skillTargets.length
|
|
1093
|
-
? [
|
|
1150
|
+
? [
|
|
1151
|
+
'',
|
|
1152
|
+
'And these Synomem-owned skills:',
|
|
1153
|
+
...skillTargets.map((path) => ` ${path}`),
|
|
1154
|
+
]
|
|
1094
1155
|
: []),
|
|
1095
1156
|
'',
|
|
1096
1157
|
'Server-side authorizations are not revoked; do that in the portal.',
|
|
@@ -1488,7 +1549,6 @@ export function createCli(io = defaultIo, dependencies = {}) {
|
|
|
1488
1549
|
.requiredOption('--body <body>')
|
|
1489
1550
|
.option('--tag <tag>', 'repeatable', collect, [])
|
|
1490
1551
|
.option('--topic <id>', 'topic ID (repeatable)', collect, [])
|
|
1491
|
-
.option('--reply-to <post-id>')
|
|
1492
1552
|
.action(async (options, command) => {
|
|
1493
1553
|
const global = globals(command);
|
|
1494
1554
|
const result = await withProfile(command, (service) => service.posts.create({
|
|
@@ -1496,7 +1556,6 @@ export function createCli(io = defaultIo, dependencies = {}) {
|
|
|
1496
1556
|
body: options.body,
|
|
1497
1557
|
...(options.tag.length ? { tags: options.tag } : {}),
|
|
1498
1558
|
...(options.topic.length ? { topicIds: options.topic } : {}),
|
|
1499
|
-
...(options.replyTo ? { replyTo: options.replyTo } : {}),
|
|
1500
1559
|
}));
|
|
1501
1560
|
output(io, global.json, result, `Published ${result.record.event.id}`);
|
|
1502
1561
|
});
|
|
@@ -1531,7 +1590,11 @@ export function createCli(io = defaultIo, dependencies = {}) {
|
|
|
1531
1590
|
.option('--note <text>', 'optional context for the author')
|
|
1532
1591
|
.action(async (postId, options, command) => {
|
|
1533
1592
|
const global = globals(command);
|
|
1534
|
-
const record = await withProfile(command, (service) => service.posts.acknowledge({
|
|
1593
|
+
const record = await withProfile(command, async (service) => service.posts.acknowledge({
|
|
1594
|
+
expectedVersion: (await service.posts.get(postId)).lifecycleVersion,
|
|
1595
|
+
postId,
|
|
1596
|
+
...(options.note ? { note: options.note } : {}),
|
|
1597
|
+
}));
|
|
1535
1598
|
output(io, global.json, record, `Acknowledged ${postId}`);
|
|
1536
1599
|
});
|
|
1537
1600
|
postCommand
|
|
@@ -1561,7 +1624,11 @@ export function createCli(io = defaultIo, dependencies = {}) {
|
|
|
1561
1624
|
.option('--reason <text>')
|
|
1562
1625
|
.action(async (postId, options, command) => {
|
|
1563
1626
|
const global = globals(command);
|
|
1564
|
-
const record = await withProfile(command, (service) => service.posts.archive({
|
|
1627
|
+
const record = await withProfile(command, async (service) => service.posts.archive({
|
|
1628
|
+
expectedVersion: (await service.posts.get(postId)).lifecycleVersion,
|
|
1629
|
+
postId,
|
|
1630
|
+
...(options.reason ? { reason: options.reason } : {}),
|
|
1631
|
+
}));
|
|
1565
1632
|
output(io, global.json, record, `Archived ${postId}`);
|
|
1566
1633
|
});
|
|
1567
1634
|
/* ------------------------------------------------------------ kudos */
|
|
@@ -1579,7 +1646,7 @@ export function createCli(io = defaultIo, dependencies = {}) {
|
|
|
1579
1646
|
.action(async (recipient, options, command) => {
|
|
1580
1647
|
const global = globals(command);
|
|
1581
1648
|
const result = await withProfile(command, (service) => service.kudos.give({
|
|
1582
|
-
|
|
1649
|
+
recipient: targetRef(recipient),
|
|
1583
1650
|
title: options.title,
|
|
1584
1651
|
reason: options.reason,
|
|
1585
1652
|
visibility: options.visibility,
|
|
@@ -1612,7 +1679,11 @@ export function createCli(io = defaultIo, dependencies = {}) {
|
|
|
1612
1679
|
.option('--note <text>')
|
|
1613
1680
|
.action(async (id, options, command) => {
|
|
1614
1681
|
const global = globals(command);
|
|
1615
|
-
const record = await withProfile(command, (service) => service.kudos.acknowledge({
|
|
1682
|
+
const record = await withProfile(command, async (service) => service.kudos.acknowledge({
|
|
1683
|
+
expectedVersion: (await service.kudos.get(id)).lifecycleVersion,
|
|
1684
|
+
kudosId: id,
|
|
1685
|
+
...(options.note ? { note: options.note } : {}),
|
|
1686
|
+
}));
|
|
1616
1687
|
output(io, global.json, record, `Acknowledged ${id}.`);
|
|
1617
1688
|
});
|
|
1618
1689
|
kudosCommand
|
|
@@ -1622,7 +1693,8 @@ export function createCli(io = defaultIo, dependencies = {}) {
|
|
|
1622
1693
|
.option('--administrative', 'mark as an administrative revocation', false)
|
|
1623
1694
|
.action(async (id, options, command) => {
|
|
1624
1695
|
const global = globals(command);
|
|
1625
|
-
const record = await withProfile(command, (service) => service.kudos.revoke({
|
|
1696
|
+
const record = await withProfile(command, async (service) => service.kudos.revoke({
|
|
1697
|
+
expectedVersion: (await service.kudos.get(id)).lifecycleVersion,
|
|
1626
1698
|
kudosId: id,
|
|
1627
1699
|
reason: options.reason,
|
|
1628
1700
|
administrative: options.administrative,
|
|
@@ -1675,28 +1747,30 @@ export function createCli(io = defaultIo, dependencies = {}) {
|
|
|
1675
1747
|
});
|
|
1676
1748
|
/* ------------------------------------------------------------ cross-kind */
|
|
1677
1749
|
program
|
|
1678
|
-
.command('inbox
|
|
1679
|
-
.description(
|
|
1680
|
-
.option('--limit <number>', '
|
|
1681
|
-
.option('--
|
|
1682
|
-
.
|
|
1683
|
-
|
|
1684
|
-
const page = await withProfile(command, (service
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
...(options.cursor ? { cursor: options.cursor } : {}),
|
|
1694
|
-
});
|
|
1695
|
-
});
|
|
1696
|
-
output(io, global.json, page, page.items.length
|
|
1697
|
-
? `${page.items.map(lineForItem).join('\n')}${page.hasMore ? `\nNext cursor: ${page.nextCursor}` : ''}`
|
|
1750
|
+
.command('inbox')
|
|
1751
|
+
.description('Read notifications belonging to the profile effective actor')
|
|
1752
|
+
.option('--limit <number>', 'Maximum results', '20')
|
|
1753
|
+
.option('--after <cursor>', 'Signed continuation cursor')
|
|
1754
|
+
.option('--view <view>', 'all, unread, action_required or default', 'default')
|
|
1755
|
+
.action(async (options, command) => {
|
|
1756
|
+
const page = await withProfile(command, (service) => service.notifications.list({
|
|
1757
|
+
limit: Number(options.limit),
|
|
1758
|
+
view: options.view,
|
|
1759
|
+
...(options.after ? { after: options.after } : {}),
|
|
1760
|
+
}));
|
|
1761
|
+
output(io, globals(command).json, page, page.items.length
|
|
1762
|
+
? page.items
|
|
1763
|
+
.map((item) => `${item.read ? 'Read' : 'Unread'} ${item.root?.kind ?? 'record'}: ${item.root?.title ?? item.rootId} [${item.id}]`)
|
|
1764
|
+
.join('\n')
|
|
1698
1765
|
: 'Inbox is clear.');
|
|
1699
1766
|
});
|
|
1767
|
+
program
|
|
1768
|
+
.command('notification-read <notification-id>')
|
|
1769
|
+
.description('Mark your notification read without performing a lifecycle action')
|
|
1770
|
+
.action(async (id, _options, command) => {
|
|
1771
|
+
await withProfile(command, (service) => service.notifications.read(id));
|
|
1772
|
+
output(io, globals(command).json, { updated: true }, 'Notification marked read.');
|
|
1773
|
+
});
|
|
1700
1774
|
program
|
|
1701
1775
|
.command('list')
|
|
1702
1776
|
.description('List compact summaries across all record types')
|
|
@@ -1751,7 +1825,7 @@ export function createCli(io = defaultIo, dependencies = {}) {
|
|
|
1751
1825
|
.action(async (recipient, options, command) => {
|
|
1752
1826
|
const global = globals(command);
|
|
1753
1827
|
const result = await withProfile(command, (service) => service.memos.send({
|
|
1754
|
-
|
|
1828
|
+
recipient: targetRef(recipient),
|
|
1755
1829
|
subject: options.subject,
|
|
1756
1830
|
body: options.body,
|
|
1757
1831
|
visibility: options.visibility,
|
|
@@ -1771,7 +1845,7 @@ export function createCli(io = defaultIo, dependencies = {}) {
|
|
|
1771
1845
|
.action(async (options, command) => {
|
|
1772
1846
|
const global = globals(command);
|
|
1773
1847
|
const page = await withProfile(command, (service) => service.memos.list({
|
|
1774
|
-
...(options.participant ? {
|
|
1848
|
+
...(options.participant ? { participant: targetRef(options.participant) } : {}),
|
|
1775
1849
|
...(options.status ? { status: options.status } : {}),
|
|
1776
1850
|
limit: Number(options.limit),
|
|
1777
1851
|
...(options.cursor ? { cursor: options.cursor } : {}),
|
|
@@ -1795,7 +1869,8 @@ export function createCli(io = defaultIo, dependencies = {}) {
|
|
|
1795
1869
|
.option('--idempotency-key <key>')
|
|
1796
1870
|
.action(async (id, options, command) => {
|
|
1797
1871
|
const global = globals(command);
|
|
1798
|
-
const record = await withProfile(command, (service) => service.memos[operation]({
|
|
1872
|
+
const record = await withProfile(command, async (service) => service.memos[operation]({
|
|
1873
|
+
expectedVersion: (await service.memos.get(id)).lifecycleVersion,
|
|
1799
1874
|
memoId: id,
|
|
1800
1875
|
...(options.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : {}),
|
|
1801
1876
|
}));
|
|
@@ -1818,7 +1893,7 @@ export function createCli(io = defaultIo, dependencies = {}) {
|
|
|
1818
1893
|
.action(async (options, command) => {
|
|
1819
1894
|
const global = globals(command);
|
|
1820
1895
|
const result = await withProfile(command, (service) => service.notes.create({
|
|
1821
|
-
...(options.owner ? {
|
|
1896
|
+
...(options.owner ? { owner: targetRef(options.owner) } : {}),
|
|
1822
1897
|
title: options.title,
|
|
1823
1898
|
body: options.body,
|
|
1824
1899
|
...(options.tag.length ? { tags: options.tag } : {}),
|
|
@@ -1836,7 +1911,7 @@ export function createCli(io = defaultIo, dependencies = {}) {
|
|
|
1836
1911
|
.action(async (options, command) => {
|
|
1837
1912
|
const global = globals(command);
|
|
1838
1913
|
const page = await withProfile(command, (service) => service.notes.list({
|
|
1839
|
-
...(options.owner ? {
|
|
1914
|
+
...(options.owner ? { participant: targetRef(options.owner) } : {}),
|
|
1840
1915
|
...(options.status ? { status: options.status } : {}),
|
|
1841
1916
|
limit: Number(options.limit),
|
|
1842
1917
|
}));
|
|
@@ -1876,7 +1951,8 @@ export function createCli(io = defaultIo, dependencies = {}) {
|
|
|
1876
1951
|
.option('--idempotency-key <key>')
|
|
1877
1952
|
.action(async (id, options, command) => {
|
|
1878
1953
|
const global = globals(command);
|
|
1879
|
-
const record = await withProfile(command, (service) => service.notes.archive({
|
|
1954
|
+
const record = await withProfile(command, async (service) => service.notes.archive({
|
|
1955
|
+
expectedVersion: (await service.notes.get(id)).lifecycleVersion,
|
|
1880
1956
|
noteId: id,
|
|
1881
1957
|
...(options.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : {}),
|
|
1882
1958
|
}));
|
|
@@ -1946,21 +2022,31 @@ export function createCli(io = defaultIo, dependencies = {}) {
|
|
|
1946
2022
|
.action(async (id, options, command) => {
|
|
1947
2023
|
const global = globals(command);
|
|
1948
2024
|
const key = options.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : {};
|
|
1949
|
-
const record = await withProfile(command, (service) => operation === 'complete'
|
|
2025
|
+
const record = await withProfile(command, async (service) => operation === 'complete'
|
|
1950
2026
|
? service.todos.complete({
|
|
2027
|
+
expectedVersion: (await service.todos.get(id)).lifecycleVersion,
|
|
1951
2028
|
todoId: id,
|
|
1952
2029
|
...(options.note ? { note: options.note } : {}),
|
|
1953
2030
|
...key,
|
|
1954
2031
|
})
|
|
1955
2032
|
: operation === 'cancel'
|
|
1956
2033
|
? service.todos.cancel({
|
|
2034
|
+
expectedVersion: (await service.todos.get(id)).lifecycleVersion,
|
|
1957
2035
|
todoId: id,
|
|
1958
2036
|
...(options.reason ? { reason: options.reason } : {}),
|
|
1959
2037
|
...key,
|
|
1960
2038
|
})
|
|
1961
2039
|
: operation === 'archive'
|
|
1962
|
-
? service.todos.archive({
|
|
1963
|
-
|
|
2040
|
+
? service.todos.archive({
|
|
2041
|
+
expectedVersion: (await service.todos.get(id)).lifecycleVersion,
|
|
2042
|
+
todoId: id,
|
|
2043
|
+
...key,
|
|
2044
|
+
})
|
|
2045
|
+
: service.todos.reopen({
|
|
2046
|
+
expectedVersion: (await service.todos.get(id)).lifecycleVersion,
|
|
2047
|
+
todoId: id,
|
|
2048
|
+
...key,
|
|
2049
|
+
}));
|
|
1964
2050
|
output(io, global.json, record, `Todo ${id} is now ${record.status}.`);
|
|
1965
2051
|
});
|
|
1966
2052
|
}
|
|
@@ -1982,7 +2068,7 @@ export function createCli(io = defaultIo, dependencies = {}) {
|
|
|
1982
2068
|
.action(async (assignee, options, command) => {
|
|
1983
2069
|
const global = globals(command);
|
|
1984
2070
|
const result = await withProfile(command, (service) => service.tasks.create({
|
|
1985
|
-
|
|
2071
|
+
assignee: targetRef(assignee),
|
|
1986
2072
|
title: options.title,
|
|
1987
2073
|
...(options.description ? { description: options.description } : {}),
|
|
1988
2074
|
priority: Number(options.priority),
|
|
@@ -2003,7 +2089,7 @@ export function createCli(io = defaultIo, dependencies = {}) {
|
|
|
2003
2089
|
.action(async (options, command) => {
|
|
2004
2090
|
const global = globals(command);
|
|
2005
2091
|
const page = await withProfile(command, (service) => service.tasks.list({
|
|
2006
|
-
...(options.assignee ? {
|
|
2092
|
+
...(options.assignee ? { participant: targetRef(options.assignee) } : {}),
|
|
2007
2093
|
...(options.status ? { status: options.status } : {}),
|
|
2008
2094
|
limit: Number(options.limit),
|
|
2009
2095
|
}));
|
|
@@ -2045,6 +2131,22 @@ export function createCli(io = defaultIo, dependencies = {}) {
|
|
|
2045
2131
|
}));
|
|
2046
2132
|
output(io, global.json, record, `Updated task ${id} to version ${record.current.version}.`);
|
|
2047
2133
|
});
|
|
2134
|
+
taskCommand
|
|
2135
|
+
.command('override <task-id>')
|
|
2136
|
+
.requiredOption('--decision <status>', 'open or rejected')
|
|
2137
|
+
.requiredOption('--reason <text>')
|
|
2138
|
+
.requiredOption('--expected-version <number>')
|
|
2139
|
+
.option('--idempotency-key <key>')
|
|
2140
|
+
.action(async (id, options, command) => {
|
|
2141
|
+
const record = await withProfile(command, (service) => service.tasks.overrideDecision({
|
|
2142
|
+
taskId: id,
|
|
2143
|
+
nextStatus: options.decision,
|
|
2144
|
+
reason: options.reason,
|
|
2145
|
+
expectedVersion: Number(options.expectedVersion),
|
|
2146
|
+
...(options.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : {}),
|
|
2147
|
+
}));
|
|
2148
|
+
output(io, globals(command).json, record, `Task decision overridden: ${record.status}.`);
|
|
2149
|
+
});
|
|
2048
2150
|
for (const operation of ['accept', 'reject', 'complete', 'reopen', 'cancel']) {
|
|
2049
2151
|
const command_ = taskCommand
|
|
2050
2152
|
.command(`${operation} <task-id>`)
|
|
@@ -2062,34 +2164,157 @@ export function createCli(io = defaultIo, dependencies = {}) {
|
|
|
2062
2164
|
command_.action(async (id, options, command) => {
|
|
2063
2165
|
const global = globals(command);
|
|
2064
2166
|
const key = options.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : {};
|
|
2065
|
-
const record = await withProfile(command, (service) => operation === 'accept'
|
|
2167
|
+
const record = await withProfile(command, async (service) => operation === 'accept'
|
|
2066
2168
|
? service.tasks.accept({
|
|
2169
|
+
expectedVersion: (await service.tasks.get(id)).lifecycleVersion,
|
|
2067
2170
|
taskId: id,
|
|
2068
2171
|
...(options.response ? { response: options.response } : {}),
|
|
2069
2172
|
...key,
|
|
2070
2173
|
})
|
|
2071
2174
|
: operation === 'reject'
|
|
2072
2175
|
? service.tasks.reject({
|
|
2176
|
+
expectedVersion: (await service.tasks.get(id)).lifecycleVersion,
|
|
2073
2177
|
taskId: id,
|
|
2074
2178
|
response: options.response ?? options.reason ?? '',
|
|
2075
2179
|
...key,
|
|
2076
2180
|
})
|
|
2077
2181
|
: operation === 'complete'
|
|
2078
2182
|
? service.tasks.complete({
|
|
2183
|
+
expectedVersion: (await service.tasks.get(id)).lifecycleVersion,
|
|
2079
2184
|
taskId: id,
|
|
2080
2185
|
...(options.note ? { note: options.note } : {}),
|
|
2081
2186
|
...key,
|
|
2082
2187
|
})
|
|
2083
2188
|
: operation === 'cancel'
|
|
2084
2189
|
? service.tasks.cancel({
|
|
2190
|
+
expectedVersion: (await service.tasks.get(id)).lifecycleVersion,
|
|
2085
2191
|
taskId: id,
|
|
2086
2192
|
...(options.reason ? { reason: options.reason } : {}),
|
|
2087
2193
|
...key,
|
|
2088
2194
|
})
|
|
2089
|
-
: service.tasks.reopen({
|
|
2195
|
+
: service.tasks.reopen({
|
|
2196
|
+
expectedVersion: (await service.tasks.get(id)).lifecycleVersion,
|
|
2197
|
+
taskId: id,
|
|
2198
|
+
...key,
|
|
2199
|
+
}));
|
|
2090
2200
|
output(io, global.json, record, `Task ${id} is ${record.status}.`);
|
|
2091
2201
|
});
|
|
2092
2202
|
}
|
|
2203
|
+
program
|
|
2204
|
+
.command('search <query>')
|
|
2205
|
+
.option('--kind <kind>')
|
|
2206
|
+
.option('--after <cursor>')
|
|
2207
|
+
.option('--limit <number>')
|
|
2208
|
+
.action(async (q, options, command) => {
|
|
2209
|
+
const page = await withProfile(command, (service) => service.search({
|
|
2210
|
+
q,
|
|
2211
|
+
...(options.kind ? { kinds: [options.kind] } : {}),
|
|
2212
|
+
...(options.after ? { after: options.after } : {}),
|
|
2213
|
+
...(options.limit ? { limit: Number(options.limit) } : {}),
|
|
2214
|
+
}));
|
|
2215
|
+
output(io, globals(command).json, page, page.items
|
|
2216
|
+
.map((hit) => `${hit.kind}: ${hit.title} [${hit.rootId}]
|
|
2217
|
+
${hit.snippet}`)
|
|
2218
|
+
.join('\n') || 'No visible results.');
|
|
2219
|
+
});
|
|
2220
|
+
const bookmarkCommand = program.command('bookmark').description('Your own saved records');
|
|
2221
|
+
bookmarkCommand
|
|
2222
|
+
.command('list')
|
|
2223
|
+
.option('--after <cursor>')
|
|
2224
|
+
.option('--limit <number>')
|
|
2225
|
+
.action(async (options, command) => {
|
|
2226
|
+
const page = await withProfile(command, (service) => service.bookmarks.list({
|
|
2227
|
+
...options,
|
|
2228
|
+
...(options.limit ? { limit: Number(options.limit) } : {}),
|
|
2229
|
+
}));
|
|
2230
|
+
output(io, globals(command).json, page, page.items
|
|
2231
|
+
.map((entry) => `${entry.root.kind}: ${entry.root.title} [${entry.root.id}]`)
|
|
2232
|
+
.join('\n') || 'No visible bookmarks.');
|
|
2233
|
+
});
|
|
2234
|
+
for (const operation of ['set', 'remove'])
|
|
2235
|
+
bookmarkCommand
|
|
2236
|
+
.command(`${operation} <root-id>`)
|
|
2237
|
+
.action(async (rootId, _options, command) => {
|
|
2238
|
+
const result = await withProfile(command, (service) => service.bookmarks.set({ rootId, present: operation === 'set' }));
|
|
2239
|
+
output(io, globals(command).json, result, operation === 'set' ? 'Bookmark saved.' : 'Bookmark removed.');
|
|
2240
|
+
});
|
|
2241
|
+
const replyCommand = program
|
|
2242
|
+
.command('reply')
|
|
2243
|
+
.description('Discuss any visible record without changing its lifecycle');
|
|
2244
|
+
replyCommand
|
|
2245
|
+
.command('create <root-id>')
|
|
2246
|
+
.requiredOption('--body <text>', 'Immutable reply body')
|
|
2247
|
+
.option('--parent <reply-id>', 'Reply to a parent in this thread')
|
|
2248
|
+
.option('--mention <actor>', 'Mention a typed human/agent', (value, previous) => [...previous, value], [])
|
|
2249
|
+
.option('--idempotency-key <key>', 'Stable retry key')
|
|
2250
|
+
.action(async (rootId, options, command) => {
|
|
2251
|
+
const result = await withProfile(command, (service) => service.replies.create({
|
|
2252
|
+
rootId,
|
|
2253
|
+
body: options.body,
|
|
2254
|
+
...(options.parent ? { parentId: options.parent } : {}),
|
|
2255
|
+
mentions: options.mention.map(targetRef),
|
|
2256
|
+
...(options.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : {}),
|
|
2257
|
+
}));
|
|
2258
|
+
output(io, globals(command).json, result, `Reply ${result.id} created by ${result.author.kind}:${result.author.id}.`);
|
|
2259
|
+
});
|
|
2260
|
+
replyCommand
|
|
2261
|
+
.command('show <reply-id>')
|
|
2262
|
+
.action(async (id, _options, command) => {
|
|
2263
|
+
const result = await withProfile(command, (service) => service.replies.get(id));
|
|
2264
|
+
output(io, globals(command).json, result, result.deleted ? 'Deleted reply.' : (result.body ?? ''));
|
|
2265
|
+
});
|
|
2266
|
+
replyCommand
|
|
2267
|
+
.command('delete <reply-id>')
|
|
2268
|
+
.requiredOption('--expected-version <number>', 'Current reply version')
|
|
2269
|
+
.option('--reason <text>', 'Moderation reason')
|
|
2270
|
+
.option('--idempotency-key <key>', 'Stable retry key')
|
|
2271
|
+
.action(async (replyId, options, command) => {
|
|
2272
|
+
const result = await withProfile(command, (service) => service.replies.delete({
|
|
2273
|
+
replyId,
|
|
2274
|
+
expectedVersion: Number(options.expectedVersion),
|
|
2275
|
+
...(options.reason ? { reason: options.reason } : {}),
|
|
2276
|
+
...(options.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : {}),
|
|
2277
|
+
}));
|
|
2278
|
+
output(io, globals(command).json, result, 'Visible reply removed; audit history remains.');
|
|
2279
|
+
});
|
|
2280
|
+
const threadCommand = program.command('thread').description('Read and follow record timelines');
|
|
2281
|
+
threadCommand
|
|
2282
|
+
.command('show <root-id>')
|
|
2283
|
+
.option('--after <cursor>', 'Continue a signed snapshot')
|
|
2284
|
+
.option('--limit <number>', 'Page size')
|
|
2285
|
+
.action(async (rootId, options, command) => {
|
|
2286
|
+
const result = await withProfile(command, (service) => service.threads.get({
|
|
2287
|
+
rootId,
|
|
2288
|
+
...(options.after ? { after: options.after } : {}),
|
|
2289
|
+
...(options.limit ? { limit: Number(options.limit) } : {}),
|
|
2290
|
+
}));
|
|
2291
|
+
output(io, globals(command).json, result, JSON.stringify(result, null, 2));
|
|
2292
|
+
});
|
|
2293
|
+
threadCommand
|
|
2294
|
+
.command('subscription <root-id>')
|
|
2295
|
+
.option('--follow', 'Follow new replies')
|
|
2296
|
+
.option('--mute', 'Mute notifications')
|
|
2297
|
+
.action(async (rootId, options, command) => {
|
|
2298
|
+
await withProfile(command, (service) => service.threads.subscription({
|
|
2299
|
+
rootId,
|
|
2300
|
+
following: !!options.follow,
|
|
2301
|
+
muted: !!options.mute,
|
|
2302
|
+
}));
|
|
2303
|
+
output(io, globals(command).json, { updated: true }, 'Thread subscription updated.');
|
|
2304
|
+
});
|
|
2305
|
+
program
|
|
2306
|
+
.command('react <target-id> <code>')
|
|
2307
|
+
.option('--remove', 'Set absent instead of present')
|
|
2308
|
+
.option('--idempotency-key <key>', 'Stable retry key')
|
|
2309
|
+
.action(async (targetId, code, options, command) => {
|
|
2310
|
+
const result = await withProfile(command, (service) => service.reactions.set({
|
|
2311
|
+
targetId,
|
|
2312
|
+
code,
|
|
2313
|
+
present: !options.remove,
|
|
2314
|
+
...(options.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : {}),
|
|
2315
|
+
}));
|
|
2316
|
+
output(io, globals(command).json, result, 'Reaction updated.');
|
|
2317
|
+
});
|
|
2093
2318
|
/* ------------------------------------------------------------ maintenance */
|
|
2094
2319
|
const projectionCommand = program
|
|
2095
2320
|
.command('projection')
|
|
@@ -2146,11 +2371,16 @@ export function createCli(io = defaultIo, dependencies = {}) {
|
|
|
2146
2371
|
program
|
|
2147
2372
|
.command('export')
|
|
2148
2373
|
.description('Export canonical events for portability')
|
|
2374
|
+
.option('--raw', 'filesystem-owner recovery: export unchanged JSON events without opening or migrating the store', false)
|
|
2149
2375
|
.addOption(new Option('--format <format>').choices(['json', 'jsonl', 'markdown']).default('json'))
|
|
2150
2376
|
.option('--output <path>', 'write to an explicit destination instead of stdout')
|
|
2151
2377
|
.action(async (options, command) => {
|
|
2152
2378
|
const global = globals(command);
|
|
2153
|
-
|
|
2379
|
+
if (options.raw && options.format === 'markdown')
|
|
2380
|
+
throw new SynomemError('INVALID_INPUT', 'Raw recovery supports json or jsonl only.');
|
|
2381
|
+
const content = options.raw
|
|
2382
|
+
? exportRawLocalEvents(global.home, options.format)
|
|
2383
|
+
: await withManagement(command, (service) => service.export(options.format));
|
|
2154
2384
|
if (options.output) {
|
|
2155
2385
|
const destination = resolve(options.output);
|
|
2156
2386
|
atomicWriteFile(destination, content, 0o600);
|