synomem 0.9.6 → 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 +9 -0
- package/README.md +5 -3
- 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 +329 -22
- 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 -365
- 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 +2 -0
- package/docs/storage-format.md +4 -15
- package/openapi/synomem-v1.yaml +187 -10
- package/package.json +2 -1
- package/skills/synomem/SKILL.md +2 -0
- 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 +412 -96
- 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 -697
- package/src/types.ts +67 -32
package/src/cli.ts
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import type { RecordKind as HpRecordKind } from './types.js';
|
|
3
|
+
import type { BookmarkInput as HpBookmarkInput } from './bookmarks.js';
|
|
4
|
+
import type { NotificationInput as HpNotificationInput } from './notifications.js';
|
|
5
|
+
import type { ReactionCode as HpReactionCode } from './participation.js';
|
|
6
|
+
import type { ActorRef } from './policy.js';
|
|
2
7
|
import { existsSync, readFileSync, realpathSync, rmSync } from 'node:fs';
|
|
3
8
|
import { spawn } from 'node:child_process';
|
|
4
9
|
import { join, resolve } from 'node:path';
|
|
5
10
|
import { pathToFileURL } from 'node:url';
|
|
6
11
|
import { Command, CommanderError, Option } from 'commander';
|
|
7
|
-
import { ensureLocalStore,
|
|
12
|
+
import { ensureLocalStore, openLocalOwnerService, exportRawLocalEvents } from './backend.js';
|
|
8
13
|
import { cloudApiUrl } from './cloud.js';
|
|
9
14
|
import { resolveHome } from './config.js';
|
|
10
15
|
import {
|
|
@@ -78,12 +83,10 @@ import type {
|
|
|
78
83
|
} from './types.js';
|
|
79
84
|
import { packageVersion } from './version.js';
|
|
80
85
|
import { listLocalWorkspaces, localWorkspaceHome } from './workspaces.js';
|
|
81
|
-
|
|
82
86
|
export interface CliIo {
|
|
83
87
|
stdout: (text: string) => void;
|
|
84
88
|
stderr: (text: string) => void;
|
|
85
89
|
}
|
|
86
|
-
|
|
87
90
|
export interface CliDependencies {
|
|
88
91
|
/** Injected so interactive steps can be driven by a test without a terminal. */
|
|
89
92
|
promptIo?: PromptIo;
|
|
@@ -106,14 +109,11 @@ export interface CliDependencies {
|
|
|
106
109
|
planId?: string;
|
|
107
110
|
}) => Promise<ImportPreview | ImportResult>;
|
|
108
111
|
}
|
|
109
|
-
|
|
110
112
|
const defaultIo: CliIo = {
|
|
111
113
|
stdout: (text) => process.stdout.write(text),
|
|
112
114
|
stderr: (text) => process.stderr.write(text),
|
|
113
115
|
};
|
|
114
|
-
|
|
115
116
|
const cliExitCodes = new WeakMap<Command, number>();
|
|
116
|
-
|
|
117
117
|
/**
|
|
118
118
|
* Identity overrides the profile model replaced. Named here so using one fails
|
|
119
119
|
* with a message that points at `--profile`, rather than a generic unknown
|
|
@@ -135,7 +135,6 @@ const OBSOLETE_ENV = [
|
|
|
135
135
|
'SYNOMEM_AGENT_ID',
|
|
136
136
|
'SYNOMEM_WORKSPACE',
|
|
137
137
|
];
|
|
138
|
-
|
|
139
138
|
function obsoleteIdentityInput(argv: string[], env: NodeJS.ProcessEnv): string | undefined {
|
|
140
139
|
const args = argv.slice(2);
|
|
141
140
|
for (const argument of args) {
|
|
@@ -156,11 +155,9 @@ function obsoleteIdentityInput(argv: string[], env: NodeJS.ProcessEnv): string |
|
|
|
156
155
|
}
|
|
157
156
|
return OBSOLETE_ENV.find((name) => env[name]?.trim());
|
|
158
157
|
}
|
|
159
|
-
|
|
160
158
|
function collect(value: string, previous: string[]): string[] {
|
|
161
159
|
return [...previous, value];
|
|
162
160
|
}
|
|
163
|
-
|
|
164
161
|
function skillRuntimes(values: string[]): SkillRuntime[] | undefined {
|
|
165
162
|
const normalized = values.map((value) => (value === 'grokbot' ? 'grok' : value));
|
|
166
163
|
const invalid = normalized.find(
|
|
@@ -172,9 +169,7 @@ function skillRuntimes(values: string[]): SkillRuntime[] | undefined {
|
|
|
172
169
|
if (!normalized.length || normalized.includes('all')) return undefined;
|
|
173
170
|
return normalized as SkillRuntime[];
|
|
174
171
|
}
|
|
175
|
-
|
|
176
172
|
const skillRuntimeHelp = `${skillRuntimeNames.join(', ')}, grokbot (alias for grok), or all`;
|
|
177
|
-
|
|
178
173
|
function parseEvidence(value: string): EvidenceReference {
|
|
179
174
|
const separator = value.indexOf(':');
|
|
180
175
|
if (separator < 1 || separator === value.length - 1) {
|
|
@@ -185,7 +180,6 @@ function parseEvidence(value: string): EvidenceReference {
|
|
|
185
180
|
value: value.slice(separator + 1),
|
|
186
181
|
};
|
|
187
182
|
}
|
|
188
|
-
|
|
189
183
|
function taskDue(options: {
|
|
190
184
|
dueDate?: string;
|
|
191
185
|
dueAt?: string;
|
|
@@ -201,7 +195,6 @@ function taskDue(options: {
|
|
|
201
195
|
}
|
|
202
196
|
return undefined;
|
|
203
197
|
}
|
|
204
|
-
|
|
205
198
|
function exitCode(code: SynomemErrorCode): number {
|
|
206
199
|
if (code.endsWith('_NOT_FOUND')) return 3;
|
|
207
200
|
if (
|
|
@@ -218,7 +211,6 @@ function exitCode(code: SynomemErrorCode): number {
|
|
|
218
211
|
if (code === 'INTERNAL_ERROR') return 1;
|
|
219
212
|
return 2;
|
|
220
213
|
}
|
|
221
|
-
|
|
222
214
|
function lineForSummary(record: KudosSummary): string {
|
|
223
215
|
const state =
|
|
224
216
|
record.revocationStatus === 'revoked'
|
|
@@ -226,19 +218,19 @@ function lineForSummary(record: KudosSummary): string {
|
|
|
226
218
|
: record.status === 'acknowledged'
|
|
227
219
|
? 'acknowledged'
|
|
228
220
|
: 'new';
|
|
229
|
-
return `${record.id} ${record.createdAt.slice(0, 10)} ${record.
|
|
221
|
+
return `${record.id} ${record.createdAt.slice(0, 10)} ${record.recipient?.id} [${state}] ${record.title}`;
|
|
230
222
|
}
|
|
231
|
-
|
|
232
223
|
function lineForItem(item: ItemSummary): string {
|
|
233
224
|
return `${item.id} ${item.createdAt.slice(0, 10)} ${item.kind.padEnd(5)} [${item.status}] ${item.title}`;
|
|
234
225
|
}
|
|
235
|
-
|
|
236
226
|
function itemListInput(options: Record<string, string | string[]>): ItemListInput {
|
|
237
227
|
return {
|
|
238
228
|
...(Array.isArray(options.kind) && options.kind.length
|
|
239
229
|
? { kinds: options.kind as ItemListInput['kinds'] }
|
|
240
230
|
: {}),
|
|
241
|
-
...(typeof options.participant === 'string'
|
|
231
|
+
...(typeof options.participant === 'string'
|
|
232
|
+
? { participant: targetRef(options.participant) }
|
|
233
|
+
: {}),
|
|
242
234
|
...(typeof options.author === 'string' ? { actorId: options.author } : {}),
|
|
243
235
|
...(typeof options.status === 'string' ? { status: options.status } : {}),
|
|
244
236
|
...(typeof options.tag === 'string' ? { tag: options.tag } : {}),
|
|
@@ -251,14 +243,20 @@ function itemListInput(options: Record<string, string | string[]>): ItemListInpu
|
|
|
251
243
|
offset: Number(options.offset ?? 0),
|
|
252
244
|
};
|
|
253
245
|
}
|
|
254
|
-
|
|
246
|
+
function targetRef(value: string): ActorRef {
|
|
247
|
+
const match = /^(human|agent):(.+)$/.exec(value);
|
|
248
|
+
if (match) return { kind: match[1] as 'human' | 'agent', id: match[2]! };
|
|
249
|
+
if (value.includes(':'))
|
|
250
|
+
throw new SynomemError('INVALID_INPUT', 'Use human:<handle> or agent:<handle>.');
|
|
251
|
+
return { kind: 'agent', id: value };
|
|
252
|
+
}
|
|
255
253
|
function showRecord(record: KudosRecord): string {
|
|
256
254
|
const event = record.event;
|
|
257
255
|
const evidence = event.evidence?.map((item) => ` - ${item.kind}: ${item.value}`).join('\n');
|
|
258
256
|
return [
|
|
259
257
|
event.title,
|
|
260
258
|
`ID: ${event.id}`,
|
|
261
|
-
`Recipient: ${event.recipientDisplayName} (${event.
|
|
259
|
+
`Recipient: ${event.recipientDisplayName} (${event.recipient?.id})`,
|
|
262
260
|
`From: ${event.actor.displayName ?? event.actor.id} (${event.actor.kind}:${event.actor.id})`,
|
|
263
261
|
`Date: ${event.createdAt}`,
|
|
264
262
|
`Visibility: ${event.visibility}`,
|
|
@@ -274,11 +272,9 @@ function showRecord(record: KudosRecord): string {
|
|
|
274
272
|
.filter((value) => value !== undefined)
|
|
275
273
|
.join('\n');
|
|
276
274
|
}
|
|
277
|
-
|
|
278
275
|
function output(io: CliIo, json: boolean, value: unknown, human: string): void {
|
|
279
276
|
io.stdout(json ? `${JSON.stringify(value, null, 2)}\n` : `${human}\n`);
|
|
280
277
|
}
|
|
281
|
-
|
|
282
278
|
function addListOptions(command: Command): Command {
|
|
283
279
|
return command
|
|
284
280
|
.option('--recipient <agent>')
|
|
@@ -297,10 +293,9 @@ function addListOptions(command: Command): Command {
|
|
|
297
293
|
.option('--cursor <cursor>', 'opaque cursor returned by the previous page')
|
|
298
294
|
.option('--offset <number>', 'pagination offset', '0');
|
|
299
295
|
}
|
|
300
|
-
|
|
301
296
|
function listInput(options: Record<string, string>): KudosListInput {
|
|
302
297
|
return {
|
|
303
|
-
...(options.recipient ? {
|
|
298
|
+
...(options.recipient ? { recipient: targetRef(options.recipient) } : {}),
|
|
304
299
|
...(options.author ? { actorId: options.author } : {}),
|
|
305
300
|
...(options.authorKind ? { actorKind: options.authorKind as KudosListInput['actorKind'] } : {}),
|
|
306
301
|
...(options.tag ? { tag: options.tag } : {}),
|
|
@@ -318,11 +313,9 @@ function listInput(options: Record<string, string>): KudosListInput {
|
|
|
318
313
|
offset: Number(options.offset),
|
|
319
314
|
};
|
|
320
315
|
}
|
|
321
|
-
|
|
322
316
|
function describeEffective(context: EffectiveContext): string {
|
|
323
317
|
return `${context.actor.displayName ?? context.actor.id} (${context.actor.kind}:${context.actor.id}) in ${context.workspaceId}`;
|
|
324
318
|
}
|
|
325
|
-
|
|
326
319
|
interface Globals {
|
|
327
320
|
home: string;
|
|
328
321
|
explicitHome?: string;
|
|
@@ -330,7 +323,6 @@ interface Globals {
|
|
|
330
323
|
profile?: string;
|
|
331
324
|
preset?: string;
|
|
332
325
|
}
|
|
333
|
-
|
|
334
326
|
export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies = {}): Command {
|
|
335
327
|
const env = dependencies.env ?? process.env;
|
|
336
328
|
const cwd = dependencies.cwd ?? process.cwd();
|
|
@@ -340,7 +332,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
340
332
|
const oauthLogin = dependencies.oauthLogin ?? loginWithOAuth;
|
|
341
333
|
const storesFor = dependencies.credentialStores ?? defaultCredentialStores;
|
|
342
334
|
const profileStoreFor = dependencies.profileStore ?? ((home: string) => new ProfileStore(home));
|
|
343
|
-
|
|
344
335
|
const globals = (command: Command): Globals => {
|
|
345
336
|
const options = command.optsWithGlobals<{
|
|
346
337
|
home?: string;
|
|
@@ -356,7 +347,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
356
347
|
...(options.preset ? { preset: options.preset } : {}),
|
|
357
348
|
};
|
|
358
349
|
};
|
|
359
|
-
|
|
360
350
|
const resolverDeps = (home: string): ResolverDependencies => ({
|
|
361
351
|
stores: storesFor(home),
|
|
362
352
|
env,
|
|
@@ -368,7 +358,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
368
358
|
? { createLocalResolver: dependencies.createLocalResolver }
|
|
369
359
|
: {}),
|
|
370
360
|
});
|
|
371
|
-
|
|
372
361
|
const selectionFor = (global: Globals, config: ProfilesConfig) =>
|
|
373
362
|
resolveSelection(config, {
|
|
374
363
|
...(global.profile ? { profile: global.profile } : {}),
|
|
@@ -377,7 +366,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
377
366
|
cwd,
|
|
378
367
|
home: global.home,
|
|
379
368
|
});
|
|
380
|
-
|
|
381
369
|
/**
|
|
382
370
|
* Runs a domain command as the selected profile's single context. Every
|
|
383
371
|
* acting identity comes from here — there is no per-command actor flag.
|
|
@@ -413,7 +401,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
413
401
|
await resolver.close?.();
|
|
414
402
|
}
|
|
415
403
|
};
|
|
416
|
-
|
|
417
404
|
/**
|
|
418
405
|
* Store administration: agent management, rebuild, backup, export, doctor.
|
|
419
406
|
*
|
|
@@ -442,14 +429,13 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
442
429
|
}
|
|
443
430
|
const storeHome =
|
|
444
431
|
profile && isLocalProfile(profile) ? (profile.home ?? global.home) : global.home;
|
|
445
|
-
const client = await
|
|
432
|
+
const client = await openLocalOwnerService(storeHome);
|
|
446
433
|
try {
|
|
447
434
|
return await operation(client, undefined, storeHome);
|
|
448
435
|
} finally {
|
|
449
436
|
await client.close();
|
|
450
437
|
}
|
|
451
438
|
};
|
|
452
|
-
|
|
453
439
|
const program = new Command();
|
|
454
440
|
cliExitCodes.set(program, 0);
|
|
455
441
|
program
|
|
@@ -462,9 +448,69 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
462
448
|
.option('--json', 'emit stable machine-readable JSON', false)
|
|
463
449
|
.showSuggestionAfterError()
|
|
464
450
|
.configureOutput({ writeOut: io.stdout, writeErr: io.stderr });
|
|
465
|
-
|
|
451
|
+
const humanCommand = program.command('human').description('Local human identities');
|
|
452
|
+
humanCommand
|
|
453
|
+
.command('register <handle>')
|
|
454
|
+
.requiredOption('--name <display-name>')
|
|
455
|
+
.action(async (handle: string, options: { name: string }, command: Command) => {
|
|
456
|
+
await withManagement(command, async (service) => {
|
|
457
|
+
const human = await service.actors.registerHuman({
|
|
458
|
+
id: handle,
|
|
459
|
+
handle,
|
|
460
|
+
displayName: options.name,
|
|
461
|
+
});
|
|
462
|
+
output(
|
|
463
|
+
io,
|
|
464
|
+
globals(command).json,
|
|
465
|
+
human,
|
|
466
|
+
`Registered ${human.displayName} (human:${human.handle}).`,
|
|
467
|
+
);
|
|
468
|
+
});
|
|
469
|
+
});
|
|
470
|
+
program
|
|
471
|
+
.command('actors')
|
|
472
|
+
.description('Search addressable humans and agents')
|
|
473
|
+
.option('--query <text>', 'Search name, handle or ID')
|
|
474
|
+
.option('--kind <kind>', 'human or agent')
|
|
475
|
+
.option('--cursor <cursor>', 'Signed continuation cursor')
|
|
476
|
+
.option('--limit <number>', 'Maximum actors', Number, 20)
|
|
477
|
+
.action(
|
|
478
|
+
async (
|
|
479
|
+
options: { query?: string; kind?: 'human' | 'agent'; cursor?: string; limit: number },
|
|
480
|
+
command: Command,
|
|
481
|
+
) => {
|
|
482
|
+
await withProfile(command, async (service) => {
|
|
483
|
+
const page = await service.actors.list(options);
|
|
484
|
+
output(
|
|
485
|
+
io,
|
|
486
|
+
globals(command).json,
|
|
487
|
+
page,
|
|
488
|
+
page.items
|
|
489
|
+
.map((actor) => `${actor.kind}:${actor.handle} — ${actor.displayName}`)
|
|
490
|
+
.join('\n') + (page.nextCursor ? `\nNext cursor: ${page.nextCursor}` : ''),
|
|
491
|
+
);
|
|
492
|
+
});
|
|
493
|
+
},
|
|
494
|
+
);
|
|
495
|
+
program
|
|
496
|
+
.command('actor-profile <target>')
|
|
497
|
+
.description('Visible authored records and separate kudos/useful counts')
|
|
498
|
+
.option('--after <cursor>', 'Continue authored record page')
|
|
499
|
+
.option('--limit <number>', 'Maximum records', Number, 20)
|
|
500
|
+
.action(
|
|
501
|
+
async (target: string, options: { after?: string; limit: number }, command: Command) => {
|
|
502
|
+
await withProfile(command, async (service) => {
|
|
503
|
+
const profile = await service.actors.profile({ target: targetRef(target), ...options });
|
|
504
|
+
output(
|
|
505
|
+
io,
|
|
506
|
+
globals(command).json,
|
|
507
|
+
profile,
|
|
508
|
+
`${profile.actor.displayName}: ${profile.counts.kudosReceived} kudos, ${profile.counts.usefulReceived} useful reactions`,
|
|
509
|
+
);
|
|
510
|
+
});
|
|
511
|
+
},
|
|
512
|
+
);
|
|
466
513
|
/* ------------------------------------------------------------ setup */
|
|
467
|
-
|
|
468
514
|
program
|
|
469
515
|
.command('setup')
|
|
470
516
|
.description('Set up a local store with its first agent and a matching fixed profile')
|
|
@@ -506,9 +552,8 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
506
552
|
const store = profileStoreFor(global.home);
|
|
507
553
|
ensureLocalStore(global.home);
|
|
508
554
|
const config = store.read();
|
|
509
|
-
|
|
510
555
|
const existing = config.profiles[profileName];
|
|
511
|
-
const client = await
|
|
556
|
+
const client = await openLocalOwnerService(global.home);
|
|
512
557
|
let agent;
|
|
513
558
|
let resumed = false;
|
|
514
559
|
try {
|
|
@@ -556,7 +601,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
556
601
|
} finally {
|
|
557
602
|
await client.close();
|
|
558
603
|
}
|
|
559
|
-
|
|
560
604
|
const actor = { kind: 'agent' as const, id: agent.id };
|
|
561
605
|
const profile: LocalProfile = {
|
|
562
606
|
backend: 'local',
|
|
@@ -597,13 +641,10 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
597
641
|
);
|
|
598
642
|
},
|
|
599
643
|
);
|
|
600
|
-
|
|
601
644
|
/* ------------------------------------------------------------ connections */
|
|
602
|
-
|
|
603
645
|
const connectionCommand = program
|
|
604
646
|
.command('connection')
|
|
605
647
|
.description('Hosted credentials: one per harness installation, shared by its profiles');
|
|
606
|
-
|
|
607
648
|
const saveCredential = async (
|
|
608
649
|
global: Globals,
|
|
609
650
|
name: string,
|
|
@@ -619,11 +660,13 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
619
660
|
store.write(next);
|
|
620
661
|
return next;
|
|
621
662
|
};
|
|
622
|
-
|
|
623
663
|
const verifyConnection = async (
|
|
624
664
|
apiUrl: string,
|
|
625
665
|
bearer: string,
|
|
626
|
-
): Promise<{
|
|
666
|
+
): Promise<{
|
|
667
|
+
identity: IdentityDescription;
|
|
668
|
+
contexts: ContextSummary[];
|
|
669
|
+
}> => {
|
|
627
670
|
const identity = await describeIdentity({
|
|
628
671
|
baseUrl: apiUrl,
|
|
629
672
|
accessToken: bearer,
|
|
@@ -636,7 +679,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
636
679
|
});
|
|
637
680
|
return { identity, contexts: listing.contexts };
|
|
638
681
|
};
|
|
639
|
-
|
|
640
682
|
const connectionSummary = (
|
|
641
683
|
name: string,
|
|
642
684
|
identity: IdentityDescription,
|
|
@@ -654,7 +696,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
654
696
|
? `Next: synomem profile create <name> --connection ${name} --context <context-id>`
|
|
655
697
|
: 'Authorize an agent for it on the consent screen or in the portal.',
|
|
656
698
|
].join('\n');
|
|
657
|
-
|
|
658
699
|
connectionCommand
|
|
659
700
|
.command('login')
|
|
660
701
|
.description('Sign in through the browser (OAuth 2.1 + PKCE) and store the credential')
|
|
@@ -695,7 +736,7 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
695
736
|
);
|
|
696
737
|
}
|
|
697
738
|
const callbackPort = Number(options.callbackPort);
|
|
698
|
-
if (!Number.isSafeInteger(callbackPort) || callbackPort < 1 || callbackPort >
|
|
739
|
+
if (!Number.isSafeInteger(callbackPort) || callbackPort < 1 || callbackPort > 65535) {
|
|
699
740
|
throw new SynomemError('INVALID_INPUT', '--callback-port must be from 1 through 65535.');
|
|
700
741
|
}
|
|
701
742
|
const credential = await oauthLogin({
|
|
@@ -729,7 +770,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
729
770
|
);
|
|
730
771
|
},
|
|
731
772
|
);
|
|
732
|
-
|
|
733
773
|
connectionCommand
|
|
734
774
|
.command('add-key')
|
|
735
775
|
.description('Store a member-owned access key (read from stdin, never an argument)')
|
|
@@ -737,7 +777,14 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
737
777
|
.option('--api-url <url>', 'internal: alternate API origin')
|
|
738
778
|
.option('--store <where>', 'keychain (default), file, or environment')
|
|
739
779
|
.action(
|
|
740
|
-
async (
|
|
780
|
+
async (
|
|
781
|
+
options: {
|
|
782
|
+
name: string;
|
|
783
|
+
apiUrl?: string;
|
|
784
|
+
store?: string;
|
|
785
|
+
},
|
|
786
|
+
command: Command,
|
|
787
|
+
) => {
|
|
741
788
|
const global = globals(command);
|
|
742
789
|
const name = assertName(options.name, 'connection');
|
|
743
790
|
const apiUrl = secureUrl(options.apiUrl ?? cloudApiUrl(env), 'API URL').origin;
|
|
@@ -793,7 +840,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
793
840
|
);
|
|
794
841
|
},
|
|
795
842
|
);
|
|
796
|
-
|
|
797
843
|
connectionCommand
|
|
798
844
|
.command('list')
|
|
799
845
|
.description('List connections and the profiles using each, without secrets')
|
|
@@ -819,15 +865,12 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
819
865
|
? connections
|
|
820
866
|
.map(
|
|
821
867
|
(connection) =>
|
|
822
|
-
`${connection.name} ${connection.kind} ${connection.store} ${connection.label ?? ''}\n profiles: ${
|
|
823
|
-
connection.profiles.join(', ') || 'none'
|
|
824
|
-
}`,
|
|
868
|
+
`${connection.name} ${connection.kind} ${connection.store} ${connection.label ?? ''}\n profiles: ${connection.profiles.join(', ') || 'none'}`,
|
|
825
869
|
)
|
|
826
870
|
.join('\n')
|
|
827
871
|
: 'No connections. Run `synomem connection login --name <name>`.',
|
|
828
872
|
);
|
|
829
873
|
});
|
|
830
|
-
|
|
831
874
|
connectionCommand
|
|
832
875
|
.command('status [name]')
|
|
833
876
|
.description('Check that a connection (or every connection) authenticates')
|
|
@@ -875,80 +918,93 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
875
918
|
.map((result) =>
|
|
876
919
|
result.ok
|
|
877
920
|
? `${String(result.name)} ok (${String(result.store)}) — ${String(result.contexts)} context(s)`
|
|
878
|
-
: `${String(result.name)} FAILED ${
|
|
921
|
+
: `${String(result.name)} FAILED ${
|
|
922
|
+
(
|
|
923
|
+
result.error as {
|
|
924
|
+
message: string;
|
|
925
|
+
}
|
|
926
|
+
).message
|
|
927
|
+
}`,
|
|
879
928
|
)
|
|
880
929
|
.join('\n')
|
|
881
930
|
: 'No connections.',
|
|
882
931
|
);
|
|
883
932
|
if (failed) cliExitCodes.set(program, 4);
|
|
884
933
|
});
|
|
885
|
-
|
|
886
934
|
connectionCommand
|
|
887
935
|
.command('remove')
|
|
888
936
|
.description('Forget a connection and delete its locally stored secret')
|
|
889
937
|
.requiredOption('--name <name>')
|
|
890
938
|
.option('--force', 'also remove the profiles that use it', false)
|
|
891
|
-
.action(
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
}
|
|
909
|
-
const profiles = Object.fromEntries(
|
|
910
|
-
Object.entries(config.profiles).filter(
|
|
911
|
-
([profileName]) => !dependents.includes(profileName),
|
|
912
|
-
),
|
|
913
|
-
);
|
|
914
|
-
const harnessPresets = Object.fromEntries(
|
|
915
|
-
Object.entries(config.harnessPresets)
|
|
916
|
-
.map(
|
|
917
|
-
([preset, members]) =>
|
|
918
|
-
[preset, members.filter((member) => !dependents.includes(member))] as const,
|
|
939
|
+
.action(
|
|
940
|
+
async (
|
|
941
|
+
options: {
|
|
942
|
+
name: string;
|
|
943
|
+
force: boolean;
|
|
944
|
+
},
|
|
945
|
+
command: Command,
|
|
946
|
+
) => {
|
|
947
|
+
const global = globals(command);
|
|
948
|
+
const store = profileStoreFor(global.home);
|
|
949
|
+
const config = store.read();
|
|
950
|
+
const entry = config.credentials[options.name];
|
|
951
|
+
if (!entry)
|
|
952
|
+
throw new SynomemError('CONFIG_INVALID', `Unknown connection "${options.name}".`);
|
|
953
|
+
const dependents = Object.entries(config.profiles)
|
|
954
|
+
.filter(
|
|
955
|
+
([, profile]) => !isLocalProfile(profile) && profile.credentialRef === options.name,
|
|
919
956
|
)
|
|
920
|
-
.
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
957
|
+
.map(([profileName]) => profileName);
|
|
958
|
+
if (dependents.length && !options.force) {
|
|
959
|
+
throw new SynomemError(
|
|
960
|
+
'INVALID_INPUT',
|
|
961
|
+
`Profiles ${dependents.join(', ')} use connection ${options.name}. Remove them first, or pass --force to remove them too.`,
|
|
962
|
+
);
|
|
963
|
+
}
|
|
964
|
+
if (entry.store !== 'environment') {
|
|
965
|
+
await storeFor(storesFor(global.home), entry).delete(entry.secretRef!);
|
|
966
|
+
}
|
|
967
|
+
const profiles = Object.fromEntries(
|
|
968
|
+
Object.entries(config.profiles).filter(
|
|
969
|
+
([profileName]) => !dependents.includes(profileName),
|
|
970
|
+
),
|
|
971
|
+
);
|
|
972
|
+
const harnessPresets = Object.fromEntries(
|
|
973
|
+
Object.entries(config.harnessPresets)
|
|
974
|
+
.map(
|
|
975
|
+
([preset, members]) =>
|
|
976
|
+
[preset, members.filter((member) => !dependents.includes(member))] as const,
|
|
977
|
+
)
|
|
978
|
+
.filter(([, members]) => members.length > 0),
|
|
979
|
+
);
|
|
980
|
+
const credentials = Object.fromEntries(
|
|
981
|
+
Object.entries(config.credentials).filter(([name]) => name !== options.name),
|
|
982
|
+
);
|
|
983
|
+
store.write({
|
|
984
|
+
...config,
|
|
985
|
+
credentials,
|
|
986
|
+
profiles,
|
|
987
|
+
harnessPresets,
|
|
988
|
+
...(config.defaultProfile && dependents.includes(config.defaultProfile)
|
|
989
|
+
? { defaultProfile: undefined }
|
|
990
|
+
: {}),
|
|
991
|
+
});
|
|
992
|
+
output(
|
|
993
|
+
io,
|
|
994
|
+
global.json,
|
|
995
|
+
{ removed: options.name, profilesRemoved: dependents },
|
|
996
|
+
[
|
|
997
|
+
`Removed connection ${options.name} and its local secret.`,
|
|
998
|
+
...(dependents.length ? [`Also removed profiles: ${dependents.join(', ')}.`] : []),
|
|
999
|
+
'The server-side authorization still exists: revoke it in the portal (Connections) to stop it everywhere.',
|
|
1000
|
+
].join('\n'),
|
|
1001
|
+
);
|
|
1002
|
+
},
|
|
1003
|
+
);
|
|
946
1004
|
/* ------------------------------------------------------------ profiles */
|
|
947
|
-
|
|
948
1005
|
const profileCommand = program
|
|
949
1006
|
.command('profile')
|
|
950
1007
|
.description('Named identities: one stable context through one connection or local store');
|
|
951
|
-
|
|
952
1008
|
profileCommand
|
|
953
1009
|
.command('create <name>')
|
|
954
1010
|
.description('Create a profile for a context this connection may already use')
|
|
@@ -956,7 +1012,8 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
956
1012
|
.option('--context <context-id>', 'exact context id (see `connection status`)')
|
|
957
1013
|
.option('--agent <handle-or-id>', 'narrow by agent handle, id or display name')
|
|
958
1014
|
.option('--workspace <name-or-id>', 'narrow by workspace name or id')
|
|
959
|
-
.option('--
|
|
1015
|
+
.option('--human <handle-or-id>', 'local registered human identity')
|
|
1016
|
+
.option('--local', 'a local-store profile for an existing human or agent', false)
|
|
960
1017
|
.option('--store-home <path>', 'local store home (default: the Synomem home)')
|
|
961
1018
|
.option('--default', 'make this the default profile', false)
|
|
962
1019
|
.action(
|
|
@@ -966,6 +1023,7 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
966
1023
|
connection?: string;
|
|
967
1024
|
context?: string;
|
|
968
1025
|
agent?: string;
|
|
1026
|
+
human?: string;
|
|
969
1027
|
workspace?: string;
|
|
970
1028
|
local: boolean;
|
|
971
1029
|
storeHome?: string;
|
|
@@ -983,37 +1041,41 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
983
1041
|
`Profile ${name} already exists. Remove it first to change what it points at.`,
|
|
984
1042
|
);
|
|
985
1043
|
}
|
|
986
|
-
|
|
987
1044
|
if (options.local) {
|
|
988
|
-
if (!options.agent)
|
|
1045
|
+
if ((!options.agent && !options.human) || (options.agent && options.human))
|
|
989
1046
|
throw new SynomemError(
|
|
990
1047
|
'INVALID_INPUT',
|
|
991
|
-
'A local profile needs --
|
|
1048
|
+
'A local profile needs exactly one of --human or --agent.',
|
|
992
1049
|
);
|
|
993
1050
|
const storeHome = options.storeHome ? resolve(options.storeHome) : global.home;
|
|
994
|
-
const client = await
|
|
1051
|
+
const client = await openLocalOwnerService(storeHome);
|
|
995
1052
|
let agent;
|
|
996
1053
|
try {
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1054
|
+
if (options.human) {
|
|
1055
|
+
agent = await client.actors.get({ kind: 'human', id: options.human });
|
|
1056
|
+
} else {
|
|
1057
|
+
const resolution = await client.agents.resolve(options.agent!);
|
|
1058
|
+
if (!resolution.match) {
|
|
1059
|
+
throw new SynomemError(
|
|
1060
|
+
resolution.candidates.length ? 'CONTEXT_AMBIGUOUS' : 'AGENT_NOT_FOUND',
|
|
1061
|
+
resolution.candidates.length
|
|
1062
|
+
? `"${options.agent}" matches several agents: ${resolution.candidates.map((c) => `${c.handle} (${c.id})`).join(', ')}.`
|
|
1063
|
+
: `No local agent answers to "${options.agent}". Profiles never create agents: run \`synomem agent create\` first.`,
|
|
1064
|
+
);
|
|
1065
|
+
}
|
|
1066
|
+
agent = resolution.match;
|
|
1005
1067
|
}
|
|
1006
|
-
agent = resolution.match;
|
|
1007
1068
|
} finally {
|
|
1008
1069
|
await client.close();
|
|
1009
1070
|
}
|
|
1010
1071
|
const profile: LocalProfile = {
|
|
1011
1072
|
backend: 'local',
|
|
1073
|
+
actorKind: options.human ? 'human' : 'agent',
|
|
1012
1074
|
...(storeHome !== global.home ? { home: storeHome } : {}),
|
|
1013
1075
|
actorId: agent.id,
|
|
1014
1076
|
actorName: agent.displayName,
|
|
1015
1077
|
contextId: localContextId(storeHome, {
|
|
1016
|
-
kind: 'agent',
|
|
1078
|
+
kind: options.human ? 'human' : 'agent',
|
|
1017
1079
|
id: agent.id,
|
|
1018
1080
|
}),
|
|
1019
1081
|
};
|
|
@@ -1030,7 +1092,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
1030
1092
|
);
|
|
1031
1093
|
return;
|
|
1032
1094
|
}
|
|
1033
|
-
|
|
1034
1095
|
if (!options.connection) {
|
|
1035
1096
|
throw new SynomemError(
|
|
1036
1097
|
'INVALID_INPUT',
|
|
@@ -1128,7 +1189,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
1128
1189
|
);
|
|
1129
1190
|
},
|
|
1130
1191
|
);
|
|
1131
|
-
|
|
1132
1192
|
profileCommand
|
|
1133
1193
|
.command('list')
|
|
1134
1194
|
.description('List profiles and presets')
|
|
@@ -1163,7 +1223,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
1163
1223
|
: 'No profiles. Run `synomem setup --backend local`, or `synomem connection login` then `synomem profile create`.',
|
|
1164
1224
|
);
|
|
1165
1225
|
});
|
|
1166
|
-
|
|
1167
1226
|
profileCommand
|
|
1168
1227
|
.command('show <name>')
|
|
1169
1228
|
.description('Show one profile')
|
|
@@ -1179,49 +1238,56 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
1179
1238
|
describeProfile(name, profile),
|
|
1180
1239
|
);
|
|
1181
1240
|
});
|
|
1182
|
-
|
|
1183
1241
|
profileCommand
|
|
1184
1242
|
.command('remove <name>')
|
|
1185
1243
|
.description('Remove a profile (its connection and credential are kept)')
|
|
1186
1244
|
.option('--force', 'also remove it from presets that use it', false)
|
|
1187
|
-
.action(
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1245
|
+
.action(
|
|
1246
|
+
(
|
|
1247
|
+
name: string,
|
|
1248
|
+
options: {
|
|
1249
|
+
force: boolean;
|
|
1250
|
+
},
|
|
1251
|
+
command: Command,
|
|
1252
|
+
) => {
|
|
1253
|
+
const global = globals(command);
|
|
1254
|
+
const store = profileStoreFor(global.home);
|
|
1255
|
+
const config = store.read();
|
|
1256
|
+
if (!config.profiles[name])
|
|
1257
|
+
throw new SynomemError('CONFIG_INVALID', `Unknown profile "${name}".`);
|
|
1258
|
+
const presets = Object.entries(config.harnessPresets)
|
|
1259
|
+
.filter(([, members]) => members.includes(name))
|
|
1260
|
+
.map(([preset]) => preset);
|
|
1261
|
+
if (presets.length && !options.force) {
|
|
1262
|
+
throw new SynomemError(
|
|
1263
|
+
'INVALID_INPUT',
|
|
1264
|
+
`Presets ${presets.join(', ')} use profile ${name}. Pass --force to remove it from them too.`,
|
|
1265
|
+
);
|
|
1266
|
+
}
|
|
1267
|
+
const profiles = Object.fromEntries(
|
|
1268
|
+
Object.entries(config.profiles).filter(([profileName]) => profileName !== name),
|
|
1200
1269
|
);
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
);
|
|
1223
|
-
});
|
|
1224
|
-
|
|
1270
|
+
store.write({
|
|
1271
|
+
...config,
|
|
1272
|
+
profiles,
|
|
1273
|
+
harnessPresets: Object.fromEntries(
|
|
1274
|
+
Object.entries(config.harnessPresets)
|
|
1275
|
+
.map(
|
|
1276
|
+
([preset, members]) =>
|
|
1277
|
+
[preset, members.filter((member) => member !== name)] as const,
|
|
1278
|
+
)
|
|
1279
|
+
.filter(([, members]) => members.length > 0),
|
|
1280
|
+
),
|
|
1281
|
+
...(config.defaultProfile === name ? { defaultProfile: undefined } : {}),
|
|
1282
|
+
});
|
|
1283
|
+
output(
|
|
1284
|
+
io,
|
|
1285
|
+
global.json,
|
|
1286
|
+
{ removed: name },
|
|
1287
|
+
`Removed profile ${name}. Its connection and credential were kept.`,
|
|
1288
|
+
);
|
|
1289
|
+
},
|
|
1290
|
+
);
|
|
1225
1291
|
profileCommand
|
|
1226
1292
|
.command('default <name>')
|
|
1227
1293
|
.description('Make a profile the default when nothing else selects one')
|
|
@@ -1234,7 +1300,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
1234
1300
|
store.write({ ...config, defaultProfile: name });
|
|
1235
1301
|
output(io, global.json, { defaultProfile: name }, `Default profile: ${name}.`);
|
|
1236
1302
|
});
|
|
1237
|
-
|
|
1238
1303
|
profileCommand
|
|
1239
1304
|
.command('use <name>')
|
|
1240
1305
|
.description('Bind this directory to a profile (writes .synomem/project.json)')
|
|
@@ -1251,11 +1316,9 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
1251
1316
|
`Wrote ${path}\n\nCommands and MCP servers started here now act as profile ${name} unless --profile or SYNOMEM_PROFILE says otherwise.`,
|
|
1252
1317
|
);
|
|
1253
1318
|
});
|
|
1254
|
-
|
|
1255
1319
|
const presetCommand = program
|
|
1256
1320
|
.command('preset')
|
|
1257
1321
|
.description('Several profiles served by one explicit-context MCP server');
|
|
1258
|
-
|
|
1259
1322
|
presetCommand
|
|
1260
1323
|
.command('create <name> <profiles...>')
|
|
1261
1324
|
.description('Create a preset from existing profiles')
|
|
@@ -1281,7 +1344,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
1281
1344
|
`Created preset ${name}: ${members.join(', ')}.\n\nStart it with:\n synomem mcp --preset ${name} --contexts explicit`,
|
|
1282
1345
|
);
|
|
1283
1346
|
});
|
|
1284
|
-
|
|
1285
1347
|
presetCommand.command('list').action((_options, command: Command) => {
|
|
1286
1348
|
const global = globals(command);
|
|
1287
1349
|
const config = profileStoreFor(global.home).read();
|
|
@@ -1294,7 +1356,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
1294
1356
|
.join('\n') || 'No presets.',
|
|
1295
1357
|
);
|
|
1296
1358
|
});
|
|
1297
|
-
|
|
1298
1359
|
presetCommand.command('remove <name>').action((name: string, _options, command: Command) => {
|
|
1299
1360
|
const global = globals(command);
|
|
1300
1361
|
const store = profileStoreFor(global.home);
|
|
@@ -1307,9 +1368,7 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
1307
1368
|
store.write({ ...config, harnessPresets });
|
|
1308
1369
|
output(io, global.json, { removed: name }, `Removed preset ${name}.`);
|
|
1309
1370
|
});
|
|
1310
|
-
|
|
1311
1371
|
/* ------------------------------------------------------------ identity */
|
|
1312
|
-
|
|
1313
1372
|
program
|
|
1314
1373
|
.command('whoami')
|
|
1315
1374
|
.description('Show the selected profile, its effective context, and its credential source')
|
|
@@ -1373,13 +1432,10 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
1373
1432
|
await resolver.close?.();
|
|
1374
1433
|
}
|
|
1375
1434
|
});
|
|
1376
|
-
|
|
1377
1435
|
/* ------------------------------------------------------------ local stores */
|
|
1378
|
-
|
|
1379
1436
|
const workspaceCommand = program
|
|
1380
1437
|
.command('workspace')
|
|
1381
1438
|
.description('Local stores on this machine, each a separate SQLite database');
|
|
1382
|
-
|
|
1383
1439
|
workspaceCommand
|
|
1384
1440
|
.command('list')
|
|
1385
1441
|
.description('List the local stores on this machine')
|
|
@@ -1398,7 +1454,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
1398
1454
|
.join('\n') || 'No local stores.',
|
|
1399
1455
|
);
|
|
1400
1456
|
});
|
|
1401
|
-
|
|
1402
1457
|
workspaceCommand
|
|
1403
1458
|
.command('create <name>')
|
|
1404
1459
|
.description('Create a local store (then `profile create --local --store-home <path>`)')
|
|
@@ -1411,11 +1466,8 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
1411
1466
|
ensureLocalStore(home);
|
|
1412
1467
|
output(io, global.json, { name, home }, `Created local store ${name} at ${home}.`);
|
|
1413
1468
|
});
|
|
1414
|
-
|
|
1415
1469
|
/* ------------------------------------------------------------ remote import */
|
|
1416
|
-
|
|
1417
1470
|
const remoteCommand = program.command('remote').description('Hosted workspace administration');
|
|
1418
|
-
|
|
1419
1471
|
remoteCommand
|
|
1420
1472
|
.command('import')
|
|
1421
1473
|
.description(
|
|
@@ -1426,7 +1478,11 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
1426
1478
|
.option('--confirm <plan-id>', 'commit the exact bundle authorized by a preview')
|
|
1427
1479
|
.action(
|
|
1428
1480
|
async (
|
|
1429
|
-
options: {
|
|
1481
|
+
options: {
|
|
1482
|
+
fromHome: string;
|
|
1483
|
+
preview?: boolean;
|
|
1484
|
+
confirm?: string;
|
|
1485
|
+
},
|
|
1430
1486
|
command: Command,
|
|
1431
1487
|
) => {
|
|
1432
1488
|
const global = globals(command);
|
|
@@ -1492,91 +1548,97 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
1492
1548
|
output(io, global.json, result, human);
|
|
1493
1549
|
},
|
|
1494
1550
|
);
|
|
1495
|
-
|
|
1496
1551
|
/* ------------------------------------------------------------ reset */
|
|
1497
|
-
|
|
1498
1552
|
program
|
|
1499
1553
|
.command('reset')
|
|
1500
1554
|
.description('Remove Synomem configuration, the local database, profiles and stored secrets')
|
|
1501
1555
|
.option('--integrations', 'also remove installed skills', false)
|
|
1502
1556
|
.option('--yes', 'apply the displayed plan', false)
|
|
1503
|
-
.action(
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1557
|
+
.action(
|
|
1558
|
+
async (
|
|
1559
|
+
options: {
|
|
1560
|
+
integrations: boolean;
|
|
1561
|
+
yes: boolean;
|
|
1562
|
+
},
|
|
1563
|
+
command: Command,
|
|
1564
|
+
) => {
|
|
1565
|
+
const global = globals(command);
|
|
1566
|
+
const home = global.home;
|
|
1567
|
+
const store = profileStoreFor(home);
|
|
1568
|
+
const config = existsSync(store.path) ? store.read() : undefined;
|
|
1569
|
+
const secrets = Object.entries(config?.credentials ?? {})
|
|
1570
|
+
.filter(([, entry]) => entry.store !== 'environment')
|
|
1571
|
+
.map(([name, entry]) => ({ name, entry }));
|
|
1572
|
+
// Every target is an exact path; no recursive delete is derived from a
|
|
1573
|
+
// variable that might be empty.
|
|
1574
|
+
const targets = [
|
|
1575
|
+
join(home, 'config.json'),
|
|
1576
|
+
join(home, 'profiles.json'),
|
|
1577
|
+
join(home, 'synomem.sqlite3'),
|
|
1578
|
+
join(home, 'synomem.sqlite3-wal'),
|
|
1579
|
+
join(home, 'synomem.sqlite3-shm'),
|
|
1580
|
+
...secrets
|
|
1581
|
+
.filter(({ entry }) => entry.store === 'file')
|
|
1582
|
+
.map(({ entry }) => join(home, 'credentials', `${entry.secretRef}.json`)),
|
|
1583
|
+
].filter((path) => existsSync(path));
|
|
1584
|
+
const keychain = secrets
|
|
1585
|
+
.filter(({ entry }) => entry.store === 'keychain')
|
|
1586
|
+
.map(({ name }) => name);
|
|
1587
|
+
const skillPlan = options.integrations ? uninstallSkill({ apply: false }) : undefined;
|
|
1588
|
+
const skillTargets =
|
|
1589
|
+
skillPlan?.locations
|
|
1590
|
+
.filter((location) => location.state === 'current' || location.state === 'stale')
|
|
1591
|
+
.map((location) => location.target) ?? [];
|
|
1592
|
+
if (!options.yes) {
|
|
1593
|
+
output(
|
|
1594
|
+
io,
|
|
1595
|
+
global.json,
|
|
1596
|
+
{ targets, keychain, skillTargets, applied: false },
|
|
1597
|
+
[
|
|
1598
|
+
'This will remove:',
|
|
1599
|
+
...(targets.length ? targets.map((path) => ` ${path}`) : [' (no files found)']),
|
|
1600
|
+
...(keychain.length
|
|
1601
|
+
? [
|
|
1602
|
+
'',
|
|
1603
|
+
'And the keychain secrets of connections:',
|
|
1604
|
+
...keychain.map((name) => ` ${name}`),
|
|
1605
|
+
]
|
|
1606
|
+
: []),
|
|
1607
|
+
...(skillTargets.length
|
|
1608
|
+
? [
|
|
1609
|
+
'',
|
|
1610
|
+
'And these Synomem-owned skills:',
|
|
1611
|
+
...skillTargets.map((path) => ` ${path}`),
|
|
1612
|
+
]
|
|
1613
|
+
: []),
|
|
1614
|
+
'',
|
|
1615
|
+
'Server-side authorizations are not revoked; do that in the portal.',
|
|
1616
|
+
'Run with --yes to continue.',
|
|
1617
|
+
].join('\n'),
|
|
1618
|
+
);
|
|
1619
|
+
return;
|
|
1620
|
+
}
|
|
1621
|
+
const stores = storesFor(home);
|
|
1622
|
+
for (const { entry } of secrets.filter(({ entry }) => entry.store === 'keychain')) {
|
|
1623
|
+
await stores.keychain.delete(entry.secretRef!).catch(() => false);
|
|
1624
|
+
}
|
|
1625
|
+
for (const path of targets) rmSync(path, { force: true });
|
|
1626
|
+
const skillResult = options.integrations ? uninstallSkill({ apply: true }) : undefined;
|
|
1533
1627
|
output(
|
|
1534
1628
|
io,
|
|
1535
1629
|
global.json,
|
|
1536
|
-
{ targets, keychain,
|
|
1630
|
+
{ removed: targets, keychain, skills: skillResult?.locations ?? [] },
|
|
1537
1631
|
[
|
|
1538
|
-
|
|
1539
|
-
...(
|
|
1540
|
-
...(keychain.length
|
|
1541
|
-
? [
|
|
1542
|
-
'',
|
|
1543
|
-
'And the keychain secrets of connections:',
|
|
1544
|
-
...keychain.map((name) => ` ${name}`),
|
|
1545
|
-
]
|
|
1546
|
-
: []),
|
|
1547
|
-
...(skillTargets.length
|
|
1548
|
-
? ['', 'And these Synomem-owned skills:', ...skillTargets.map((path) => ` ${path}`)]
|
|
1549
|
-
: []),
|
|
1550
|
-
'',
|
|
1551
|
-
'Server-side authorizations are not revoked; do that in the portal.',
|
|
1552
|
-
'Run with --yes to continue.',
|
|
1632
|
+
`Removed ${targets.length} file(s)${keychain.length ? ` and ${keychain.length} keychain secret(s)` : ''}.`,
|
|
1633
|
+
...(skillResult ? [`Skill locations processed: ${skillResult.locations.length}.`] : []),
|
|
1553
1634
|
].join('\n'),
|
|
1554
1635
|
);
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
const stores = storesFor(home);
|
|
1558
|
-
for (const { entry } of secrets.filter(({ entry }) => entry.store === 'keychain')) {
|
|
1559
|
-
await stores.keychain.delete(entry.secretRef!).catch(() => false);
|
|
1560
|
-
}
|
|
1561
|
-
for (const path of targets) rmSync(path, { force: true });
|
|
1562
|
-
const skillResult = options.integrations ? uninstallSkill({ apply: true }) : undefined;
|
|
1563
|
-
output(
|
|
1564
|
-
io,
|
|
1565
|
-
global.json,
|
|
1566
|
-
{ removed: targets, keychain, skills: skillResult?.locations ?? [] },
|
|
1567
|
-
[
|
|
1568
|
-
`Removed ${targets.length} file(s)${keychain.length ? ` and ${keychain.length} keychain secret(s)` : ''}.`,
|
|
1569
|
-
...(skillResult ? [`Skill locations processed: ${skillResult.locations.length}.`] : []),
|
|
1570
|
-
].join('\n'),
|
|
1571
|
-
);
|
|
1572
|
-
});
|
|
1573
|
-
|
|
1636
|
+
},
|
|
1637
|
+
);
|
|
1574
1638
|
/* ------------------------------------------------------------ agents */
|
|
1575
|
-
|
|
1576
1639
|
const agentCommand = program
|
|
1577
1640
|
.command('agent')
|
|
1578
1641
|
.description('Create and inspect stable agent identities');
|
|
1579
|
-
|
|
1580
1642
|
agentCommand
|
|
1581
1643
|
.command('create <handle>')
|
|
1582
1644
|
.description('Create an agent. The canonical ID is generated, not chosen.')
|
|
@@ -1587,7 +1649,12 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
1587
1649
|
.action(
|
|
1588
1650
|
async (
|
|
1589
1651
|
handle: string,
|
|
1590
|
-
options: {
|
|
1652
|
+
options: {
|
|
1653
|
+
name: string;
|
|
1654
|
+
alias: string[];
|
|
1655
|
+
description?: string;
|
|
1656
|
+
createProfile: boolean;
|
|
1657
|
+
},
|
|
1591
1658
|
command: Command,
|
|
1592
1659
|
) => {
|
|
1593
1660
|
const global = globals(command);
|
|
@@ -1635,17 +1702,13 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
1635
1702
|
io,
|
|
1636
1703
|
global.json,
|
|
1637
1704
|
{ ...agent, ...(profileName ? { profileCreated: profileName } : {}) },
|
|
1638
|
-
`Created ${agent.displayName}\n\nHandle: ${agent.handle}\nAgent ID: ${agent.id}${
|
|
1639
|
-
profileName ? `\nProfile: ${profileName} (synomem mcp --profile ${profileName})` : ''
|
|
1640
|
-
}`,
|
|
1705
|
+
`Created ${agent.displayName}\n\nHandle: ${agent.handle}\nAgent ID: ${agent.id}${profileName ? `\nProfile: ${profileName} (synomem mcp --profile ${profileName})` : ''}`,
|
|
1641
1706
|
);
|
|
1642
1707
|
},
|
|
1643
1708
|
);
|
|
1644
|
-
|
|
1645
1709
|
const aliasCommand = agentCommand
|
|
1646
1710
|
.command('alias')
|
|
1647
1711
|
.description('Add or remove discovery aliases without replacing the set');
|
|
1648
|
-
|
|
1649
1712
|
aliasCommand
|
|
1650
1713
|
.command('add <agent> <alias...>')
|
|
1651
1714
|
.description('Add aliases, keeping the ones already there')
|
|
@@ -1656,7 +1719,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
1656
1719
|
);
|
|
1657
1720
|
output(io, global.json, profile, `Aliases: ${(profile.aliases ?? []).join(', ') || 'none'}`);
|
|
1658
1721
|
});
|
|
1659
|
-
|
|
1660
1722
|
aliasCommand
|
|
1661
1723
|
.command('remove <agent> <alias...>')
|
|
1662
1724
|
.description('Remove aliases, keeping the rest')
|
|
@@ -1667,7 +1729,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
1667
1729
|
);
|
|
1668
1730
|
output(io, global.json, profile, `Aliases: ${(profile.aliases ?? []).join(', ') || 'none'}`);
|
|
1669
1731
|
});
|
|
1670
|
-
|
|
1671
1732
|
agentCommand
|
|
1672
1733
|
.command('rename <agent> <handle>')
|
|
1673
1734
|
.description('Change an agent handle. Its canonical ID never changes.')
|
|
@@ -1683,7 +1744,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
1683
1744
|
`Handle: ${profile.handle}\nAgent ID: ${profile.id} (unchanged)`,
|
|
1684
1745
|
);
|
|
1685
1746
|
});
|
|
1686
|
-
|
|
1687
1747
|
agentCommand
|
|
1688
1748
|
.command('archive <agent>')
|
|
1689
1749
|
.description('Stop an agent acting, keeping its records and history')
|
|
@@ -1692,7 +1752,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
1692
1752
|
const profile = await withManagement(command, (service) => service.agents.archive(agent));
|
|
1693
1753
|
output(io, global.json, profile, `Archived ${profile.handle} (${profile.id})`);
|
|
1694
1754
|
});
|
|
1695
|
-
|
|
1696
1755
|
agentCommand
|
|
1697
1756
|
.command('restore <agent>')
|
|
1698
1757
|
.description('Let an archived agent act again')
|
|
@@ -1701,7 +1760,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
1701
1760
|
const profile = await withManagement(command, (service) => service.agents.restore(agent));
|
|
1702
1761
|
output(io, global.json, profile, `Restored ${profile.handle} (${profile.id})`);
|
|
1703
1762
|
});
|
|
1704
|
-
|
|
1705
1763
|
agentCommand
|
|
1706
1764
|
.command('list')
|
|
1707
1765
|
.description('List known agent identities')
|
|
@@ -1712,15 +1770,12 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
1712
1770
|
? agents
|
|
1713
1771
|
.map(
|
|
1714
1772
|
(profile) =>
|
|
1715
|
-
`${profile.handle} ${profile.displayName}${profile.status === 'archived' ? ' [archived]' : ''}${
|
|
1716
|
-
profile.aliases?.length ? ` aliases: ${profile.aliases.join(', ')}` : ''
|
|
1717
|
-
}\n ${profile.id}`,
|
|
1773
|
+
`${profile.handle} ${profile.displayName}${profile.status === 'archived' ? ' [archived]' : ''}${profile.aliases?.length ? ` aliases: ${profile.aliases.join(', ')}` : ''}\n ${profile.id}`,
|
|
1718
1774
|
)
|
|
1719
1775
|
.join('\n')
|
|
1720
1776
|
: 'No agents configured.';
|
|
1721
1777
|
output(io, global.json, { agents }, human);
|
|
1722
1778
|
});
|
|
1723
|
-
|
|
1724
1779
|
agentCommand
|
|
1725
1780
|
.command('show <id>')
|
|
1726
1781
|
.description('Show one agent profile, resolving aliases')
|
|
@@ -1734,7 +1789,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
1734
1789
|
`${profile.displayName}\n\nHandle: ${profile.handle}\nAgent ID: ${profile.id}\nStatus: ${profile.status}\n\n${profile.description ?? 'No description.'}`,
|
|
1735
1790
|
);
|
|
1736
1791
|
});
|
|
1737
|
-
|
|
1738
1792
|
agentCommand
|
|
1739
1793
|
.command('update <id>')
|
|
1740
1794
|
.description('Update an agent profile without rewriting history')
|
|
@@ -1745,7 +1799,12 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
1745
1799
|
.action(
|
|
1746
1800
|
async (
|
|
1747
1801
|
id: string,
|
|
1748
|
-
options: {
|
|
1802
|
+
options: {
|
|
1803
|
+
name?: string;
|
|
1804
|
+
alias: string[];
|
|
1805
|
+
clearAliases: boolean;
|
|
1806
|
+
description?: string;
|
|
1807
|
+
},
|
|
1749
1808
|
command: Command,
|
|
1750
1809
|
) => {
|
|
1751
1810
|
const global = globals(command);
|
|
@@ -1760,7 +1819,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
1760
1819
|
output(io, global.json, profile, `Updated ${profile.displayName} (${profile.id})`);
|
|
1761
1820
|
},
|
|
1762
1821
|
);
|
|
1763
|
-
|
|
1764
1822
|
agentCommand
|
|
1765
1823
|
.command('resolve <name>')
|
|
1766
1824
|
.description('Resolve a name or alias to one agent, or list the candidates')
|
|
@@ -1776,7 +1834,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
1776
1834
|
: `No agent answers to "${resolution.query}".`;
|
|
1777
1835
|
output(io, global.json, resolution, human);
|
|
1778
1836
|
});
|
|
1779
|
-
|
|
1780
1837
|
agentCommand
|
|
1781
1838
|
.command('directory')
|
|
1782
1839
|
.description('List agents with their runtime bindings')
|
|
@@ -1801,9 +1858,7 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
1801
1858
|
: 'No agents configured.';
|
|
1802
1859
|
output(io, global.json, { entries }, human);
|
|
1803
1860
|
});
|
|
1804
|
-
|
|
1805
1861
|
const runtimeCommand = agentCommand.command('runtime').description('Record where an agent runs');
|
|
1806
|
-
|
|
1807
1862
|
runtimeCommand
|
|
1808
1863
|
.command('bind <agent>')
|
|
1809
1864
|
.description('Bind an agent to a runtime')
|
|
@@ -1812,7 +1867,10 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
1812
1867
|
.action(
|
|
1813
1868
|
async (
|
|
1814
1869
|
agent: string,
|
|
1815
|
-
options: {
|
|
1870
|
+
options: {
|
|
1871
|
+
runtime: string;
|
|
1872
|
+
runtimeProfile?: string;
|
|
1873
|
+
},
|
|
1816
1874
|
command: Command,
|
|
1817
1875
|
) => {
|
|
1818
1876
|
const global = globals(command);
|
|
@@ -1831,7 +1889,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
1831
1889
|
);
|
|
1832
1890
|
},
|
|
1833
1891
|
);
|
|
1834
|
-
|
|
1835
1892
|
runtimeCommand
|
|
1836
1893
|
.command('list [agent]')
|
|
1837
1894
|
.description('List runtime bindings for one agent, or for every agent')
|
|
@@ -1862,7 +1919,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
1862
1919
|
: 'No agent in this workspace has a runtime binding.';
|
|
1863
1920
|
output(io, global.json, { agents: result }, human);
|
|
1864
1921
|
});
|
|
1865
|
-
|
|
1866
1922
|
runtimeCommand
|
|
1867
1923
|
.command('unbind <binding-id>')
|
|
1868
1924
|
.description('Remove a runtime binding')
|
|
@@ -1878,52 +1934,60 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
1878
1934
|
removed ? `Removed binding ${bindingId}.` : `No binding ${bindingId}.`,
|
|
1879
1935
|
);
|
|
1880
1936
|
});
|
|
1881
|
-
|
|
1882
1937
|
/* ------------------------------------------------------------ topics */
|
|
1883
|
-
|
|
1884
1938
|
const topicCommand = program
|
|
1885
1939
|
.command('topic')
|
|
1886
1940
|
.description('Create and manage topics — a stable, reusable subject any record can carry');
|
|
1887
|
-
|
|
1888
1941
|
topicCommand
|
|
1889
1942
|
.command('create <display-name>')
|
|
1890
1943
|
.description('Create a topic. Any actor may create one.')
|
|
1891
1944
|
.option('--alias <name>', 'alias (repeatable)', collect, [])
|
|
1892
|
-
.action(
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
)
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1945
|
+
.action(
|
|
1946
|
+
async (
|
|
1947
|
+
displayName: string,
|
|
1948
|
+
options: {
|
|
1949
|
+
alias: string[];
|
|
1950
|
+
},
|
|
1951
|
+
command: Command,
|
|
1952
|
+
) => {
|
|
1953
|
+
const global = globals(command);
|
|
1954
|
+
const topic = await withProfile(command, (service) =>
|
|
1955
|
+
service.topics.create({
|
|
1956
|
+
displayName,
|
|
1957
|
+
...(options.alias.length ? { aliases: options.alias } : {}),
|
|
1958
|
+
}),
|
|
1959
|
+
);
|
|
1960
|
+
output(io, global.json, topic, `Created ${topic.displayName}\n\nTopic ID: ${topic.id}`);
|
|
1961
|
+
},
|
|
1962
|
+
);
|
|
1903
1963
|
topicCommand
|
|
1904
1964
|
.command('list')
|
|
1905
1965
|
.description('List known topics')
|
|
1906
1966
|
.option('--status <status>', 'active or archived')
|
|
1907
|
-
.action(
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
)
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1967
|
+
.action(
|
|
1968
|
+
async (
|
|
1969
|
+
options: {
|
|
1970
|
+
status?: string;
|
|
1971
|
+
},
|
|
1972
|
+
command: Command,
|
|
1973
|
+
) => {
|
|
1974
|
+
const global = globals(command);
|
|
1975
|
+
const topics = await withProfile(command, (service) =>
|
|
1976
|
+
service.topics.list(
|
|
1977
|
+
options.status ? { status: options.status as 'active' | 'archived' } : {},
|
|
1978
|
+
),
|
|
1979
|
+
);
|
|
1980
|
+
const human = topics.length
|
|
1981
|
+
? topics
|
|
1982
|
+
.map(
|
|
1983
|
+
(topic) =>
|
|
1984
|
+
`${topic.displayName}${topic.status === 'archived' ? ' [archived]' : ''}${topic.aliases?.length ? ` aliases: ${topic.aliases.join(', ')}` : ''}\n ${topic.id}`,
|
|
1985
|
+
)
|
|
1986
|
+
.join('\n')
|
|
1987
|
+
: 'No topics yet.';
|
|
1988
|
+
output(io, global.json, { topics }, human);
|
|
1989
|
+
},
|
|
1990
|
+
);
|
|
1927
1991
|
topicCommand
|
|
1928
1992
|
.command('show <id>')
|
|
1929
1993
|
.description('Show one topic, resolving aliases')
|
|
@@ -1937,7 +2001,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
1937
2001
|
`${topic.displayName}\n\nTopic ID: ${topic.id}\nStatus: ${topic.status}\nAliases: ${topic.aliases?.join(', ') ?? 'none'}`,
|
|
1938
2002
|
);
|
|
1939
2003
|
});
|
|
1940
|
-
|
|
1941
2004
|
topicCommand
|
|
1942
2005
|
.command('resolve <name>')
|
|
1943
2006
|
.description('Resolve a name or alias to one topic, or list the candidates')
|
|
@@ -1953,7 +2016,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
1953
2016
|
: `No topic answers to "${resolution.query}".`;
|
|
1954
2017
|
output(io, global.json, resolution, human);
|
|
1955
2018
|
});
|
|
1956
|
-
|
|
1957
2019
|
topicCommand
|
|
1958
2020
|
.command('rename <id> <display-name>')
|
|
1959
2021
|
.description("Change a topic's display name. Its ID never changes.")
|
|
@@ -1969,7 +2031,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
1969
2031
|
`Renamed to ${topic.displayName}\nTopic ID: ${topic.id} (unchanged)`,
|
|
1970
2032
|
);
|
|
1971
2033
|
});
|
|
1972
|
-
|
|
1973
2034
|
topicCommand
|
|
1974
2035
|
.command('archive <id>')
|
|
1975
2036
|
.description('Stop a topic being attached to new records, keeping the ones it already has')
|
|
@@ -1978,7 +2039,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
1978
2039
|
const topic = await withProfile(command, (service) => service.topics.archive(id));
|
|
1979
2040
|
output(io, global.json, topic, `Archived ${topic.displayName} (${topic.id})`);
|
|
1980
2041
|
});
|
|
1981
|
-
|
|
1982
2042
|
topicCommand
|
|
1983
2043
|
.command('restore <id>')
|
|
1984
2044
|
.description('Let an archived topic be attached to new records again')
|
|
@@ -1987,13 +2047,10 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
1987
2047
|
const topic = await withProfile(command, (service) => service.topics.restore(id));
|
|
1988
2048
|
output(io, global.json, topic, `Restored ${topic.displayName} (${topic.id})`);
|
|
1989
2049
|
});
|
|
1990
|
-
|
|
1991
2050
|
/* ------------------------------------------------------------ skills */
|
|
1992
|
-
|
|
1993
2051
|
const skillCommand = program
|
|
1994
2052
|
.command('skill')
|
|
1995
2053
|
.description('Install and maintain the packaged agent skill');
|
|
1996
|
-
|
|
1997
2054
|
skillCommand
|
|
1998
2055
|
.command('install')
|
|
1999
2056
|
.description('Plan or install the skill for detected agent runtimes')
|
|
@@ -2003,7 +2060,12 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
2003
2060
|
.option('--link', 'symlink to the packaged skill instead of copying it', false)
|
|
2004
2061
|
.action(
|
|
2005
2062
|
async (
|
|
2006
|
-
options: {
|
|
2063
|
+
options: {
|
|
2064
|
+
runtime: string[];
|
|
2065
|
+
yes: boolean;
|
|
2066
|
+
force: boolean;
|
|
2067
|
+
link: boolean;
|
|
2068
|
+
},
|
|
2007
2069
|
command: Command,
|
|
2008
2070
|
) => {
|
|
2009
2071
|
const global = globals(command);
|
|
@@ -2049,40 +2111,51 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
2049
2111
|
);
|
|
2050
2112
|
},
|
|
2051
2113
|
);
|
|
2052
|
-
|
|
2053
2114
|
skillCommand
|
|
2054
2115
|
.command('status')
|
|
2055
2116
|
.description('Show installed, stale, missing, or conflicting skill copies')
|
|
2056
2117
|
.option('--runtime <runtime>', `${skillRuntimeHelp} (repeatable)`, collect, [])
|
|
2057
|
-
.action(
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2118
|
+
.action(
|
|
2119
|
+
(
|
|
2120
|
+
options: {
|
|
2121
|
+
runtime: string[];
|
|
2122
|
+
},
|
|
2123
|
+
command: Command,
|
|
2124
|
+
) => {
|
|
2125
|
+
const global = globals(command);
|
|
2126
|
+
const result = skillStatus({
|
|
2127
|
+
runtimes: skillRuntimes(options.runtime),
|
|
2128
|
+
...(global.profile ? { profile: global.profile } : {}),
|
|
2129
|
+
});
|
|
2130
|
+
output(io, global.json, result, formatSkillResult(result, 'status'));
|
|
2131
|
+
},
|
|
2132
|
+
);
|
|
2066
2133
|
skillCommand
|
|
2067
2134
|
.command('uninstall')
|
|
2068
2135
|
.description('Plan or remove Synomem-owned skill installations')
|
|
2069
2136
|
.option('--runtime <runtime>', `${skillRuntimeHelp} (repeatable)`, collect, [])
|
|
2070
2137
|
.option('--yes', 'apply the displayed plan', false)
|
|
2071
2138
|
.option('--force', 'remove a conflicting synomem directory', false)
|
|
2072
|
-
.action(
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2139
|
+
.action(
|
|
2140
|
+
(
|
|
2141
|
+
options: {
|
|
2142
|
+
runtime: string[];
|
|
2143
|
+
yes: boolean;
|
|
2144
|
+
force: boolean;
|
|
2145
|
+
},
|
|
2146
|
+
command: Command,
|
|
2147
|
+
) => {
|
|
2148
|
+
const global = globals(command);
|
|
2149
|
+
const result = uninstallSkill({
|
|
2150
|
+
runtimes: skillRuntimes(options.runtime),
|
|
2151
|
+
apply: options.yes,
|
|
2152
|
+
force: options.force,
|
|
2153
|
+
});
|
|
2154
|
+
output(io, global.json, result, formatSkillResult(result, 'uninstall'));
|
|
2155
|
+
},
|
|
2156
|
+
);
|
|
2082
2157
|
/* ------------------------------------------------------------ posts */
|
|
2083
|
-
|
|
2084
2158
|
const postCommand = program.command('post').description('Publish to everyone in the workspace');
|
|
2085
|
-
|
|
2086
2159
|
postCommand
|
|
2087
2160
|
.command('create')
|
|
2088
2161
|
.description('Publish a post the whole workspace can read')
|
|
@@ -2090,10 +2163,14 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
2090
2163
|
.requiredOption('--body <body>')
|
|
2091
2164
|
.option('--tag <tag>', 'repeatable', collect, [])
|
|
2092
2165
|
.option('--topic <id>', 'topic ID (repeatable)', collect, [])
|
|
2093
|
-
.option('--reply-to <post-id>')
|
|
2094
2166
|
.action(
|
|
2095
2167
|
async (
|
|
2096
|
-
options: {
|
|
2168
|
+
options: {
|
|
2169
|
+
title: string;
|
|
2170
|
+
body: string;
|
|
2171
|
+
tag: string[];
|
|
2172
|
+
topic: string[];
|
|
2173
|
+
},
|
|
2097
2174
|
command: Command,
|
|
2098
2175
|
) => {
|
|
2099
2176
|
const global = globals(command);
|
|
@@ -2103,33 +2180,37 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
2103
2180
|
body: options.body,
|
|
2104
2181
|
...(options.tag.length ? { tags: options.tag } : {}),
|
|
2105
2182
|
...(options.topic.length ? { topicIds: options.topic } : {}),
|
|
2106
|
-
...(options.replyTo ? { replyTo: options.replyTo } : {}),
|
|
2107
2183
|
}),
|
|
2108
2184
|
);
|
|
2109
2185
|
output(io, global.json, result, `Published ${result.record.event.id}`);
|
|
2110
2186
|
},
|
|
2111
2187
|
);
|
|
2112
|
-
|
|
2113
2188
|
postCommand
|
|
2114
2189
|
.command('list')
|
|
2115
2190
|
.description('List posts in this workspace')
|
|
2116
2191
|
.option('--limit <n>', 'default 10, maximum 50')
|
|
2117
|
-
.action(
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2192
|
+
.action(
|
|
2193
|
+
async (
|
|
2194
|
+
options: {
|
|
2195
|
+
limit?: string;
|
|
2196
|
+
},
|
|
2197
|
+
command: Command,
|
|
2198
|
+
) => {
|
|
2199
|
+
const global = globals(command);
|
|
2200
|
+
const page = await withProfile(command, (service) =>
|
|
2201
|
+
service.posts.list(options.limit ? { limit: Number(options.limit) } : {}),
|
|
2202
|
+
);
|
|
2203
|
+
const human = page.items.length
|
|
2204
|
+
? page.items.map((item) => `${item.id} ${item.title}`).join('\n')
|
|
2205
|
+
: 'No posts yet.';
|
|
2206
|
+
output(io, global.json, page, human);
|
|
2207
|
+
},
|
|
2208
|
+
);
|
|
2209
|
+
postCommand
|
|
2210
|
+
.command('show <post-id>')
|
|
2211
|
+
.description('Show one post with its acknowledgements')
|
|
2212
|
+
.action(async (postId: string, _options, command: Command) => {
|
|
2213
|
+
const global = globals(command);
|
|
2133
2214
|
const record = await withProfile(command, (service) => service.posts.get(postId));
|
|
2134
2215
|
const acks = record.acknowledgments.length
|
|
2135
2216
|
? record.acknowledgments
|
|
@@ -2143,19 +2224,29 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
2143
2224
|
`${record.title}\n\n${record.body}\n\nAcknowledged by:\n${acks}`,
|
|
2144
2225
|
);
|
|
2145
2226
|
});
|
|
2146
|
-
|
|
2147
2227
|
postCommand
|
|
2148
2228
|
.command('acknowledge <post-id>')
|
|
2149
2229
|
.description('Say you have seen a post')
|
|
2150
2230
|
.option('--note <text>', 'optional context for the author')
|
|
2151
|
-
.action(
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2231
|
+
.action(
|
|
2232
|
+
async (
|
|
2233
|
+
postId: string,
|
|
2234
|
+
options: {
|
|
2235
|
+
note?: string;
|
|
2236
|
+
},
|
|
2237
|
+
command: Command,
|
|
2238
|
+
) => {
|
|
2239
|
+
const global = globals(command);
|
|
2240
|
+
const record = await withProfile(command, async (service) =>
|
|
2241
|
+
service.posts.acknowledge({
|
|
2242
|
+
expectedVersion: (await service.posts.get(postId)).lifecycleVersion!,
|
|
2243
|
+
postId,
|
|
2244
|
+
...(options.note ? { note: options.note } : {}),
|
|
2245
|
+
}),
|
|
2246
|
+
);
|
|
2247
|
+
output(io, global.json, record, `Acknowledged ${postId}`);
|
|
2248
|
+
},
|
|
2249
|
+
);
|
|
2159
2250
|
postCommand
|
|
2160
2251
|
.command('roster <post-id>')
|
|
2161
2252
|
.description('Who has acknowledged a post, and who has not')
|
|
@@ -2177,23 +2268,31 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
2177
2268
|
}
|
|
2178
2269
|
output(io, global.json, roster, lines.join('\n'));
|
|
2179
2270
|
});
|
|
2180
|
-
|
|
2181
2271
|
postCommand
|
|
2182
2272
|
.command('archive <post-id>')
|
|
2183
2273
|
.description('Archive a post you wrote')
|
|
2184
2274
|
.option('--reason <text>')
|
|
2185
|
-
.action(
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2275
|
+
.action(
|
|
2276
|
+
async (
|
|
2277
|
+
postId: string,
|
|
2278
|
+
options: {
|
|
2279
|
+
reason?: string;
|
|
2280
|
+
},
|
|
2281
|
+
command: Command,
|
|
2282
|
+
) => {
|
|
2283
|
+
const global = globals(command);
|
|
2284
|
+
const record = await withProfile(command, async (service) =>
|
|
2285
|
+
service.posts.archive({
|
|
2286
|
+
expectedVersion: (await service.posts.get(postId)).lifecycleVersion!,
|
|
2287
|
+
postId,
|
|
2288
|
+
...(options.reason ? { reason: options.reason } : {}),
|
|
2289
|
+
}),
|
|
2290
|
+
);
|
|
2291
|
+
output(io, global.json, record, `Archived ${postId}`);
|
|
2292
|
+
},
|
|
2293
|
+
);
|
|
2193
2294
|
/* ------------------------------------------------------------ kudos */
|
|
2194
|
-
|
|
2195
2295
|
const kudosCommand = program.command('kudos').description('Give and manage agent recognition');
|
|
2196
|
-
|
|
2197
2296
|
kudosCommand
|
|
2198
2297
|
.command('give <recipient>')
|
|
2199
2298
|
.description('Give specific, evidence-based kudos to an agent')
|
|
@@ -2221,7 +2320,7 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
2221
2320
|
const global = globals(command);
|
|
2222
2321
|
const result = await withProfile(command, (service) =>
|
|
2223
2322
|
service.kudos.give({
|
|
2224
|
-
|
|
2323
|
+
recipient: targetRef(recipient),
|
|
2225
2324
|
title: options.title,
|
|
2226
2325
|
reason: options.reason,
|
|
2227
2326
|
visibility: options.visibility,
|
|
@@ -2240,7 +2339,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
2240
2339
|
);
|
|
2241
2340
|
},
|
|
2242
2341
|
);
|
|
2243
|
-
|
|
2244
2342
|
addListOptions(kudosCommand.command('list').description('List and filter kudos')).action(
|
|
2245
2343
|
async (options: Record<string, string>, command: Command) => {
|
|
2246
2344
|
const global = globals(command);
|
|
@@ -2255,7 +2353,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
2255
2353
|
);
|
|
2256
2354
|
},
|
|
2257
2355
|
);
|
|
2258
|
-
|
|
2259
2356
|
kudosCommand
|
|
2260
2357
|
.command('show <kudos-id>')
|
|
2261
2358
|
.description('Show one kudos item and its current state')
|
|
@@ -2264,19 +2361,29 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
2264
2361
|
const record = await withProfile(command, (service) => service.kudos.get(id));
|
|
2265
2362
|
output(io, global.json, record, showRecord(record));
|
|
2266
2363
|
});
|
|
2267
|
-
|
|
2268
2364
|
kudosCommand
|
|
2269
2365
|
.command('acknowledge <kudos-id>')
|
|
2270
2366
|
.description('Record that you (the recipient) reviewed kudos')
|
|
2271
2367
|
.option('--note <text>')
|
|
2272
|
-
.action(
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2368
|
+
.action(
|
|
2369
|
+
async (
|
|
2370
|
+
id: string,
|
|
2371
|
+
options: {
|
|
2372
|
+
note?: string;
|
|
2373
|
+
},
|
|
2374
|
+
command: Command,
|
|
2375
|
+
) => {
|
|
2376
|
+
const global = globals(command);
|
|
2377
|
+
const record = await withProfile(command, async (service) =>
|
|
2378
|
+
service.kudos.acknowledge({
|
|
2379
|
+
expectedVersion: (await service.kudos.get(id)).lifecycleVersion!,
|
|
2380
|
+
kudosId: id,
|
|
2381
|
+
...(options.note ? { note: options.note } : {}),
|
|
2382
|
+
}),
|
|
2383
|
+
);
|
|
2384
|
+
output(io, global.json, record, `Acknowledged ${id}.`);
|
|
2385
|
+
},
|
|
2386
|
+
);
|
|
2280
2387
|
kudosCommand
|
|
2281
2388
|
.command('revoke <kudos-id>')
|
|
2282
2389
|
.description('Record a revocation while preserving history')
|
|
@@ -2285,12 +2392,16 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
2285
2392
|
.action(
|
|
2286
2393
|
async (
|
|
2287
2394
|
id: string,
|
|
2288
|
-
options: {
|
|
2395
|
+
options: {
|
|
2396
|
+
reason: string;
|
|
2397
|
+
administrative: boolean;
|
|
2398
|
+
},
|
|
2289
2399
|
command: Command,
|
|
2290
2400
|
) => {
|
|
2291
2401
|
const global = globals(command);
|
|
2292
|
-
const record = await withProfile(command, (service) =>
|
|
2402
|
+
const record = await withProfile(command, async (service) =>
|
|
2293
2403
|
service.kudos.revoke({
|
|
2404
|
+
expectedVersion: (await service.kudos.get(id)).lifecycleVersion!,
|
|
2294
2405
|
kudosId: id,
|
|
2295
2406
|
reason: options.reason,
|
|
2296
2407
|
administrative: options.administrative,
|
|
@@ -2299,7 +2410,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
2299
2410
|
output(io, global.json, record, `Revoked ${id}; the audit trail was preserved.`);
|
|
2300
2411
|
},
|
|
2301
2412
|
);
|
|
2302
|
-
|
|
2303
2413
|
kudosCommand
|
|
2304
2414
|
.command('wins [agent]')
|
|
2305
2415
|
.description('Print the generated WINS.md path or content (local stores)')
|
|
@@ -2308,7 +2418,10 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
2308
2418
|
.action(
|
|
2309
2419
|
async (
|
|
2310
2420
|
agentId: string | undefined,
|
|
2311
|
-
options: {
|
|
2421
|
+
options: {
|
|
2422
|
+
open: boolean;
|
|
2423
|
+
print: boolean;
|
|
2424
|
+
},
|
|
2312
2425
|
command: Command,
|
|
2313
2426
|
) => {
|
|
2314
2427
|
const global = globals(command);
|
|
@@ -2355,7 +2468,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
2355
2468
|
output(io, global.json, details, options.print ? details.content.trimEnd() : details.path);
|
|
2356
2469
|
},
|
|
2357
2470
|
);
|
|
2358
|
-
|
|
2359
2471
|
addListOptions(
|
|
2360
2472
|
kudosCommand.command('stats').description('Show aggregate kudos statistics'),
|
|
2361
2473
|
).action(async (options: Record<string, string>, command: Command) => {
|
|
@@ -2368,48 +2480,51 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
2368
2480
|
`Total: ${stats.total}\nActive: ${stats.active}\nAcknowledged: ${stats.acknowledged}\nRevoked: ${stats.revoked}`,
|
|
2369
2481
|
);
|
|
2370
2482
|
});
|
|
2371
|
-
|
|
2372
2483
|
/* ------------------------------------------------------------ cross-kind */
|
|
2373
|
-
|
|
2374
2484
|
program
|
|
2375
|
-
.command('inbox
|
|
2376
|
-
.description(
|
|
2377
|
-
.option('--limit <number>', '
|
|
2378
|
-
.option('--
|
|
2485
|
+
.command('inbox')
|
|
2486
|
+
.description('Read notifications belonging to the profile effective actor')
|
|
2487
|
+
.option('--limit <number>', 'Maximum results', '20')
|
|
2488
|
+
.option('--after <cursor>', 'Signed continuation cursor')
|
|
2489
|
+
.option('--view <view>', 'all, unread, action_required or default', 'default')
|
|
2379
2490
|
.action(
|
|
2380
2491
|
async (
|
|
2381
|
-
|
|
2382
|
-
|
|
2492
|
+
options: {
|
|
2493
|
+
limit: string;
|
|
2494
|
+
after?: string;
|
|
2495
|
+
view: HpNotificationInput['view'];
|
|
2496
|
+
},
|
|
2383
2497
|
command: Command,
|
|
2384
2498
|
) => {
|
|
2385
|
-
const
|
|
2386
|
-
|
|
2387
|
-
const recipient =
|
|
2388
|
-
agentId ?? (context.actor.kind === 'agent' ? context.actor.id : undefined);
|
|
2389
|
-
if (!recipient) {
|
|
2390
|
-
throw new SynomemError(
|
|
2391
|
-
'INVALID_INPUT',
|
|
2392
|
-
'This profile is not an agent; name the agent whose inbox to show.',
|
|
2393
|
-
);
|
|
2394
|
-
}
|
|
2395
|
-
return service.items.list({
|
|
2396
|
-
participantAgentId: recipient,
|
|
2397
|
-
pending: true,
|
|
2499
|
+
const page = await withProfile(command, (service) =>
|
|
2500
|
+
service.notifications.list({
|
|
2398
2501
|
limit: Number(options.limit),
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2502
|
+
view: options.view,
|
|
2503
|
+
...(options.after ? { after: options.after } : {}),
|
|
2504
|
+
}),
|
|
2505
|
+
);
|
|
2402
2506
|
output(
|
|
2403
2507
|
io,
|
|
2404
|
-
|
|
2508
|
+
globals(command).json,
|
|
2405
2509
|
page,
|
|
2406
2510
|
page.items.length
|
|
2407
|
-
?
|
|
2511
|
+
? page.items
|
|
2512
|
+
.map(
|
|
2513
|
+
(item) =>
|
|
2514
|
+
`${item.read ? 'Read' : 'Unread'} ${item.root?.kind ?? 'record'}: ${item.root?.title ?? item.rootId} [${item.id}]`,
|
|
2515
|
+
)
|
|
2516
|
+
.join('\n')
|
|
2408
2517
|
: 'Inbox is clear.',
|
|
2409
2518
|
);
|
|
2410
2519
|
},
|
|
2411
2520
|
);
|
|
2412
|
-
|
|
2521
|
+
program
|
|
2522
|
+
.command('notification-read <notification-id>')
|
|
2523
|
+
.description('Mark your notification read without performing a lifecycle action')
|
|
2524
|
+
.action(async (id: string, _options: object, command: Command) => {
|
|
2525
|
+
await withProfile(command, (service) => service.notifications.read(id));
|
|
2526
|
+
output(io, globals(command).json, { updated: true }, 'Notification marked read.');
|
|
2527
|
+
});
|
|
2413
2528
|
program
|
|
2414
2529
|
.command('list')
|
|
2415
2530
|
.description('List compact summaries across all record types')
|
|
@@ -2437,7 +2552,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
2437
2552
|
: 'No items found.',
|
|
2438
2553
|
);
|
|
2439
2554
|
});
|
|
2440
|
-
|
|
2441
2555
|
program
|
|
2442
2556
|
.command('changes')
|
|
2443
2557
|
.description('List compact changes across all record types after an opaque watermark')
|
|
@@ -2445,7 +2559,14 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
2445
2559
|
.option('--after <watermark>', 'watermark or change cursor from a previous response')
|
|
2446
2560
|
.option('--limit <number>', 'maximum changes (default 20, maximum 100)', '20')
|
|
2447
2561
|
.action(
|
|
2448
|
-
async (
|
|
2562
|
+
async (
|
|
2563
|
+
options: {
|
|
2564
|
+
after?: string;
|
|
2565
|
+
limit: string;
|
|
2566
|
+
kind: string[];
|
|
2567
|
+
},
|
|
2568
|
+
command: Command,
|
|
2569
|
+
) => {
|
|
2449
2570
|
const global = globals(command);
|
|
2450
2571
|
const page = await withProfile(command, (service) =>
|
|
2451
2572
|
service.items.changes({
|
|
@@ -2465,11 +2586,8 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
2465
2586
|
output(io, global.json, page, human);
|
|
2466
2587
|
},
|
|
2467
2588
|
);
|
|
2468
|
-
|
|
2469
2589
|
/* ------------------------------------------------------------ memos */
|
|
2470
|
-
|
|
2471
2590
|
const memoCommand = program.command('memo').description('Send and manage durable messages');
|
|
2472
|
-
|
|
2473
2591
|
memoCommand
|
|
2474
2592
|
.command('send <recipient>')
|
|
2475
2593
|
.description('Send a durable one-to-one memo')
|
|
@@ -2495,7 +2613,7 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
2495
2613
|
const global = globals(command);
|
|
2496
2614
|
const result = await withProfile(command, (service) =>
|
|
2497
2615
|
service.memos.send({
|
|
2498
|
-
|
|
2616
|
+
recipient: targetRef(recipient),
|
|
2499
2617
|
subject: options.subject,
|
|
2500
2618
|
body: options.body,
|
|
2501
2619
|
visibility: options.visibility,
|
|
@@ -2512,7 +2630,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
2512
2630
|
);
|
|
2513
2631
|
},
|
|
2514
2632
|
);
|
|
2515
|
-
|
|
2516
2633
|
memoCommand
|
|
2517
2634
|
.command('list')
|
|
2518
2635
|
.description('List memos')
|
|
@@ -2522,13 +2639,18 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
2522
2639
|
.option('--cursor <cursor>')
|
|
2523
2640
|
.action(
|
|
2524
2641
|
async (
|
|
2525
|
-
options: {
|
|
2642
|
+
options: {
|
|
2643
|
+
participant?: string;
|
|
2644
|
+
status?: string;
|
|
2645
|
+
limit: string;
|
|
2646
|
+
cursor?: string;
|
|
2647
|
+
},
|
|
2526
2648
|
command: Command,
|
|
2527
2649
|
) => {
|
|
2528
2650
|
const global = globals(command);
|
|
2529
2651
|
const page = await withProfile(command, (service) =>
|
|
2530
2652
|
service.memos.list({
|
|
2531
|
-
...(options.participant ? {
|
|
2653
|
+
...(options.participant ? { participant: targetRef(options.participant) } : {}),
|
|
2532
2654
|
...(options.status ? { status: options.status } : {}),
|
|
2533
2655
|
limit: Number(options.limit),
|
|
2534
2656
|
...(options.cursor ? { cursor: options.cursor } : {}),
|
|
@@ -2537,7 +2659,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
2537
2659
|
output(io, global.json, page, page.items.map(lineForItem).join('\n') || 'No memos found.');
|
|
2538
2660
|
},
|
|
2539
2661
|
);
|
|
2540
|
-
|
|
2541
2662
|
memoCommand
|
|
2542
2663
|
.command('show <memo-id>')
|
|
2543
2664
|
.description('Show one memo')
|
|
@@ -2551,7 +2672,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
2551
2672
|
`${record.event.subject}\nID: ${record.event.id}\nStatus: ${record.status}\n\n${record.event.body}`,
|
|
2552
2673
|
);
|
|
2553
2674
|
});
|
|
2554
|
-
|
|
2555
2675
|
for (const operation of ['read', 'archive'] as const) {
|
|
2556
2676
|
memoCommand
|
|
2557
2677
|
.command(`${operation} <memo-id>`)
|
|
@@ -2561,24 +2681,30 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
2561
2681
|
: 'Archive a memo addressed to you',
|
|
2562
2682
|
)
|
|
2563
2683
|
.option('--idempotency-key <key>')
|
|
2564
|
-
.action(
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
)
|
|
2572
|
-
|
|
2573
|
-
|
|
2684
|
+
.action(
|
|
2685
|
+
async (
|
|
2686
|
+
id: string,
|
|
2687
|
+
options: {
|
|
2688
|
+
idempotencyKey?: string;
|
|
2689
|
+
},
|
|
2690
|
+
command: Command,
|
|
2691
|
+
) => {
|
|
2692
|
+
const global = globals(command);
|
|
2693
|
+
const record = await withProfile(command, async (service) =>
|
|
2694
|
+
service.memos[operation]({
|
|
2695
|
+
expectedVersion: (await service.memos.get(id)).lifecycleVersion!,
|
|
2696
|
+
memoId: id,
|
|
2697
|
+
...(options.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : {}),
|
|
2698
|
+
}),
|
|
2699
|
+
);
|
|
2700
|
+
output(io, global.json, record, `Memo ${id} is ${record.status}.`);
|
|
2701
|
+
},
|
|
2702
|
+
);
|
|
2574
2703
|
}
|
|
2575
|
-
|
|
2576
2704
|
/* ------------------------------------------------------------ notes */
|
|
2577
|
-
|
|
2578
2705
|
const noteCommand = program
|
|
2579
2706
|
.command('note')
|
|
2580
2707
|
.description('Retain and revise agent-owned knowledge');
|
|
2581
|
-
|
|
2582
2708
|
noteCommand
|
|
2583
2709
|
.command('create')
|
|
2584
2710
|
.description('Create an owner-private note')
|
|
@@ -2603,7 +2729,7 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
2603
2729
|
const global = globals(command);
|
|
2604
2730
|
const result = await withProfile(command, (service) =>
|
|
2605
2731
|
service.notes.create({
|
|
2606
|
-
...(options.owner ? {
|
|
2732
|
+
...(options.owner ? { owner: targetRef(options.owner) } : {}),
|
|
2607
2733
|
title: options.title,
|
|
2608
2734
|
body: options.body,
|
|
2609
2735
|
...(options.tag.length ? { tags: options.tag } : {}),
|
|
@@ -2619,7 +2745,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
2619
2745
|
);
|
|
2620
2746
|
},
|
|
2621
2747
|
);
|
|
2622
|
-
|
|
2623
2748
|
noteCommand
|
|
2624
2749
|
.command('list')
|
|
2625
2750
|
.description('List notes')
|
|
@@ -2627,11 +2752,18 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
2627
2752
|
.option('--status <status>')
|
|
2628
2753
|
.option('--limit <number>', 'maximum results', '10')
|
|
2629
2754
|
.action(
|
|
2630
|
-
async (
|
|
2755
|
+
async (
|
|
2756
|
+
options: {
|
|
2757
|
+
owner?: string;
|
|
2758
|
+
status?: string;
|
|
2759
|
+
limit: string;
|
|
2760
|
+
},
|
|
2761
|
+
command: Command,
|
|
2762
|
+
) => {
|
|
2631
2763
|
const global = globals(command);
|
|
2632
2764
|
const page = await withProfile(command, (service) =>
|
|
2633
2765
|
service.notes.list({
|
|
2634
|
-
...(options.owner ? {
|
|
2766
|
+
...(options.owner ? { participant: targetRef(options.owner) } : {}),
|
|
2635
2767
|
...(options.status ? { status: options.status } : {}),
|
|
2636
2768
|
limit: Number(options.limit),
|
|
2637
2769
|
}),
|
|
@@ -2639,7 +2771,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
2639
2771
|
output(io, global.json, page, page.items.map(lineForItem).join('\n') || 'No notes found.');
|
|
2640
2772
|
},
|
|
2641
2773
|
);
|
|
2642
|
-
|
|
2643
2774
|
noteCommand
|
|
2644
2775
|
.command('show <note-id>')
|
|
2645
2776
|
.description('Show one note')
|
|
@@ -2653,7 +2784,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
2653
2784
|
`${record.current.title}\nID: ${record.event.id}\nVersion: ${record.current.version}\n\n${record.current.body}`,
|
|
2654
2785
|
);
|
|
2655
2786
|
});
|
|
2656
|
-
|
|
2657
2787
|
noteCommand
|
|
2658
2788
|
.command('revise <note-id>')
|
|
2659
2789
|
.description('Revise a note (optimistic concurrency)')
|
|
@@ -2688,28 +2818,33 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
2688
2818
|
output(io, global.json, record, `Revised note ${id} to version ${record.current.version}.`);
|
|
2689
2819
|
},
|
|
2690
2820
|
);
|
|
2691
|
-
|
|
2692
2821
|
noteCommand
|
|
2693
2822
|
.command('archive <note-id>')
|
|
2694
2823
|
.description('Archive a note')
|
|
2695
2824
|
.option('--idempotency-key <key>')
|
|
2696
|
-
.action(
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
)
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2825
|
+
.action(
|
|
2826
|
+
async (
|
|
2827
|
+
id: string,
|
|
2828
|
+
options: {
|
|
2829
|
+
idempotencyKey?: string;
|
|
2830
|
+
},
|
|
2831
|
+
command: Command,
|
|
2832
|
+
) => {
|
|
2833
|
+
const global = globals(command);
|
|
2834
|
+
const record = await withProfile(command, async (service) =>
|
|
2835
|
+
service.notes.archive({
|
|
2836
|
+
expectedVersion: (await service.notes.get(id)).lifecycleVersion!,
|
|
2837
|
+
noteId: id,
|
|
2838
|
+
...(options.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : {}),
|
|
2839
|
+
}),
|
|
2840
|
+
);
|
|
2841
|
+
output(io, global.json, record, `Archived note ${id}.`);
|
|
2842
|
+
},
|
|
2843
|
+
);
|
|
2707
2844
|
/* ------------------------------------------------------------ todos */
|
|
2708
|
-
|
|
2709
2845
|
const todoCommand = program
|
|
2710
2846
|
.command('todo')
|
|
2711
2847
|
.description('Create and manage your own private reminders');
|
|
2712
|
-
|
|
2713
2848
|
todoCommand
|
|
2714
2849
|
.command('create')
|
|
2715
2850
|
.description('Create a private todo')
|
|
@@ -2757,32 +2892,38 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
2757
2892
|
);
|
|
2758
2893
|
},
|
|
2759
2894
|
);
|
|
2760
|
-
|
|
2761
2895
|
todoCommand
|
|
2762
2896
|
.command('list')
|
|
2763
2897
|
.description('List your todos')
|
|
2764
2898
|
.option('--status <status>')
|
|
2765
2899
|
.option('--limit <number>', 'maximum results', '10')
|
|
2766
|
-
.action(
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
)
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2900
|
+
.action(
|
|
2901
|
+
async (
|
|
2902
|
+
options: {
|
|
2903
|
+
status?: string;
|
|
2904
|
+
limit: string;
|
|
2905
|
+
},
|
|
2906
|
+
command: Command,
|
|
2907
|
+
) => {
|
|
2908
|
+
const global = globals(command);
|
|
2909
|
+
const page = await withProfile(command, (service) =>
|
|
2910
|
+
service.todos.list({
|
|
2911
|
+
...(options.status ? { status: options.status } : {}),
|
|
2912
|
+
limit: Number(options.limit),
|
|
2913
|
+
}),
|
|
2914
|
+
);
|
|
2915
|
+
output(
|
|
2916
|
+
io,
|
|
2917
|
+
global.json,
|
|
2918
|
+
page,
|
|
2919
|
+
page.items.length
|
|
2920
|
+
? page.items
|
|
2921
|
+
.map((item) => `${item.id} ${item.status.padEnd(9)} ${item.title}`)
|
|
2922
|
+
.join('\n')
|
|
2923
|
+
: 'No todos.',
|
|
2924
|
+
);
|
|
2925
|
+
},
|
|
2926
|
+
);
|
|
2786
2927
|
todoCommand
|
|
2787
2928
|
.command('show <todo-id>')
|
|
2788
2929
|
.description('Show one of your todos')
|
|
@@ -2796,7 +2937,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
2796
2937
|
`${record.current.title}\nStatus: ${record.status}\nPriority: ${record.current.priority}\nVersion: ${record.current.version}${record.current.details ? `\n\n${record.current.details}` : ''}`,
|
|
2797
2938
|
);
|
|
2798
2939
|
});
|
|
2799
|
-
|
|
2800
2940
|
for (const operation of ['complete', 'reopen', 'cancel', 'archive'] as const) {
|
|
2801
2941
|
todoCommand
|
|
2802
2942
|
.command(`${operation} <todo-id>`)
|
|
@@ -2807,37 +2947,48 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
2807
2947
|
.action(
|
|
2808
2948
|
async (
|
|
2809
2949
|
id: string,
|
|
2810
|
-
options: {
|
|
2950
|
+
options: {
|
|
2951
|
+
note?: string;
|
|
2952
|
+
reason?: string;
|
|
2953
|
+
idempotencyKey?: string;
|
|
2954
|
+
},
|
|
2811
2955
|
command: Command,
|
|
2812
2956
|
) => {
|
|
2813
2957
|
const global = globals(command);
|
|
2814
2958
|
const key = options.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : {};
|
|
2815
|
-
const record = await withProfile(command, (service) =>
|
|
2959
|
+
const record = await withProfile(command, async (service) =>
|
|
2816
2960
|
operation === 'complete'
|
|
2817
2961
|
? service.todos.complete({
|
|
2962
|
+
expectedVersion: (await service.todos.get(id)).lifecycleVersion!,
|
|
2818
2963
|
todoId: id,
|
|
2819
2964
|
...(options.note ? { note: options.note } : {}),
|
|
2820
2965
|
...key,
|
|
2821
2966
|
})
|
|
2822
2967
|
: operation === 'cancel'
|
|
2823
2968
|
? service.todos.cancel({
|
|
2969
|
+
expectedVersion: (await service.todos.get(id)).lifecycleVersion!,
|
|
2824
2970
|
todoId: id,
|
|
2825
2971
|
...(options.reason ? { reason: options.reason } : {}),
|
|
2826
2972
|
...key,
|
|
2827
2973
|
})
|
|
2828
2974
|
: operation === 'archive'
|
|
2829
|
-
? service.todos.archive({
|
|
2830
|
-
|
|
2975
|
+
? service.todos.archive({
|
|
2976
|
+
expectedVersion: (await service.todos.get(id)).lifecycleVersion!,
|
|
2977
|
+
todoId: id,
|
|
2978
|
+
...key,
|
|
2979
|
+
})
|
|
2980
|
+
: service.todos.reopen({
|
|
2981
|
+
expectedVersion: (await service.todos.get(id)).lifecycleVersion!,
|
|
2982
|
+
todoId: id,
|
|
2983
|
+
...key,
|
|
2984
|
+
}),
|
|
2831
2985
|
);
|
|
2832
2986
|
output(io, global.json, record, `Todo ${id} is now ${record.status}.`);
|
|
2833
2987
|
},
|
|
2834
2988
|
);
|
|
2835
2989
|
}
|
|
2836
|
-
|
|
2837
2990
|
/* ------------------------------------------------------------ tasks */
|
|
2838
|
-
|
|
2839
2991
|
const taskCommand = program.command('task').description('Create and manage agent tasks');
|
|
2840
|
-
|
|
2841
2992
|
taskCommand
|
|
2842
2993
|
.command('create <assignee>')
|
|
2843
2994
|
.description('Assign a task to an agent')
|
|
@@ -2871,7 +3022,7 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
2871
3022
|
const global = globals(command);
|
|
2872
3023
|
const result = await withProfile(command, (service) =>
|
|
2873
3024
|
service.tasks.create({
|
|
2874
|
-
|
|
3025
|
+
assignee: targetRef(assignee),
|
|
2875
3026
|
title: options.title,
|
|
2876
3027
|
...(options.description ? { description: options.description } : {}),
|
|
2877
3028
|
priority: Number(options.priority) as 1 | 2 | 3 | 4,
|
|
@@ -2890,7 +3041,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
2890
3041
|
);
|
|
2891
3042
|
},
|
|
2892
3043
|
);
|
|
2893
|
-
|
|
2894
3044
|
taskCommand
|
|
2895
3045
|
.command('list')
|
|
2896
3046
|
.description('List tasks')
|
|
@@ -2898,11 +3048,18 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
2898
3048
|
.option('--status <status>')
|
|
2899
3049
|
.option('--limit <number>', 'maximum results', '10')
|
|
2900
3050
|
.action(
|
|
2901
|
-
async (
|
|
3051
|
+
async (
|
|
3052
|
+
options: {
|
|
3053
|
+
assignee?: string;
|
|
3054
|
+
status?: string;
|
|
3055
|
+
limit: string;
|
|
3056
|
+
},
|
|
3057
|
+
command: Command,
|
|
3058
|
+
) => {
|
|
2902
3059
|
const global = globals(command);
|
|
2903
3060
|
const page = await withProfile(command, (service) =>
|
|
2904
3061
|
service.tasks.list({
|
|
2905
|
-
...(options.assignee ? {
|
|
3062
|
+
...(options.assignee ? { participant: targetRef(options.assignee) } : {}),
|
|
2906
3063
|
...(options.status ? { status: options.status } : {}),
|
|
2907
3064
|
limit: Number(options.limit),
|
|
2908
3065
|
}),
|
|
@@ -2910,7 +3067,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
2910
3067
|
output(io, global.json, page, page.items.map(lineForItem).join('\n') || 'No tasks found.');
|
|
2911
3068
|
},
|
|
2912
3069
|
);
|
|
2913
|
-
|
|
2914
3070
|
taskCommand
|
|
2915
3071
|
.command('show <task-id>')
|
|
2916
3072
|
.description('Show one task')
|
|
@@ -2924,7 +3080,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
2924
3080
|
`${record.current.title}\nID: ${record.event.id}\nStatus: ${record.status}\nVersion: ${record.current.version}`,
|
|
2925
3081
|
);
|
|
2926
3082
|
});
|
|
2927
|
-
|
|
2928
3083
|
taskCommand
|
|
2929
3084
|
.command('update <task-id>')
|
|
2930
3085
|
.description('Update a task (optimistic concurrency)')
|
|
@@ -2972,7 +3127,35 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
2972
3127
|
output(io, global.json, record, `Updated task ${id} to version ${record.current.version}.`);
|
|
2973
3128
|
},
|
|
2974
3129
|
);
|
|
2975
|
-
|
|
3130
|
+
taskCommand
|
|
3131
|
+
.command('override <task-id>')
|
|
3132
|
+
.requiredOption('--decision <status>', 'open or rejected')
|
|
3133
|
+
.requiredOption('--reason <text>')
|
|
3134
|
+
.requiredOption('--expected-version <number>')
|
|
3135
|
+
.option('--idempotency-key <key>')
|
|
3136
|
+
.action(
|
|
3137
|
+
async (
|
|
3138
|
+
id: string,
|
|
3139
|
+
options: {
|
|
3140
|
+
decision: 'open' | 'rejected';
|
|
3141
|
+
reason: string;
|
|
3142
|
+
expectedVersion: string;
|
|
3143
|
+
idempotencyKey?: string;
|
|
3144
|
+
},
|
|
3145
|
+
command: Command,
|
|
3146
|
+
) => {
|
|
3147
|
+
const record = await withProfile(command, (service) =>
|
|
3148
|
+
service.tasks.overrideDecision({
|
|
3149
|
+
taskId: id,
|
|
3150
|
+
nextStatus: options.decision,
|
|
3151
|
+
reason: options.reason,
|
|
3152
|
+
expectedVersion: Number(options.expectedVersion),
|
|
3153
|
+
...(options.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : {}),
|
|
3154
|
+
}),
|
|
3155
|
+
);
|
|
3156
|
+
output(io, globals(command).json, record, `Task decision overridden: ${record.status}.`);
|
|
3157
|
+
},
|
|
3158
|
+
);
|
|
2976
3159
|
for (const operation of ['accept', 'reject', 'complete', 'reopen', 'cancel'] as const) {
|
|
2977
3160
|
const command_ = taskCommand
|
|
2978
3161
|
.command(`${operation} <task-id>`)
|
|
@@ -2989,49 +3172,251 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
2989
3172
|
command_.action(
|
|
2990
3173
|
async (
|
|
2991
3174
|
id: string,
|
|
2992
|
-
options: {
|
|
3175
|
+
options: {
|
|
3176
|
+
note?: string;
|
|
3177
|
+
reason?: string;
|
|
3178
|
+
response?: string;
|
|
3179
|
+
idempotencyKey?: string;
|
|
3180
|
+
},
|
|
2993
3181
|
command: Command,
|
|
2994
3182
|
) => {
|
|
2995
3183
|
const global = globals(command);
|
|
2996
3184
|
const key = options.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : {};
|
|
2997
|
-
const record = await withProfile(command, (service) =>
|
|
3185
|
+
const record = await withProfile(command, async (service) =>
|
|
2998
3186
|
operation === 'accept'
|
|
2999
3187
|
? service.tasks.accept({
|
|
3188
|
+
expectedVersion: (await service.tasks.get(id)).lifecycleVersion!,
|
|
3000
3189
|
taskId: id,
|
|
3001
3190
|
...(options.response ? { response: options.response } : {}),
|
|
3002
3191
|
...key,
|
|
3003
3192
|
})
|
|
3004
3193
|
: operation === 'reject'
|
|
3005
3194
|
? service.tasks.reject({
|
|
3195
|
+
expectedVersion: (await service.tasks.get(id)).lifecycleVersion!,
|
|
3006
3196
|
taskId: id,
|
|
3007
3197
|
response: options.response ?? options.reason ?? '',
|
|
3008
3198
|
...key,
|
|
3009
3199
|
})
|
|
3010
3200
|
: operation === 'complete'
|
|
3011
3201
|
? service.tasks.complete({
|
|
3202
|
+
expectedVersion: (await service.tasks.get(id)).lifecycleVersion!,
|
|
3012
3203
|
taskId: id,
|
|
3013
3204
|
...(options.note ? { note: options.note } : {}),
|
|
3014
3205
|
...key,
|
|
3015
3206
|
})
|
|
3016
3207
|
: operation === 'cancel'
|
|
3017
3208
|
? service.tasks.cancel({
|
|
3209
|
+
expectedVersion: (await service.tasks.get(id)).lifecycleVersion!,
|
|
3018
3210
|
taskId: id,
|
|
3019
3211
|
...(options.reason ? { reason: options.reason } : {}),
|
|
3020
3212
|
...key,
|
|
3021
3213
|
})
|
|
3022
|
-
: service.tasks.reopen({
|
|
3214
|
+
: service.tasks.reopen({
|
|
3215
|
+
expectedVersion: (await service.tasks.get(id)).lifecycleVersion!,
|
|
3216
|
+
taskId: id,
|
|
3217
|
+
...key,
|
|
3218
|
+
}),
|
|
3023
3219
|
);
|
|
3024
3220
|
output(io, global.json, record, `Task ${id} is ${record.status}.`);
|
|
3025
3221
|
},
|
|
3026
3222
|
);
|
|
3027
3223
|
}
|
|
3028
|
-
|
|
3224
|
+
program
|
|
3225
|
+
.command('search <query>')
|
|
3226
|
+
.option('--kind <kind>')
|
|
3227
|
+
.option('--after <cursor>')
|
|
3228
|
+
.option('--limit <number>')
|
|
3229
|
+
.action(
|
|
3230
|
+
async (
|
|
3231
|
+
q: string,
|
|
3232
|
+
options: { kind?: string; after?: string; limit?: string },
|
|
3233
|
+
command: Command,
|
|
3234
|
+
) => {
|
|
3235
|
+
const page = await withProfile(command, (service) =>
|
|
3236
|
+
service.search({
|
|
3237
|
+
q,
|
|
3238
|
+
...(options.kind ? { kinds: [options.kind as HpRecordKind] } : {}),
|
|
3239
|
+
...(options.after ? { after: options.after } : {}),
|
|
3240
|
+
...(options.limit ? { limit: Number(options.limit) } : {}),
|
|
3241
|
+
}),
|
|
3242
|
+
);
|
|
3243
|
+
output(
|
|
3244
|
+
io,
|
|
3245
|
+
globals(command).json,
|
|
3246
|
+
page,
|
|
3247
|
+
page.items
|
|
3248
|
+
.map(
|
|
3249
|
+
(hit) => `${hit.kind}: ${hit.title} [${hit.rootId}]
|
|
3250
|
+
${hit.snippet}`,
|
|
3251
|
+
)
|
|
3252
|
+
.join('\n') || 'No visible results.',
|
|
3253
|
+
);
|
|
3254
|
+
},
|
|
3255
|
+
);
|
|
3256
|
+
const bookmarkCommand = program.command('bookmark').description('Your own saved records');
|
|
3257
|
+
bookmarkCommand
|
|
3258
|
+
.command('list')
|
|
3259
|
+
.option('--after <cursor>')
|
|
3260
|
+
.option('--limit <number>')
|
|
3261
|
+
.action(async (options: { after?: string; limit?: string }, command: Command) => {
|
|
3262
|
+
const page = await withProfile(command, (service) =>
|
|
3263
|
+
service.bookmarks.list({
|
|
3264
|
+
...options,
|
|
3265
|
+
...(options.limit ? { limit: Number(options.limit) } : {}),
|
|
3266
|
+
} as HpBookmarkInput),
|
|
3267
|
+
);
|
|
3268
|
+
output(
|
|
3269
|
+
io,
|
|
3270
|
+
globals(command).json,
|
|
3271
|
+
page,
|
|
3272
|
+
page.items
|
|
3273
|
+
.map((entry) => `${entry.root.kind}: ${entry.root.title} [${entry.root.id}]`)
|
|
3274
|
+
.join('\n') || 'No visible bookmarks.',
|
|
3275
|
+
);
|
|
3276
|
+
});
|
|
3277
|
+
for (const operation of ['set', 'remove'] as const)
|
|
3278
|
+
bookmarkCommand
|
|
3279
|
+
.command(`${operation} <root-id>`)
|
|
3280
|
+
.action(async (rootId: string, _options: object, command: Command) => {
|
|
3281
|
+
const result = await withProfile(command, (service) =>
|
|
3282
|
+
service.bookmarks.set({ rootId, present: operation === 'set' }),
|
|
3283
|
+
);
|
|
3284
|
+
output(
|
|
3285
|
+
io,
|
|
3286
|
+
globals(command).json,
|
|
3287
|
+
result,
|
|
3288
|
+
operation === 'set' ? 'Bookmark saved.' : 'Bookmark removed.',
|
|
3289
|
+
);
|
|
3290
|
+
});
|
|
3291
|
+
const replyCommand = program
|
|
3292
|
+
.command('reply')
|
|
3293
|
+
.description('Discuss any visible record without changing its lifecycle');
|
|
3294
|
+
replyCommand
|
|
3295
|
+
.command('create <root-id>')
|
|
3296
|
+
.requiredOption('--body <text>', 'Immutable reply body')
|
|
3297
|
+
.option('--parent <reply-id>', 'Reply to a parent in this thread')
|
|
3298
|
+
.option(
|
|
3299
|
+
'--mention <actor>',
|
|
3300
|
+
'Mention a typed human/agent',
|
|
3301
|
+
(value: string, previous: string[]) => [...previous, value],
|
|
3302
|
+
[],
|
|
3303
|
+
)
|
|
3304
|
+
.option('--idempotency-key <key>', 'Stable retry key')
|
|
3305
|
+
.action(
|
|
3306
|
+
async (
|
|
3307
|
+
rootId: string,
|
|
3308
|
+
options: { body: string; parent?: string; mention: string[]; idempotencyKey?: string },
|
|
3309
|
+
command: Command,
|
|
3310
|
+
) => {
|
|
3311
|
+
const result = await withProfile(command, (service) =>
|
|
3312
|
+
service.replies.create({
|
|
3313
|
+
rootId,
|
|
3314
|
+
body: options.body,
|
|
3315
|
+
...(options.parent ? { parentId: options.parent } : {}),
|
|
3316
|
+
mentions: options.mention.map(targetRef),
|
|
3317
|
+
...(options.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : {}),
|
|
3318
|
+
}),
|
|
3319
|
+
);
|
|
3320
|
+
output(
|
|
3321
|
+
io,
|
|
3322
|
+
globals(command).json,
|
|
3323
|
+
result,
|
|
3324
|
+
`Reply ${result.id} created by ${result.author.kind}:${result.author.id}.`,
|
|
3325
|
+
);
|
|
3326
|
+
},
|
|
3327
|
+
);
|
|
3328
|
+
replyCommand
|
|
3329
|
+
.command('show <reply-id>')
|
|
3330
|
+
.action(async (id: string, _options: object, command: Command) => {
|
|
3331
|
+
const result = await withProfile(command, (service) => service.replies.get(id));
|
|
3332
|
+
output(
|
|
3333
|
+
io,
|
|
3334
|
+
globals(command).json,
|
|
3335
|
+
result,
|
|
3336
|
+
result.deleted ? 'Deleted reply.' : (result.body ?? ''),
|
|
3337
|
+
);
|
|
3338
|
+
});
|
|
3339
|
+
replyCommand
|
|
3340
|
+
.command('delete <reply-id>')
|
|
3341
|
+
.requiredOption('--expected-version <number>', 'Current reply version')
|
|
3342
|
+
.option('--reason <text>', 'Moderation reason')
|
|
3343
|
+
.option('--idempotency-key <key>', 'Stable retry key')
|
|
3344
|
+
.action(
|
|
3345
|
+
async (
|
|
3346
|
+
replyId: string,
|
|
3347
|
+
options: { expectedVersion: string; reason?: string; idempotencyKey?: string },
|
|
3348
|
+
command: Command,
|
|
3349
|
+
) => {
|
|
3350
|
+
const result = await withProfile(command, (service) =>
|
|
3351
|
+
service.replies.delete({
|
|
3352
|
+
replyId,
|
|
3353
|
+
expectedVersion: Number(options.expectedVersion),
|
|
3354
|
+
...(options.reason ? { reason: options.reason } : {}),
|
|
3355
|
+
...(options.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : {}),
|
|
3356
|
+
}),
|
|
3357
|
+
);
|
|
3358
|
+
output(io, globals(command).json, result, 'Visible reply removed; audit history remains.');
|
|
3359
|
+
},
|
|
3360
|
+
);
|
|
3361
|
+
const threadCommand = program.command('thread').description('Read and follow record timelines');
|
|
3362
|
+
threadCommand
|
|
3363
|
+
.command('show <root-id>')
|
|
3364
|
+
.option('--after <cursor>', 'Continue a signed snapshot')
|
|
3365
|
+
.option('--limit <number>', 'Page size')
|
|
3366
|
+
.action(
|
|
3367
|
+
async (rootId: string, options: { after?: string; limit?: string }, command: Command) => {
|
|
3368
|
+
const result = await withProfile(command, (service) =>
|
|
3369
|
+
service.threads.get({
|
|
3370
|
+
rootId,
|
|
3371
|
+
...(options.after ? { after: options.after } : {}),
|
|
3372
|
+
...(options.limit ? { limit: Number(options.limit) } : {}),
|
|
3373
|
+
}),
|
|
3374
|
+
);
|
|
3375
|
+
output(io, globals(command).json, result, JSON.stringify(result, null, 2));
|
|
3376
|
+
},
|
|
3377
|
+
);
|
|
3378
|
+
threadCommand
|
|
3379
|
+
.command('subscription <root-id>')
|
|
3380
|
+
.option('--follow', 'Follow new replies')
|
|
3381
|
+
.option('--mute', 'Mute notifications')
|
|
3382
|
+
.action(
|
|
3383
|
+
async (rootId: string, options: { follow?: boolean; mute?: boolean }, command: Command) => {
|
|
3384
|
+
await withProfile(command, (service) =>
|
|
3385
|
+
service.threads.subscription({
|
|
3386
|
+
rootId,
|
|
3387
|
+
following: !!options.follow,
|
|
3388
|
+
muted: !!options.mute,
|
|
3389
|
+
}),
|
|
3390
|
+
);
|
|
3391
|
+
output(io, globals(command).json, { updated: true }, 'Thread subscription updated.');
|
|
3392
|
+
},
|
|
3393
|
+
);
|
|
3394
|
+
program
|
|
3395
|
+
.command('react <target-id> <code>')
|
|
3396
|
+
.option('--remove', 'Set absent instead of present')
|
|
3397
|
+
.option('--idempotency-key <key>', 'Stable retry key')
|
|
3398
|
+
.action(
|
|
3399
|
+
async (
|
|
3400
|
+
targetId: string,
|
|
3401
|
+
code: HpReactionCode,
|
|
3402
|
+
options: { remove?: boolean; idempotencyKey?: string },
|
|
3403
|
+
command: Command,
|
|
3404
|
+
) => {
|
|
3405
|
+
const result = await withProfile(command, (service) =>
|
|
3406
|
+
service.reactions.set({
|
|
3407
|
+
targetId,
|
|
3408
|
+
code,
|
|
3409
|
+
present: !options.remove,
|
|
3410
|
+
...(options.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : {}),
|
|
3411
|
+
}),
|
|
3412
|
+
);
|
|
3413
|
+
output(io, globals(command).json, result, 'Reaction updated.');
|
|
3414
|
+
},
|
|
3415
|
+
);
|
|
3029
3416
|
/* ------------------------------------------------------------ maintenance */
|
|
3030
|
-
|
|
3031
3417
|
const projectionCommand = program
|
|
3032
3418
|
.command('projection')
|
|
3033
3419
|
.description('Inspect the generated files Synomem derives from events');
|
|
3034
|
-
|
|
3035
3420
|
projectionCommand
|
|
3036
3421
|
.command('status')
|
|
3037
3422
|
.description('Report whether the generated files match the canonical events (local stores)')
|
|
@@ -3061,7 +3446,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
3061
3446
|
for (const path of status.unexpected) lines.push(` unexpected ${path}`);
|
|
3062
3447
|
output(io, global.json, status, lines.join('\n'));
|
|
3063
3448
|
});
|
|
3064
|
-
|
|
3065
3449
|
program
|
|
3066
3450
|
.command('rebuild')
|
|
3067
3451
|
.description('Regenerate current-state and filesystem projections from canonical events')
|
|
@@ -3075,7 +3459,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
3075
3459
|
`Rebuilt ${result.generated.length} file(s); removed ${result.removed.length} stale file(s).`,
|
|
3076
3460
|
);
|
|
3077
3461
|
});
|
|
3078
|
-
|
|
3079
3462
|
program
|
|
3080
3463
|
.command('backup <destination>')
|
|
3081
3464
|
.description('Create a transactionally consistent SQLite backup (local stores)')
|
|
@@ -3092,21 +3475,33 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
3092
3475
|
});
|
|
3093
3476
|
output(io, global.json, { path }, `Created backup at ${path}`);
|
|
3094
3477
|
});
|
|
3095
|
-
|
|
3096
3478
|
program
|
|
3097
3479
|
.command('export')
|
|
3098
3480
|
.description('Export canonical events for portability')
|
|
3481
|
+
.option(
|
|
3482
|
+
'--raw',
|
|
3483
|
+
'filesystem-owner recovery: export unchanged JSON events without opening or migrating the store',
|
|
3484
|
+
false,
|
|
3485
|
+
)
|
|
3099
3486
|
.addOption(
|
|
3100
3487
|
new Option('--format <format>').choices(['json', 'jsonl', 'markdown']).default('json'),
|
|
3101
3488
|
)
|
|
3102
3489
|
.option('--output <path>', 'write to an explicit destination instead of stdout')
|
|
3103
3490
|
.action(
|
|
3104
3491
|
async (
|
|
3105
|
-
options: {
|
|
3492
|
+
options: {
|
|
3493
|
+
format: 'json' | 'jsonl' | 'markdown';
|
|
3494
|
+
raw: boolean;
|
|
3495
|
+
output?: string;
|
|
3496
|
+
},
|
|
3106
3497
|
command: Command,
|
|
3107
3498
|
) => {
|
|
3108
3499
|
const global = globals(command);
|
|
3109
|
-
|
|
3500
|
+
if (options.raw && options.format === 'markdown')
|
|
3501
|
+
throw new SynomemError('INVALID_INPUT', 'Raw recovery supports json or jsonl only.');
|
|
3502
|
+
const content = options.raw
|
|
3503
|
+
? exportRawLocalEvents(global.home, options.format as 'json' | 'jsonl')
|
|
3504
|
+
: await withManagement(command, (service) => service.export(options.format));
|
|
3110
3505
|
if (options.output) {
|
|
3111
3506
|
const destination = resolve(options.output);
|
|
3112
3507
|
atomicWriteFile(destination, content, 0o600);
|
|
@@ -3121,7 +3516,6 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
3121
3516
|
}
|
|
3122
3517
|
},
|
|
3123
3518
|
);
|
|
3124
|
-
|
|
3125
3519
|
program
|
|
3126
3520
|
.command('doctor')
|
|
3127
3521
|
.description('Run safe diagnostics')
|
|
@@ -3134,9 +3528,7 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
3134
3528
|
output(io, global.json, result, human);
|
|
3135
3529
|
if (!result.healthy) cliExitCodes.set(program, 5);
|
|
3136
3530
|
});
|
|
3137
|
-
|
|
3138
3531
|
/* ------------------------------------------------------------ mcp */
|
|
3139
|
-
|
|
3140
3532
|
program
|
|
3141
3533
|
.command('mcp')
|
|
3142
3534
|
.description('Run the MCP server over stdio for a profile (fixed) or a preset (explicit)')
|
|
@@ -3146,34 +3538,39 @@ export function createCli(io: CliIo = defaultIo, dependencies: CliDependencies =
|
|
|
3146
3538
|
'explicit',
|
|
3147
3539
|
]),
|
|
3148
3540
|
)
|
|
3149
|
-
.action(
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
);
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3541
|
+
.action(
|
|
3542
|
+
async (
|
|
3543
|
+
options: {
|
|
3544
|
+
contexts?: 'fixed' | 'explicit';
|
|
3545
|
+
},
|
|
3546
|
+
command: Command,
|
|
3547
|
+
) => {
|
|
3548
|
+
const global = globals(command);
|
|
3549
|
+
const config = profileStoreFor(global.home).read();
|
|
3550
|
+
const selection = selectionFor(global, config);
|
|
3551
|
+
if (!selection) throw noSelectionError();
|
|
3552
|
+
if (selection.kind === 'preset' && options.contexts !== 'explicit') {
|
|
3553
|
+
throw new SynomemError(
|
|
3554
|
+
'INVALID_INPUT',
|
|
3555
|
+
`Preset ${selection.name} serves several identities; start it with --contexts explicit so every tool call names its context.`,
|
|
3556
|
+
);
|
|
3557
|
+
}
|
|
3558
|
+
if (selection.kind === 'profile' && options.contexts === 'explicit') {
|
|
3559
|
+
throw new SynomemError(
|
|
3560
|
+
'INVALID_INPUT',
|
|
3561
|
+
'A profile is one fixed identity. Use --preset <name> --contexts explicit for several.',
|
|
3562
|
+
);
|
|
3563
|
+
}
|
|
3564
|
+
const deps = resolverDeps(global.home);
|
|
3565
|
+
const resolver: ContextResolver =
|
|
3566
|
+
selection.kind === 'profile'
|
|
3567
|
+
? profileResolver(global.home, config, selection.name, deps)
|
|
3568
|
+
: presetResolver(global.home, config, selection.name, deps);
|
|
3569
|
+
await (dependencies.startMcpServer ?? runStdioServer)({ resolver });
|
|
3570
|
+
},
|
|
3571
|
+
);
|
|
3174
3572
|
return program;
|
|
3175
3573
|
}
|
|
3176
|
-
|
|
3177
3574
|
/**
|
|
3178
3575
|
* Serves one resolver over stdio until the client disconnects.
|
|
3179
3576
|
*
|
|
@@ -3197,7 +3594,6 @@ async function runStdioServer(options: { resolver: ContextResolver }): Promise<v
|
|
|
3197
3594
|
await runtime.close().catch(() => undefined);
|
|
3198
3595
|
await options.resolver.close?.();
|
|
3199
3596
|
}
|
|
3200
|
-
|
|
3201
3597
|
export async function runCli(
|
|
3202
3598
|
argv = process.argv,
|
|
3203
3599
|
io: CliIo = defaultIo,
|
|
@@ -3235,7 +3631,6 @@ export async function runCli(
|
|
|
3235
3631
|
return exitCode(synomemError.code);
|
|
3236
3632
|
}
|
|
3237
3633
|
}
|
|
3238
|
-
|
|
3239
3634
|
if (process.argv[1] && import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href) {
|
|
3240
3635
|
process.exitCode = await runCli();
|
|
3241
3636
|
}
|