@empirical-gauzza/cli 1.0.19 → 1.0.21

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.
@@ -1,81 +1,84 @@
1
- 'use strict';
2
-
3
- const { log, warn, uiHeader, uiSuccess } = require('./ui');
4
- const { detectInstallTargets } = require('./installers/detect');
5
- const {
6
- installNativePlugin,
7
- OPTIONAL_PLUGIN_NAMES = ['empirical-work-history', 'empirical-scar-tissue', 'empirical-todo']
8
- } = require('./installers/nativePlugin');
9
- const { checkNativePluginUpdates } = require('./pluginUpdateCheck');
10
-
11
- const SUPPORTED_TARGETS = ['codex', 'copilot', 'claude'];
12
-
13
- function isPluginCommand(args = []) {
14
- return args[0] === 'plugin';
15
- }
16
-
17
- function resolveTargets(targetArg) {
18
- if (targetArg === 'all') return SUPPORTED_TARGETS;
19
- if (targetArg && SUPPORTED_TARGETS.includes(targetArg)) return [targetArg];
20
- if (targetArg) {
21
- throw new Error(`Unsupported plugin target "${targetArg}". Use codex, copilot, claude, or all.`);
22
- }
23
-
24
- const detected = detectInstallTargets().map((target) => target.id);
25
- if (detected.length === 0) {
26
- throw new Error('No supported CLI detected. Specify a target explicitly: codex, copilot, claude, or all.');
27
- }
28
- return detected;
29
- }
30
-
31
- function resolvePluginNames(args = []) {
32
- const names = ['empirical-memory'];
33
- if (args.includes('--all-optional') || args.includes('--work-history')) names.push('empirical-work-history');
34
- if (args.includes('--all-optional') || args.includes('--scar-tissue')) names.push('empirical-scar-tissue');
35
- if (args.includes('--all-optional') || args.includes('--todo')) names.push('empirical-todo');
36
- return names.filter((name) => name === 'empirical-memory' || OPTIONAL_PLUGIN_NAMES.includes(name));
37
- }
38
-
39
- async function runPluginCommand(args = []) {
40
- const action = args[0];
41
- if (action === 'check') {
42
- const result = await checkNativePluginUpdates({
43
- force: args.includes('--force'),
44
- interactive: Boolean(process.stdin.isTTY)
45
- });
46
- if (result.skipped && result.reason === 'rate_limited') {
47
- log('Native plugin update check was recently completed.');
48
- } else if (result.outdated?.length) {
49
- log(uiSuccess('Native plugin update check complete.'));
50
- } else if (!result.skipped) {
51
- log('Native plugins are up to date.');
52
- }
53
- return;
54
- }
55
- if (action !== 'install') {
56
- throw new Error('Usage: empirical plugin install <codex|copilot|claude|all> or empirical plugin check [--force]');
57
- }
58
-
59
- const targets = resolveTargets(args[1]);
60
- const pluginNames = resolvePluginNames(args.slice(2));
61
- log(uiHeader('Empirical native plugin installer'));
62
- log('');
63
- for (const target of targets) {
64
- log(`Installing empirical-memory for ${target}...`);
65
- if (pluginNames.length === 1) installNativePlugin(target);
66
- else installNativePlugin(target, pluginNames);
67
- log(uiSuccess(`${target} plugins installed through its marketplace: ${pluginNames.join(', ')}.`));
68
- }
69
- log('');
70
- log('Native plugin installation does not configure MCP or credentials.');
71
- log('Optional packages: --work-history, --scar-tissue, --todo, or --all-optional.');
72
- log('Use `empirical auth login` for normal CLI OAuth, or the full `empirical install` flow for MCP setup.');
73
- }
74
-
75
- module.exports = {
76
- SUPPORTED_TARGETS,
77
- isPluginCommand,
78
- resolveTargets,
79
- resolvePluginNames,
80
- runPluginCommand
81
- };
1
+ 'use strict';
2
+
3
+ const { log, warn, uiHeader, uiSuccess } = require('./ui');
4
+ const { detectInstallTargets } = require('./installers/detect');
5
+ const {
6
+ installNativePlugin,
7
+ OPTIONAL_PLUGIN_NAMES = ['empirical-work-history', 'empirical-scar-tissue', 'empirical-todo']
8
+ } = require('./installers/nativePlugin');
9
+ const { checkNativePluginUpdates } = require('./pluginUpdateCheck');
10
+
11
+ const SUPPORTED_TARGETS = ['codex', 'copilot', 'claude'];
12
+
13
+ function isPluginCommand(args = []) {
14
+ return args[0] === 'plugin';
15
+ }
16
+
17
+ function resolveTargets(targetArg) {
18
+ if (targetArg === 'all') return SUPPORTED_TARGETS;
19
+ if (targetArg && SUPPORTED_TARGETS.includes(targetArg)) return [targetArg];
20
+ if (targetArg) {
21
+ throw new Error(`Unsupported plugin target "${targetArg}". Use codex, copilot, claude, or all.`);
22
+ }
23
+
24
+ const detected = detectInstallTargets().map((target) => target.id);
25
+ if (detected.length === 0) {
26
+ throw new Error('No supported CLI detected. Specify a target explicitly: codex, copilot, claude, or all.');
27
+ }
28
+ return detected;
29
+ }
30
+
31
+ function resolvePluginNames(args = []) {
32
+ const names = ['empirical-memory'];
33
+ if (args.includes('--all-optional') || args.includes('--work-history')) names.push('empirical-work-history');
34
+ if (args.includes('--all-optional') || args.includes('--scar-tissue')) names.push('empirical-scar-tissue');
35
+ if (args.includes('--all-optional') || args.includes('--todo')) names.push('empirical-todo');
36
+ return names.filter((name) => name === 'empirical-memory' || OPTIONAL_PLUGIN_NAMES.includes(name));
37
+ }
38
+
39
+ async function runPluginCommand(args = []) {
40
+ const action = args[0];
41
+ if (action === 'check') {
42
+ const result = await checkNativePluginUpdates({
43
+ force: args.includes('--force'),
44
+ interactive: Boolean(process.stdin.isTTY)
45
+ });
46
+ if (result.skipped && result.reason === 'rate_limited') {
47
+ log('Native plugin update check was recently completed.');
48
+ } else if (result.outdated?.length) {
49
+ log(uiSuccess('Native plugin update check complete.'));
50
+ } else if (!result.skipped) {
51
+ log('Native plugins are up to date.');
52
+ }
53
+ if (result.notInstalled?.length) {
54
+ for (const target of result.notInstalled) log(`${target}: not installed`);
55
+ }
56
+ return;
57
+ }
58
+ if (action !== 'install') {
59
+ throw new Error('Usage: empirical plugin install <codex|copilot|claude|all> or empirical plugin check [--force]');
60
+ }
61
+
62
+ const targets = resolveTargets(args[1]);
63
+ const pluginNames = resolvePluginNames(args.slice(2));
64
+ log(uiHeader('Empirical native plugin installer'));
65
+ log('');
66
+ for (const target of targets) {
67
+ log(`Installing empirical-memory for ${target}...`);
68
+ if (pluginNames.length === 1) installNativePlugin(target);
69
+ else installNativePlugin(target, pluginNames);
70
+ log(uiSuccess(`${target} plugins installed through its marketplace: ${pluginNames.join(', ')}.`));
71
+ }
72
+ log('');
73
+ log('Native plugin installation does not configure MCP or credentials.');
74
+ log('Optional packages: --work-history, --scar-tissue, --todo, or --all-optional.');
75
+ log('Use `empirical auth login` for normal CLI OAuth, or the full `empirical install` flow for MCP setup.');
76
+ }
77
+
78
+ module.exports = {
79
+ SUPPORTED_TARGETS,
80
+ isPluginCommand,
81
+ resolveTargets,
82
+ resolvePluginNames,
83
+ runPluginCommand
84
+ };
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const { askYesNo, ask, log, warn, uiHeader, parseSelection } = require('./ui');
3
+ const { askYesNo, ask, warn, uiHeader, parseSelection } = require('./ui');
4
4
  const { runResolvedCommand, commandExists, summarizeProcessOutput } = require('./fsUtils');
