@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 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 OAuth session cleared.');
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
- warn('Usage: empirical auth <login|logout|status> (or: empirical whoami)');
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 Clear the locally stored session');
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>]');