@empirical-gauzza/cli 1.0.16 → 1.0.18
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/help.js +1 -14
- package/lib/pluginUpdateCheck.js +148 -143
- package/package.json +1 -1
package/lib/help.js
CHANGED
|
@@ -13,8 +13,6 @@ function showHelp() {
|
|
|
13
13
|
log('');
|
|
14
14
|
log('Usage:');
|
|
15
15
|
log(' empirical install');
|
|
16
|
-
log(' empirical -install');
|
|
17
|
-
log(' empirical --install');
|
|
18
16
|
log(' empirical plugin install <codex|copilot|claude|all> [--all-optional]');
|
|
19
17
|
log(' empirical plugin check [--force]');
|
|
20
18
|
log(' empirical policy show [--refresh]');
|
|
@@ -45,23 +43,12 @@ function showHelp() {
|
|
|
45
43
|
log(' empirical workspace invite revoke <token> Revoke a previously created invite');
|
|
46
44
|
log(' empirical workspace delete <number|name|id> [--confirm]');
|
|
47
45
|
log(' Soft-delete a team workspace (dry-run without --confirm)');
|
|
48
|
-
log(' empirical oauth bootstrap openclaw [--print-access-token] (legacy alias)');
|
|
49
|
-
log(' empirical oauth refresh openclaw [--print-access-token] (legacy alias)');
|
|
50
|
-
log(' empirical --oauth-bootstrap-headless [--print-access-token]');
|
|
51
|
-
log(' empirical --oauth-refresh-headless [--print-access-token]');
|
|
52
|
-
log(' empirical --oauth-bootstrap-openclaw [--print-access-token] (legacy alias)');
|
|
53
|
-
log(' empirical --oauth-refresh-openclaw [--print-access-token] (legacy alias)');
|
|
54
46
|
log('');
|
|
55
47
|
log('Options:');
|
|
56
|
-
log(' install
|
|
48
|
+
log(' install Interactive installer for Codex/Copilot/Claude CLI integration');
|
|
57
49
|
log(' plugin install <target> Install core plugin; add --all-optional or capability flags');
|
|
58
50
|
log(' plugin check [--force] Check for native plugin updates');
|
|
59
51
|
log(' policy show/set Inspect or update native Empirical behavior policy');
|
|
60
|
-
log(' Step 1 checks/provisions OAuth client credentials locally');
|
|
61
|
-
log(' --oauth-bootstrap-headless, --oauth-bootstrap-openclaw');
|
|
62
|
-
log(' Headless OAuth bootstrap using client_credentials (client ID + client secret)');
|
|
63
|
-
log(' --oauth-refresh-headless, --oauth-refresh-openclaw');
|
|
64
|
-
log(' Refresh headless token from stored refresh token file (if available)');
|
|
65
52
|
log(' --client-id <id> Client ID override for headless bootstrap');
|
|
66
53
|
log(' --client-secret <sec> Client secret override for headless bootstrap');
|
|
67
54
|
log(' --print-access-token Print only access token (for scripting)');
|
package/lib/pluginUpdateCheck.js
CHANGED
|
@@ -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
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
+
};
|