5
5
  const { readDeviceConfig, writeDeviceConfig } = require('./deviceConfig');
6
6
  const { installNativePlugin } = require('./installers/nativePlugin');
@@ -34,12 +34,35 @@ function extractVersion(output, pluginName = 'empirical-memory') {
34
34
  const match = rows.find((row) => row && (row.name === pluginName || row.plugin === pluginName));
35
35
  if (match?.version) return String(match.version);
36
36
  } catch (_error) {
37
- // Native CLIs may print a table instead of JSON.
37
+ // Native CLIs may print a table or a human-readable list instead of JSON.
38
38
  }
39
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;
40
+
41
+ // Neither native CLI emits JSON for `plugin list`. Codex prints a table row per plugin
42
+ // ("empirical-memory@empirical-memory installed, enabled 1.1.1 <path>"); Claude prints a
43
+ // bulleted list with the version on its OWN line two lines below the name ("❯ empirical-
44
+ // memory@empirical-memory" / " Version: 1.1.2"). A naive "first line containing pluginName"
45
+ // search (the previous approach) false-matched marketplace-header/path lines that mention the
46
+ // plugin name but carry no version at all (e.g. "Marketplace `empirical-memory`"), and for
47
+ // Claude's format could never find a version regardless since name and version never share a
48
+ // line. Anchoring on "<name>@<marketplace>" is what both real formats share and headers/paths
49
+ // don't, so that's the marker used to find the actual entry line before looking for a version
50
+ // on it or just below it.
51
+ const escapedName = pluginName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
52
+ const entryLinePattern = new RegExp(`${escapedName}@[\\w.-]+`, 'i');
53
+ const lines = text.split(/\r?\n/);
54
+ const entryIndex = lines.findIndex((line) => entryLinePattern.test(line));
55
+ if (entryIndex === -1) return null;
56
+
57
+ const sameLineMatch = lines[entryIndex].match(/(\d+\.\d+\.\d+[\w.+-]*)/);
58
+ if (sameLineMatch) return sameLineMatch[1];
59
+
60
+ for (let i = entryIndex + 1; i < Math.min(lines.length, entryIndex + 5); i += 1) {
61
+ const versionMatch = lines[i].match(/Version:\s*(\S+)/i);
62
+ if (versionMatch) return versionMatch[1] === 'unknown' ? null : versionMatch[1];
63
+ if (entryLinePattern.test(lines[i])) break;
64
+ }
65
+ return null;
43
66
  }
