synomem 0.5.3 → 0.6.1
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 +43 -0
- package/README.md +30 -12
- package/dist/cli.d.ts +5 -3
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +245 -47
- 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/configure.d.ts.map +1 -1
- package/dist/configure.js +5 -4
- package/dist/configure.js.map +1 -1
- package/dist/discover.d.ts +9 -11
- package/dist/discover.d.ts.map +1 -1
- package/dist/discover.js +14 -15
- package/dist/discover.js.map +1 -1
- package/dist/import.d.ts +4 -4
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +46 -0
- 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/remote.d.ts.map +1 -1
- package/dist/remote.js +9 -0
- package/dist/remote.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/src/cli.ts +305 -50
- package/src/client.ts +10 -0
- package/src/config.ts +4 -0
- package/src/configure.ts +5 -4
- package/src/discover.ts +18 -19
- package/src/index.ts +22 -1
- package/src/mcp/index.ts +62 -0
- 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/remote.ts +9 -0
- 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/dist/cli.js
CHANGED
|
@@ -8,8 +8,10 @@ import { configuredServiceFactory, readSynomemConfig, writeSynomemBackend } from
|
|
|
8
8
|
import { cloudApiUrl } from './cloud.js';
|
|
9
9
|
import { resolveHome } from './config.js';
|
|
10
10
|
import { assertInteractive, confirmPlan, credentialFingerprint, credentialStoreChoices, environmentInstructions, readAccessToken, runConfigWizard, writeCredentialFile, } from './configure.js';
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
11
|
+
import { discoverAccessKeyWorkspaces, discoverOrganizations, workspaceChoices, } from './discover.js';
|
|
12
|
+
import { DEFAULT_WORKSPACE, listLocalWorkspaces, localWorkspaceHome } from './workspaces.js';
|
|
13
|
+
import { findProjectSelection, resolveWorkspaceSelection, writeProjectSelection, } from './project.js';
|
|
14
|
+
import { defaultPromptIo, select } from './prompt.js';
|
|
13
15
|
import { credentialReference, OsCredentialStore } from './credentials.js';
|
|
14
16
|
import { asSynomemError, SynomemError } from './errors.js';
|
|
15
17
|
import { atomicWriteFile } from './fs-utils.js';
|
|
@@ -61,11 +63,19 @@ function actor(kind, id, displayName) {
|
|
|
61
63
|
* backend binds the credential to one actor, so SYNOMEM_ACTOR_ID/KIND/NAME must be able to
|
|
62
64
|
* override them or those commands cannot authenticate.
|
|
63
65
|
*/
|
|
64
|
-
function defaultActor(env, fallbackKind, fallbackId
|
|
65
|
-
|
|
66
|
+
function defaultActor(env, fallbackKind, fallbackId,
|
|
67
|
+
/** `--actor`, which outranks the environment: it is said on this invocation. */
|
|
68
|
+
override) {
|
|
69
|
+
const id = override?.trim() || env.SYNOMEM_ACTOR_ID?.trim();
|
|
66
70
|
if (!id)
|
|
67
71
|
return actor(fallbackKind, fallbackId);
|
|
68
|
-
|
|
72
|
+
const kind = override?.trim()
|
|
73
|
+
? // An explicit --actor names an agent unless told otherwise; the historical
|
|
74
|
+
// fallbacks here are `system`/`cli`, which is not what somebody means when
|
|
75
|
+
// they name one.
|
|
76
|
+
env.SYNOMEM_ACTOR_KIND?.trim() || 'agent'
|
|
77
|
+
: env.SYNOMEM_ACTOR_KIND?.trim() || fallbackKind;
|
|
78
|
+
return actor(kind, id, env.SYNOMEM_ACTOR_NAME?.trim());
|
|
69
79
|
}
|
|
70
80
|
function taskDue(options) {
|
|
71
81
|
if (options.dueDate && options.dueAt)
|
|
@@ -148,9 +158,28 @@ function showRecord(record) {
|
|
|
148
158
|
function output(io, json, value, human) {
|
|
149
159
|
io.stdout(json ? `${JSON.stringify(value, null, 2)}\n` : `${human}\n`);
|
|
150
160
|
}
|
|
151
|
-
|
|
152
|
-
|
|
161
|
+
/**
|
|
162
|
+
* The resolved global options for a command.
|
|
163
|
+
*
|
|
164
|
+
* `--workspace` is turned into a home HERE, before any service exists, which is
|
|
165
|
+
* the whole reason it costs nothing downstream: a local workspace is a separate
|
|
166
|
+
* database in its own home, and choosing one is choosing a home. Nothing in the
|
|
167
|
+
* domain, the commands, or the MCP tools learns that a workspace was selected.
|
|
168
|
+
*
|
|
169
|
+
* On a remote backend the name means a hosted workspace instead, which
|
|
170
|
+
* `backend use remote --workspace` already handles; passing both here would be
|
|
171
|
+
* two different answers to the same question, so it is refused.
|
|
172
|
+
*/
|
|
173
|
+
/** `parent child`, so a subcommand name cannot be confused with another's. */
|
|
174
|
+
function commandPath(command) {
|
|
175
|
+
const parent = command.parent?.name();
|
|
176
|
+
return parent && parent !== 'synomem' ? `${parent} ${command.name()}` : command.name();
|
|
153
177
|
}
|
|
178
|
+
/**
|
|
179
|
+
* Commands where `--workspace` names a HOSTED workspace being configured,
|
|
180
|
+
* rather than a local one to act in.
|
|
181
|
+
*/
|
|
182
|
+
const CONFIGURES_BACKEND = new Set(['config init', 'backend use', 'remote import']);
|
|
154
183
|
async function withService(serviceFactory, home, configuredActor, operation) {
|
|
155
184
|
const client = serviceFactory({ ...(home ? { home } : {}), actor: configuredActor });
|
|
156
185
|
await client.init();
|
|
@@ -195,11 +224,80 @@ function listInput(options) {
|
|
|
195
224
|
offset: Number(options.offset),
|
|
196
225
|
};
|
|
197
226
|
}
|
|
198
|
-
|
|
227
|
+
/**
|
|
228
|
+
* The value `--actor` should supply to commands that name an actor.
|
|
229
|
+
*
|
|
230
|
+
* Read from argv directly, before the commands are built, because Commander
|
|
231
|
+
* evaluates option defaults at DECLARATION time: a `--as` declared without one
|
|
232
|
+
* is required, and a `--as` declared with one is already satisfied. Supplying
|
|
233
|
+
* it here is a single change point instead of a fallback threaded through
|
|
234
|
+
* twenty action bodies, and `--as` still wins when both are given because an
|
|
235
|
+
* explicitly passed option overrides its default.
|
|
236
|
+
*/
|
|
237
|
+
function actorDefault(argv, env) {
|
|
238
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
239
|
+
const argument = argv[index];
|
|
240
|
+
if (argument === '--actor')
|
|
241
|
+
return argv[index + 1]?.trim() || undefined;
|
|
242
|
+
if (argument.startsWith('--actor='))
|
|
243
|
+
return argument.slice('--actor='.length).trim() || undefined;
|
|
244
|
+
}
|
|
245
|
+
if (env.SYNOMEM_ACTOR_ID?.trim())
|
|
246
|
+
return env.SYNOMEM_ACTOR_ID.trim();
|
|
247
|
+
/*
|
|
248
|
+
* Last, the project's own binding. `workspace use --as` exists so a
|
|
249
|
+
* repository can settle both questions once — which workspace, and as whom —
|
|
250
|
+
* and a command run there needs neither flag afterwards.
|
|
251
|
+
*/
|
|
252
|
+
try {
|
|
253
|
+
return findProjectSelection()?.actor;
|
|
254
|
+
}
|
|
255
|
+
catch {
|
|
256
|
+
// A malformed project file is reported by the resolver when the command
|
|
257
|
+
// actually runs, with the path in the message. Failing here would turn it
|
|
258
|
+
// into an error before any command had been parsed.
|
|
259
|
+
return undefined;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
export function createCli(io = defaultIo, serviceFactory = configuredServiceFactory, dependencies = {}, argv = process.argv) {
|
|
199
263
|
const env = dependencies.env ?? process.env;
|
|
264
|
+
const actingDefault = actorDefault(argv, env);
|
|
265
|
+
const globals = (command) => {
|
|
266
|
+
const options = command.optsWithGlobals();
|
|
267
|
+
if (CONFIGURES_BACKEND.has(commandPath(command)))
|
|
268
|
+
return options;
|
|
269
|
+
/*
|
|
270
|
+
* Resolution runs even with no `--workspace`, because a project's
|
|
271
|
+
* `.synomem/config.json` selects one without anybody passing a flag — that is
|
|
272
|
+
* the whole point of it. `--home` still wins outright: it names a home
|
|
273
|
+
* directly rather than a workspace within one.
|
|
274
|
+
*/
|
|
275
|
+
if (!options.home) {
|
|
276
|
+
const selection = resolveWorkspaceSelection({
|
|
277
|
+
...(options.workspace ? { flag: options.workspace } : {}),
|
|
278
|
+
...(options.actor ? { actorFlag: options.actor } : {}),
|
|
279
|
+
env,
|
|
280
|
+
});
|
|
281
|
+
return { ...options, home: selection.home, actor: selection.actor ?? options.actor };
|
|
282
|
+
}
|
|
283
|
+
if (!options.workspace)
|
|
284
|
+
return options;
|
|
285
|
+
/*
|
|
286
|
+
* One flag, one meaning — "which workspace" — resolved differently by the
|
|
287
|
+
* handful of commands that CONFIGURE a backend rather than act inside one.
|
|
288
|
+
* For those, the value is a hosted workspace ID to be written to the config,
|
|
289
|
+
* so it is passed through raw and they read `workspace`. Everywhere else it
|
|
290
|
+
* names a local workspace, which is a home.
|
|
291
|
+
*
|
|
292
|
+
* These commands used to declare their own `--workspace`, which does not
|
|
293
|
+
* work: Commander gives a duplicated long flag to the parent, so the
|
|
294
|
+
* subcommand never received it at all.
|
|
295
|
+
*/
|
|
296
|
+
return { ...options, home: localWorkspaceHome(options.workspace, options.home) };
|
|
297
|
+
};
|
|
200
298
|
const credentialStore = dependencies.credentialStore ?? new OsCredentialStore();
|
|
201
299
|
const oauthLogin = dependencies.oauthLogin ?? loginWithOAuth;
|
|
202
|
-
const
|
|
300
|
+
const discoverWorkspaces = dependencies.discoverAccessKeyWorkspaces ?? discoverAccessKeyWorkspaces;
|
|
203
301
|
const promptIo = dependencies.promptIo ?? defaultPromptIo();
|
|
204
302
|
const verifyRemoteCredential = dependencies.verifyRemoteCredential ??
|
|
205
303
|
(async (options) => {
|
|
@@ -224,9 +322,86 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
|
|
|
224
322
|
.description('Local-first communication, memory, recognition, and task infrastructure for agents')
|
|
225
323
|
.version(packageVersion())
|
|
226
324
|
.option('--home <path>', 'storage root (defaults to SYNOMEM_HOME or ~/.synomem)')
|
|
325
|
+
// A local workspace is its own database under the root, so this selects a
|
|
326
|
+
// home. On a remote backend the hosted workspace is chosen by
|
|
327
|
+
// `backend use remote --workspace` instead.
|
|
328
|
+
.option('--workspace <name>', 'local workspace to act in (see `synomem workspace list`)')
|
|
329
|
+
.option('--actor <id>', 'act as this agent (overrides SYNOMEM_ACTOR_ID)')
|
|
227
330
|
.option('--json', 'emit stable machine-readable JSON', false)
|
|
228
331
|
.showSuggestionAfterError()
|
|
229
332
|
.configureOutput({ writeOut: io.stdout, writeErr: io.stderr });
|
|
333
|
+
/*
|
|
334
|
+
* Local workspaces.
|
|
335
|
+
*
|
|
336
|
+
* Each is a separate database in its own home, which is what makes the
|
|
337
|
+
* isolation real: SQLite has no row-level security, so a shared file would
|
|
338
|
+
* rest on every query remembering to filter, with nothing to catch a miss.
|
|
339
|
+
* Separate files mean cross-workspace leakage is not something anybody can
|
|
340
|
+
* write by accident.
|
|
341
|
+
*/
|
|
342
|
+
const workspaceCommand = program
|
|
343
|
+
.command('workspace')
|
|
344
|
+
.description('Work in a separate local store, isolated from the others');
|
|
345
|
+
workspaceCommand
|
|
346
|
+
.command('list')
|
|
347
|
+
.description('List the local workspaces on this machine')
|
|
348
|
+
.action((_options, command) => {
|
|
349
|
+
const global = globals(command);
|
|
350
|
+
// Read from disk, so nothing is listed that does not exist.
|
|
351
|
+
const workspaces = listLocalWorkspaces(global.home);
|
|
352
|
+
// Which one is in effect here, and what decided it — a flag, the
|
|
353
|
+
// environment, a project file, or nothing.
|
|
354
|
+
const selection = resolveWorkspaceSelection({ env });
|
|
355
|
+
const human = workspaces
|
|
356
|
+
.map((workspace) => `${workspace.name === DEFAULT_WORKSPACE ? '*' : ' '} ${workspace.name.padEnd(24)} ${workspace.initialized ? workspace.home : `${workspace.home} (not initialized)`}`)
|
|
357
|
+
.join('\n');
|
|
358
|
+
output(io, global.json, { workspaces, active: selection.workspace ?? DEFAULT_WORKSPACE, source: selection.source }, [
|
|
359
|
+
human,
|
|
360
|
+
'',
|
|
361
|
+
`Acting in: ${selection.workspace ?? DEFAULT_WORKSPACE} (from ${selection.source})`,
|
|
362
|
+
'',
|
|
363
|
+
'Bind a directory with `synomem workspace use <name>`, or pass --workspace once.',
|
|
364
|
+
'A local workspace is a separate store on this machine; a hosted workspace is shared,',
|
|
365
|
+
'and is selected with `backend use remote --workspace`.',
|
|
366
|
+
].join('\n'));
|
|
367
|
+
});
|
|
368
|
+
workspaceCommand
|
|
369
|
+
.command('use <name>')
|
|
370
|
+
.description('Bind this directory to a workspace, for every session opened here')
|
|
371
|
+
.option('--as <actor-id>', 'also always write as this agent', actingDefault)
|
|
372
|
+
.action((name, options, command) => {
|
|
373
|
+
const global = globals(command);
|
|
374
|
+
// Validated by resolving it, so a name that could never work is refused
|
|
375
|
+
// before a file claiming it is written.
|
|
376
|
+
const home = localWorkspaceHome(name, undefined);
|
|
377
|
+
const path = writeProjectSelection(process.cwd(), {
|
|
378
|
+
workspace: name,
|
|
379
|
+
...(options.as ? { actor: options.as } : {}),
|
|
380
|
+
});
|
|
381
|
+
output(io, global.json, { path, workspace: name, home, ...(options.as ? { actor: options.as } : {}) }, [
|
|
382
|
+
`Wrote ${path}`,
|
|
383
|
+
'',
|
|
384
|
+
`Every Synomem command and MCP server started in this directory now acts in ${name}${options.as ? ` as ${options.as}` : ''}, with no flag.`,
|
|
385
|
+
`Records live in ${home} — nothing is stored in this directory.`,
|
|
386
|
+
'',
|
|
387
|
+
'Commit it to share the choice with the repository, or ignore it to keep it yours.',
|
|
388
|
+
].join('\n'));
|
|
389
|
+
});
|
|
390
|
+
workspaceCommand
|
|
391
|
+
.command('create <name>')
|
|
392
|
+
.description('Create a local workspace and initialize its store')
|
|
393
|
+
.action(async (name, _options, command) => {
|
|
394
|
+
const global = globals(command);
|
|
395
|
+
const home = localWorkspaceHome(name, global.home);
|
|
396
|
+
if (readSynomemConfig(home)) {
|
|
397
|
+
throw new SynomemError('INVALID_INPUT', `Workspace already exists: ${name}`);
|
|
398
|
+
}
|
|
399
|
+
writeSynomemBackend({ kind: 'local' }, home);
|
|
400
|
+
// Opening it once creates the database, so `list` does not report a
|
|
401
|
+
// workspace that exists in name only.
|
|
402
|
+
await withClient(home, defaultActor(env, 'system', 'cli'), async () => undefined);
|
|
403
|
+
output(io, global.json, { name, home }, `Created workspace ${name} at ${home}.\nAct in it with --workspace ${name}.`);
|
|
404
|
+
});
|
|
230
405
|
const remoteCommand = program.command('remote').description('Administer a remote workspace');
|
|
231
406
|
/*
|
|
232
407
|
* The browser counterpart to an access key naming its own workspace.
|
|
@@ -347,18 +522,43 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
|
|
|
347
522
|
const home = plan.home;
|
|
348
523
|
const serviceUrl = plan.serviceUrl ?? cloudApiUrl(env);
|
|
349
524
|
/*
|
|
350
|
-
*
|
|
351
|
-
*
|
|
352
|
-
*
|
|
353
|
-
*
|
|
354
|
-
*
|
|
355
|
-
* network round trip to configure a machine.
|
|
525
|
+
* A member-owned access key reaches every workspace in its organization,
|
|
526
|
+
* never just one, so the workspace is discovered and then chosen — not
|
|
527
|
+
* typed from memory, and not assumed. An explicit --workspace still wins
|
|
528
|
+
* regardless: automation should not depend on a network round trip, and a
|
|
529
|
+
* person who already knows which one they want should not be asked again.
|
|
356
530
|
*/
|
|
357
531
|
let workspaceId = plan.workspaceId;
|
|
358
532
|
if (plan.backend === 'remote' && !workspaceId && token) {
|
|
359
|
-
const
|
|
360
|
-
|
|
361
|
-
|
|
533
|
+
const discovered = await discoverWorkspaces({ baseUrl: serviceUrl, accessToken: token });
|
|
534
|
+
if (discovered.workspaces.length === 0) {
|
|
535
|
+
throw new SynomemError('INVALID_INPUT', [
|
|
536
|
+
"This access key's organization has no workspaces yet.",
|
|
537
|
+
'Create one in the Synomem portal, then re-run this command — or pass',
|
|
538
|
+
'--workspace <workspace-id> once one exists.',
|
|
539
|
+
].join('\n'));
|
|
540
|
+
}
|
|
541
|
+
else if (discovered.workspaces.length === 1) {
|
|
542
|
+
workspaceId = discovered.workspaces[0].id;
|
|
543
|
+
io.stdout(`Using this key's only workspace: ${discovered.workspaces[0].displayName} (${workspaceId}).\n`);
|
|
544
|
+
}
|
|
545
|
+
else if (promptIo.interactive) {
|
|
546
|
+
workspaceId = await select(promptIo, 'Which workspace should this machine use?', discovered.workspaces.map((workspace) => ({
|
|
547
|
+
value: workspace.id,
|
|
548
|
+
label: workspace.displayName,
|
|
549
|
+
detail: workspace.id,
|
|
550
|
+
})));
|
|
551
|
+
}
|
|
552
|
+
else {
|
|
553
|
+
throw new SynomemError('INVALID_INPUT', [
|
|
554
|
+
'This access key can reach more than one workspace, so a non-interactive',
|
|
555
|
+
'setup needs to be told which one:',
|
|
556
|
+
'',
|
|
557
|
+
...discovered.workspaces.map((workspace) => ` ${workspace.id} ${workspace.displayName}`),
|
|
558
|
+
'',
|
|
559
|
+
'Re-run with --workspace <workspace-id>.',
|
|
560
|
+
].join('\n'));
|
|
561
|
+
}
|
|
362
562
|
}
|
|
363
563
|
if (plan.backend === 'remote' && !workspaceId) {
|
|
364
564
|
/*
|
|
@@ -449,7 +649,6 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
|
|
|
449
649
|
.description('Configure Synomem without prompting')
|
|
450
650
|
.option('--backend <kind>', 'local or remote')
|
|
451
651
|
.option('--auth <method>', 'browser or access-key')
|
|
452
|
-
.option('--workspace <id>', 'remote workspace ID')
|
|
453
652
|
.option('--credential-store <where>', 'auto, keychain, file, or environment', 'auto')
|
|
454
653
|
// The token is read from stdin, never taken as an argument: an argument is
|
|
455
654
|
// kept by the shell history and visible in the process list.
|
|
@@ -461,10 +660,10 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
|
|
|
461
660
|
throw new SynomemError('INVALID_INPUT', 'Pass --backend local or --backend remote.');
|
|
462
661
|
}
|
|
463
662
|
const backend = options.backend;
|
|
464
|
-
// An access key
|
|
465
|
-
// required when there is no key to ask.
|
|
466
|
-
if (backend === 'remote' && !
|
|
467
|
-
throw new SynomemError('INVALID_INPUT', 'Remote setup requires --workspace, or --access-token-stdin so the key can
|
|
663
|
+
// An access key can be asked which workspaces it reaches, so
|
|
664
|
+
// --workspace is only required when there is no key to ask.
|
|
665
|
+
if (backend === 'remote' && !global.workspace && !options.accessTokenStdin) {
|
|
666
|
+
throw new SynomemError('INVALID_INPUT', 'Remote setup requires --workspace, or --access-token-stdin so the key can be asked.');
|
|
468
667
|
}
|
|
469
668
|
const token = options.accessTokenStdin ? await readAccessToken(promptIo) : undefined;
|
|
470
669
|
if (backend === 'remote' && options.auth === 'access-key' && !token) {
|
|
@@ -477,7 +676,7 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
|
|
|
477
676
|
? {
|
|
478
677
|
serviceUrl: cloudApiUrl(env),
|
|
479
678
|
auth: options.auth ?? 'access-key',
|
|
480
|
-
workspaceId:
|
|
679
|
+
workspaceId: global.workspace,
|
|
481
680
|
credentialStore: options.credentialStore,
|
|
482
681
|
}
|
|
483
682
|
: {}),
|
|
@@ -594,13 +793,12 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
|
|
|
594
793
|
// never ask for a service address, because a person has no way to tell a
|
|
595
794
|
// real one from a phished one.
|
|
596
795
|
.option('--url <url>', 'internal: alternate HTTPS origin')
|
|
597
|
-
.option('--workspace <id>', 'remote workspace ID')
|
|
598
796
|
.action((kind, options, command) => {
|
|
599
797
|
const global = globals(command);
|
|
600
798
|
if (kind !== 'local' && kind !== 'remote') {
|
|
601
799
|
throw new SynomemError('INVALID_INPUT', 'Backend kind must be local or remote.');
|
|
602
800
|
}
|
|
603
|
-
if (kind === 'remote' && !
|
|
801
|
+
if (kind === 'remote' && !global.workspace) {
|
|
604
802
|
throw new SynomemError('INVALID_INPUT', 'Remote backend selection requires --workspace.');
|
|
605
803
|
}
|
|
606
804
|
const config = writeSynomemBackend(kind === 'local'
|
|
@@ -608,7 +806,7 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
|
|
|
608
806
|
: {
|
|
609
807
|
kind: 'remote',
|
|
610
808
|
baseUrl: options.url ?? cloudApiUrl(env),
|
|
611
|
-
workspaceId:
|
|
809
|
+
workspaceId: global.workspace,
|
|
612
810
|
}, global.home);
|
|
613
811
|
output(io, global.json, { backend: config.backend }, `Selected ${config.backend.kind} Synomem backend.`);
|
|
614
812
|
});
|
|
@@ -1066,7 +1264,7 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
|
|
|
1066
1264
|
postCommand
|
|
1067
1265
|
.command('create')
|
|
1068
1266
|
.description('Publish a post the whole workspace can read')
|
|
1069
|
-
.requiredOption('--as <actor-id>')
|
|
1267
|
+
.requiredOption('--as <actor-id>', 'actor to act as (defaults to --actor)', actingDefault)
|
|
1070
1268
|
.option('--actor-kind <kind>', 'human, agent, or system', 'agent')
|
|
1071
1269
|
.requiredOption('--title <title>')
|
|
1072
1270
|
.requiredOption('--body <body>')
|
|
@@ -1085,7 +1283,7 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
|
|
|
1085
1283
|
postCommand
|
|
1086
1284
|
.command('list')
|
|
1087
1285
|
.description('List posts in this workspace')
|
|
1088
|
-
.requiredOption('--as <actor-id>')
|
|
1286
|
+
.requiredOption('--as <actor-id>', 'actor to act as (defaults to --actor)', actingDefault)
|
|
1089
1287
|
.option('--actor-kind <kind>', 'human, agent, or system', 'agent')
|
|
1090
1288
|
.option('--limit <n>', 'default 10, maximum 50')
|
|
1091
1289
|
.action(async (options, command) => {
|
|
@@ -1099,7 +1297,7 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
|
|
|
1099
1297
|
postCommand
|
|
1100
1298
|
.command('show <post-id>')
|
|
1101
1299
|
.description('Show one post with its acknowledgements')
|
|
1102
|
-
.requiredOption('--as <actor-id>')
|
|
1300
|
+
.requiredOption('--as <actor-id>', 'actor to act as (defaults to --actor)', actingDefault)
|
|
1103
1301
|
.option('--actor-kind <kind>', 'human, agent, or system', 'agent')
|
|
1104
1302
|
.action(async (postId, options, command) => {
|
|
1105
1303
|
const global = globals(command);
|
|
@@ -1114,7 +1312,7 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
|
|
|
1114
1312
|
postCommand
|
|
1115
1313
|
.command('acknowledge <post-id>')
|
|
1116
1314
|
.description('Say you have seen a post')
|
|
1117
|
-
.requiredOption('--as <actor-id>')
|
|
1315
|
+
.requiredOption('--as <actor-id>', 'actor to act as (defaults to --actor)', actingDefault)
|
|
1118
1316
|
.option('--actor-kind <kind>', 'human, agent, or system', 'agent')
|
|
1119
1317
|
.option('--note <text>', 'optional context for the author')
|
|
1120
1318
|
.action(async (postId, options, command) => {
|
|
@@ -1128,7 +1326,7 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
|
|
|
1128
1326
|
postCommand
|
|
1129
1327
|
.command('roster <post-id>')
|
|
1130
1328
|
.description('Who has acknowledged a post, and who has not')
|
|
1131
|
-
.requiredOption('--as <actor-id>')
|
|
1329
|
+
.requiredOption('--as <actor-id>', 'actor to act as (defaults to --actor)', actingDefault)
|
|
1132
1330
|
.option('--actor-kind <kind>', 'human, agent, or system', 'agent')
|
|
1133
1331
|
.action(async (postId, options, command) => {
|
|
1134
1332
|
const global = globals(command);
|
|
@@ -1153,7 +1351,7 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
|
|
|
1153
1351
|
postCommand
|
|
1154
1352
|
.command('archive <post-id>')
|
|
1155
1353
|
.description('Archive a post you wrote')
|
|
1156
|
-
.requiredOption('--as <actor-id>')
|
|
1354
|
+
.requiredOption('--as <actor-id>', 'actor to act as (defaults to --actor)', actingDefault)
|
|
1157
1355
|
.option('--actor-kind <kind>', 'human, agent, or system', 'agent')
|
|
1158
1356
|
.option('--reason <text>')
|
|
1159
1357
|
.action(async (postId, options, command) => {
|
|
@@ -1165,7 +1363,7 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
|
|
|
1165
1363
|
kudosCommand
|
|
1166
1364
|
.command('give <recipient>')
|
|
1167
1365
|
.description('Give specific, evidence-based kudos to an agent')
|
|
1168
|
-
.requiredOption('--from <actor-id>', 'stable ID of the giver')
|
|
1366
|
+
.requiredOption('--from <actor-id>', 'stable ID of the giver (defaults to --actor)', actingDefault)
|
|
1169
1367
|
.requiredOption('--actor-kind <kind>', 'human, agent, or system')
|
|
1170
1368
|
.option('--actor-name <display-name>')
|
|
1171
1369
|
.requiredOption('--title <title>')
|
|
@@ -1258,7 +1456,7 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
|
|
|
1258
1456
|
const memoCommand = program.command('memo').description('Send and manage durable messages');
|
|
1259
1457
|
memoCommand
|
|
1260
1458
|
.command('send <recipient>')
|
|
1261
|
-
.requiredOption('--from <actor-id>')
|
|
1459
|
+
.requiredOption('--from <actor-id>', 'actor to act as (defaults to --actor)', actingDefault)
|
|
1262
1460
|
.option('--actor-kind <kind>', 'human, agent, or system', 'agent')
|
|
1263
1461
|
.option('--actor-name <name>')
|
|
1264
1462
|
.requiredOption('--subject <subject>')
|
|
@@ -1319,7 +1517,7 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
|
|
|
1319
1517
|
.description('Retain and revise agent-owned knowledge');
|
|
1320
1518
|
noteCommand
|
|
1321
1519
|
.command('create')
|
|
1322
|
-
.requiredOption('--as <actor-id>')
|
|
1520
|
+
.requiredOption('--as <actor-id>', 'actor to act as (defaults to --actor)', actingDefault)
|
|
1323
1521
|
.option('--actor-kind <kind>', 'agent or human', 'agent')
|
|
1324
1522
|
.option('--owner <agent-id>')
|
|
1325
1523
|
.requiredOption('--title <title>')
|
|
@@ -1358,7 +1556,7 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
|
|
|
1358
1556
|
});
|
|
1359
1557
|
noteCommand
|
|
1360
1558
|
.command('revise <note-id>')
|
|
1361
|
-
.requiredOption('--as <actor-id>')
|
|
1559
|
+
.requiredOption('--as <actor-id>', 'actor to act as (defaults to --actor)', actingDefault)
|
|
1362
1560
|
.option('--actor-kind <kind>', 'agent or human', 'agent')
|
|
1363
1561
|
.requiredOption('--expected-version <number>')
|
|
1364
1562
|
.option('--title <title>')
|
|
@@ -1379,7 +1577,7 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
|
|
|
1379
1577
|
});
|
|
1380
1578
|
noteCommand
|
|
1381
1579
|
.command('archive <note-id>')
|
|
1382
|
-
.requiredOption('--as <actor-id>')
|
|
1580
|
+
.requiredOption('--as <actor-id>', 'actor to act as (defaults to --actor)', actingDefault)
|
|
1383
1581
|
.option('--actor-kind <kind>', 'agent or human', 'agent')
|
|
1384
1582
|
.option('--idempotency-key <key>')
|
|
1385
1583
|
.action(async (id, options, command) => {
|
|
@@ -1395,7 +1593,7 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
|
|
|
1395
1593
|
.description('Create and manage your own private reminders');
|
|
1396
1594
|
todoCommand
|
|
1397
1595
|
.command('create')
|
|
1398
|
-
.requiredOption('--as <actor-id>')
|
|
1596
|
+
.requiredOption('--as <actor-id>', 'actor to act as (defaults to --actor)', actingDefault)
|
|
1399
1597
|
.option('--actor-kind <kind>', 'human, agent, or system', 'agent')
|
|
1400
1598
|
.requiredOption('--title <title>')
|
|
1401
1599
|
.option('--details <text>', 'private working detail')
|
|
@@ -1419,7 +1617,7 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
|
|
|
1419
1617
|
});
|
|
1420
1618
|
todoCommand
|
|
1421
1619
|
.command('list')
|
|
1422
|
-
.requiredOption('--as <actor-id>')
|
|
1620
|
+
.requiredOption('--as <actor-id>', 'actor to act as (defaults to --actor)', actingDefault)
|
|
1423
1621
|
.option('--actor-kind <kind>', 'human, agent, or system', 'agent')
|
|
1424
1622
|
.option('--status <status>')
|
|
1425
1623
|
.option('--limit <number>', 'maximum results', '10')
|
|
@@ -1437,7 +1635,7 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
|
|
|
1437
1635
|
});
|
|
1438
1636
|
todoCommand
|
|
1439
1637
|
.command('show <todo-id>')
|
|
1440
|
-
.requiredOption('--as <actor-id>')
|
|
1638
|
+
.requiredOption('--as <actor-id>', 'actor to act as (defaults to --actor)', actingDefault)
|
|
1441
1639
|
.option('--actor-kind <kind>', 'human, agent, or system', 'agent')
|
|
1442
1640
|
.action(async (id, options, command) => {
|
|
1443
1641
|
const global = globals(command);
|
|
@@ -1447,7 +1645,7 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
|
|
|
1447
1645
|
for (const operation of ['complete', 'reopen', 'cancel', 'archive']) {
|
|
1448
1646
|
todoCommand
|
|
1449
1647
|
.command(`${operation} <todo-id>`)
|
|
1450
|
-
.requiredOption('--as <actor-id>')
|
|
1648
|
+
.requiredOption('--as <actor-id>', 'actor to act as (defaults to --actor)', actingDefault)
|
|
1451
1649
|
.option('--actor-kind <kind>', 'human, agent, or system', 'agent')
|
|
1452
1650
|
.option('--note <text>')
|
|
1453
1651
|
.option('--reason <text>')
|
|
@@ -1485,7 +1683,7 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
|
|
|
1485
1683
|
const taskCommand = program.command('task').description('Create and manage agent tasks');
|
|
1486
1684
|
taskCommand
|
|
1487
1685
|
.command('create <assignee>')
|
|
1488
|
-
.requiredOption('--from <actor-id>')
|
|
1686
|
+
.requiredOption('--from <actor-id>', 'actor to act as (defaults to --actor)', actingDefault)
|
|
1489
1687
|
.option('--actor-kind <kind>', 'human, agent, or system', 'agent')
|
|
1490
1688
|
.requiredOption('--title <title>')
|
|
1491
1689
|
.option('--description <text>')
|
|
@@ -1531,7 +1729,7 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
|
|
|
1531
1729
|
});
|
|
1532
1730
|
taskCommand
|
|
1533
1731
|
.command('update <task-id>')
|
|
1534
|
-
.requiredOption('--as <actor-id>')
|
|
1732
|
+
.requiredOption('--as <actor-id>', 'actor to act as (defaults to --actor)', actingDefault)
|
|
1535
1733
|
.option('--actor-kind <kind>', 'agent or human', 'agent')
|
|
1536
1734
|
.requiredOption('--expected-version <number>')
|
|
1537
1735
|
.option('--title <title>')
|
|
@@ -1561,7 +1759,7 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
|
|
|
1561
1759
|
for (const operation of ['accept', 'reject', 'complete', 'reopen', 'cancel']) {
|
|
1562
1760
|
const command_ = taskCommand
|
|
1563
1761
|
.command(`${operation} <task-id>`)
|
|
1564
|
-
.requiredOption('--as <actor-id>')
|
|
1762
|
+
.requiredOption('--as <actor-id>', 'actor to act as (defaults to --actor)', actingDefault)
|
|
1565
1763
|
.option('--actor-kind <kind>', 'agent or human', 'agent')
|
|
1566
1764
|
.option('--note <text>')
|
|
1567
1765
|
.option('--reason <text>')
|
|
@@ -1637,7 +1835,7 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
|
|
|
1637
1835
|
kudosCommand
|
|
1638
1836
|
.command('revoke <kudos-id>')
|
|
1639
1837
|
.description('Record a revocation while preserving history')
|
|
1640
|
-
.requiredOption('--as <actor-id>')
|
|
1838
|
+
.requiredOption('--as <actor-id>', 'actor to act as (defaults to --actor)', actingDefault)
|
|
1641
1839
|
.option('--actor-kind <kind>', 'human, agent, or system', 'human')
|
|
1642
1840
|
.requiredOption('--reason <reason>')
|
|
1643
1841
|
.option('--administrative', 'mark as an administrative revocation', false)
|
|
@@ -1779,7 +1977,7 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
|
|
|
1779
1977
|
return program;
|
|
1780
1978
|
}
|
|
1781
1979
|
export async function runCli(argv = process.argv, io = defaultIo, serviceFactory = configuredServiceFactory, dependencies = {}) {
|
|
1782
|
-
const program = createCli(io, serviceFactory, dependencies);
|
|
1980
|
+
const program = createCli(io, serviceFactory, dependencies, argv);
|
|
1783
1981
|
program.exitOverride();
|
|
1784
1982
|
try {
|
|
1785
1983
|
await program.parseAsync(argv);
|