@empirical-gauzza/cli 1.0.39 → 1.0.41
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/bin/empirical.js +1 -1
- package/lib/constants.js +6 -2
- package/lib/help.js +3 -1
- package/lib/installWizard.js +17 -3
- package/lib/shapeCommands.js +33 -11
- package/lib/workspaceCommands.js +71 -12
- package/package.json +33 -33
package/bin/empirical.js
CHANGED
package/lib/constants.js
CHANGED
|
@@ -352,13 +352,15 @@ A new interview is charged. \`setup abandon\` stops one but does not return what
|
|
|
352
352
|
empirical workspace shape show --workspace-id <id>
|
|
353
353
|
empirical workspace shape add-type "Reviewer" --workspace-id <id> --statuses "Not Yet Invited, Invited, Call Completed (done)" --fields "Email:text:required; Open to a call:select:Yes|No|Not asked; Wave:number" [--apply]
|
|
354
354
|
empirical workspace shape add-status <type-key> "Declined" --done --workspace-id <id> [--apply]
|
|
355
|
+
empirical workspace shape add-status <type-key> "Call Scheduled" --after form_submitted --workspace-id <id> [--apply]
|
|
356
|
+
empirical workspace shape reorder-statuses <type-key> new,form_submitted,call_scheduled,call_held,won --workspace-id <id> [--apply]
|
|
355
357
|
empirical workspace shape add-field <type-key> "Wave" --kind number --workspace-id <id> [--apply]
|
|
356
358
|
empirical workspace shape edit --file <edit.json> [--mapping-file <m.json>] --workspace-id <id> [--apply]
|
|
357
359
|
empirical workspace shape set --file <shape.json> --workspace-id <id> [--apply]
|
|
358
360
|
|
|
359
361
|
The interview is the guided way and is charged. These are the direct way: you name the change and nothing is charged. Every change is a dry run until you add \`--apply\`: it is checked against the workspace's current shape, the result is printed, and with \`--apply\` it is saved only if the shape is still the one that was shown. You must manage the workspace; \`--apply\` needs \`--workspace-id\` written out, so a stale directory binding cannot aim a change at the wrong workspace.
|
|
360
362
|
|
|
361
|
-
\`add-type\` on a workspace with no shape yet also needs \`--purpose "what this workspace is for"\` and creates its first shape. A kind of item with statuses must mark one as finished with \`(done)\`, and takes at most six fields. Field kinds: text, longText, number, date, select, multiSelect, person, link, url. \`--category\` and \`--node-type\` say how an item is stored as a memory (default project and entity). Keys are made from labels once and never change; refer to a type or status later by its key, which \`show\` prints in brackets.
|
|
363
|
+
\`add-type\` on a workspace with no shape yet also needs \`--purpose "what this workspace is for"\` and creates its first shape. A kind of item with statuses must mark one as finished with \`(done)\`, and takes at most six fields. Field kinds: text, longText, number, date, select, multiSelect, person, link, url. \`--category\` and \`--node-type\` say how an item is stored as a memory (default project and entity). Keys are made from labels once and never change; refer to a type or status later by its key, which \`show\` prints in brackets. A new status goes just before the first finished status unless \`--after <key>\` or \`--before <key>\` places it; \`reorder-statuses\` takes every status key of the type once, in the new order (the first status is where new items start). Neither moves any item.
|
|
362
364
|
|
|
363
365
|
\`edit\` takes the server's merge format: \`{"itemTypes":{"add":[...],"update":[{"key":"reviewer","statuses":{"add":[{"label":"Declined","isDone":true}]}}],"remove":["old-type"]}}\`. Removing or renaming something that has stored values needs \`--mapping-file\` saying where those values go. To save an item into the shape: \`empirical memory record --category lead --summary "..." --item-type reviewer --item-status invited --field email=a@b.com\` (run with \`EMPIRICAL_WORKSPACE_ID=<id>\` or after \`workspace use\`).
|
|
364
366
|
|
|
@@ -386,8 +388,10 @@ Resolution order for every memory command, first match wins: the \`EMPIRICAL_WOR
|
|
|
386
388
|
empirical workspace invite create --role writer --expires-in-days 7 --max-uses 3
|
|
387
389
|
empirical workspace invite list
|
|
388
390
|
empirical workspace invite revoke <token>
|
|
391
|
+
empirical workspace invite email ana@example.com ben@example.com --role writer --note "Come help us review the book."
|
|
392
|
+
empirical workspace invite email ana@example.com --note-file ./invite-note.txt --from-name "Matt" --workspace-id ws_team_abc
|
|
389
393
|
|
|
390
|
-
Invite commands operate on the active team workspace
|
|
394
|
+
Invite commands operate on the active team workspace, or the one named by \`--workspace-id\`. Create prints a shareable URL. Email sends each address its own invite (one address per seat, expires in 14 days) and prints who was invited and why anyone was not; \`--note\` (up to 1000 characters) appears in the email as your own words, and \`--from-name\` changes the name the email says it is from ("Matt invited you") for that send only; without it your account's display name is used.
|
|
391
395
|
|
|
392
396
|
### Soft-delete a demo workspace
|
|
393
397
|
|
package/lib/help.js
CHANGED
|
@@ -53,13 +53,15 @@ function showHelp() {
|
|
|
53
53
|
log(' Re-shape a team workspace you manage by interview. Without --workspace-id it');
|
|
54
54
|
log(' confirms the workspace bound to this directory with you, at a terminal only;');
|
|
55
55
|
log(' in a script or with --accept-cost it needs --workspace-id. A new interview is charged');
|
|
56
|
-
log(' empirical workspace shape show|set|edit|add-type|add-status|add-field --workspace-id <id> ...');
|
|
56
|
+
log(' empirical workspace shape show|set|edit|add-type|add-status|add-field|reorder-statuses --workspace-id <id> ...');
|
|
57
57
|
log(' Edit the item types, statuses and fields of a workspace by hand, with no interview and no');
|
|
58
58
|
log(' charge. A dry run until --apply. Details: empirical doc workspace');
|
|
59
59
|
log(' empirical workspace invite create [--role reader|writer|manager] [--expires-in-days <n>] [--max-uses <n>]');
|
|
60
60
|
log(' Create a shareable invite link for the active team workspace');
|
|
61
61
|
log(' empirical workspace invite list List active invites for the active team workspace');
|
|
62
62
|
log(' empirical workspace invite revoke <token> Revoke a previously created invite');
|
|
63
|
+
log(' empirical workspace invite email <address...> [--role reader|writer] [--note <text> | --note-file <path>] [--from-name <name>]');
|
|
64
|
+
log(' Email each person an invite, with your own note in the email');
|
|
63
65
|
log(' empirical workspace delete <number|name|id> [--confirm]');
|
|
64
66
|
log(' Soft-delete a team workspace (dry-run without --confirm)');
|
|
65
67
|
log('');
|
package/lib/installWizard.js
CHANGED
|
@@ -36,6 +36,17 @@ function pluginNamesForInstallerOptions(options = {}) {
|
|
|
36
36
|
return names.filter((name) => name === 'empirical-memory' || OPTIONAL_PLUGIN_NAMES.includes(name));
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
// Which detected CLIs to install for. At a terminal the person picks (Enter for all). With no
|
|
40
|
+
// terminal, which is how an agent like Claude Code runs `empirical install` for someone, there is
|
|
41
|
+
// nobody to answer, so it takes the default the list already shows: every detected CLI. Asking
|
|
42
|
+
// anyway read an empty stdin and stopped with "nothing installed", so the agent's install did
|
|
43
|
+
// nothing unless it knew to pipe an answer in.
|
|
44
|
+
async function chooseInstallTargets(targets, { interactive = Boolean(process.stdin.isTTY), askFn = ask } = {}) {
|
|
45
|
+
if (!interactive) return targets.map((_, index) => index);
|
|
46
|
+
const answer = await askFn(uiQuestion('Press Enter for all, or select numbers (e.g. 1,2): '));
|
|
47
|
+
return parseSelection(answer, targets.length);
|
|
48
|
+
}
|
|
49
|
+
|
|
39
50
|
async function runInstallFlow() {
|
|
40
51
|
log(uiHeader(`Empirical installer v${getCliVersion()}`));
|
|
41
52
|
log('');
|
|
@@ -54,8 +65,10 @@ async function runInstallFlow() {
|
|
|
54
65
|
});
|
|
55
66
|
log('');
|
|
56
67
|
|
|
57
|
-
const
|
|
58
|
-
|
|
68
|
+
const selectedIndexes = await chooseInstallTargets(targets);
|
|
69
|
+
if (!process.stdin.isTTY) {
|
|
70
|
+
log(`No terminal attached, so installing for every detected CLI: ${targets.map((t) => t.label).join(', ')}.`);
|
|
71
|
+
}
|
|
59
72
|
|
|
60
73
|
if (selectedIndexes.length === 0) {
|
|
61
74
|
log('No valid selection; nothing installed.');
|
|
@@ -407,5 +420,6 @@ async function runInstallFlow() {
|
|
|
407
420
|
|
|
408
421
|
module.exports = {
|
|
409
422
|
runInstallFlow,
|
|
410
|
-
pluginNamesForInstallerOptions
|
|
423
|
+
pluginNamesForInstallerOptions,
|
|
424
|
+
chooseInstallTargets
|
|
411
425
|
};
|
package/lib/shapeCommands.js
CHANGED
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
// empirical workspace shape set --workspace-id <id> --file <shape.json> [--apply]
|
|
7
7
|
// empirical workspace shape edit --workspace-id <id> --file <edit.json> [--apply]
|
|
8
8
|
// empirical workspace shape add-type --workspace-id <id> <label> [--statuses "A,B,C (done)"] ...
|
|
9
|
-
// empirical workspace shape add-status --workspace-id <id> <type-key> <label> [--done]
|
|
9
|
+
// empirical workspace shape add-status --workspace-id <id> <type-key> <label> [--done] [--after <key> | --before <key>]
|
|
10
|
+
// empirical workspace shape reorder-statuses --workspace-id <id> <type-key> <key,key,...>
|
|
10
11
|
// empirical workspace shape add-field --workspace-id <id> <type-key> <label> --kind <kind> ...
|
|
11
12
|
//
|
|
12
13
|
// The interview is charged (100 Empirical Tokens, plus 25 per change to a draft) and takes nine
|
|
@@ -16,23 +17,24 @@
|
|
|
16
17
|
//
|
|
17
18
|
// Every change is a DRY RUN until --apply. A change goes to /shape/preview first, which validates it
|
|
18
19
|
// against the active shape and returns a confirmDigest; --apply sends that digest back, so an edit
|
|
19
|
-
// is only ever applied to the shape that was shown. The commands here
|
|
20
|
-
//
|
|
20
|
+
// is only ever applied to the shape that was shown. The commands here ADD (a type, a status, a field)
|
|
21
|
+
// and REORDER statuses; removing needs a mapping for the stored values, which `edit --mapping-file`
|
|
22
|
+
// accepts. A new status goes just before the first finished one unless --after or --before says where.
|
|
21
23
|
|
|
22
24
|
const fs = require('fs');
|
|
23
25
|
const { log, warn } = require('./ui');
|
|
24
26
|
const { httpJson } = require('./http');
|
|
25
27
|
const { parseOption, parseFlag } = require('./env');
|
|
26
28
|
|
|
27
|
-
const SHAPE_ACTIONS = ['show', 'set', 'edit', 'add-type', 'add-status', 'add-field'];
|
|
29
|
+
const SHAPE_ACTIONS = ['show', 'set', 'edit', 'add-type', 'add-status', 'add-field', 'reorder-statuses'];
|
|
28
30
|
const FIELD_KINDS = ['text', 'longText', 'number', 'date', 'select', 'multiSelect', 'person', 'link', 'url'];
|
|
29
31
|
const VALUE_OPTIONS = new Set([
|
|
30
32
|
'--workspace-id', '--file', '--mapping-file', '--purpose', '--plural', '--category', '--node-type',
|
|
31
|
-
'--icon', '--color', '--statuses', '--fields', '--nest-under', '--kind', '--options'
|
|
33
|
+
'--icon', '--color', '--statuses', '--fields', '--nest-under', '--kind', '--options', '--after', '--before'
|
|
32
34
|
]);
|
|
33
35
|
|
|
34
36
|
const SHAPE_USAGE = [
|
|
35
|
-
'Usage: empirical workspace shape <show|set|edit|add-type|add-status|add-field> --workspace-id <id> ...',
|
|
37
|
+
'Usage: empirical workspace shape <show|set|edit|add-type|add-status|add-field|reorder-statuses> --workspace-id <id> ...',
|
|
36
38
|
' show print the workspace\'s item types, statuses and fields',
|
|
37
39
|
' set --file <shape.json> [--apply] give a workspace with no shape its first shape',
|
|
38
40
|
' edit --file <edit.json> [--mapping-file <m.json>] [--apply]',
|
|
@@ -40,7 +42,9 @@ const SHAPE_USAGE = [
|
|
|
40
42
|
' add-type <label> [--statuses "A,B,C (done)"] [--fields "Email:text:required;Wave:number"]',
|
|
41
43
|
' [--plural <label>] [--category <cat>] [--node-type <type>] [--icon <i>] [--color <c>]',
|
|
42
44
|
' [--nest-under <type-key,...>] [--purpose <text>] (--purpose only when the workspace has no shape yet)',
|
|
43
|
-
' add-status <type-key> <label> [--done]',
|
|
45
|
+
' add-status <type-key> <label> [--done] [--after <status-key> | --before <status-key>]',
|
|
46
|
+
' without --after/--before it goes just before the first finished status',
|
|
47
|
+
' reorder-statuses <type-key> <key,key,...> every status key of that type once, in the new order',
|
|
44
48
|
' add-field <type-key> <label> [--kind text|longText|number|date|select|multiSelect|person|link|url]',
|
|
45
49
|
' [--required] [--options "Yes|No|Not asked"]',
|
|
46
50
|
'Changes are a dry run until you add --apply. Running the interview instead: `empirical workspace shape` with no action.'
|
|
@@ -116,8 +120,15 @@ function buildTypeFromFlags(args, label) {
|
|
|
116
120
|
return type;
|
|
117
121
|
}
|
|
118
122
|
|
|
119
|
-
function buildAddStatusEdit(typeKey, label, isDone) {
|
|
120
|
-
|
|
123
|
+
function buildAddStatusEdit(typeKey, label, isDone, position = {}) {
|
|
124
|
+
const status = isDone ? { label, isDone: true } : { label };
|
|
125
|
+
if (position.after) status.after = position.after;
|
|
126
|
+
if (position.before) status.before = position.before;
|
|
127
|
+
return { itemTypes: { update: [{ key: typeKey, statuses: { add: [status] } }] } };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function buildReorderStatusesEdit(typeKey, keys) {
|
|
131
|
+
return { itemTypes: { update: [{ key: typeKey, statuses: { order: keys } }] } };
|
|
121
132
|
}
|
|
122
133
|
|
|
123
134
|
function buildAddFieldEdit(typeKey, field) {
|
|
@@ -278,8 +289,18 @@ async function runManualShapeCommand({ args, baseUrl, authHeaders, resolveBound
|
|
|
278
289
|
|
|
279
290
|
if (action === 'add-status') {
|
|
280
291
|
const [typeKey, label] = positionals(args);
|
|
281
|
-
if (!typeKey || !label) throw new Error('Usage: empirical workspace shape add-status <type-key> <label> [--done] --workspace-id <id> [--apply]');
|
|
282
|
-
|
|
292
|
+
if (!typeKey || !label) throw new Error('Usage: empirical workspace shape add-status <type-key> <label> [--done] [--after <key> | --before <key>] --workspace-id <id> [--apply]');
|
|
293
|
+
const after = parseOption(args, '--after', '');
|
|
294
|
+
const before = parseOption(args, '--before', '');
|
|
295
|
+
if (after && before) throw new Error('Use --after or --before, not both.');
|
|
296
|
+
return previewAndMaybeApply({ baseUrl, workspaceId, authHeaders, edit: buildAddStatusEdit(typeKey, label, parseFlag(args, '--done'), { after, before }), mapping, apply, asJson });
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if (action === 'reorder-statuses') {
|
|
300
|
+
const [typeKey, list] = positionals(args);
|
|
301
|
+
const keys = String(list || '').split(',').map((k) => k.trim()).filter(Boolean);
|
|
302
|
+
if (!typeKey || !keys.length) throw new Error('Usage: empirical workspace shape reorder-statuses <type-key> <key,key,...> --workspace-id <id> [--apply] (`shape show` prints the keys)');
|
|
303
|
+
return previewAndMaybeApply({ baseUrl, workspaceId, authHeaders, edit: buildReorderStatusesEdit(typeKey, keys), mapping, apply, asJson });
|
|
283
304
|
}
|
|
284
305
|
|
|
285
306
|
if (action === 'add-field') {
|
|
@@ -307,6 +328,7 @@ module.exports = {
|
|
|
307
328
|
parseFieldList,
|
|
308
329
|
buildTypeFromFlags,
|
|
309
330
|
buildAddStatusEdit,
|
|
331
|
+
buildReorderStatusesEdit,
|
|
310
332
|
buildAddFieldEdit,
|
|
311
333
|
formatShape,
|
|
312
334
|
runManualShapeCommand
|
package/lib/workspaceCommands.js
CHANGED
|
@@ -53,7 +53,55 @@ async function resolveShapeTarget({ requested, local, answersFile, acceptCost, i
|
|
|
53
53
|
function isWorkspaceCommand(args) {
|
|
54
54
|
if (args[0] !== 'workspace') return false;
|
|
55
55
|
if (['list', 'current', 'select', 'use', 'unbind', 'create', 'delete', 'shape'].includes(args[1])) return true;
|
|
56
|
-
return args[1] === 'invite' && ['create', 'list', 'revoke'].includes(args[2]);
|
|
56
|
+
return args[1] === 'invite' && ['create', 'list', 'revoke', 'email'].includes(args[2]);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Mirrors WORKSPACE_INVITE_NOTE_MAX in @empirical/shared for a local pre-check only; the CLI is
|
|
60
|
+
// published on its own and cannot import it. The server re-checks and is the one that decides.
|
|
61
|
+
const INVITE_NOTE_MAX = 1000;
|
|
62
|
+
|
|
63
|
+
const NOT_SENT_REASONS = {
|
|
64
|
+
invalid_email: 'not a valid email address',
|
|
65
|
+
already_member: 'already a member',
|
|
66
|
+
already_invited: 'already has a pending invite',
|
|
67
|
+
seat_limit: 'the workspace has no free seat',
|
|
68
|
+
recipient_suppressed: 'this address has bounced or unsubscribed before',
|
|
69
|
+
email_not_configured: 'email is not set up on this server',
|
|
70
|
+
send_failed: 'the email could not be sent'
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
// empirical workspace invite email <address...> [--role reader|writer] [--note <text> | --note-file <path>] [--from-name <name>]
|
|
74
|
+
async function runInviteEmail({ args, inviteUrl, authHeaders, target }) {
|
|
75
|
+
const emails = positionalArgs(args, 3).join(' ').split(/[\s,;]+/).map((e) => e.trim()).filter(Boolean);
|
|
76
|
+
if (!emails.length) {
|
|
77
|
+
throw new Error('Usage: empirical workspace invite email <address...> [--role reader|writer] [--note <text> | --note-file <path>] [--from-name <name>] [--workspace-id <id>]');
|
|
78
|
+
}
|
|
79
|
+
const role = parseOption(args, '--role', 'reader');
|
|
80
|
+
if (!['reader', 'writer'].includes(role)) throw new Error('--role must be reader or writer for an emailed invite.');
|
|
81
|
+
const noteFile = parseOption(args, '--note-file', '');
|
|
82
|
+
let note = parseOption(args, '--note', '');
|
|
83
|
+
if (noteFile) {
|
|
84
|
+
if (note) throw new Error('Use --note or --note-file, not both.');
|
|
85
|
+
note = require('fs').readFileSync(noteFile, 'utf8');
|
|
86
|
+
}
|
|
87
|
+
note = String(note || '').replace(/\r\n?/g, '\n').trim();
|
|
88
|
+
if (note.length > INVITE_NOTE_MAX) throw new Error(`Keep the note under ${INVITE_NOTE_MAX} characters (it is ${note.length}).`);
|
|
89
|
+
// The name the email says it is from, for this send only. Empty means your account's display name.
|
|
90
|
+
const fromName = String(parseOption(args, '--from-name', '') || '').replace(/\s+/g, ' ').trim();
|
|
91
|
+
if (fromName && fromName.includes('@')) throw new Error('--from-name takes a name, not an email address.');
|
|
92
|
+
if (fromName && (fromName.length < 2 || fromName.length > 64)) throw new Error('--from-name must be 2 to 64 characters.');
|
|
93
|
+
|
|
94
|
+
const payload = await httpJson(`${inviteUrl}/email`, 'POST', { emails, accessRole: role, ...(note ? { note } : {}), ...(fromName ? { fromName } : {}) }, authHeaders);
|
|
95
|
+
const results = Array.isArray(payload?.results) ? payload.results : [];
|
|
96
|
+
const sent = results.filter((r) => r.sent);
|
|
97
|
+
const notSent = results.filter((r) => !r.sent);
|
|
98
|
+
if (sent.length) log(uiSuccess(`Invited to ${target.label} as ${role}: ${sent.map((r) => r.email).join(', ')}`));
|
|
99
|
+
for (const r of notSent) {
|
|
100
|
+
log(`Not sent to ${r.email}: ${NOT_SENT_REASONS[r.skippedReason] || r.skippedReason || 'unknown reason'}.${r.detail ? ` ${r.detail}` : ''}`);
|
|
101
|
+
}
|
|
102
|
+
if (sent.length && payload?.fromName) log(`Sent as ${payload.fromName}.`);
|
|
103
|
+
if (sent.length && note) log('Your note was included.');
|
|
104
|
+
if (!sent.length) process.exitCode = 1;
|
|
57
105
|
}
|
|
58
106
|
|
|
59
107
|
function describeSelectionSource(selection) {
|
|
@@ -72,7 +120,7 @@ function workspaceOptions(payload) {
|
|
|
72
120
|
}
|
|
73
121
|
|
|
74
122
|
function positionalArgs(args, startIndex) {
|
|
75
|
-
const valueOptions = new Set(['--description', '--role', '--expires-in-days', '--max-uses', '--answers', '--workspace-id']);
|
|
123
|
+
const valueOptions = new Set(['--description', '--role', '--expires-in-days', '--max-uses', '--answers', '--workspace-id', '--note', '--note-file', '--from-name']);
|
|
76
124
|
const positional = [];
|
|
77
125
|
for (let index = startIndex; index < args.length; index += 1) {
|
|
78
126
|
if (valueOptions.has(args[index])) {
|
|
@@ -400,17 +448,23 @@ async function runWorkspaceCommand(args) {
|
|
|
400
448
|
if (action === 'invite') {
|
|
401
449
|
requireClientId(clientId);
|
|
402
450
|
const inviteAction = args[2];
|
|
403
|
-
const
|
|
404
|
-
|
|
405
|
-
if (
|
|
406
|
-
|
|
451
|
+
const namedWorkspace = parseOption(args, '--workspace-id', '');
|
|
452
|
+
let target;
|
|
453
|
+
if (namedWorkspace) {
|
|
454
|
+
target = { workspaceId: namedWorkspace, label: namedWorkspace };
|
|
455
|
+
} else {
|
|
456
|
+
const currentPayload = await httpJson(currentUrl, 'GET', null, authHeaders);
|
|
457
|
+
const current = currentPayload?.current;
|
|
458
|
+
if (!current?.key || current.key === 'default_brain') {
|
|
459
|
+
throw new Error('Workspace invites require an active team workspace. Use `empirical workspace use <workspace>` first, or pass --workspace-id.');
|
|
460
|
+
}
|
|
461
|
+
const teamPayload = await httpJson(teamBrainsUrl, 'GET', null, authHeaders);
|
|
462
|
+
target = resolveWorkspaceTarget(
|
|
463
|
+
current.workspaceId || current.key,
|
|
464
|
+
{ workspaces: [{ key: current.key, label: current.label, workspaceId: current.workspaceId }] },
|
|
465
|
+
teamPayload
|
|
466
|
+
);
|
|
407
467
|
}
|
|
408
|
-
const teamPayload = await httpJson(teamBrainsUrl, 'GET', null, authHeaders);
|
|
409
|
-
const target = resolveWorkspaceTarget(
|
|
410
|
-
current.workspaceId || current.key,
|
|
411
|
-
{ workspaces: [{ key: current.key, label: current.label, workspaceId: current.workspaceId }] },
|
|
412
|
-
teamPayload
|
|
413
|
-
);
|
|
414
468
|
if (!target?.workspaceId) {
|
|
415
469
|
throw new Error('The active workspace is not a team workspace available for invites.');
|
|
416
470
|
}
|
|
@@ -434,6 +488,11 @@ async function runWorkspaceCommand(args) {
|
|
|
434
488
|
return;
|
|
435
489
|
}
|
|
436
490
|
|
|
491
|
+
if (inviteAction === 'email') {
|
|
492
|
+
await runInviteEmail({ args, inviteUrl, authHeaders, target });
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
|
|
437
496
|
if (inviteAction === 'list') {
|
|
438
497
|
const payload = await httpJson(inviteUrl, 'GET', null, authHeaders);
|
|
439
498
|
const invites = Array.isArray(payload?.invites) ? payload.invites : [];
|
package/package.json
CHANGED
|
@@ -1,33 +1,33 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@empirical-gauzza/cli",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "Persistent cross-tool memory for AI agents. Give Claude Code, Codex, Copilot, Cursor, and ChatGPT one shared memory: save project context once, recall it everywhere via CLI or MCP.",
|
|
5
|
-
"keywords": [
|
|
6
|
-
"memory",
|
|
7
|
-
"mcp",
|
|
8
|
-
"agent memory",
|
|
9
|
-
"ai memory",
|
|
10
|
-
"claude",
|
|
11
|
-
"chatgpt",
|
|
12
|
-
"model context protocol",
|
|
13
|
-
"persistent memory",
|
|
14
|
-
"claude-code",
|
|
15
|
-
"codex",
|
|
16
|
-
"knowledge base"
|
|
17
|
-
],
|
|
18
|
-
"type": "commonjs",
|
|
19
|
-
"license": "MIT",
|
|
20
|
-
"homepage": "https://empirical.gauzza.com/docs/cli-details",
|
|
21
|
-
"publishConfig": {
|
|
22
|
-
"access": "public"
|
|
23
|
-
},
|
|
24
|
-
"bin": {
|
|
25
|
-
"empirical": "bin/empirical.js"
|
|
26
|
-
},
|
|
27
|
-
"engines": {
|
|
28
|
-
"node": ">=18.0.0"
|
|
29
|
-
},
|
|
30
|
-
"dependencies": {
|
|
31
|
-
"gray-matter": "^4.0.3"
|
|
32
|
-
}
|
|
33
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@empirical-gauzza/cli",
|
|
3
|
+
"version": "1.0.41",
|
|
4
|
+
"description": "Persistent cross-tool memory for AI agents. Give Claude Code, Codex, Copilot, Cursor, and ChatGPT one shared memory: save project context once, recall it everywhere via CLI or MCP.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"memory",
|
|
7
|
+
"mcp",
|
|
8
|
+
"agent memory",
|
|
9
|
+
"ai memory",
|
|
10
|
+
"claude",
|
|
11
|
+
"chatgpt",
|
|
12
|
+
"model context protocol",
|
|
13
|
+
"persistent memory",
|
|
14
|
+
"claude-code",
|
|
15
|
+
"codex",
|
|
16
|
+
"knowledge base"
|
|
17
|
+
],
|
|
18
|
+
"type": "commonjs",
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"homepage": "https://empirical.gauzza.com/docs/cli-details",
|
|
21
|
+
"publishConfig": {
|
|
22
|
+
"access": "public"
|
|
23
|
+
},
|
|
24
|
+
"bin": {
|
|
25
|
+
"empirical": "bin/empirical.js"
|
|
26
|
+
},
|
|
27
|
+
"engines": {
|
|
28
|
+
"node": ">=18.0.0"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"gray-matter": "^4.0.3"
|
|
32
|
+
}
|
|
33
|
+
}
|