@empirical-gauzza/cli 1.0.21 → 1.0.23
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/README.md +63 -63
- package/bin/empirical.js +12 -0
- package/lib/auth.js +83 -2
- package/lib/blueprintCommands.js +128 -0
- package/lib/constants.js +41 -1
- package/lib/doc.js +1 -1
- package/lib/env.js +49 -49
- package/lib/help.js +6 -1
- package/lib/installers/detect.js +47 -47
- package/lib/installers/hooks.js +12 -12
- package/lib/installers/skill-variants/SKILL.both.md +165 -154
- package/lib/installers/skill-variants/SKILL.cli.md +161 -150
- package/lib/installers/skill-variants/SKILL.mcp.md +140 -134
- package/lib/memoryCommands.js +92 -3
- package/lib/oauthClient.js +222 -222
- package/lib/policyCommands.js +144 -144
- package/lib/setupCommands.js +123 -0
- package/lib/ui.js +7 -1
- package/lib/workspaceContext.js +47 -47
- package/package.json +33 -33
- package/tests/ui.test.js +42 -0
package/README.md
CHANGED
|
@@ -1,63 +1,63 @@
|
|
|
1
|
-
# @empirical-gauzza/cli
|
|
2
|
-
|
|
3
|
-
Command-line installer and local MCP wiring helper for [Empirical](https://empirical.gauzza.com) — portable memory infrastructure for AI agents, assistants, and coding tools.
|
|
4
|
-
|
|
5
|
-
Full documentation: **[empirical.gauzza.com/docs/cli-details](https://empirical.gauzza.com/docs/cli-details)**
|
|
6
|
-
|
|
7
|
-
## Install
|
|
8
|
-
|
|
9
|
-
```bash
|
|
10
|
-
npm install -g @empirical-gauzza/cli
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
## Quick start
|
|
14
|
-
|
|
15
|
-
```bash
|
|
16
|
-
empirical install # interactive setup for Claude Code, Codex, or Copilot CLI
|
|
17
|
-
empirical auth login # browser-based OAuth login
|
|
18
|
-
empirical doctor # verify token, API connectivity, and workspace status
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
Full walkthrough: [empirical.gauzza.com/docs/install](https://empirical.gauzza.com/docs/install)
|
|
22
|
-
|
|
23
|
-
## What it does
|
|
24
|
-
|
|
25
|
-
- Installs and configures the Empirical MCP plugin for supported coding agents (Claude Code, Codex CLI, Copilot CLI)
|
|
26
|
-
- Manages OAuth login, including headless/CI bootstrap and Docker token flows
|
|
27
|
-
- Reads, writes, and maintains memories from the command line
|
|
28
|
-
- Manages Skills Kits — create, sync, and push authored Skills across machines
|
|
29
|
-
- Manages workspaces, invites, and registered devices
|
|
30
|
-
- Exposes agent-readable docs and a connectivity `doctor` check
|
|
31
|
-
|
|
32
|
-
## Commands
|
|
33
|
-
|
|
34
|
-
| Command | Docs |
|
|
35
|
-
| --- | --- |
|
|
36
|
-
| `empirical install` | [/docs/install](https://empirical.gauzza.com/docs/install#connect) |
|
|
37
|
-
| `empirical plugin install / check` | [/docs/cli-plugins](https://empirical.gauzza.com/docs/cli-plugins) |
|
|
38
|
-
| `empirical auth login / logout` | [/docs/cli-auth](https://empirical.gauzza.com/docs/cli-auth) |
|
|
39
|
-
| `empirical oauth bootstrap / refresh` | [/docs/cli-oauth](https://empirical.gauzza.com/docs/cli-oauth) |
|
|
40
|
-
| `empirical policy show / set` | [/docs/cli-policy](https://empirical.gauzza.com/docs/cli-policy) |
|
|
41
|
-
| `empirical memory query / record / update / delete / export ...` | [/docs/cli-memory](https://empirical.gauzza.com/docs/cli-memory) |
|
|
42
|
-
| `empirical workspace list / create / invite ...` | [/docs/cli-workspaces](https://empirical.gauzza.com/docs/cli-workspaces) |
|
|
43
|
-
| `empirical kit create / sync / use ...` | [/docs/cli-kit](https://empirical.gauzza.com/docs/cli-kit) |
|
|
44
|
-
| `empirical skill push / history / rollback` | [/docs/cli-skill](https://empirical.gauzza.com/docs/cli-skill) |
|
|
45
|
-
| `empirical device list / show` | [/docs/cli-devices](https://empirical.gauzza.com/docs/cli-devices) |
|
|
46
|
-
| `empirical doc [topic]` | [/docs/cli-doc-command](https://empirical.gauzza.com/docs/cli-doc-command) |
|
|
47
|
-
| `empirical doctor` | [/docs/cli-doctor](https://empirical.gauzza.com/docs/cli-doctor) |
|
|
48
|
-
|
|
49
|
-
Run `empirical --help` at any time for the full, current command list, or `empirical doc` for agent-readable topic docs.
|
|
50
|
-
|
|
51
|
-
## Security model
|
|
52
|
-
|
|
53
|
-
API keys and OAuth tokens are stored locally and are never printed in full after initial issuance. See [empirical.gauzza.com/docs/security-model](https://empirical.gauzza.com/docs/security-model) for details on credential handling and headless/CI usage.
|
|
54
|
-
|
|
55
|
-
## Links
|
|
56
|
-
|
|
57
|
-
- [Documentation](https://empirical.gauzza.com/docs/install)
|
|
58
|
-
- [Homepage](https://empirical.gauzza.com)
|
|
59
|
-
- [Support](https://empirical.gauzza.com/support/)
|
|
60
|
-
|
|
61
|
-
## License
|
|
62
|
-
|
|
63
|
-
MIT
|
|
1
|
+
# @empirical-gauzza/cli
|
|
2
|
+
|
|
3
|
+
Command-line installer and local MCP wiring helper for [Empirical](https://empirical.gauzza.com) — portable memory infrastructure for AI agents, assistants, and coding tools.
|
|
4
|
+
|
|
5
|
+
Full documentation: **[empirical.gauzza.com/docs/cli-details](https://empirical.gauzza.com/docs/cli-details)**
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install -g @empirical-gauzza/cli
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Quick start
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
empirical install # interactive setup for Claude Code, Codex, or Copilot CLI
|
|
17
|
+
empirical auth login # browser-based OAuth login
|
|
18
|
+
empirical doctor # verify token, API connectivity, and workspace status
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Full walkthrough: [empirical.gauzza.com/docs/install](https://empirical.gauzza.com/docs/install)
|
|
22
|
+
|
|
23
|
+
## What it does
|
|
24
|
+
|
|
25
|
+
- Installs and configures the Empirical MCP plugin for supported coding agents (Claude Code, Codex CLI, Copilot CLI)
|
|
26
|
+
- Manages OAuth login, including headless/CI bootstrap and Docker token flows
|
|
27
|
+
- Reads, writes, and maintains memories from the command line
|
|
28
|
+
- Manages Skills Kits — create, sync, and push authored Skills across machines
|
|
29
|
+
- Manages workspaces, invites, and registered devices
|
|
30
|
+
- Exposes agent-readable docs and a connectivity `doctor` check
|
|
31
|
+
|
|
32
|
+
## Commands
|
|
33
|
+
|
|
34
|
+
| Command | Docs |
|
|
35
|
+
| --- | --- |
|
|
36
|
+
| `empirical install` | [/docs/install](https://empirical.gauzza.com/docs/install#connect) |
|
|
37
|
+
| `empirical plugin install / check` | [/docs/cli-plugins](https://empirical.gauzza.com/docs/cli-plugins) |
|
|
38
|
+
| `empirical auth login / logout` | [/docs/cli-auth](https://empirical.gauzza.com/docs/cli-auth) |
|
|
39
|
+
| `empirical oauth bootstrap / refresh` | [/docs/cli-oauth](https://empirical.gauzza.com/docs/cli-oauth) |
|
|
40
|
+
| `empirical policy show / set` | [/docs/cli-policy](https://empirical.gauzza.com/docs/cli-policy) |
|
|
41
|
+
| `empirical memory query / record / update / delete / export ...` | [/docs/cli-memory](https://empirical.gauzza.com/docs/cli-memory) |
|
|
42
|
+
| `empirical workspace list / create / invite ...` | [/docs/cli-workspaces](https://empirical.gauzza.com/docs/cli-workspaces) |
|
|
43
|
+
| `empirical kit create / sync / use ...` | [/docs/cli-kit](https://empirical.gauzza.com/docs/cli-kit) |
|
|
44
|
+
| `empirical skill push / history / rollback` | [/docs/cli-skill](https://empirical.gauzza.com/docs/cli-skill) |
|
|
45
|
+
| `empirical device list / show` | [/docs/cli-devices](https://empirical.gauzza.com/docs/cli-devices) |
|
|
46
|
+
| `empirical doc [topic]` | [/docs/cli-doc-command](https://empirical.gauzza.com/docs/cli-doc-command) |
|
|
47
|
+
| `empirical doctor` | [/docs/cli-doctor](https://empirical.gauzza.com/docs/cli-doctor) |
|
|
48
|
+
|
|
49
|
+
Run `empirical --help` at any time for the full, current command list, or `empirical doc` for agent-readable topic docs.
|
|
50
|
+
|
|
51
|
+
## Security model
|
|
52
|
+
|
|
53
|
+
API keys and OAuth tokens are stored locally and are never printed in full after initial issuance. See [empirical.gauzza.com/docs/security-model](https://empirical.gauzza.com/docs/security-model) for details on credential handling and headless/CI usage.
|
|
54
|
+
|
|
55
|
+
## Links
|
|
56
|
+
|
|
57
|
+
- [Documentation](https://empirical.gauzza.com/docs/install)
|
|
58
|
+
- [Homepage](https://empirical.gauzza.com)
|
|
59
|
+
- [Support](https://empirical.gauzza.com/support/)
|
|
60
|
+
|
|
61
|
+
## License
|
|
62
|
+
|
|
63
|
+
MIT
|
package/bin/empirical.js
CHANGED
|
@@ -23,6 +23,8 @@ const { runDoc } = require('../lib/doc');
|
|
|
23
23
|
const { showVersion } = require('../lib/help');
|
|
24
24
|
const { isPluginCommand, runPluginCommand } = require('../lib/pluginCommands');
|
|
25
25
|
const { runPolicyCommand } = require('../lib/policyCommands');
|
|
26
|
+
const { runSetupCommand } = require('../lib/setupCommands');
|
|
27
|
+
const { runBlueprintCommand } = require('../lib/blueprintCommands');
|
|
26
28
|
const { checkNativePluginUpdates } = require('../lib/pluginUpdateCheck');
|
|
27
29
|
|
|
28
30
|
async function main() {
|
|
@@ -104,6 +106,16 @@ async function main() {
|
|
|
104
106
|
return;
|
|
105
107
|
}
|
|
106
108
|
|
|
109
|
+
if (args[0] === 'setup') {
|
|
110
|
+
await runSetupCommand(args.slice(1));
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (args[0] === 'blueprint') {
|
|
115
|
+
await runBlueprintCommand(args.slice(1));
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
107
119
|
if (isAuthCommand(args)) {
|
|
108
120
|
await runAuthCommand(args);
|
|
109
121
|
return;
|
package/lib/auth.js
CHANGED
|
@@ -401,13 +401,79 @@ async function runAuthCommand(args) {
|
|
|
401
401
|
return;
|
|
402
402
|
}
|
|
403
403
|
|
|
404
|
+
// `logout` used to only delete the local session file, which left every refresh token on
|
|
405
|
+
// the account still redeemable forever. It now revokes server-side first, so logging out
|
|
406
|
+
// actually ends the session. --local-only keeps the old behavior for the case where you
|
|
407
|
+
// just want this machine to forget its credentials without signing out your other clients.
|
|
404
408
|
if (action === 'logout') {
|
|
409
|
+
const localOnly = args.includes('--local-only');
|
|
410
|
+
if (!localOnly) {
|
|
411
|
+
const baseUrl = resolveApiBaseUrl();
|
|
412
|
+
const token = resolveAccessToken();
|
|
413
|
+
if (token) {
|
|
414
|
+
try {
|
|
415
|
+
const res = await revokeRefreshTokens(baseUrl, token, { clientId: parseOption(args, '--client-id') });
|
|
416
|
+
const data = (res && res.data) ? res.data : res;
|
|
417
|
+
log(`Revoked ${data.revoked} refresh token(s) server-side; ${data.remaining} remaining.`);
|
|
418
|
+
} catch (error) {
|
|
419
|
+
warn(`Server-side revoke failed (${error.message}). Clearing the local session anyway; run 'empirical auth logout' again once you can reach the API.`);
|
|
420
|
+
process.exitCode = 1;
|
|
421
|
+
}
|
|
422
|
+
} else {
|
|
423
|
+
warn('No stored credential found, so nothing could be revoked server-side. Clearing local session only.');
|
|
424
|
+
}
|
|
425
|
+
}
|
|
405
426
|
clearOAuthSession();
|
|
406
|
-
log('Empirical
|
|
427
|
+
log(localOnly ? 'Local Empirical session cleared (server-side tokens left intact).' : 'Empirical session cleared.');
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// Rotate: mint a replacement API key. `--replace-all` uses the destructive server-side
|
|
432
|
+
// rotate that wipes every existing key; the default appends a new key and leaves the
|
|
433
|
+
// others working, so you can cut over before revoking the old one.
|
|
434
|
+
if (action === 'rotate-key') {
|
|
435
|
+
const baseUrl = resolveApiBaseUrl();
|
|
436
|
+
const token = requireAccessToken();
|
|
437
|
+
const result = await fetchSelfServiceCredentials(baseUrl, token, {
|
|
438
|
+
rotate: args.includes('--replace-all'),
|
|
439
|
+
label: parseOption(args, '--label')
|
|
440
|
+
});
|
|
441
|
+
const data = (result && result.data) ? result.data : result;
|
|
442
|
+
if (!data || !data.apiKey) {
|
|
443
|
+
warn('The server did not return a new key. Nothing was changed.');
|
|
444
|
+
process.exitCode = 1;
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
log(JSON.stringify({ clientId: data.clientId || data.userId, apiKey: data.apiKey, keyId: data.keyId || null }, null, 2));
|
|
448
|
+
log('\nStore this now: the key is shown once and only its hash is kept.');
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
if (action === 'list-keys') {
|
|
453
|
+
const baseUrl = resolveApiBaseUrl();
|
|
454
|
+
const token = requireAccessToken();
|
|
455
|
+
const result = await fetchSelfServiceCredentialStatus(baseUrl, token);
|
|
456
|
+
log(JSON.stringify((result && result.data) ? result.data : result, null, 2));
|
|
407
457
|
return;
|
|
408
458
|
}
|
|
409
459
|
|
|
410
|
-
|
|
460
|
+
if (action === 'revoke-key') {
|
|
461
|
+
const baseUrl = resolveApiBaseUrl();
|
|
462
|
+
const token = requireAccessToken();
|
|
463
|
+
const id = parseOption(args, '--key-id');
|
|
464
|
+
const all = args.includes('--all');
|
|
465
|
+
if (!id && !all) {
|
|
466
|
+
warn('Usage: empirical auth revoke-key (--key-id <id> | --all) [list ids with: empirical auth list-keys]');
|
|
467
|
+
process.exitCode = 1;
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
const result = await revokeSelfServiceCredential(baseUrl, token, { id: all ? undefined : id });
|
|
471
|
+
log(JSON.stringify((result && result.data) ? result.data : result, null, 2));
|
|
472
|
+
log(all ? '\nEvery API key on this account is revoked.' : `\nKey ${id} revoked.`);
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
warn('Usage: empirical auth <login|logout|status|rotate-key|list-keys|revoke-key> (or: empirical whoami)');
|
|
411
477
|
process.exitCode = 1;
|
|
412
478
|
}
|
|
413
479
|
|
|
@@ -601,6 +667,19 @@ async function fetchSelfServiceCredentialStatus(baseUrl, accessToken) {
|
|
|
601
667
|
return httpJson(url, 'GET', undefined, { Authorization: `Bearer ${accessToken}` });
|
|
602
668
|
}
|
|
603
669
|
|
|
670
|
+
// Revoke one API key by id, or every key when `id` is omitted.
|
|
671
|
+
async function revokeSelfServiceCredential(baseUrl, accessToken, { id } = {}) {
|
|
672
|
+
const url = new URL('/api/v1/users/me/credentials', baseUrl).toString();
|
|
673
|
+
return httpJson(url, 'DELETE', id ? { id } : {}, { Authorization: `Bearer ${accessToken}` });
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
// Revoke OAuth refresh tokens: one client's when `clientId` is given, otherwise all of them.
|
|
677
|
+
// This is what makes `empirical auth logout` end the session on the server and not just on disk.
|
|
678
|
+
async function revokeRefreshTokens(baseUrl, accessToken, { clientId } = {}) {
|
|
679
|
+
const url = new URL('/api/v1/users/me/refresh-tokens', baseUrl).toString();
|
|
680
|
+
return httpJson(url, 'DELETE', clientId ? { clientId } : {}, { Authorization: `Bearer ${accessToken}` });
|
|
681
|
+
}
|
|
682
|
+
|
|
604
683
|
async function selfProvisionHeadlessCredentials({ baseUrl, allowBrowserLogin = true } = {}) {
|
|
605
684
|
let bearer = resolveAccessToken();
|
|
606
685
|
|
|
@@ -881,6 +960,8 @@ module.exports = {
|
|
|
881
960
|
ensureOAuthClient,
|
|
882
961
|
fetchSelfServiceCredentials,
|
|
883
962
|
fetchSelfServiceCredentialStatus,
|
|
963
|
+
revokeSelfServiceCredential,
|
|
964
|
+
revokeRefreshTokens,
|
|
884
965
|
selfProvisionHeadlessCredentials,
|
|
885
966
|
runHeadlessBootstrap,
|
|
886
967
|
runHeadlessRefresh
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { httpJson, printApiError } = require('./http');
|
|
4
|
+
const { parseOption } = require('./env');
|
|
5
|
+
const { requireAccessToken, resolveApiBaseUrl, buildApiAuthHeaders } = require('./auth');
|
|
6
|
+
const { warn, log } = require('./ui');
|
|
7
|
+
|
|
8
|
+
function apiUrl(pathSuffix) {
|
|
9
|
+
return new URL(`/api/v1/blueprints${pathSuffix}`, resolveApiBaseUrl()).toString();
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async function callApi(method, pathSuffix, body) {
|
|
13
|
+
const accessToken = requireAccessToken();
|
|
14
|
+
try {
|
|
15
|
+
const response = await httpJson(apiUrl(pathSuffix), method, body, buildApiAuthHeaders(accessToken));
|
|
16
|
+
return response && response.data ? response.data : response;
|
|
17
|
+
} catch (error) {
|
|
18
|
+
printApiError(error);
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async function waitForInstall(installId) {
|
|
24
|
+
for (let i = 0; i < 25; i += 1) {
|
|
25
|
+
const status = await callApi('GET', `/installs/${installId}`, undefined);
|
|
26
|
+
if (['succeeded', 'failed', 'rolled_back', 'dead_letter', 'uninstalled'].includes(String(status.jobStatus))) {
|
|
27
|
+
return status;
|
|
28
|
+
}
|
|
29
|
+
await new Promise((resolve) => setTimeout(resolve, 900));
|
|
30
|
+
}
|
|
31
|
+
return { installId, jobStatus: 'running' };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function runList(args) {
|
|
35
|
+
const category = parseOption(args, '--category', '');
|
|
36
|
+
const qs = category ? `?category=${encodeURIComponent(category)}` : '';
|
|
37
|
+
const { items } = await callApi('GET', qs, undefined);
|
|
38
|
+
log(JSON.stringify(items, null, 2));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function runShow(args) {
|
|
42
|
+
const [blueprintId] = args;
|
|
43
|
+
if (!blueprintId) {
|
|
44
|
+
warn('Usage: empirical blueprint show <blueprintId>');
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
const result = await callApi('GET', `/${blueprintId}`, undefined);
|
|
48
|
+
log(JSON.stringify(result, null, 2));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function runClaim(args) {
|
|
52
|
+
const [blueprintId] = args;
|
|
53
|
+
if (!blueprintId) {
|
|
54
|
+
warn('Usage: empirical blueprint claim <blueprintId>');
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
const result = await callApi('POST', `/${blueprintId}/claim`, {});
|
|
58
|
+
log(JSON.stringify(result, null, 2));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function runInstall(args) {
|
|
62
|
+
const blueprintId = args[0] && !args[0].startsWith('--') ? args[0] : '';
|
|
63
|
+
if (!blueprintId) {
|
|
64
|
+
warn('Usage: empirical blueprint install <blueprintId> [--version <v>]');
|
|
65
|
+
process.exit(1);
|
|
66
|
+
}
|
|
67
|
+
const version = parseOption(args, '--version', '');
|
|
68
|
+
|
|
69
|
+
const detail = await callApi('GET', `/${blueprintId}`, undefined);
|
|
70
|
+
if (!detail.owned && detail.priceModel === 'free') {
|
|
71
|
+
await callApi('POST', `/${blueprintId}/claim`, {});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const installResult = await callApi('POST', `/${blueprintId}/install`, version ? { version } : {});
|
|
75
|
+
const finalStatus = await waitForInstall(installResult.installId);
|
|
76
|
+
log(JSON.stringify(finalStatus, null, 2));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async function runStatus(args) {
|
|
80
|
+
const [installId] = args;
|
|
81
|
+
if (!installId) {
|
|
82
|
+
warn('Usage: empirical blueprint status <installId>');
|
|
83
|
+
process.exit(1);
|
|
84
|
+
}
|
|
85
|
+
const result = await callApi('GET', `/installs/${installId}`, undefined);
|
|
86
|
+
log(JSON.stringify(result, null, 2));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async function runInstalled() {
|
|
90
|
+
const { items } = await callApi('GET', '/installed', undefined);
|
|
91
|
+
log(JSON.stringify(items, null, 2));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async function runUpdate(args) {
|
|
95
|
+
const [blueprintId] = args;
|
|
96
|
+
if (!blueprintId) {
|
|
97
|
+
warn('Usage: empirical blueprint update <blueprintId>');
|
|
98
|
+
process.exit(1);
|
|
99
|
+
}
|
|
100
|
+
const result = await callApi('POST', `/${blueprintId}/update`, {});
|
|
101
|
+
log(JSON.stringify(result, null, 2));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async function runUninstall(args) {
|
|
105
|
+
const [blueprintId, installId] = args;
|
|
106
|
+
if (!blueprintId || !installId) {
|
|
107
|
+
warn('Usage: empirical blueprint uninstall <blueprintId> <installId>');
|
|
108
|
+
process.exit(1);
|
|
109
|
+
}
|
|
110
|
+
const result = await callApi('POST', `/${blueprintId}/uninstall`, { installId });
|
|
111
|
+
log(JSON.stringify(result, null, 2));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async function runBlueprintCommand(args) {
|
|
115
|
+
const [sub, ...rest] = args;
|
|
116
|
+
if (sub === 'list') return runList(rest);
|
|
117
|
+
if (sub === 'show') return runShow(rest);
|
|
118
|
+
if (sub === 'claim') return runClaim(rest);
|
|
119
|
+
if (sub === 'install') return runInstall(rest);
|
|
120
|
+
if (sub === 'status') return runStatus(rest);
|
|
121
|
+
if (sub === 'installed') return runInstalled();
|
|
122
|
+
if (sub === 'update') return runUpdate(rest);
|
|
123
|
+
if (sub === 'uninstall') return runUninstall(rest);
|
|
124
|
+
warn('Usage: empirical blueprint <list|show|claim|install|status|installed|update|uninstall> ...');
|
|
125
|
+
process.exit(1);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
module.exports = { runBlueprintCommand };
|
package/lib/constants.js
CHANGED
|
@@ -60,9 +60,22 @@ Save a new memory.
|
|
|
60
60
|
|
|
61
61
|
empirical memory record --category build --summary "Use ESM modules for all new packages" [--tags ts,node] [--node-type observation]
|
|
62
62
|
|
|
63
|
+
Passing text that contains code (backticks, \${...}, $(...)): do NOT put it in a double-quoted
|
|
64
|
+
\`--summary "..."\` -- the shell substitutes those away before the CLI runs and the write still
|
|
65
|
+
succeeds with the code silently missing. Use one of:
|
|
66
|
+
|
|
67
|
+
empirical memory record --category build --summary-file ./note.md --tags ts
|
|
68
|
+
empirical memory record --category build --summary - --tags ts <<'EOF'
|
|
69
|
+
Use \`createChat()\`; raw calls leak \${stage} and 400.
|
|
70
|
+
EOF
|
|
71
|
+
|
|
72
|
+
(a single-quoted heredoc delimiter, 'EOF', disables substitution). From an MCP-connected agent,
|
|
73
|
+
prefer the record_graph_memory tool, which takes JSON and never touches a shell.
|
|
74
|
+
|
|
63
75
|
Flags:
|
|
64
76
|
- \`--category\` (required unless --json-file): memory category
|
|
65
|
-
- \`--summary\` (required unless --json-file): memory summary text
|
|
77
|
+
- \`--summary\` (required unless --summary-file/--json-file): memory summary text; \`-\` reads stdin
|
|
78
|
+
- \`--summary-file\`: path to a file whose contents become the summary (safe for code)
|
|
66
79
|
- \`--tags\`: comma-separated tags
|
|
67
80
|
- \`--node-type\`: node type, default \`observation\`
|
|
68
81
|
- \`--json-file\`: path to JSON file with full memory payload
|
|
@@ -431,6 +444,33 @@ Opens the browser, completes OAuth with the Empirical account, and saves a CLI s
|
|
|
431
444
|
\`~/.empirical/oauth-session.json\`. Use this for normal interactive CLI use when you do not
|
|
432
445
|
want to provision a permanent API key.
|
|
433
446
|
|
|
447
|
+
## Logging out (and actually revoking)
|
|
448
|
+
|
|
449
|
+
empirical auth logout # revoke refresh tokens server-side, then clear locally
|
|
450
|
+
empirical auth logout --local-only # forget this machine only, leave other clients signed in
|
|
451
|
+
empirical auth logout --client-id <id> # revoke just one client's refresh tokens
|
|
452
|
+
|
|
453
|
+
Plain \`logout\` calls \`DELETE /api/v1/users/me/refresh-tokens\`, so the session ends on the
|
|
454
|
+
server and not merely on disk. This matters because refresh tokens are written without an
|
|
455
|
+
expiry: before this existed, a leaked refresh token stayed redeemable forever and no command,
|
|
456
|
+
endpoint or portal screen could revoke it.
|
|
457
|
+
|
|
458
|
+
## Managing API keys
|
|
459
|
+
|
|
460
|
+
empirical auth list-keys # ids, labels, created dates
|
|
461
|
+
empirical auth rotate-key [--label <name>] # mint an ADDITIONAL key, others keep working
|
|
462
|
+
empirical auth rotate-key --replace-all # destructive: wipe every key, mint exactly one
|
|
463
|
+
empirical auth revoke-key --key-id <id> # revoke one key
|
|
464
|
+
empirical auth revoke-key --all # revoke every key on the account
|
|
465
|
+
|
|
466
|
+
Rotating without \`--replace-all\` is the safe cutover: mint the new key, move your integrations
|
|
467
|
+
over, then \`revoke-key --key-id\` the old one. A key is displayed once at mint time; only its
|
|
468
|
+
hash is stored, so there is no way to read it back later.
|
|
469
|
+
|
|
470
|
+
Minting requires a signed-in session (browser login or a fresh OAuth session), because a
|
|
471
|
+
permanent API key must not be able to mint more credentials. Revoking does not: it only ever
|
|
472
|
+
reduces access, and the CLI needs to be able to revoke with the credential it already holds.
|
|
473
|
+
|
|
434
474
|
## Bootstrap command (headless / non-interactive)
|
|
435
475
|
|
|
436
476
|
empirical oauth bootstrap headless --write-env
|
package/lib/doc.js
CHANGED
|
@@ -11,7 +11,7 @@ function runDoc(args) {
|
|
|
11
11
|
user: 'Get current user identity (MCP: get_user; CLI: doctor --output json)',
|
|
12
12
|
workspace: 'List, view, and switch workspaces',
|
|
13
13
|
doctor: 'Check token, connectivity, and workspace health',
|
|
14
|
-
agents: 'Supported agents, integration modes, and native plugin setup',
|
|
14
|
+
agents: 'Supported agents, integration modes, and native plugin setup',
|
|
15
15
|
auth: 'Token types, bootstrap, refresh, resolution order',
|
|
16
16
|
install: 'Install Empirical for Codex / Copilot / Claude Code',
|
|
17
17
|
kit: 'Create, source, activate, and sync Skills Kits',
|
package/lib/env.js
CHANGED
|
@@ -1,49 +1,49 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const { ask } = require('./ui');
|
|
4
|
-
|
|
5
|
-
function getEnv(name, fallback = '') {
|
|
6
|
-
return String(process.env[name] ?? fallback).trim();
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
function requireEnv(name) {
|
|
10
|
-
const value = getEnv(name);
|
|
11
|
-
if (!value) {
|
|
12
|
-
throw new Error(`Missing required env var: ${name}`);
|
|
13
|
-
}
|
|
14
|
-
return value;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
async function resolveRequiredValueFromEnvOrPrompt(envName, promptLabel) {
|
|
18
|
-
const value = getEnv(envName);
|
|
19
|
-
if (value) return value;
|
|
20
|
-
|
|
21
|
-
if (!process.stdin.isTTY) {
|
|
22
|
-
throw new Error(`Missing required env var: ${envName}`);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const answer = (await ask(`${promptLabel}: `)).trim();
|
|
26
|
-
if (!answer) {
|
|
27
|
-
throw new Error(`${promptLabel} is required`);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
return answer;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function parseFlag(args, flagName) {
|
|
34
|
-
return args.includes(flagName);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function parseOption(args, optionName, fallback = '') {
|
|
38
|
-
const index = args.indexOf(optionName);
|
|
39
|
-
if (index === -1) return fallback;
|
|
40
|
-
return String(args[index + 1] || '').trim() || fallback;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
module.exports = {
|
|
44
|
-
getEnv,
|
|
45
|
-
requireEnv,
|
|
46
|
-
resolveRequiredValueFromEnvOrPrompt,
|
|
47
|
-
parseFlag,
|
|
48
|
-
parseOption
|
|
49
|
-
};
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { ask } = require('./ui');
|
|
4
|
+
|
|
5
|
+
function getEnv(name, fallback = '') {
|
|
6
|
+
return String(process.env[name] ?? fallback).trim();
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function requireEnv(name) {
|
|
10
|
+
const value = getEnv(name);
|
|
11
|
+
if (!value) {
|
|
12
|
+
throw new Error(`Missing required env var: ${name}`);
|
|
13
|
+
}
|
|
14
|
+
return value;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async function resolveRequiredValueFromEnvOrPrompt(envName, promptLabel) {
|
|
18
|
+
const value = getEnv(envName);
|
|
19
|
+
if (value) return value;
|
|
20
|
+
|
|
21
|
+
if (!process.stdin.isTTY) {
|
|
22
|
+
throw new Error(`Missing required env var: ${envName}`);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const answer = (await ask(`${promptLabel}: `)).trim();
|
|
26
|
+
if (!answer) {
|
|
27
|
+
throw new Error(`${promptLabel} is required`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return answer;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function parseFlag(args, flagName) {
|
|
34
|
+
return args.includes(flagName);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function parseOption(args, optionName, fallback = '') {
|
|
38
|
+
const index = args.indexOf(optionName);
|
|
39
|
+
if (index === -1) return fallback;
|
|
40
|
+
return String(args[index + 1] || '').trim() || fallback;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
module.exports = {
|
|
44
|
+
getEnv,
|
|
45
|
+
requireEnv,
|
|
46
|
+
resolveRequiredValueFromEnvOrPrompt,
|
|
47
|
+
parseFlag,
|
|
48
|
+
parseOption
|
|
49
|
+
};
|
package/lib/help.js
CHANGED
|
@@ -18,8 +18,13 @@ function showHelp() {
|
|
|
18
18
|
log(' empirical policy show [--refresh]');
|
|
19
19
|
log(' empirical policy set --json <policy>');
|
|
20
20
|
log(' empirical auth login Interactive browser login');
|
|
21
|
-
log(' empirical auth logout
|
|
21
|
+
log(' empirical auth logout [--local-only] Revoke refresh tokens server-side, then clear the local session');
|
|
22
22
|
log(' empirical auth status Print user, token type, expiry, workspace as JSON (alias: whoami)');
|
|
23
|
+
log(' empirical auth list-keys List the API keys on this account');
|
|
24
|
+
log(' empirical auth rotate-key [--label <name>] [--replace-all]');
|
|
25
|
+
log(' Mint a new API key (--replace-all wipes the existing ones)');
|
|
26
|
+
log(' empirical auth revoke-key (--key-id <id> | --all)');
|
|
27
|
+
log(' Revoke one API key, or every key on the account');
|
|
23
28
|
log(' empirical oauth bootstrap headless [--print-access-token]');
|
|
24
29
|
log(' First-time token setup for CI/headless/API-key use');
|
|
25
30
|
log(' empirical oauth bootstrap headless --write-env [--env-output <path>]');
|