44
67
 
45
68
  function readInstalledVersions(target, pluginNames) {
@@ -91,32 +114,42 @@ async function checkNativePluginUpdates({ force = false, interactive = process.s
91
114
  config.pluginUpdates = { checkedAt: Date.now(), latestVersions: latestVersion, installed };
92
115
  writeDeviceConfig(config);
93
116
 
94
- const outdated = SUPPORTED_TARGETS.flatMap((target) => pluginNames
117
+ // `installed[target]` is null when that target's CLI is not on PATH (or its `plugin list`
118
+ // call failed) -- readInstalledVersions() returns null in that case, and it is not "no
119
+ // outdated plugins", it is "we could not check this target at all". Skip those targets
120
+ // instead of crashing on `installed[target][name]`.
121
+ const outdated = SUPPORTED_TARGETS.filter((target) => installed[target]).flatMap((target) => pluginNames
95
122
  .filter((name) => compareVersions(latestVersion[name], installed[target][name]) === 1)
96
123
  .map((name) => ({ target, name, installed: installed[target][name], latest: latestVersion[name] })));
124
+ const notInstalled = SUPPORTED_TARGETS.filter((target) => !installed[target]);
97
125
  const latestSignature = JSON.stringify(latestVersion);
98
126
  if (outdated.length === 0 || cache.offeredVersion === latestSignature) {
99
- return { latestVersion, installed, outdated };
127
+ return { latestVersion, installed, outdated, notInstalled };
100
128
  }
101
129
 
102
130
  if (!interactive) {
103
- return { latestVersion, installed, outdated, promptSuppressed: true };
131
+ return { latestVersion, installed, outdated, notInstalled, promptSuppressed: true };
104
132
  }
105
133
 
134
+ // All output below is advisory/interactive (header + prompts), and this check runs
135
+ // automatically ahead of most commands (see bin/empirical.js), so it must never land on
136
+ // stdout: any command whose stdout contract is machine-parsed (e.g. `memory query`) would
137
+ // otherwise get this header and prompt text as preamble ahead of its JSON. Route it through
138
+ // stderr instead -- ask()/askYesNo() already accept an output-stream parameter for this.
106
139
  // latestVersion is a map of pluginName -> version (e.g. {'empirical-memory': '1.1.2', ...}),
107
140
  // not a single scalar -- interpolating it directly stringified to "[object Object]". Prefer
108
141
  // the core plugin's version since that's the one most users recognize; the per-target
109
142
  // breakdown line right below already lists every outdated plugin/version precisely.
110
143
  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);
144
+ warn(uiHeader(`A newer empirical-memory plugin is available (${headlineVersion}).`));
145
+ warn(`Installed versions: ${outdated.map((item) => `${item.target} ${item.name} ${item.installed} -> ${item.latest}`).join(', ')}`);
146
+ const update = await askYesNo('Update the native plugin now?', true, process.stderr);
114
147
  if (!update) {
115
148
  config.pluginUpdates = { ...config.pluginUpdates, offeredVersion: latestSignature, outcome: 'declined' };
116
149
  writeDeviceConfig(config);
117
- return { latestVersion, installed, outdated, declined: true };
150
+ return { latestVersion, installed, outdated, notInstalled, declined: true };
118
151
  }
119
- const selection = (await ask('Which agents should be updated? (all, or numbers 1-' + outdated.length + ') ')).trim().toLowerCase();
152
+ const selection = (await ask('Which agents should be updated? (all, or numbers 1-' + outdated.length + ') ', process.stderr)).trim().toLowerCase();
120
153
  const outdatedTargets = [...new Set(outdated.map((item) => item.target))];
121
154
  const selectedTargets = selection === 'all' || !selection
122
155
  ? outdatedTargets
@@ -133,7 +166,7 @@ async function checkNativePluginUpdates({ force = false, interactive = process.s
133
166
  }
134
167
  config.pluginUpdates = { ...config.pluginUpdates, offeredVersion: latestSignature, outcome: results };
135
168
  writeDeviceConfig(config);
136
- return { latestVersion, installed, outdated, results };
169
+ return { latestVersion, installed, outdated, notInstalled, results };
137
170
  }
138
171
 
139
172
  module.exports = {
@@ -142,6 +175,7 @@ module.exports = {
142
175
  parseVersion,
143
176
  compareVersions,
144
177
  extractVersion,
178
+ readInstalledVersions,
145
179
  shouldCheck,
146
180
  fetchLatestVersions,
147
181
  checkNativePluginUpdates
@@ -0,0 +1,25 @@
1
+ 'use strict';
2
+
3
+ // Per-agent skill file path conventions, mirrored exactly from the hardcoded paths already used
4
+ // by installers/claude.js, installers/codex.js, installers/copilot.js (for the single
5
+ // empirical-memory skill) — parameterized by skill name instead of hardcoded to it.
6
+ //
7
+ // Split out of skillSync.js into its own dependency-free module so both skillSync.js and
8
+ // installers/nativePlugin.js (C-001's mode-aware Skill overwrite) can use the same path
9
+ // convention without creating a require cycle: skillSync.js -> installers/detect.js ->
10
+ // installers/claude.js|codex.js|copilot.js -> installers/nativePlugin.js would loop straight
11
+ // back into skillSync.js if nativePlugin.js required it directly.
12
+ const os = require('os');
13
+ const path = require('path');
14
+
15
+ function skillFilePath(targetId, skillName) {
16
+ const home = os.homedir();
17
+ if (targetId === 'claude') return path.join(home, '.claude', 'skills', skillName, 'SKILL.md');
18
+ if (targetId === 'codex') return path.join(home, '.agents', 'skills', skillName, 'SKILL.md');
19
+ if (targetId === 'copilot') return path.join(home, '.copilot', 'skills', skillName, 'SKILL.md');
20
+ return null;
21
+ }
22
+
23
+ module.exports = {
24
+ skillFilePath
25
+ };
package/lib/skillSync.js CHANGED
@@ -7,11 +7,10 @@
7
7
  const crypto = require('crypto');
8
8
  const fs = require('fs');
9
9
  const matter = require('gray-matter');
10
- const os = require('os');
11
10
  const path = require('path');
12
11
  const { httpJson } = require('./http');
13
12
  const { log, warn, createPrompter } = require('./ui');
14
- const { detectInstallTargets } = require('./installers/detect');
13
+ const { detectInstallTargets } = require('./installers/detect');
15
14
  const {
16
15
  readDeviceConfig,
17
16
  writeDeviceConfig,
@@ -20,47 +19,40 @@ const {
20
19
  writeSyncManifest,
21
20
  readPendingReview,
22
21
  writePendingReview
23
- } = require('./deviceConfig');
24
-
25
- const SYNC_READ_MAX_ATTEMPTS = 2;
26
- const SYNC_READ_RETRY_DELAY_MS = 250;
27
-
28
- function isRetryableSyncReadError(error) {
29
- if (error?.statusCode) return false;
30
- return /socket hang up|ECONNRESET|ECONNREFUSED|ETIMEDOUT|EPIPE|ENOTFOUND|network/i.test(error?.message || '');
31
- }
32
-
33
- async function fetchSyncRead(url, headers, label) {
34
- let lastError;
35
- for (let attempt = 1; attempt <= SYNC_READ_MAX_ATTEMPTS; attempt += 1) {
36
- try {
37
- return await httpJson(url, 'GET', undefined, headers);
38
- } catch (error) {
39
- lastError = error;
40
- if (attempt >= SYNC_READ_MAX_ATTEMPTS || !isRetryableSyncReadError(error)) break;
41
- await new Promise((resolve) => setTimeout(resolve, SYNC_READ_RETRY_DELAY_MS));
42
- }
43
- }
44
-
45
- const detail = lastError?.message || 'unknown request error';
46
- const wrapped = new Error(`${label} request failed: ${detail}`);
47
- if (lastError?.code) wrapped.code = lastError.code;
48
- if (lastError?.statusCode) wrapped.statusCode = lastError.statusCode;
49
- wrapped.cause = lastError;
50
- throw wrapped;
51
- }
52
-
53
- // Per-agent skill file path conventions, mirrored exactly from the hardcoded paths already
54
- // used by installers/claude.js, installers/codex.js, installers/copilot.js (for the single
55
- // empirical-memory skill) — just parameterized by skill name instead of hardcoded to it.
56
- function skillFilePath(targetId, skillName) {
57
- const home = os.homedir();
58
- if (targetId === 'claude') return path.join(home, '.claude', 'skills', skillName, 'SKILL.md');
59
- if (targetId === 'codex') return path.join(home, '.agents', 'skills', skillName, 'SKILL.md');
60
- if (targetId === 'copilot') return path.join(home, '.copilot', 'skills', skillName, 'SKILL.md');
61
- return null;
22
+ } = require('./deviceConfig');
23
+
24
+ const SYNC_READ_MAX_ATTEMPTS = 2;
25
+ const SYNC_READ_RETRY_DELAY_MS = 250;
26
+
27
+ function isRetryableSyncReadError(error) {
28
+ if (error?.statusCode) return false;
29
+ return /socket hang up|ECONNRESET|ECONNREFUSED|ETIMEDOUT|EPIPE|ENOTFOUND|network/i.test(error?.message || '');
30
+ }
31
+
32
+ async function fetchSyncRead(url, headers, label) {
33
+ let lastError;
34
+ for (let attempt = 1; attempt <= SYNC_READ_MAX_ATTEMPTS; attempt += 1) {
35
+ try {
36
+ return await httpJson(url, 'GET', undefined, headers);
37
+ } catch (error) {
38
+ lastError = error;
39
+ if (attempt >= SYNC_READ_MAX_ATTEMPTS || !isRetryableSyncReadError(error)) break;
40
+ await new Promise((resolve) => setTimeout(resolve, SYNC_READ_RETRY_DELAY_MS));
41
+ }
42
+ }
43
+
44
+ const detail = lastError?.message || 'unknown request error';
45
+ const wrapped = new Error(`${label} request failed: ${detail}`);
46
+ if (lastError?.code) wrapped.code = lastError.code;
47
+ if (lastError?.statusCode) wrapped.statusCode = lastError.statusCode;
48
+ wrapped.cause = lastError;
49
+ throw wrapped;
62
50
  }
63
51
 
52
+ // skillFilePath moved to ./skillPaths.js (see that file for why) — re-exported below for
53
+ // existing callers of skillSync.js that import it from here.
54
+ const { skillFilePath } = require('./skillPaths');
55
+
64
56
  function hashContent(content) {
65
57
  return crypto.createHash('sha256').update(content, 'utf8').digest('hex');
66
58
  }
@@ -102,14 +94,14 @@ function buildSkillFileContent(skill) {
102
94
  // ────────────────────────────────────────────────────────────────────────────────────────
103
95
  // CRITICAL TRAP TO AVOID (see PRD US-011): the server's resolveBuiltin() (see
104
96
  // packages/server/src/services/sourceResolver.service.ts) currently returns ONE hardcoded
105
- // PLACEHOLDER Skill named `empirical-memory`, whose bodyMarkdown is literally
106
- // "Placeholder body — real content wiring is a later story." — it is NOT the native
107
- // empirical-memory plugin content. The plugin repository owns the real Skill, and native
108
- // agent plugin installers are responsible for installing it.
97
+ // PLACEHOLDER Skill named `empirical-memory`, whose bodyMarkdown is literally
98
+ // "Placeholder body — real content wiring is a later story." — it is NOT the native
99
+ // empirical-memory plugin content. The plugin repository owns the real Skill, and native
100
+ // agent plugin installers are responsible for installing it.
109
101
  //
110
- // If this generalized writer were allowed to write a resolved Skill named `empirical-memory`,
111
- // it would silently overwrite that real plugin file with the server's placeholder stub text on
112
- // every sync — a real regression for every existing user. So: any
102
+ // If this generalized writer were allowed to write a resolved Skill named `empirical-memory`,
103
+ // it would silently overwrite that real plugin file with the server's placeholder stub text on
104
+ // every sync — a real regression for every existing user. So: any
113
105
  // resolved Skill named exactly `empirical-memory` is filtered out here, unconditionally,
114
106
  // before any writing/tracking/removal-consideration happens. A future story that finally
115
107
  // wires up real builtin content server-side (not this one) needs to revisit this filter.
@@ -352,10 +344,10 @@ async function performSync({ accessToken, baseUrl, dryRun = false, force = false
352
344
  }
353
345
 
354
346
  const versionUrl = new URL(`/api/v1/devices/${encodeURIComponent(deviceId)}/version`, baseUrl).toString();
355
- const versionResponse = await fetchSyncRead(versionUrl, {
356
- Authorization: `Bearer ${accessToken}`,
357
- 'x-empirical-surface': 'cli'
358
- }, 'Kit version');
347
+ const versionResponse = await fetchSyncRead(versionUrl, {
348
+ Authorization: `Bearer ${accessToken}`,
349
+ 'x-empirical-surface': 'cli'
350
+ }, 'Kit version');
359
351
  const versionData = (versionResponse && versionResponse.data) ? versionResponse.data : versionResponse;
360
352
  const version = versionData.version;
361
353
 
@@ -364,10 +356,10 @@ async function performSync({ accessToken, baseUrl, dryRun = false, force = false
364
356
  }
365
357
 
366
358
  const resolvedUrl = new URL(`/api/v1/devices/${encodeURIComponent(deviceId)}/resolved`, baseUrl).toString();
367
- const resolvedResponse = await fetchSyncRead(resolvedUrl, {
368
- Authorization: `Bearer ${accessToken}`,
369
- 'x-empirical-surface': 'cli'
370
- }, 'Resolved Kit skills');
359
+ const resolvedResponse = await fetchSyncRead(resolvedUrl, {
360
+ Authorization: `Bearer ${accessToken}`,
361
+ 'x-empirical-surface': 'cli'
362
+ }, 'Resolved Kit skills');
371
363
  const resolvedData = (resolvedResponse && resolvedResponse.data) ? resolvedResponse.data : resolvedResponse;
372
364
  const skills = filterOutEmpiricalMemory(resolvedData.skills);
373
365
  const tierSkipped = resolvedData.skipped || [];
package/package.json CHANGED
@@ -1,7 +1,20 @@
1
1
  {
2
2
  "name": "@empirical-gauzza/cli",
3
- "version": "1.0.19",
4
- "description": "Empirical CLI installer and local MCP wiring helper",
3
+ "version": "1.0.21",
4
+ "description": "Persistent cross-tool memory for AI agents. Give Claude Code, Codex, Copilot, Cursor, and ChatGPT one shared memory: save project context once, recall it everywhere via CLI or MCP.",
5
+ "keywords": [
6
+ "memory",
7
+ "mcp",
8
+ "agent memory",
9
+ "ai memory",
10
+ "claude",
11
+ "chatgpt",
12
+ "model context protocol",
13
+ "persistent memory",
14
+ "claude-code",
15
+ "codex",
16
+ "knowledge base"
17
+ ],
5
18
  "type": "commonjs",
6
19
  "license": "MIT",
7
20
  "homepage": "https://empirical.gauzza.com/docs/cli-details",