@empirical-gauzza/cli 1.0.15 → 1.0.17

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/lib/constants.js CHANGED
@@ -202,7 +202,7 @@ Invite commands operate on the active team workspace. Create prints a shareable
202
202
  empirical workspace delete <number|name|id>
203
203
  empirical workspace delete <number|name|id> --confirm
204
204
 
205
- Deletion dry-runs by default. \`--confirm\` calls the existing soft-delete endpoint; the Default Brain cannot be deleted. If the active workspace is deleted, the CLI selects the Default Brain.
205
+ Deletion dry-runs by default. \`--confirm\` calls the existing soft-delete endpoint; the Default Workspace cannot be deleted. If the active workspace is deleted, the CLI selects the Default Workspace.
206
206
  `,
207
207
 
208
208
  doctor: `# Empirical Doctor
package/lib/help.js CHANGED
@@ -19,26 +19,32 @@ function showHelp() {
19
19
  log(' empirical plugin check [--force]');
20
20
  log(' empirical policy show [--refresh]');
21
21
  log(' empirical policy set --json <policy>');
22
- log(' empirical auth login');
23
- log(' empirical auth logout');
22
+ log(' empirical auth login Interactive browser login');
23
+ log(' empirical auth logout Clear the locally stored session');
24
24
  log(' empirical oauth bootstrap headless [--print-access-token]');
25
+ log(' First-time token setup for CI/headless/API-key use');
25
26
  log(' empirical oauth bootstrap headless --write-env [--env-output <path>]');
26
27
  log(' empirical oauth refresh headless [--print-access-token]');
28
+ log(' Renew an existing headless token before/after it expires');
27
29
  log(' empirical oauth refresh headless --write-env [--env-output <path>]');
28
30
  log(' empirical oauth bootstrap docker [--print-access-token]');
31
+ log(' Same as bootstrap headless, tuned for container startup');
29
32
  log(' empirical oauth refresh docker [--print-access-token]');
30
33
  log(' empirical oauth client set [--client-id <id>] [--client-secret <secret>]');
31
34
  log(' empirical oauth client show');
32
35
  log(' empirical oauth client clear');
33
- log(' empirical workspace list');
34
- log(' empirical workspace current');
35
- log(' empirical workspace select [<number|name>]');
36
- log(' empirical workspace use <number|name|id>');
36
+ log(' empirical workspace list List workspaces you can select, and which one is active');
37
+ log(' empirical workspace current Show the currently active workspace');
38
+ log(' empirical workspace select [<number|name>] Set the active workspace (prompts if omitted)');
39
+ log(' empirical workspace use <number|name|id> Alias for `workspace select`');
37
40
  log(' empirical workspace create <name> [--description <text>]');
41
+ log(' Create a new shared team workspace');
38
42
  log(' empirical workspace invite create [--role reader|writer|manager] [--expires-in-days <n>] [--max-uses <n>]');
39
- log(' empirical workspace invite list');
40
- log(' empirical workspace invite revoke <token>');
43
+ log(' Create a shareable invite link for the active team workspace');
44
+ log(' empirical workspace invite list List active invites for the active team workspace');
45
+ log(' empirical workspace invite revoke <token> Revoke a previously created invite');
41
46
  log(' empirical workspace delete <number|name|id> [--confirm]');
47
+ log(' Soft-delete a team workspace (dry-run without --confirm)');
42
48
  log(' empirical oauth bootstrap openclaw [--print-access-token] (legacy alias)');
43
49
  log(' empirical oauth refresh openclaw [--print-access-token] (legacy alias)');
44
50
  log(' empirical --oauth-bootstrap-headless [--print-access-token]');
@@ -1,143 +1,148 @@
1
- 'use strict';
2
-
3
- const { askYesNo, ask, log, warn, uiHeader, parseSelection } = require('./ui');
4
- const { runResolvedCommand, commandExists, summarizeProcessOutput } = require('./fsUtils');
5
- const { readDeviceConfig, writeDeviceConfig } = require('./deviceConfig');
6
- const { installNativePlugin } = require('./installers/nativePlugin');
7
-
8
- const PLUGIN_METADATA_URL = 'https://raw.githubusercontent.com/mattgauzza/empirical-memory-plugin/main/.claude-plugin/marketplace.json';
9
- const CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
10
- const SUPPORTED_TARGETS = ['codex', 'claude', 'copilot'];
11
-
12
- function parseVersion(value) {
13
- const match = String(value || '').trim().match(/^v?(\d+)\.(\d+)\.(\d+)/i);
14
- return match ? match.slice(1).map(Number) : null;
15
- }
16
-
17
- function compareVersions(left, right) {
18
- const a = parseVersion(left);
19
- const b = parseVersion(right);
20
- if (!a || !b) return null;
21
- for (let i = 0; i < 3; i += 1) {
22
- if (a[i] !== b[i]) return a[i] > b[i] ? 1 : -1;
23
- }
24
- return 0;
25
- }
26
-
27
- function extractVersion(output, pluginName = 'empirical-memory') {
28
- const text = String(output || '');
29
- const jsonCandidates = [text, ...text.split(/\r?\n/).map((line) => line.trim())];
30
- for (const candidate of jsonCandidates) {
31
- try {
32
- const value = JSON.parse(candidate);
33
- const rows = Array.isArray(value) ? value : [value, ...(Array.isArray(value?.plugins) ? value.plugins : [])];
34
- const match = rows.find((row) => row && (row.name === pluginName || row.plugin === pluginName));
35
- if (match?.version) return String(match.version);
36
- } catch (_error) {
37
- // Native CLIs may print a table instead of JSON.
38
- }
39
- }
40
- const line = text.split(/\r?\n/).find((value) => value.toLowerCase().includes(pluginName.toLowerCase()));
41
- const match = line && line.match(new RegExp(`${pluginName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}[^\\d]*(\\d+\\.\\d+\\.\\d+)`, 'i'));
42
- return match ? match[1] : null;
43
- }
44
-
45
- function readInstalledVersions(target, pluginNames) {
46
- const commands = {
47
- codex: ['codex', ['plugin', 'list']],
48
- claude: ['claude', ['plugin', 'list']],
49
- copilot: ['copilot', ['plugin', 'list']]
50
- };
51
- const [command, args] = commands[target];
52
- if (!commandExists(command)) return null;
53
- const result = runResolvedCommand(command, args, { stdio: 'pipe' });
54
- if (!result || result.status !== 0) return null;
55
- const output = `${result.stdout || ''}\n${result.stderr || ''}`;
56
- return Object.fromEntries(pluginNames.map((name) => [name, extractVersion(output, name)]));
57
- }
58
-
59
- async function fetchLatestVersions(fetchImpl = globalThis.fetch) {
60
- if (typeof fetchImpl !== 'function') return null;
61
- const response = await fetchImpl(PLUGIN_METADATA_URL, { signal: AbortSignal.timeout(5000) });
62
- if (!response.ok) throw new Error(`marketplace metadata returned HTTP ${response.status}`);
63
- const data = await response.json();
64
- if (Array.isArray(data?.plugins)) {
65
- return Object.fromEntries(data.plugins.map((plugin) => [plugin.name, plugin.version]).filter(([, version]) => version));
66
- }
67
- return data?.version ? { 'empirical-memory': data.version } : {};
68
- }
69
-
70
- function shouldCheck(cache, now = Date.now(), force = false) {
71
- return force || !cache?.checkedAt || now - Number(cache.checkedAt) >= CHECK_INTERVAL_MS;
72
- }
73
-
74
- async function checkNativePluginUpdates({ force = false, interactive = process.stdin.isTTY, fetchImpl } = {}) {
75
- if (!interactive && !force) return { skipped: true, reason: 'noninteractive' };
76
- const config = readDeviceConfig();
77
- const cache = config.pluginUpdates || {};
78
- if (!shouldCheck(cache, Date.now(), force)) return { skipped: true, reason: 'rate_limited' };
79
-
80
- let latestVersion;
81
- try {
82
- latestVersion = await fetchLatestVersions(fetchImpl);
83
- } catch (error) {
84
- config.pluginUpdates = { ...cache, checkedAt: Date.now(), lastError: error.message };
85
- writeDeviceConfig(config);
86
- return { skipped: true, reason: 'marketplace_unavailable' };
87
- }
88
- const pluginNames = Object.keys(latestVersion);
89
- const installed = {};
90
- for (const target of SUPPORTED_TARGETS) installed[target] = readInstalledVersions(target, pluginNames);
91
- config.pluginUpdates = { checkedAt: Date.now(), latestVersions: latestVersion, installed };
92
- writeDeviceConfig(config);
93
-
94
- const outdated = SUPPORTED_TARGETS.flatMap((target) => pluginNames
95
- .filter((name) => compareVersions(latestVersion[name], installed[target][name]) === 1)
96
- .map((name) => ({ target, name, installed: installed[target][name], latest: latestVersion[name] })));
97
- const latestSignature = JSON.stringify(latestVersion);
98
- if (outdated.length === 0 || cache.offeredVersion === latestSignature) {
99
- return { latestVersion, installed, outdated };
100
- }
101
-
102
- if (!interactive) {
103
- return { latestVersion, installed, outdated, promptSuppressed: true };
104
- }
105
-
106
- log(uiHeader(`A newer empirical-memory plugin is available (${latestVersion}).`));
107
- log(`Installed versions: ${outdated.map((item) => `${item.target} ${item.name} ${item.installed} -> ${item.latest}`).join(', ')}`);
108
- const update = await askYesNo('Update the native plugin now?', true);
109
- if (!update) {
110
- config.pluginUpdates = { ...config.pluginUpdates, offeredVersion: latestSignature, outcome: 'declined' };
111
- writeDeviceConfig(config);
112
- return { latestVersion, installed, outdated, declined: true };
113
- }
114
- const selection = (await ask('Which agents should be updated? (all, or numbers 1-' + outdated.length + ') ')).trim().toLowerCase();
115
- const outdatedTargets = [...new Set(outdated.map((item) => item.target))];
116
- const selectedTargets = selection === 'all' || !selection
117
- ? outdatedTargets
118
- : parseSelection(selection, outdatedTargets.length).map((index) => outdatedTargets[index]);
119
- const results = {};
120
- for (const target of selectedTargets) {
121
- try {
122
- installNativePlugin(target, [...new Set(outdated.filter((item) => item.target === target).map((item) => item.name))]);
123
- results[target] = 'updated';
124
- } catch (error) {
125
- results[target] = `failed: ${summarizeProcessOutput({ stderr: error.message })}`;
126
- warn(`Could not update ${target}: ${error.message}`);
127
- }
128
- }
129
- config.pluginUpdates = { ...config.pluginUpdates, offeredVersion: latestSignature, outcome: results };
130
- writeDeviceConfig(config);
131
- return { latestVersion, installed, outdated, results };
132
- }
133
-
134
- module.exports = {
135
- CHECK_INTERVAL_MS,
136
- SUPPORTED_TARGETS,
137
- parseVersion,
138
- compareVersions,
139
- extractVersion,
140
- shouldCheck,
141
- fetchLatestVersions,
142
- checkNativePluginUpdates
143
- };
1
+ 'use strict';
2
+
3
+ const { askYesNo, ask, log, warn, uiHeader, parseSelection } = require('./ui');
4
+ const { runResolvedCommand, commandExists, summarizeProcessOutput } = require('./fsUtils');
5
+ const { readDeviceConfig, writeDeviceConfig } = require('./deviceConfig');
6
+ const { installNativePlugin } = require('./installers/nativePlugin');
7
+
8
+ const PLUGIN_METADATA_URL = 'https://raw.githubusercontent.com/mattgauzza/empirical-memory-plugin/main/.claude-plugin/marketplace.json';
9
+ const CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
10
+ const SUPPORTED_TARGETS = ['codex', 'claude', 'copilot'];
11
+
12
+ function parseVersion(value) {
13
+ const match = String(value || '').trim().match(/^v?(\d+)\.(\d+)\.(\d+)/i);
14
+ return match ? match.slice(1).map(Number) : null;
15
+ }
16
+
17
+ function compareVersions(left, right) {
18
+ const a = parseVersion(left);
19
+ const b = parseVersion(right);
20
+ if (!a || !b) return null;
21
+ for (let i = 0; i < 3; i += 1) {
22
+ if (a[i] !== b[i]) return a[i] > b[i] ? 1 : -1;
23
+ }
24
+ return 0;
25
+ }
26
+
27
+ function extractVersion(output, pluginName = 'empirical-memory') {
28
+ const text = String(output || '');
29
+ const jsonCandidates = [text, ...text.split(/\r?\n/).map((line) => line.trim())];
30
+ for (const candidate of jsonCandidates) {
31
+ try {
32
+ const value = JSON.parse(candidate);
33
+ const rows = Array.isArray(value) ? value : [value, ...(Array.isArray(value?.plugins) ? value.plugins : [])];
34
+ const match = rows.find((row) => row && (row.name === pluginName || row.plugin === pluginName));
35
+ if (match?.version) return String(match.version);
36
+ } catch (_error) {
37
+ // Native CLIs may print a table instead of JSON.
38
+ }
39
+ }
40
+ const line = text.split(/\r?\n/).find((value) => value.toLowerCase().includes(pluginName.toLowerCase()));
41
+ const match = line && line.match(new RegExp(`${pluginName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}[^\\d]*(\\d+\\.\\d+\\.\\d+)`, 'i'));
42
+ return match ? match[1] : null;
43
+ }
44
+
45
+ function readInstalledVersions(target, pluginNames) {
46
+ const commands = {
47
+ codex: ['codex', ['plugin', 'list']],
48
+ claude: ['claude', ['plugin', 'list']],
49
+ copilot: ['copilot', ['plugin', 'list']]
50
+ };
51
+ const [command, args] = commands[target];
52
+ if (!commandExists(command)) return null;
53
+ const result = runResolvedCommand(command, args, { stdio: 'pipe' });
54
+ if (!result || result.status !== 0) return null;
55
+ const output = `${result.stdout || ''}\n${result.stderr || ''}`;
56
+ return Object.fromEntries(pluginNames.map((name) => [name, extractVersion(output, name)]));
57
+ }
58
+
59
+ async function fetchLatestVersions(fetchImpl = globalThis.fetch) {
60
+ if (typeof fetchImpl !== 'function') return null;
61
+ const response = await fetchImpl(PLUGIN_METADATA_URL, { signal: AbortSignal.timeout(5000) });
62
+ if (!response.ok) throw new Error(`marketplace metadata returned HTTP ${response.status}`);
63
+ const data = await response.json();
64
+ if (Array.isArray(data?.plugins)) {
65
+ return Object.fromEntries(data.plugins.map((plugin) => [plugin.name, plugin.version]).filter(([, version]) => version));
66
+ }
67
+ return data?.version ? { 'empirical-memory': data.version } : {};
68
+ }
69
+
70
+ function shouldCheck(cache, now = Date.now(), force = false) {
71
+ return force || !cache?.checkedAt || now - Number(cache.checkedAt) >= CHECK_INTERVAL_MS;
72
+ }
73
+
74
+ async function checkNativePluginUpdates({ force = false, interactive = process.stdin.isTTY, fetchImpl } = {}) {
75
+ if (!interactive && !force) return { skipped: true, reason: 'noninteractive' };
76
+ const config = readDeviceConfig();
77
+ const cache = config.pluginUpdates || {};
78
+ if (!shouldCheck(cache, Date.now(), force)) return { skipped: true, reason: 'rate_limited' };
79
+
80
+ let latestVersion;
81
+ try {
82
+ latestVersion = await fetchLatestVersions(fetchImpl);
83
+ } catch (error) {
84
+ config.pluginUpdates = { ...cache, checkedAt: Date.now(), lastError: error.message };
85
+ writeDeviceConfig(config);
86
+ return { skipped: true, reason: 'marketplace_unavailable' };
87
+ }
88
+ const pluginNames = Object.keys(latestVersion);
89
+ const installed = {};
90
+ for (const target of SUPPORTED_TARGETS) installed[target] = readInstalledVersions(target, pluginNames);
91
+ config.pluginUpdates = { checkedAt: Date.now(), latestVersions: latestVersion, installed };
92
+ writeDeviceConfig(config);
93
+
94
+ const outdated = SUPPORTED_TARGETS.flatMap((target) => pluginNames
95
+ .filter((name) => compareVersions(latestVersion[name], installed[target][name]) === 1)
96
+ .map((name) => ({ target, name, installed: installed[target][name], latest: latestVersion[name] })));
97
+ const latestSignature = JSON.stringify(latestVersion);
98
+ if (outdated.length === 0 || cache.offeredVersion === latestSignature) {
99
+ return { latestVersion, installed, outdated };
100
+ }
101
+
102
+ if (!interactive) {
103
+ return { latestVersion, installed, outdated, promptSuppressed: true };
104
+ }
105
+
106
+ // latestVersion is a map of pluginName -> version (e.g. {'empirical-memory': '1.1.2', ...}),
107
+ // not a single scalar -- interpolating it directly stringified to "[object Object]". Prefer
108
+ // the core plugin's version since that's the one most users recognize; the per-target
109
+ // breakdown line right below already lists every outdated plugin/version precisely.
110
+ const headlineVersion = latestVersion['empirical-memory'] || Object.values(latestVersion)[0] || 'update available';
111
+ log(uiHeader(`A newer empirical-memory plugin is available (${headlineVersion}).`));
112
+ log(`Installed versions: ${outdated.map((item) => `${item.target} ${item.name} ${item.installed} -> ${item.latest}`).join(', ')}`);
113
+ const update = await askYesNo('Update the native plugin now?', true);
114
+ if (!update) {
115
+ config.pluginUpdates = { ...config.pluginUpdates, offeredVersion: latestSignature, outcome: 'declined' };
116
+ writeDeviceConfig(config);
117
+ return { latestVersion, installed, outdated, declined: true };
118
+ }
119
+ const selection = (await ask('Which agents should be updated? (all, or numbers 1-' + outdated.length + ') ')).trim().toLowerCase();
120
+ const outdatedTargets = [...new Set(outdated.map((item) => item.target))];
121
+ const selectedTargets = selection === 'all' || !selection
122
+ ? outdatedTargets
123
+ : parseSelection(selection, outdatedTargets.length).map((index) => outdatedTargets[index]);
124
+ const results = {};
125
+ for (const target of selectedTargets) {
126
+ try {
127
+ installNativePlugin(target, [...new Set(outdated.filter((item) => item.target === target).map((item) => item.name))]);
128
+ results[target] = 'updated';
129
+ } catch (error) {
130
+ results[target] = `failed: ${summarizeProcessOutput({ stderr: error.message })}`;
131
+ warn(`Could not update ${target}: ${error.message}`);
132
+ }
133
+ }
134
+ config.pluginUpdates = { ...config.pluginUpdates, offeredVersion: latestSignature, outcome: results };
135
+ writeDeviceConfig(config);
136
+ return { latestVersion, installed, outdated, results };
137
+ }
138
+
139
+ module.exports = {
140
+ CHECK_INTERVAL_MS,
141
+ SUPPORTED_TARGETS,
142
+ parseVersion,
143
+ compareVersions,
144
+ extractVersion,
145
+ shouldCheck,
146
+ fetchLatestVersions,
147
+ checkNativePluginUpdates
148
+ };
@@ -174,11 +174,11 @@ async function runWorkspaceCommand(args) {
174
174
  const selected = payload.defaultOption.selected
175
175
  ? payload.defaultOption
176
176
  : (payload.workspaces || []).find((ws) => ws.selected) || payload.defaultOption;
177
- log(`Current workspace: ${selected.label || 'Default Brain'}`);
177
+ log(`Current workspace: ${selected.label || 'Default Workspace'}`);
178
178
  return;
179
179
  }
180
180
  const payload = await httpJson(currentUrl, 'GET', null, authHeaders);
181
- log(`Current workspace: ${payload?.current?.label || 'Default Brain'}`);
181
+ log(`Current workspace: ${payload?.current?.label || 'Default Workspace'}`);
182
182
  if (payload?.locked) {
183
183
  log('This OAuth client is admin-locked to a workspace.');
184
184
  }
@@ -206,7 +206,7 @@ async function runWorkspaceCommand(args) {
206
206
  }
207
207
 
208
208
  if (!requested) {
209
- throw new Error('Please provide a workspace selection, e.g. `empirical workspace use 1` or `empirical workspace use "Default Brain"`.');
209
+ throw new Error('Please provide a workspace selection, e.g. `empirical workspace use 1` or `empirical workspace use "Default Workspace"`.');
210
210
  }
211
211
 
212
212
  const target = resolveWorkspaceTarget(requested, payload, teamPayload);
@@ -250,7 +250,7 @@ async function runWorkspaceCommand(args) {
250
250
  if (target?.ambiguous) throw new Error(`Ambiguous workspace: ${requested}. Use its list number or workspace ID.`);
251
251
  if (!target) throw new Error(`Workspace not found: ${requested}`);
252
252
  if (target.isDefault || target.key === 'default_brain') {
253
- throw new Error('The Default Brain cannot be deleted.');
253
+ throw new Error('The Default Workspace cannot be deleted.');
254
254
  }
255
255
  if (!target.workspaceId) throw new Error(`Workspace is not a deletable team brain: ${target.label}`);
256
256
 
@@ -265,7 +265,7 @@ async function runWorkspaceCommand(args) {
265
265
  if (target.selected && clientId) {
266
266
  const updateUrl = new URL(`/oauth/clients/${encodedClientId}/workspace-selection`, baseUrl).toString();
267
267
  const updated = await httpJson(updateUrl, 'POST', { key: 'default_brain' }, authHeaders);
268
- log(uiSuccess(`Soft-deleted workspace ${targetDescription}. Active workspace reset to ${updated?.current?.label || 'Default Brain'}.`));
268
+ log(uiSuccess(`Soft-deleted workspace ${targetDescription}. Active workspace reset to ${updated?.current?.label || 'Default Workspace'}.`));
269
269
  clearSelectedWorkspace();
270
270
  } else {
271
271
  log(uiSuccess(`Soft-deleted workspace ${targetDescription}.`));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@empirical-gauzza/cli",
3
- "version": "1.0.15",
3
+ "version": "1.0.17",
4
4
  "description": "Empirical CLI installer and local MCP wiring helper",
5
5
  "type": "commonjs",
6
6
  "license": "MIT",