synomem 0.5.2 → 0.6.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 +64 -0
- package/README.md +30 -12
- package/dist/cli.d.ts +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +205 -32
- package/dist/cli.js.map +1 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +10 -0
- package/dist/client.js.map +1 -1
- package/dist/config.d.ts +1 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +4 -0
- package/dist/config.js.map +1 -1
- package/dist/import.d.ts +4 -4
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +49 -3
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp-server.js +26 -3
- package/dist/mcp-server.js.map +1 -1
- package/dist/ports/projections.d.ts +8 -0
- package/dist/ports/projections.d.ts.map +1 -1
- package/dist/project.d.ts +51 -0
- package/dist/project.d.ts.map +1 -0
- package/dist/project.js +143 -0
- package/dist/project.js.map +1 -0
- package/dist/projections.d.ts +14 -0
- package/dist/projections.d.ts.map +1 -1
- package/dist/projections.js +36 -1
- package/dist/projections.js.map +1 -1
- package/dist/schemas.d.ts +18 -4
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +29 -2
- package/dist/schemas.js.map +1 -1
- package/dist/service.d.ts +1 -0
- package/dist/service.d.ts.map +1 -1
- package/dist/storage.d.ts +13 -0
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +24 -0
- package/dist/storage.js.map +1 -1
- package/dist/types.d.ts +1 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/workspaces.d.ts +41 -0
- package/dist/workspaces.d.ts.map +1 -0
- package/dist/workspaces.js +96 -0
- package/dist/workspaces.js.map +1 -0
- package/package.json +1 -1
- package/skills/synomem/SKILL.md +9 -3
- package/skills/synomem/references/examples.md +5 -3
- package/src/cli.ts +245 -32
- package/src/client.ts +10 -0
- package/src/config.ts +4 -0
- package/src/index.ts +17 -0
- package/src/mcp/index.ts +65 -3
- package/src/mcp-server.ts +32 -5
- package/src/ports/projections.ts +9 -0
- package/src/project.ts +168 -0
- package/src/projections.ts +38 -1
- package/src/schemas.ts +44 -12
- package/src/service.ts +1 -0
- package/src/storage.ts +28 -0
- package/src/types.ts +1 -0
- package/src/workspaces.ts +107 -0
package/src/cli.ts
CHANGED
|
@@ -22,6 +22,12 @@ import {
|
|
|
22
22
|
type CredentialStoreChoice,
|
|
23
23
|
} from './configure.js';
|
|
24
24
|
import { discoverBoundWorkspace, discoverOrganizations, workspaceChoices } from './discover.js';
|
|
25
|
+
import { DEFAULT_WORKSPACE, listLocalWorkspaces, localWorkspaceHome } from './workspaces.js';
|
|
26
|
+
import {
|
|
27
|
+
findProjectSelection,
|
|
28
|
+
resolveWorkspaceSelection,
|
|
29
|
+
writeProjectSelection,
|
|
30
|
+
} from './project.js';
|
|
25
31
|
import { defaultPromptIo, type PromptIo } from './prompt.js';
|
|
26
32
|
import { credentialReference, OsCredentialStore, type CredentialStore } from './credentials.js';
|
|
27
33
|
import { asSynomemError, SynomemError, type SynomemErrorCode } from './errors.js';
|
|
@@ -148,10 +154,18 @@ function defaultActor(
|
|
|
148
154
|
env: NodeJS.ProcessEnv,
|
|
149
155
|
fallbackKind: string,
|
|
150
156
|
fallbackId: string,
|
|
157
|
+
/** `--actor`, which outranks the environment: it is said on this invocation. */
|
|
158
|
+
override?: string,
|
|
151
159
|
): ActorIdentity {
|
|
152
|
-
const id = env.SYNOMEM_ACTOR_ID?.trim();
|
|
160
|
+
const id = override?.trim() || env.SYNOMEM_ACTOR_ID?.trim();
|
|
153
161
|
if (!id) return actor(fallbackKind, fallbackId);
|
|
154
|
-
|
|
162
|
+
const kind = override?.trim()
|
|
163
|
+
? // An explicit --actor names an agent unless told otherwise; the historical
|
|
164
|
+
// fallbacks here are `system`/`cli`, which is not what somebody means when
|
|
165
|
+
// they name one.
|
|
166
|
+
env.SYNOMEM_ACTOR_KIND?.trim() || 'agent'
|
|
167
|
+
: env.SYNOMEM_ACTOR_KIND?.trim() || fallbackKind;
|
|
168
|
+
return actor(kind, id, env.SYNOMEM_ACTOR_NAME?.trim());
|
|
155
169
|
}
|
|
156
170
|
|
|
157
171
|
function taskDue(options: {
|
|
@@ -245,10 +259,30 @@ function output(io: CliIo, json: boolean, value: unknown, human: string): void {
|
|
|
245
259
|
io.stdout(json ? `${JSON.stringify(value, null, 2)}\n` : `${human}\n`);
|
|
246
260
|
}
|
|
247
261
|
|
|
248
|
-
|
|
249
|
-
|
|
262
|
+
/**
|
|
263
|
+
* The resolved global options for a command.
|
|
264
|
+
*
|
|
265
|
+
* `--workspace` is turned into a home HERE, before any service exists, which is
|
|
266
|
+
* the whole reason it costs nothing downstream: a local workspace is a separate
|
|
267
|
+
* database in its own home, and choosing one is choosing a home. Nothing in the
|
|
268
|
+
* domain, the commands, or the MCP tools learns that a workspace was selected.
|
|
269
|
+
*
|
|
270
|
+
* On a remote backend the name means a hosted workspace instead, which
|
|
271
|
+
* `backend use remote --workspace` already handles; passing both here would be
|
|
272
|
+
* two different answers to the same question, so it is refused.
|
|
273
|
+
*/
|
|
274
|
+
/** `parent child`, so a subcommand name cannot be confused with another's. */
|
|
275
|
+
function commandPath(command: Command): string {
|
|
276
|
+
const parent = command.parent?.name();
|
|
277
|
+
return parent && parent !== 'synomem' ? `${parent} ${command.name()}` : command.name();
|
|
250
278
|
}
|
|
251
279
|
|
|
280
|
+
/**
|
|
281
|
+
* Commands where `--workspace` names a HOSTED workspace being configured,
|
|
282
|
+
* rather than a local one to act in.
|
|
283
|
+
*/
|
|
284
|
+
const CONFIGURES_BACKEND = new Set(['config init', 'backend use', 'remote import']);
|
|
285
|
+
|
|
252
286
|
async function withService<T>(
|
|
253
287
|
serviceFactory: SynomemServiceFactory,
|
|
254
288
|
home: string | undefined,
|
|
@@ -302,12 +336,87 @@ function listInput(options: Record<string, string>): KudosListInput {
|
|
|
302
336
|
};
|
|
303
337
|
}
|
|
304
338
|
|
|
339
|
+
/**
|
|
340
|
+
* The value `--actor` should supply to commands that name an actor.
|
|
341
|
+
*
|
|
342
|
+
* Read from argv directly, before the commands are built, because Commander
|
|
343
|
+
* evaluates option defaults at DECLARATION time: a `--as` declared without one
|
|
344
|
+
* is required, and a `--as` declared with one is already satisfied. Supplying
|
|
345
|
+
* it here is a single change point instead of a fallback threaded through
|
|
346
|
+
* twenty action bodies, and `--as` still wins when both are given because an
|
|
347
|
+
* explicitly passed option overrides its default.
|
|
348
|
+
*/
|
|
349
|
+
function actorDefault(argv: string[], env: NodeJS.ProcessEnv): string | undefined {
|
|
350
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
351
|
+
const argument = argv[index]!;
|
|
352
|
+
if (argument === '--actor') return argv[index + 1]?.trim() || undefined;
|
|
353
|
+
if (argument.startsWith('--actor='))
|
|
354
|
+
return argument.slice('--actor='.length).trim() || undefined;
|
|
355
|
+
}
|
|
356
|
+
if (env.SYNOMEM_ACTOR_ID?.trim()) return env.SYNOMEM_ACTOR_ID.trim();
|
|
357
|
+
/*
|
|
358
|
+
* Last, the project's own binding. `workspace use --as` exists so a
|
|
359
|
+
* repository can settle both questions once — which workspace, and as whom —
|
|
360
|
+
* and a command run there needs neither flag afterwards.
|
|
361
|
+
*/
|
|
362
|
+
try {
|
|
363
|
+
return findProjectSelection()?.actor;
|
|
364
|
+
} catch {
|
|
365
|
+
// A malformed project file is reported by the resolver when the command
|
|
366
|
+
// actually runs, with the path in the message. Failing here would turn it
|
|
367
|
+
// into an error before any command had been parsed.
|
|
368
|
+
return undefined;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
305
372
|
export function createCli(
|
|
306
373
|
io: CliIo = defaultIo,
|
|
307
374
|
serviceFactory: SynomemServiceFactory = configuredServiceFactory,
|
|
308
375
|
dependencies: CliDependencies = {},
|
|
376
|
+
argv: string[] = process.argv,
|
|
309
377
|
): Command {
|
|
310
378
|
const env = dependencies.env ?? process.env;
|
|
379
|
+
const actingDefault = actorDefault(argv, env);
|
|
380
|
+
|
|
381
|
+
const globals = (
|
|
382
|
+
command: Command,
|
|
383
|
+
): { home?: string; json: boolean; actor?: string; workspace?: string } => {
|
|
384
|
+
const options = command.optsWithGlobals<{
|
|
385
|
+
home?: string;
|
|
386
|
+
json: boolean;
|
|
387
|
+
workspace?: string;
|
|
388
|
+
actor?: string;
|
|
389
|
+
}>();
|
|
390
|
+
if (CONFIGURES_BACKEND.has(commandPath(command))) return options;
|
|
391
|
+
/*
|
|
392
|
+
* Resolution runs even with no `--workspace`, because a project's
|
|
393
|
+
* `.synomem/config.json` selects one without anybody passing a flag — that is
|
|
394
|
+
* the whole point of it. `--home` still wins outright: it names a home
|
|
395
|
+
* directly rather than a workspace within one.
|
|
396
|
+
*/
|
|
397
|
+
if (!options.home) {
|
|
398
|
+
const selection = resolveWorkspaceSelection({
|
|
399
|
+
...(options.workspace ? { flag: options.workspace } : {}),
|
|
400
|
+
...(options.actor ? { actorFlag: options.actor } : {}),
|
|
401
|
+
env,
|
|
402
|
+
});
|
|
403
|
+
return { ...options, home: selection.home, actor: selection.actor ?? options.actor };
|
|
404
|
+
}
|
|
405
|
+
if (!options.workspace) return options;
|
|
406
|
+
/*
|
|
407
|
+
* One flag, one meaning — "which workspace" — resolved differently by the
|
|
408
|
+
* handful of commands that CONFIGURE a backend rather than act inside one.
|
|
409
|
+
* For those, the value is a hosted workspace ID to be written to the config,
|
|
410
|
+
* so it is passed through raw and they read `workspace`. Everywhere else it
|
|
411
|
+
* names a local workspace, which is a home.
|
|
412
|
+
*
|
|
413
|
+
* These commands used to declare their own `--workspace`, which does not
|
|
414
|
+
* work: Commander gives a duplicated long flag to the parent, so the
|
|
415
|
+
* subcommand never received it at all.
|
|
416
|
+
*/
|
|
417
|
+
return { ...options, home: localWorkspaceHome(options.workspace, options.home) };
|
|
418
|
+
};
|
|
419
|
+
|
|
311
420
|
const credentialStore = dependencies.credentialStore ?? new OsCredentialStore();
|
|
312
421
|
const oauthLogin = dependencies.oauthLogin ?? loginWithOAuth;
|
|
313
422
|
const discoverWorkspace = dependencies.discoverBoundWorkspace ?? discoverBoundWorkspace;
|
|
@@ -346,10 +455,113 @@ export function createCli(
|
|
|
346
455
|
)
|
|
347
456
|
.version(packageVersion())
|
|
348
457
|
.option('--home <path>', 'storage root (defaults to SYNOMEM_HOME or ~/.synomem)')
|
|
458
|
+
// A local workspace is its own database under the root, so this selects a
|
|
459
|
+
// home. On a remote backend the hosted workspace is chosen by
|
|
460
|
+
// `backend use remote --workspace` instead.
|
|
461
|
+
.option('--workspace <name>', 'local workspace to act in (see `synomem workspace list`)')
|
|
462
|
+
.option('--actor <id>', 'act as this agent (overrides SYNOMEM_ACTOR_ID)')
|
|
349
463
|
.option('--json', 'emit stable machine-readable JSON', false)
|
|
350
464
|
.showSuggestionAfterError()
|
|
351
465
|
.configureOutput({ writeOut: io.stdout, writeErr: io.stderr });
|
|
352
466
|
|
|
467
|
+
/*
|
|
468
|
+
* Local workspaces.
|
|
469
|
+
*
|
|
470
|
+
* Each is a separate database in its own home, which is what makes the
|
|
471
|
+
* isolation real: SQLite has no row-level security, so a shared file would
|
|
472
|
+
* rest on every query remembering to filter, with nothing to catch a miss.
|
|
473
|
+
* Separate files mean cross-workspace leakage is not something anybody can
|
|
474
|
+
* write by accident.
|
|
475
|
+
*/
|
|
476
|
+
const workspaceCommand = program
|
|
477
|
+
.command('workspace')
|
|
478
|
+
.description('Work in a separate local store, isolated from the others');
|
|
479
|
+
|
|
480
|
+
workspaceCommand
|
|
481
|
+
.command('list')
|
|
482
|
+
.description('List the local workspaces on this machine')
|
|
483
|
+
.action((_options, command: Command) => {
|
|
484
|
+
const global = globals(command);
|
|
485
|
+
// Read from disk, so nothing is listed that does not exist.
|
|
486
|
+
const workspaces = listLocalWorkspaces(global.home);
|
|
487
|
+
// Which one is in effect here, and what decided it — a flag, the
|
|
488
|
+
// environment, a project file, or nothing.
|
|
489
|
+
const selection = resolveWorkspaceSelection({ env });
|
|
490
|
+
const human = workspaces
|
|
491
|
+
.map(
|
|
492
|
+
(workspace) =>
|
|
493
|
+
`${workspace.name === DEFAULT_WORKSPACE ? '*' : ' '} ${workspace.name.padEnd(24)} ${
|
|
494
|
+
workspace.initialized ? workspace.home : `${workspace.home} (not initialized)`
|
|
495
|
+
}`,
|
|
496
|
+
)
|
|
497
|
+
.join('\n');
|
|
498
|
+
output(
|
|
499
|
+
io,
|
|
500
|
+
global.json,
|
|
501
|
+
{ workspaces, active: selection.workspace ?? DEFAULT_WORKSPACE, source: selection.source },
|
|
502
|
+
[
|
|
503
|
+
human,
|
|
504
|
+
'',
|
|
505
|
+
`Acting in: ${selection.workspace ?? DEFAULT_WORKSPACE} (from ${selection.source})`,
|
|
506
|
+
'',
|
|
507
|
+
'Bind a directory with `synomem workspace use <name>`, or pass --workspace once.',
|
|
508
|
+
'A local workspace is a separate store on this machine; a hosted workspace is shared,',
|
|
509
|
+
'and is selected with `backend use remote --workspace`.',
|
|
510
|
+
].join('\n'),
|
|
511
|
+
);
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
workspaceCommand
|
|
515
|
+
.command('use <name>')
|
|
516
|
+
.description('Bind this directory to a workspace, for every session opened here')
|
|
517
|
+
.option('--as <actor-id>', 'also always write as this agent', actingDefault)
|
|
518
|
+
.action((name: string, options: { as?: string }, command: Command) => {
|
|
519
|
+
const global = globals(command);
|
|
520
|
+
// Validated by resolving it, so a name that could never work is refused
|
|
521
|
+
// before a file claiming it is written.
|
|
522
|
+
const home = localWorkspaceHome(name, undefined);
|
|
523
|
+
const path = writeProjectSelection(process.cwd(), {
|
|
524
|
+
workspace: name,
|
|
525
|
+
...(options.as ? { actor: options.as } : {}),
|
|
526
|
+
});
|
|
527
|
+
output(
|
|
528
|
+
io,
|
|
529
|
+
global.json,
|
|
530
|
+
{ path, workspace: name, home, ...(options.as ? { actor: options.as } : {}) },
|
|
531
|
+
[
|
|
532
|
+
`Wrote ${path}`,
|
|
533
|
+
'',
|
|
534
|
+
`Every Synomem command and MCP server started in this directory now acts in ${name}${
|
|
535
|
+
options.as ? ` as ${options.as}` : ''
|
|
536
|
+
}, with no flag.`,
|
|
537
|
+
`Records live in ${home} — nothing is stored in this directory.`,
|
|
538
|
+
'',
|
|
539
|
+
'Commit it to share the choice with the repository, or ignore it to keep it yours.',
|
|
540
|
+
].join('\n'),
|
|
541
|
+
);
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
workspaceCommand
|
|
545
|
+
.command('create <name>')
|
|
546
|
+
.description('Create a local workspace and initialize its store')
|
|
547
|
+
.action(async (name: string, _options, command: Command) => {
|
|
548
|
+
const global = globals(command);
|
|
549
|
+
const home = localWorkspaceHome(name, global.home);
|
|
550
|
+
if (readSynomemConfig(home)) {
|
|
551
|
+
throw new SynomemError('INVALID_INPUT', `Workspace already exists: ${name}`);
|
|
552
|
+
}
|
|
553
|
+
writeSynomemBackend({ kind: 'local' }, home);
|
|
554
|
+
// Opening it once creates the database, so `list` does not report a
|
|
555
|
+
// workspace that exists in name only.
|
|
556
|
+
await withClient(home, defaultActor(env, 'system', 'cli'), async () => undefined);
|
|
557
|
+
output(
|
|
558
|
+
io,
|
|
559
|
+
global.json,
|
|
560
|
+
{ name, home },
|
|
561
|
+
`Created workspace ${name} at ${home}.\nAct in it with --workspace ${name}.`,
|
|
562
|
+
);
|
|
563
|
+
});
|
|
564
|
+
|
|
353
565
|
const remoteCommand = program.command('remote').description('Administer a remote workspace');
|
|
354
566
|
|
|
355
567
|
/*
|
|
@@ -631,7 +843,6 @@ export function createCli(
|
|
|
631
843
|
.description('Configure Synomem without prompting')
|
|
632
844
|
.option('--backend <kind>', 'local or remote')
|
|
633
845
|
.option('--auth <method>', 'browser or access-key')
|
|
634
|
-
.option('--workspace <id>', 'remote workspace ID')
|
|
635
846
|
.option('--credential-store <where>', 'auto, keychain, file, or environment', 'auto')
|
|
636
847
|
// The token is read from stdin, never taken as an argument: an argument is
|
|
637
848
|
// kept by the shell history and visible in the process list.
|
|
@@ -642,7 +853,6 @@ export function createCli(
|
|
|
642
853
|
options: {
|
|
643
854
|
backend?: string;
|
|
644
855
|
auth?: string;
|
|
645
|
-
workspace?: string;
|
|
646
856
|
credentialStore: string;
|
|
647
857
|
accessTokenStdin: boolean;
|
|
648
858
|
yes: boolean;
|
|
@@ -656,7 +866,7 @@ export function createCli(
|
|
|
656
866
|
const backend: BackendChoice = options.backend;
|
|
657
867
|
// An access key names its own workspace, so --workspace is only
|
|
658
868
|
// required when there is no key to ask.
|
|
659
|
-
if (backend === 'remote' && !
|
|
869
|
+
if (backend === 'remote' && !global.workspace && !options.accessTokenStdin) {
|
|
660
870
|
throw new SynomemError(
|
|
661
871
|
'INVALID_INPUT',
|
|
662
872
|
'Remote setup requires --workspace, or --access-token-stdin so the key can name its own.',
|
|
@@ -676,7 +886,7 @@ export function createCli(
|
|
|
676
886
|
? {
|
|
677
887
|
serviceUrl: cloudApiUrl(env),
|
|
678
888
|
auth: (options.auth as AuthChoice | undefined) ?? 'access-key',
|
|
679
|
-
workspaceId:
|
|
889
|
+
workspaceId: global.workspace,
|
|
680
890
|
credentialStore: options.credentialStore as CredentialStoreChoice,
|
|
681
891
|
}
|
|
682
892
|
: {}),
|
|
@@ -818,13 +1028,12 @@ export function createCli(
|
|
|
818
1028
|
// never ask for a service address, because a person has no way to tell a
|
|
819
1029
|
// real one from a phished one.
|
|
820
1030
|
.option('--url <url>', 'internal: alternate HTTPS origin')
|
|
821
|
-
.
|
|
822
|
-
.action((kind: string, options: { url?: string; workspace?: string }, command: Command) => {
|
|
1031
|
+
.action((kind: string, options: { url?: string }, command: Command) => {
|
|
823
1032
|
const global = globals(command);
|
|
824
1033
|
if (kind !== 'local' && kind !== 'remote') {
|
|
825
1034
|
throw new SynomemError('INVALID_INPUT', 'Backend kind must be local or remote.');
|
|
826
1035
|
}
|
|
827
|
-
if (kind === 'remote' && !
|
|
1036
|
+
if (kind === 'remote' && !global.workspace) {
|
|
828
1037
|
throw new SynomemError('INVALID_INPUT', 'Remote backend selection requires --workspace.');
|
|
829
1038
|
}
|
|
830
1039
|
const config = writeSynomemBackend(
|
|
@@ -833,7 +1042,7 @@ export function createCli(
|
|
|
833
1042
|
: {
|
|
834
1043
|
kind: 'remote',
|
|
835
1044
|
baseUrl: options.url ?? cloudApiUrl(env),
|
|
836
|
-
workspaceId:
|
|
1045
|
+
workspaceId: global.workspace!,
|
|
837
1046
|
},
|
|
838
1047
|
global.home,
|
|
839
1048
|
);
|
|
@@ -1493,7 +1702,7 @@ export function createCli(
|
|
|
1493
1702
|
postCommand
|
|
1494
1703
|
.command('create')
|
|
1495
1704
|
.description('Publish a post the whole workspace can read')
|
|
1496
|
-
.requiredOption('--as <actor-id>')
|
|
1705
|
+
.requiredOption('--as <actor-id>', 'actor to act as (defaults to --actor)', actingDefault)
|
|
1497
1706
|
.option('--actor-kind <kind>', 'human, agent, or system', 'agent')
|
|
1498
1707
|
.requiredOption('--title <title>')
|
|
1499
1708
|
.requiredOption('--body <body>')
|
|
@@ -1530,7 +1739,7 @@ export function createCli(
|
|
|
1530
1739
|
postCommand
|
|
1531
1740
|
.command('list')
|
|
1532
1741
|
.description('List posts in this workspace')
|
|
1533
|
-
.requiredOption('--as <actor-id>')
|
|
1742
|
+
.requiredOption('--as <actor-id>', 'actor to act as (defaults to --actor)', actingDefault)
|
|
1534
1743
|
.option('--actor-kind <kind>', 'human, agent, or system', 'agent')
|
|
1535
1744
|
.option('--limit <n>', 'default 10, maximum 50')
|
|
1536
1745
|
.action(
|
|
@@ -1549,7 +1758,7 @@ export function createCli(
|
|
|
1549
1758
|
postCommand
|
|
1550
1759
|
.command('show <post-id>')
|
|
1551
1760
|
.description('Show one post with its acknowledgements')
|
|
1552
|
-
.requiredOption('--as <actor-id>')
|
|
1761
|
+
.requiredOption('--as <actor-id>', 'actor to act as (defaults to --actor)', actingDefault)
|
|
1553
1762
|
.option('--actor-kind <kind>', 'human, agent, or system', 'agent')
|
|
1554
1763
|
.action(
|
|
1555
1764
|
async (postId: string, options: { as: string; actorKind: string }, command: Command) => {
|
|
@@ -1576,7 +1785,7 @@ export function createCli(
|
|
|
1576
1785
|
postCommand
|
|
1577
1786
|
.command('acknowledge <post-id>')
|
|
1578
1787
|
.description('Say you have seen a post')
|
|
1579
|
-
.requiredOption('--as <actor-id>')
|
|
1788
|
+
.requiredOption('--as <actor-id>', 'actor to act as (defaults to --actor)', actingDefault)
|
|
1580
1789
|
.option('--actor-kind <kind>', 'human, agent, or system', 'agent')
|
|
1581
1790
|
.option('--note <text>', 'optional context for the author')
|
|
1582
1791
|
.action(
|
|
@@ -1602,7 +1811,7 @@ export function createCli(
|
|
|
1602
1811
|
postCommand
|
|
1603
1812
|
.command('roster <post-id>')
|
|
1604
1813
|
.description('Who has acknowledged a post, and who has not')
|
|
1605
|
-
.requiredOption('--as <actor-id>')
|
|
1814
|
+
.requiredOption('--as <actor-id>', 'actor to act as (defaults to --actor)', actingDefault)
|
|
1606
1815
|
.option('--actor-kind <kind>', 'human, agent, or system', 'agent')
|
|
1607
1816
|
.action(
|
|
1608
1817
|
async (postId: string, options: { as: string; actorKind: string }, command: Command) => {
|
|
@@ -1634,7 +1843,7 @@ export function createCli(
|
|
|
1634
1843
|
postCommand
|
|
1635
1844
|
.command('archive <post-id>')
|
|
1636
1845
|
.description('Archive a post you wrote')
|
|
1637
|
-
.requiredOption('--as <actor-id>')
|
|
1846
|
+
.requiredOption('--as <actor-id>', 'actor to act as (defaults to --actor)', actingDefault)
|
|
1638
1847
|
.option('--actor-kind <kind>', 'human, agent, or system', 'agent')
|
|
1639
1848
|
.option('--reason <text>')
|
|
1640
1849
|
.action(
|
|
@@ -1659,7 +1868,11 @@ export function createCli(
|
|
|
1659
1868
|
kudosCommand
|
|
1660
1869
|
.command('give <recipient>')
|
|
1661
1870
|
.description('Give specific, evidence-based kudos to an agent')
|
|
1662
|
-
.requiredOption(
|
|
1871
|
+
.requiredOption(
|
|
1872
|
+
'--from <actor-id>',
|
|
1873
|
+
'stable ID of the giver (defaults to --actor)',
|
|
1874
|
+
actingDefault,
|
|
1875
|
+
)
|
|
1663
1876
|
.requiredOption('--actor-kind <kind>', 'human, agent, or system')
|
|
1664
1877
|
.option('--actor-name <display-name>')
|
|
1665
1878
|
.requiredOption('--title <title>')
|
|
@@ -1828,7 +2041,7 @@ export function createCli(
|
|
|
1828
2041
|
const memoCommand = program.command('memo').description('Send and manage durable messages');
|
|
1829
2042
|
memoCommand
|
|
1830
2043
|
.command('send <recipient>')
|
|
1831
|
-
.requiredOption('--from <actor-id>')
|
|
2044
|
+
.requiredOption('--from <actor-id>', 'actor to act as (defaults to --actor)', actingDefault)
|
|
1832
2045
|
.option('--actor-kind <kind>', 'human, agent, or system', 'agent')
|
|
1833
2046
|
.option('--actor-name <name>')
|
|
1834
2047
|
.requiredOption('--subject <subject>')
|
|
@@ -1945,7 +2158,7 @@ export function createCli(
|
|
|
1945
2158
|
.description('Retain and revise agent-owned knowledge');
|
|
1946
2159
|
noteCommand
|
|
1947
2160
|
.command('create')
|
|
1948
|
-
.requiredOption('--as <actor-id>')
|
|
2161
|
+
.requiredOption('--as <actor-id>', 'actor to act as (defaults to --actor)', actingDefault)
|
|
1949
2162
|
.option('--actor-kind <kind>', 'agent or human', 'agent')
|
|
1950
2163
|
.option('--owner <agent-id>')
|
|
1951
2164
|
.requiredOption('--title <title>')
|
|
@@ -2023,7 +2236,7 @@ export function createCli(
|
|
|
2023
2236
|
});
|
|
2024
2237
|
noteCommand
|
|
2025
2238
|
.command('revise <note-id>')
|
|
2026
|
-
.requiredOption('--as <actor-id>')
|
|
2239
|
+
.requiredOption('--as <actor-id>', 'actor to act as (defaults to --actor)', actingDefault)
|
|
2027
2240
|
.option('--actor-kind <kind>', 'agent or human', 'agent')
|
|
2028
2241
|
.requiredOption('--expected-version <number>')
|
|
2029
2242
|
.option('--title <title>')
|
|
@@ -2063,7 +2276,7 @@ export function createCli(
|
|
|
2063
2276
|
);
|
|
2064
2277
|
noteCommand
|
|
2065
2278
|
.command('archive <note-id>')
|
|
2066
|
-
.requiredOption('--as <actor-id>')
|
|
2279
|
+
.requiredOption('--as <actor-id>', 'actor to act as (defaults to --actor)', actingDefault)
|
|
2067
2280
|
.option('--actor-kind <kind>', 'agent or human', 'agent')
|
|
2068
2281
|
.option('--idempotency-key <key>')
|
|
2069
2282
|
.action(
|
|
@@ -2091,7 +2304,7 @@ export function createCli(
|
|
|
2091
2304
|
.description('Create and manage your own private reminders');
|
|
2092
2305
|
todoCommand
|
|
2093
2306
|
.command('create')
|
|
2094
|
-
.requiredOption('--as <actor-id>')
|
|
2307
|
+
.requiredOption('--as <actor-id>', 'actor to act as (defaults to --actor)', actingDefault)
|
|
2095
2308
|
.option('--actor-kind <kind>', 'human, agent, or system', 'agent')
|
|
2096
2309
|
.requiredOption('--title <title>')
|
|
2097
2310
|
.option('--details <text>', 'private working detail')
|
|
@@ -2141,7 +2354,7 @@ export function createCli(
|
|
|
2141
2354
|
);
|
|
2142
2355
|
todoCommand
|
|
2143
2356
|
.command('list')
|
|
2144
|
-
.requiredOption('--as <actor-id>')
|
|
2357
|
+
.requiredOption('--as <actor-id>', 'actor to act as (defaults to --actor)', actingDefault)
|
|
2145
2358
|
.option('--actor-kind <kind>', 'human, agent, or system', 'agent')
|
|
2146
2359
|
.option('--status <status>')
|
|
2147
2360
|
.option('--limit <number>', 'maximum results', '10')
|
|
@@ -2171,7 +2384,7 @@ export function createCli(
|
|
|
2171
2384
|
);
|
|
2172
2385
|
todoCommand
|
|
2173
2386
|
.command('show <todo-id>')
|
|
2174
|
-
.requiredOption('--as <actor-id>')
|
|
2387
|
+
.requiredOption('--as <actor-id>', 'actor to act as (defaults to --actor)', actingDefault)
|
|
2175
2388
|
.option('--actor-kind <kind>', 'human, agent, or system', 'agent')
|
|
2176
2389
|
.action(async (id: string, options: { as: string; actorKind: string }, command: Command) => {
|
|
2177
2390
|
const global = globals(command);
|
|
@@ -2188,7 +2401,7 @@ export function createCli(
|
|
|
2188
2401
|
for (const operation of ['complete', 'reopen', 'cancel', 'archive'] as const) {
|
|
2189
2402
|
todoCommand
|
|
2190
2403
|
.command(`${operation} <todo-id>`)
|
|
2191
|
-
.requiredOption('--as <actor-id>')
|
|
2404
|
+
.requiredOption('--as <actor-id>', 'actor to act as (defaults to --actor)', actingDefault)
|
|
2192
2405
|
.option('--actor-kind <kind>', 'human, agent, or system', 'agent')
|
|
2193
2406
|
.option('--note <text>')
|
|
2194
2407
|
.option('--reason <text>')
|
|
@@ -2244,7 +2457,7 @@ export function createCli(
|
|
|
2244
2457
|
const taskCommand = program.command('task').description('Create and manage agent tasks');
|
|
2245
2458
|
taskCommand
|
|
2246
2459
|
.command('create <assignee>')
|
|
2247
|
-
.requiredOption('--from <actor-id>')
|
|
2460
|
+
.requiredOption('--from <actor-id>', 'actor to act as (defaults to --actor)', actingDefault)
|
|
2248
2461
|
.option('--actor-kind <kind>', 'human, agent, or system', 'agent')
|
|
2249
2462
|
.requiredOption('--title <title>')
|
|
2250
2463
|
.option('--description <text>')
|
|
@@ -2334,7 +2547,7 @@ export function createCli(
|
|
|
2334
2547
|
});
|
|
2335
2548
|
taskCommand
|
|
2336
2549
|
.command('update <task-id>')
|
|
2337
|
-
.requiredOption('--as <actor-id>')
|
|
2550
|
+
.requiredOption('--as <actor-id>', 'actor to act as (defaults to --actor)', actingDefault)
|
|
2338
2551
|
.option('--actor-kind <kind>', 'agent or human', 'agent')
|
|
2339
2552
|
.requiredOption('--expected-version <number>')
|
|
2340
2553
|
.option('--title <title>')
|
|
@@ -2388,7 +2601,7 @@ export function createCli(
|
|
|
2388
2601
|
for (const operation of ['accept', 'reject', 'complete', 'reopen', 'cancel'] as const) {
|
|
2389
2602
|
const command_ = taskCommand
|
|
2390
2603
|
.command(`${operation} <task-id>`)
|
|
2391
|
-
.requiredOption('--as <actor-id>')
|
|
2604
|
+
.requiredOption('--as <actor-id>', 'actor to act as (defaults to --actor)', actingDefault)
|
|
2392
2605
|
.option('--actor-kind <kind>', 'agent or human', 'agent')
|
|
2393
2606
|
.option('--note <text>')
|
|
2394
2607
|
.option('--reason <text>')
|
|
@@ -2497,7 +2710,7 @@ export function createCli(
|
|
|
2497
2710
|
kudosCommand
|
|
2498
2711
|
.command('revoke <kudos-id>')
|
|
2499
2712
|
.description('Record a revocation while preserving history')
|
|
2500
|
-
.requiredOption('--as <actor-id>')
|
|
2713
|
+
.requiredOption('--as <actor-id>', 'actor to act as (defaults to --actor)', actingDefault)
|
|
2501
2714
|
.option('--actor-kind <kind>', 'human, agent, or system', 'human')
|
|
2502
2715
|
.requiredOption('--reason <reason>')
|
|
2503
2716
|
.option('--administrative', 'mark as an administrative revocation', false)
|
|
@@ -2727,7 +2940,7 @@ export async function runCli(
|
|
|
2727
2940
|
serviceFactory: SynomemServiceFactory = configuredServiceFactory,
|
|
2728
2941
|
dependencies: CliDependencies = {},
|
|
2729
2942
|
): Promise<number> {
|
|
2730
|
-
const program = createCli(io, serviceFactory, dependencies);
|
|
2943
|
+
const program = createCli(io, serviceFactory, dependencies, argv);
|
|
2731
2944
|
program.exitOverride();
|
|
2732
2945
|
try {
|
|
2733
2946
|
await program.parseAsync(argv);
|
package/src/client.ts
CHANGED
|
@@ -435,6 +435,15 @@ export class SynomemCore implements SynomemDomainService {
|
|
|
435
435
|
await this.repository.updateAgent(updated, event.createdAt);
|
|
436
436
|
await this.repository.insertEvent(event);
|
|
437
437
|
});
|
|
438
|
+
/*
|
|
439
|
+
* Projections are named by handle, so a rename has to move the directory
|
|
440
|
+
* before it is regenerated. Otherwise the generated files appear under the
|
|
441
|
+
* new handle and `NOTES.md` -- which belongs to the reader and is never
|
|
442
|
+
* deleted -- is left stranded under the old one.
|
|
443
|
+
*/
|
|
444
|
+
if (existing.handle !== updated.handle && this.projectionWriter.renameAgentDirectory) {
|
|
445
|
+
await this.projectionWriter.renameAgentDirectory(existing.handle, updated.handle);
|
|
446
|
+
}
|
|
438
447
|
await this.projectionWriter.syncAgent(updated.id);
|
|
439
448
|
return updated;
|
|
440
449
|
}
|
|
@@ -1915,6 +1924,7 @@ export class SynomemClient extends SynomemCore implements SynomemService {
|
|
|
1915
1924
|
binding: { workspaceId: this.storage.config.workspaceId, actor: this.actor },
|
|
1916
1925
|
administration: {
|
|
1917
1926
|
agentCreationViaMcp: this.storage.config.allowAgentCreationViaMcp,
|
|
1927
|
+
agentArchiveViaMcp: this.storage.config.allowAgentArchiveViaMcp,
|
|
1918
1928
|
rebuildViaMcp: this.storage.config.allowRebuildViaMcp,
|
|
1919
1929
|
},
|
|
1920
1930
|
projections: { ...this.storage.config.projection },
|
package/src/config.ts
CHANGED
|
@@ -12,6 +12,7 @@ export const defaultConfig: SynomemConfig = {
|
|
|
12
12
|
allowSelfAwards: false,
|
|
13
13
|
allowCrossAgentTasks: true,
|
|
14
14
|
allowAgentCreationViaMcp: false,
|
|
15
|
+
allowAgentArchiveViaMcp: false,
|
|
15
16
|
allowRebuildViaMcp: false,
|
|
16
17
|
includePrivateInStats: false,
|
|
17
18
|
projection: {
|
|
@@ -55,6 +56,7 @@ const policySchema = z.object({
|
|
|
55
56
|
allowSelfAwards: z.boolean(),
|
|
56
57
|
allowCrossAgentTasks: z.boolean(),
|
|
57
58
|
allowAgentCreationViaMcp: z.boolean(),
|
|
59
|
+
allowAgentArchiveViaMcp: z.boolean(),
|
|
58
60
|
allowRebuildViaMcp: z.boolean(),
|
|
59
61
|
includePrivateInStats: z.boolean(),
|
|
60
62
|
projection: z.object({
|
|
@@ -156,6 +158,7 @@ function environmentConfig(env: NodeJS.ProcessEnv): SynomemConfigOverrides {
|
|
|
156
158
|
const allowSelfAwards = optionalBoolean(env, 'SYNOMEM_ALLOW_SELF_AWARDS');
|
|
157
159
|
const allowCrossAgentTasks = optionalBoolean(env, 'SYNOMEM_ALLOW_CROSS_AGENT_TODOS');
|
|
158
160
|
const allowAgentCreationViaMcp = optionalBoolean(env, 'SYNOMEM_ALLOW_AGENT_CREATION_VIA_MCP');
|
|
161
|
+
const allowAgentArchiveViaMcp = optionalBoolean(env, 'SYNOMEM_ALLOW_AGENT_ARCHIVE_VIA_MCP');
|
|
159
162
|
const allowRebuildViaMcp = optionalBoolean(env, 'SYNOMEM_ALLOW_REBUILD_VIA_MCP');
|
|
160
163
|
const includePrivateInStats = optionalBoolean(env, 'SYNOMEM_INCLUDE_PRIVATE_IN_STATS');
|
|
161
164
|
const projection = {
|
|
@@ -170,6 +173,7 @@ function environmentConfig(env: NodeJS.ProcessEnv): SynomemConfigOverrides {
|
|
|
170
173
|
...(allowSelfAwards !== undefined ? { allowSelfAwards } : {}),
|
|
171
174
|
...(allowCrossAgentTasks !== undefined ? { allowCrossAgentTasks } : {}),
|
|
172
175
|
...(allowAgentCreationViaMcp !== undefined ? { allowAgentCreationViaMcp } : {}),
|
|
176
|
+
...(allowAgentArchiveViaMcp !== undefined ? { allowAgentArchiveViaMcp } : {}),
|
|
173
177
|
...(allowRebuildViaMcp !== undefined ? { allowRebuildViaMcp } : {}),
|
|
174
178
|
...(includePrivateInStats !== undefined ? { includePrivateInStats } : {}),
|
|
175
179
|
...(Object.keys(projection).length ? { projection } : {}),
|
package/src/index.ts
CHANGED
|
@@ -41,6 +41,23 @@ export type {
|
|
|
41
41
|
} from './service.js';
|
|
42
42
|
export { defaultConfig, resolveHome } from './config.js';
|
|
43
43
|
export { cloudApiUrl, SYNOMEM_CLOUD_API_URL } from './cloud.js';
|
|
44
|
+
export {
|
|
45
|
+
DEFAULT_WORKSPACE,
|
|
46
|
+
listLocalWorkspaces,
|
|
47
|
+
localWorkspaceHome,
|
|
48
|
+
WORKSPACES_DIRECTORY,
|
|
49
|
+
workspaceNameSchema,
|
|
50
|
+
} from './workspaces.js';
|
|
51
|
+
export type { LocalWorkspace } from './workspaces.js';
|
|
52
|
+
export {
|
|
53
|
+
findProjectSelection,
|
|
54
|
+
PROJECT_CONFIG_FILE,
|
|
55
|
+
PROJECT_DIRECTORY,
|
|
56
|
+
projectConfigSchema,
|
|
57
|
+
resolveWorkspaceSelection,
|
|
58
|
+
writeProjectSelection,
|
|
59
|
+
} from './project.js';
|
|
60
|
+
export type { ProjectConfig, ProjectSelection } from './project.js';
|
|
44
61
|
export { discoverBoundWorkspace, discoverOrganizations, workspaceChoices } from './discover.js';
|
|
45
62
|
export type { DiscoveredOrganization, DiscoveredWorkspace, DiscoveryOptions } from './discover.js';
|
|
46
63
|
export { asSynomemError, errorCodes, SynomemError } from './errors.js';
|