@empirical-gauzza/cli 1.0.19 → 1.0.20
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/bin/empirical.js +11 -3
- package/lib/auth.js +126 -43
- package/lib/constants.js +72 -8
- package/lib/deviceConfig.js +37 -18
- package/lib/doctor.js +484 -116
- package/lib/help.js +8 -2
- package/lib/installWizard.js +8 -2
- package/lib/installers/claude.js +110 -93
- package/lib/installers/codex.js +101 -88
- package/lib/installers/copilot.js +97 -87
- package/lib/installers/coreMemoryBlock.js +91 -0
- package/lib/installers/nativePlugin.js +147 -87
- package/lib/installers/skill-variants/SKILL.both.md +154 -0
- package/lib/installers/skill-variants/SKILL.cli.md +150 -0
- package/lib/installers/skill-variants/SKILL.mcp.md +134 -0
- package/lib/installers/skillVariants.js +59 -0
- package/lib/kitCommands.js +219 -122
- package/lib/memoryCommands.js +317 -6
- package/lib/pluginCommands.js +84 -81
- package/lib/pluginUpdateCheck.js +48 -14
- package/lib/skillPaths.js +25 -0
- package/lib/skillSync.js +48 -56
- package/package.json +15 -2
package/bin/empirical.js
CHANGED
|
@@ -32,7 +32,12 @@ async function main() {
|
|
|
32
32
|
return;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
// `-h`/`--help` short-circuits to the global help wall UNLESS a subcommand that knows how to
|
|
36
|
+
// print its own scoped usage was named first (currently just `memory`, per C-005 item 5) --
|
|
37
|
+
// otherwise `empirical memory --help` / `empirical memory query --help` printed the 90-line
|
|
38
|
+
// global wall instead of anything about `memory`.
|
|
39
|
+
const scopedHelpCommands = new Set(['memory']);
|
|
40
|
+
if ((args.includes('-h') || args.includes('--help')) && !scopedHelpCommands.has(args[0])) {
|
|
36
41
|
showHelp();
|
|
37
42
|
return;
|
|
38
43
|
}
|
|
@@ -42,8 +47,11 @@ async function main() {
|
|
|
42
47
|
return;
|
|
43
48
|
}
|
|
44
49
|
|
|
45
|
-
const updateCheckExcluded = new Set(['install', 'plugin', 'auth', 'oauth', 'policy', 'doctor']);
|
|
46
|
-
|
|
50
|
+
const updateCheckExcluded = new Set(['install', 'plugin', 'auth', 'oauth', 'policy', 'doctor', 'whoami']);
|
|
51
|
+
// Both stdin and stdout must be a real TTY: stdin.isTTY alone let this run ahead of e.g.
|
|
52
|
+
// `empirical memory query --match x | jq`, where stdin stays a TTY but stdout is a pipe --
|
|
53
|
+
// the header/prompt then leaked into the piped stream ahead of the JSON payload.
|
|
54
|
+
if (process.stdin.isTTY && process.stdout.isTTY && !updateCheckExcluded.has(args[0])) {
|
|
47
55
|
try {
|
|
48
56
|
await checkNativePluginUpdates({ interactive: true });
|
|
49
57
|
} catch (_error) {
|
package/lib/auth.js
CHANGED
|
@@ -18,12 +18,12 @@ const {
|
|
|
18
18
|
generateRandomString,
|
|
19
19
|
generatePKCE,
|
|
20
20
|
isJwtToken,
|
|
21
|
-
isJwtExpired,
|
|
22
|
-
readHeadlessToken,
|
|
23
|
-
readOAuthSession,
|
|
24
|
-
writeOAuthSession,
|
|
25
|
-
clearOAuthSession,
|
|
26
|
-
hasFreshAccessToken,
|
|
21
|
+
isJwtExpired,
|
|
22
|
+
readHeadlessToken,
|
|
23
|
+
readOAuthSession,
|
|
24
|
+
writeOAuthSession,
|
|
25
|
+
clearOAuthSession,
|
|
26
|
+
hasFreshAccessToken,
|
|
27
27
|
writeHeadlessEnvFile
|
|
28
28
|
} = require('./oauthClient');
|
|
29
29
|
const { deriveBaseUrlFromMcpUrl } = require('./constants');
|
|
@@ -124,11 +124,11 @@ async function runMcpOAuthFlow(baseUrl) {
|
|
|
124
124
|
|
|
125
125
|
if (!resolved) {
|
|
126
126
|
resolved = true;
|
|
127
|
-
resolveOAuthCode(code, codeVerifier, baseUrl)
|
|
128
|
-
.then((token) => {
|
|
129
|
-
writeOAuthSession(token);
|
|
130
|
-
resolve(token);
|
|
131
|
-
})
|
|
127
|
+
resolveOAuthCode(code, codeVerifier, baseUrl)
|
|
128
|
+
.then((token) => {
|
|
129
|
+
writeOAuthSession(token);
|
|
130
|
+
resolve(token);
|
|
131
|
+
})
|
|
132
132
|
.catch(reject);
|
|
133
133
|
}
|
|
134
134
|
} else {
|
|
@@ -191,9 +191,9 @@ async function resolveOAuthCode(code, codeVerifier, baseUrl) {
|
|
|
191
191
|
throw new Error('Token exchange succeeded but no access_token was returned.');
|
|
192
192
|
}
|
|
193
193
|
|
|
194
|
-
return {
|
|
195
|
-
baseUrl,
|
|
196
|
-
accessToken: token.access_token,
|
|
194
|
+
return {
|
|
195
|
+
baseUrl,
|
|
196
|
+
accessToken: token.access_token,
|
|
197
197
|
refreshToken: token.refresh_token || null,
|
|
198
198
|
expiresIn: token.expires_in || null,
|
|
199
199
|
tokenType: token.token_type || 'Bearer',
|
|
@@ -240,7 +240,7 @@ function resolveAccessToken() {
|
|
|
240
240
|
path.join(os.homedir(), '.empirical', 'headless-token.json')
|
|
241
241
|
].filter(Boolean);
|
|
242
242
|
|
|
243
|
-
for (const candidate of tokenFileCandidates) {
|
|
243
|
+
for (const candidate of tokenFileCandidates) {
|
|
244
244
|
if (!fs.existsSync(candidate)) continue;
|
|
245
245
|
try {
|
|
246
246
|
const parsed = JSON.parse(fs.readFileSync(candidate, 'utf8'));
|
|
@@ -253,12 +253,12 @@ function resolveAccessToken() {
|
|
|
253
253
|
} catch (_error) {
|
|
254
254
|
// ignore parse errors and try next
|
|
255
255
|
}
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
const session = readOAuthSession();
|
|
259
|
-
if (session?.accessToken && !(isJwtToken(session.accessToken) && isJwtExpired(session.accessToken))) {
|
|
260
|
-
return session.accessToken;
|
|
261
|
-
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const session = readOAuthSession();
|
|
259
|
+
if (session?.accessToken && !(isJwtToken(session.accessToken) && isJwtExpired(session.accessToken))) {
|
|
260
|
+
return session.accessToken;
|
|
261
|
+
}
|
|
262
262
|
|
|
263
263
|
// Priority 4: EMPIRICAL_API_KEY / EMPIRICAL_CLIENT_SECRET env vars, used
|
|
264
264
|
// directly as a permanent Bearer credential. In a client_credentials setup
|
|
@@ -305,30 +305,111 @@ function resolveApiBaseUrl() {
|
|
|
305
305
|
return 'https://empirical.gauzza.com';
|
|
306
306
|
}
|
|
307
307
|
|
|
308
|
-
function requireAccessToken() {
|
|
308
|
+
function requireAccessToken() {
|
|
309
309
|
const token = resolveAccessToken();
|
|
310
310
|
if (!token) {
|
|
311
|
-
warn('No access token found. Run: empirical auth login, or configure headless credentials with empirical oauth bootstrap headless.');
|
|
311
|
+
warn('No access token found. Run: empirical auth login, or configure headless credentials with empirical oauth bootstrap headless.');
|
|
312
312
|
process.exit(1);
|
|
313
313
|
}
|
|
314
314
|
return token;
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
function isAuthCommand(args) {
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
return;
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function isAuthCommand(args) {
|
|
318
|
+
if (args[0] === 'whoami') return true;
|
|
319
|
+
return args[0] === 'auth' && ['login', 'logout', 'status', 'whoami'].includes(args[1]);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Shared token introspection, used by both `auth status`/`whoami` here and by `doctor`
|
|
323
|
+
// (doctor.js's token_type/token_expiry checks call this instead of decoding the JWT payload
|
|
324
|
+
// a second time) so the two commands can never silently drift on what "expired" means.
|
|
325
|
+
function inspectToken(token) {
|
|
326
|
+
if (!token) {
|
|
327
|
+
return { present: false, tokenType: null, expiresAt: null, expired: null };
|
|
328
|
+
}
|
|
329
|
+
const parts = token.split('.');
|
|
330
|
+
if (parts.length !== 3) {
|
|
331
|
+
return { present: true, tokenType: 'apikey', expiresAt: null, expired: false };
|
|
332
|
+
}
|
|
333
|
+
try {
|
|
334
|
+
const payload = JSON.parse(Buffer.from(parts[1], 'base64url').toString());
|
|
335
|
+
if (payload && typeof payload.exp === 'number') {
|
|
336
|
+
const expiresAt = new Date(payload.exp * 1000).toISOString();
|
|
337
|
+
const expired = payload.exp < Math.floor(Date.now() / 1000);
|
|
338
|
+
return { present: true, tokenType: 'jwt', expiresAt, expired };
|
|
339
|
+
}
|
|
340
|
+
return { present: true, tokenType: 'jwt', expiresAt: null, expired: null };
|
|
341
|
+
} catch (_error) {
|
|
342
|
+
return { present: true, tokenType: 'jwt', expiresAt: null, expired: null };
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Gathers the same identity/workspace facts `doctor` reports (token presence/type/expiry, plus
|
|
347
|
+
// user + workspace from /api/v1/users/me), formatted for `auth status` / `whoami` as a single
|
|
348
|
+
// JSON object instead of doctor's multi-check report.
|
|
349
|
+
async function gatherAuthStatus() {
|
|
350
|
+
const token = resolveAccessToken();
|
|
351
|
+
const tokenInfo = inspectToken(token);
|
|
352
|
+
|
|
353
|
+
let user = null;
|
|
354
|
+
let workspaceId = null;
|
|
355
|
+
let error = null;
|
|
356
|
+
|
|
357
|
+
if (token) {
|
|
358
|
+
try {
|
|
359
|
+
const baseUrl = resolveApiBaseUrl();
|
|
360
|
+
const meUrl = new URL('/api/v1/users/me', baseUrl).toString();
|
|
361
|
+
const response = await httpJson(meUrl, 'GET', null, buildApiAuthHeaders(token));
|
|
362
|
+
const userData = (response && response.data) ? response.data : response;
|
|
363
|
+
user = {
|
|
364
|
+
id: userData.userId || userData.id || null,
|
|
365
|
+
email: userData.email || null
|
|
366
|
+
};
|
|
367
|
+
workspaceId = userData.workspaceId || userData.workspaceName || null;
|
|
368
|
+
} catch (apiError) {
|
|
369
|
+
error = apiError.message;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
return {
|
|
374
|
+
authenticated: Boolean(token) && !error && tokenInfo.expired !== true,
|
|
375
|
+
tokenPresent: tokenInfo.present,
|
|
376
|
+
tokenType: tokenInfo.tokenType,
|
|
377
|
+
tokenExpiresAt: tokenInfo.expiresAt,
|
|
378
|
+
tokenExpired: tokenInfo.expired,
|
|
379
|
+
user,
|
|
380
|
+
workspaceId,
|
|
381
|
+
error
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
async function runAuthCommand(args) {
|
|
386
|
+
const action = args[0] === 'whoami' ? 'status' : args[1];
|
|
387
|
+
|
|
388
|
+
if (action === 'login') {
|
|
389
|
+
const baseUrl = getEnv('EMPIRICAL_BASE_URL', 'https://empirical.gauzza.com');
|
|
390
|
+
await runMcpOAuthFlow(baseUrl);
|
|
391
|
+
log('Empirical OAuth login complete. CLI session saved.');
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
if (action === 'status' || action === 'whoami') {
|
|
396
|
+
const status = await gatherAuthStatus();
|
|
397
|
+
log(JSON.stringify(status));
|
|
398
|
+
if (!status.authenticated) {
|
|
399
|
+
process.exitCode = 1;
|
|
400
|
+
}
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
if (action === 'logout') {
|
|
405
|
+
clearOAuthSession();
|
|
406
|
+
log('Empirical OAuth session cleared.');
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
warn('Usage: empirical auth <login|logout|status> (or: empirical whoami)');
|
|
411
|
+
process.exitCode = 1;
|
|
412
|
+
}
|
|
332
413
|
|
|
333
414
|
function buildApiAuthHeaders(token) {
|
|
334
415
|
const context = globalThis.__empiricalCliAnalyticsContext || {};
|
|
@@ -780,9 +861,11 @@ async function runHeadlessRefresh(args) {
|
|
|
780
861
|
module.exports = {
|
|
781
862
|
oauthPreflight,
|
|
782
863
|
openBrowser,
|
|
783
|
-
runMcpOAuthFlow,
|
|
784
|
-
isAuthCommand,
|
|
785
|
-
runAuthCommand,
|
|
864
|
+
runMcpOAuthFlow,
|
|
865
|
+
isAuthCommand,
|
|
866
|
+
runAuthCommand,
|
|
867
|
+
inspectToken,
|
|
868
|
+
gatherAuthStatus,
|
|
786
869
|
resolveOAuthCode,
|
|
787
870
|
updateClaudeMcpServerAuth,
|
|
788
871
|
resolveAccessToken,
|
package/lib/constants.js
CHANGED
|
@@ -26,7 +26,7 @@ Errors are written to stderr as JSON. Successful output is written to stdout as
|
|
|
26
26
|
### query
|
|
27
27
|
Search memories by natural language.
|
|
28
28
|
|
|
29
|
-
empirical memory query --match "coding style preferences" [--top-k 3] [--category build] [--tags ts,node] [--pretty]
|
|
29
|
+
empirical memory query --match "coding style preferences" [--top-k 3] [--category build] [--tags ts,node] [--pretty] [--format table]
|
|
30
30
|
|
|
31
31
|
Flags:
|
|
32
32
|
- \`--match\` (required): natural language search string
|
|
@@ -34,6 +34,7 @@ Flags:
|
|
|
34
34
|
- \`--category\`: filter by category
|
|
35
35
|
- \`--tags\`: comma-separated tags filter
|
|
36
36
|
- \`--pretty\`: pretty-print JSON output
|
|
37
|
+
- \`--format table\`: render results as a table instead of JSON (JSON stays the default)
|
|
37
38
|
|
|
38
39
|
### get
|
|
39
40
|
Retrieve one memory by exact ID.
|
|
@@ -43,7 +44,7 @@ Retrieve one memory by exact ID.
|
|
|
43
44
|
### list
|
|
44
45
|
Browse memories deterministically. Use \`--cursor\` from the previous response while \`pagination.hasMore\` is true.
|
|
45
46
|
|
|
46
|
-
empirical memory list [--category project] [--tags canonical,launch] [--start-date <ISO>] [--end-date <ISO>] [--limit 20] [--cursor <cursor>] [--oldest] [--pretty]
|
|
47
|
+
empirical memory list [--category project] [--tags canonical,launch] [--start-date <ISO>] [--end-date <ISO>] [--limit 20] [--cursor <cursor>] [--oldest] [--pretty] [--format table]
|
|
47
48
|
|
|
48
49
|
Flags:
|
|
49
50
|
- \`--category\`, \`--tags\`: optional filters
|
|
@@ -52,6 +53,7 @@ Flags:
|
|
|
52
53
|
- \`--cursor\`: continuation cursor from a previous page
|
|
53
54
|
- \`--oldest\`: sort oldest first; default is newest first
|
|
54
55
|
- \`--pretty\`: pretty-print JSON output
|
|
56
|
+
- \`--format table\`: render results as a table instead of JSON (JSON stays the default)
|
|
55
57
|
|
|
56
58
|
### record
|
|
57
59
|
Save a new memory.
|
|
@@ -99,6 +101,43 @@ Flags:
|
|
|
99
101
|
- \`--memory-id\`: use exact memory ID
|
|
100
102
|
- \`--min-score\`: minimum similarity score, default 0.35
|
|
101
103
|
|
|
104
|
+
Bulk delete by tag, category, or everything (exactly one selector):
|
|
105
|
+
|
|
106
|
+
empirical memory delete --tag old-preference --confirm
|
|
107
|
+
empirical memory delete --category recipe --confirm
|
|
108
|
+
empirical memory delete --all --confirm
|
|
109
|
+
|
|
110
|
+
Without \`--confirm\`, always a dry-run that prints the count that would be deleted; no writes
|
|
111
|
+
happen. Re-run with \`--confirm\` to actually delete.
|
|
112
|
+
|
|
113
|
+
### export / import
|
|
114
|
+
Export memories to a versioned, round-trippable JSON document and re-import them later.
|
|
115
|
+
|
|
116
|
+
empirical memory export --scope <personal|workspace> [--out <path>] [--workspace-id <id>]
|
|
117
|
+
empirical memory import <file> [--dry-run]
|
|
118
|
+
|
|
119
|
+
Export writes \`{ schemaVersion: 1, exportedAt, memories: [...] }\`. Import reads that same
|
|
120
|
+
shape and POSTs each row through the normal create endpoint, so imported memories get the same
|
|
121
|
+
dedupe and auto-link treatment as any other write. \`--dry-run\` on import prints the count that
|
|
122
|
+
would be imported without writing anything.
|
|
123
|
+
|
|
124
|
+
### stats
|
|
125
|
+
Aggregated counts, scoped to the caller.
|
|
126
|
+
|
|
127
|
+
empirical memory stats [--format table]
|
|
128
|
+
|
|
129
|
+
Returns total count, counts by category, counts by tag, and oldest/newest memory timestamps.
|
|
130
|
+
|
|
131
|
+
### compact
|
|
132
|
+
On-demand near-duplicate scan (the same logic the nightly compaction job runs), scoped to the
|
|
133
|
+
caller only. Dry-run by default, matching a terraform plan/apply model.
|
|
134
|
+
|
|
135
|
+
empirical memory compact [--category <cat>]
|
|
136
|
+
empirical memory compact --apply [--category <cat>]
|
|
137
|
+
|
|
138
|
+
Without \`--apply\`, reports proposed merge/link candidate pairs and counts with no writes.
|
|
139
|
+
With \`--apply\`, executes the merges and links for real and reports what was applied.
|
|
140
|
+
|
|
102
141
|
## Error output contract
|
|
103
142
|
- Errors → stderr as JSON: \`{ "error": true, "code": "...", "message": "..." }\`
|
|
104
143
|
- Success → stdout as JSON (compact by default)
|
|
@@ -207,18 +246,35 @@ Deletion dry-runs by default. \`--confirm\` calls the existing soft-delete endpo
|
|
|
207
246
|
|
|
208
247
|
doctor: `# Empirical Doctor
|
|
209
248
|
|
|
210
|
-
empirical doctor [--output json]
|
|
249
|
+
empirical doctor [--output json] [--deep]
|
|
211
250
|
|
|
212
|
-
Checks the health of your Empirical CLI setup
|
|
251
|
+
Checks the health of your Empirical CLI setup, including why MCP tools might not be showing up
|
|
252
|
+
in a host CLI (the most common failure report).
|
|
213
253
|
|
|
214
|
-
## Checks performed
|
|
254
|
+
## Checks performed
|
|
215
255
|
|
|
216
256
|
1. **Token presence** — verifies a token exists via EMPIRICAL_ACCESS_TOKEN env, apiKey field,
|
|
217
257
|
or accessToken field in the token file
|
|
218
258
|
2. **Token type** — detects API key (long alphanumeric, no dots) vs JWT (3 dot-separated parts)
|
|
219
259
|
3. **Token expiry** — if JWT, decodes the payload and checks the \`exp\` field; warns if no \`exp\`
|
|
220
|
-
4. **API connectivity** — calls GET /api/users/me; 200 = OK, anything else = FAIL
|
|
221
|
-
5. **Workspace** — reads workspaceId from the /api/users/me response; warns if null
|
|
260
|
+
4. **API connectivity** — calls GET /api/v1/users/me; 200 = OK, anything else = FAIL
|
|
261
|
+
5. **Workspace** — reads workspaceId from the /api/v1/users/me response; warns if null
|
|
262
|
+
6. **MCP hosts detected** — which of codex/claude/copilot were found on this machine
|
|
263
|
+
7. **MCP config registration (per detected host)** — reads that host's own config file
|
|
264
|
+
(\`~/.claude.json\`, \`<CODEX_HOME>/config.toml\`, \`~/.copilot/mcp-config.json\`) and confirms
|
|
265
|
+
the Empirical MCP server is actually registered there with the current URL
|
|
266
|
+
8. **MCP reachability** — POSTs a real MCP \`initialize\` request to the MCP endpoint with the
|
|
267
|
+
resolved access token and confirms a valid JSON-RPC response comes back
|
|
268
|
+
9. **Native plugin presence/version (per detected host)** — runs \`<host> plugin list\` and
|
|
269
|
+
reports the installed empirical-memory (and optional) plugin versions
|
|
270
|
+
10. **Hooks presence (per detected host)** — confirms the SessionStart/UserPromptSubmit/Stop
|
|
271
|
+
hook scripts exist on disk and are registered in that host's hook manifest
|
|
272
|
+
|
|
273
|
+
## --deep
|
|
274
|
+
|
|
275
|
+
Adds a live write/query/delete round trip: records a \`doctor-probe-<timestamp>\` memory, queries
|
|
276
|
+
it back, deletes it, and reports elapsed time for each leg. Requires a valid token; writes and
|
|
277
|
+
deletes exactly one throwaway memory tagged \`doctor-probe\`.
|
|
222
278
|
|
|
223
279
|
## Output format
|
|
224
280
|
|
|
@@ -422,9 +478,17 @@ The browser OAuth session is stored separately at \`~/.empirical/oauth-session.j
|
|
|
422
478
|
|
|
423
479
|
## Verify auth status
|
|
424
480
|
|
|
481
|
+
empirical auth status
|
|
482
|
+
empirical whoami
|
|
483
|
+
|
|
484
|
+
Prints a single JSON object: \`{ authenticated, tokenPresent, tokenType, tokenExpiresAt,
|
|
485
|
+
tokenExpired, user: { id, email } | null, workspaceId, error }\`. \`whoami\` is an alias for
|
|
486
|
+
\`auth status\`. Exits 1 if not authenticated.
|
|
487
|
+
|
|
425
488
|
empirical doctor
|
|
426
489
|
|
|
427
|
-
Checks token presence, type, expiry, and API connectivity
|
|
490
|
+
Checks token presence, type, expiry, and API connectivity across a broader set of checks
|
|
491
|
+
(host MCP config registration, hooks, plugin versions).
|
|
428
492
|
`,
|
|
429
493
|
|
|
430
494
|
user: `# Empirical User Identity
|
package/lib/deviceConfig.js
CHANGED
|
@@ -20,20 +20,37 @@ function readDeviceConfig() {
|
|
|
20
20
|
return readJsonIfExists(getDeviceConfigPath()) || {};
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
function writeDeviceConfig(config) {
|
|
24
|
-
writeFile(getDeviceConfigPath(), `${JSON.stringify(config, null, 2)}\n`);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
function
|
|
33
|
-
const config = readDeviceConfig();
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
}
|
|
23
|
+
function writeDeviceConfig(config) {
|
|
24
|
+
writeFile(getDeviceConfigPath(), `${JSON.stringify(config, null, 2)}\n`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// C-001: the installer's chosen integration mode (1 = MCP, 2 = CLI-direct, 3 = both), persisted
|
|
28
|
+
// so later, unattended refreshes of the empirical-memory Skill -- a plugin auto-update
|
|
29
|
+
// (pluginUpdateCheck.js) or a `kit sync`/`kit use` run (skillSync.js) that happens long after the
|
|
30
|
+
// interactive installer finished -- still pick the mode the user actually chose, not a hardcoded
|
|
31
|
+
// default.
|
|
32
|
+
function readIntegrationMode() {
|
|
33
|
+
const config = readDeviceConfig();
|
|
34
|
+
const mode = config.integrationMode;
|
|
35
|
+
return mode === 2 || mode === 3 ? mode : 1;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function writeIntegrationMode(integrationMode) {
|
|
39
|
+
const config = readDeviceConfig();
|
|
40
|
+
config.integrationMode = integrationMode === 2 || integrationMode === 3 ? integrationMode : 1;
|
|
41
|
+
writeDeviceConfig(config);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function readPolicyCache() {
|
|
45
|
+
const config = readDeviceConfig();
|
|
46
|
+
return config.empiricalPolicy || null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function writePolicyCache(policyCache) {
|
|
50
|
+
const config = readDeviceConfig();
|
|
51
|
+
config.empiricalPolicy = policyCache;
|
|
52
|
+
writeDeviceConfig(config);
|
|
53
|
+
}
|
|
37
54
|
|
|
38
55
|
// Ensures a deviceId exists in the local config, generating+persisting one on first use.
|
|
39
56
|
// Reused across all future CLI invocations on this machine.
|
|
@@ -80,10 +97,12 @@ function writePendingReview(pendingReview) {
|
|
|
80
97
|
module.exports = {
|
|
81
98
|
getEmpiricalDir,
|
|
82
99
|
getDeviceConfigPath,
|
|
83
|
-
readDeviceConfig,
|
|
84
|
-
writeDeviceConfig,
|
|
85
|
-
|
|
86
|
-
|
|
100
|
+
readDeviceConfig,
|
|
101
|
+
writeDeviceConfig,
|
|
102
|
+
readIntegrationMode,
|
|
103
|
+
writeIntegrationMode,
|
|
104
|
+
readPolicyCache,
|
|
105
|
+
writePolicyCache,
|
|
87
106
|
ensureDeviceId,
|
|
88
107
|
getSyncManifestPath,
|
|
89
108
|
readSyncManifest,
|