@empirical-gauzza/cli 1.0.18 → 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 +12 -4
- 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/hooks.js +12 -5
- 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/lib/memoryCommands.js
CHANGED
|
@@ -6,9 +6,69 @@ const { log, warn } = require('./ui');
|
|
|
6
6
|
const { httpJson, printApiError } = require('./http');
|
|
7
7
|
const { parseOption, parseFlag } = require('./env');
|
|
8
8
|
const { requireAccessToken, resolveApiBaseUrl, buildApiAuthHeaders } = require('./auth');
|
|
9
|
-
const { MEMORY_CATEGORIES } = require('./constants');
|
|
9
|
+
const { MEMORY_CATEGORIES, DOC_TOPICS } = require('./constants');
|
|
10
10
|
const { withSelectedWorkspaceContext } = require('./workspaceContext');
|
|
11
11
|
|
|
12
|
+
// ─── Table rendering (--format table) ─────────────────────────────────────────
|
|
13
|
+
|
|
14
|
+
function truncateForTable(value, maxLen) {
|
|
15
|
+
const str = value === undefined || value === null ? '' : String(value);
|
|
16
|
+
return str.length > maxLen ? `${str.slice(0, maxLen - 1)}…` : str;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function renderTable(rows, columns) {
|
|
20
|
+
if (!Array.isArray(rows) || rows.length === 0) return '(no rows)';
|
|
21
|
+
const cols = columns && columns.length ? columns : Object.keys(rows[0]);
|
|
22
|
+
const cellText = (row, col) => (row[col] === undefined || row[col] === null ? '' : String(row[col]));
|
|
23
|
+
const widths = cols.map((col) => Math.max(col.length, ...rows.map((row) => cellText(row, col).length)));
|
|
24
|
+
const renderRow = (cells) => cells.map((cell, i) => String(cell).padEnd(widths[i])).join(' ');
|
|
25
|
+
const lines = [renderRow(cols), renderRow(widths.map((w) => '-'.repeat(w)))];
|
|
26
|
+
for (const row of rows) {
|
|
27
|
+
lines.push(renderRow(cols.map((col) => cellText(row, col))));
|
|
28
|
+
}
|
|
29
|
+
return lines.join('\n');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function renderMemoryResultsTable(items) {
|
|
33
|
+
const rows = (Array.isArray(items) ? items : []).map((m) => ({
|
|
34
|
+
id: m.id || m._id || '',
|
|
35
|
+
category: m.category || '',
|
|
36
|
+
summary: truncateForTable(m.summary, 60),
|
|
37
|
+
score: m.rankScore != null ? m.rankScore : (m.similarityScore != null ? m.similarityScore : '')
|
|
38
|
+
}));
|
|
39
|
+
return renderTable(rows, ['id', 'category', 'summary', 'score']);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// ─── Scoped `--help` (C-005 item 5) ───────────────────────────────────────────
|
|
43
|
+
// `empirical memory --help` / `empirical memory <sub> --help` print scoped usage instead of
|
|
44
|
+
// the 90-line global help wall bin/empirical.js otherwise short-circuits every --help to.
|
|
45
|
+
|
|
46
|
+
const MEMORY_SUBCOMMAND_USAGE = {
|
|
47
|
+
query: 'Usage: empirical memory query --match <query> [--top-k 3] [--category <cat>] [--tags <t1,t2>] [--pretty] [--format table]',
|
|
48
|
+
get: 'Usage: empirical memory get --memory-id <id> [--pretty]',
|
|
49
|
+
list: 'Usage: empirical memory list [--category <cat>] [--tags <t1,t2>] [--start-date <ISO>] [--end-date <ISO>] [--limit 20] [--cursor <cursor>] [--oldest] [--pretty] [--format table]',
|
|
50
|
+
record: 'Usage: empirical memory record --category <cat> --summary <text> [--tags t1,t2] [--node-type observation] [--json-file <path>] [--no-dedupe] [--no-auto-link]',
|
|
51
|
+
note: 'Usage: empirical memory note --text <note> (--match <query> | --memory-id <id>) [--min-score 0.35]',
|
|
52
|
+
update: 'Usage: empirical memory update (--match <query> | --memory-id <id>) (--patch-json <json> | --json-file <path>) [--min-score 0.35]',
|
|
53
|
+
delete: 'Usage: empirical memory delete (--match <query> | --memory-id <id>) [--confirm] [--min-score 0.35]\nUsage: empirical memory delete (--tag <t> | --category <c> | --all) --confirm',
|
|
54
|
+
duplicates: 'Usage: empirical memory duplicates --memory-id <id>',
|
|
55
|
+
merge: 'Usage: empirical memory merge --canonical-id <id> --duplicate-id <id> [--no-merge-tags] [--no-merge-relationships]',
|
|
56
|
+
export: 'Usage: empirical memory export --scope <personal|workspace> [--out <path>] [--workspace-id <id>]',
|
|
57
|
+
import: 'Usage: empirical memory import <file> [--dry-run]',
|
|
58
|
+
stats: 'Usage: empirical memory stats [--format table]',
|
|
59
|
+
compact: 'Usage: empirical memory compact [--apply] [--category <cat>]'
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
function printMemoryHelp(sub) {
|
|
63
|
+
if (sub && MEMORY_SUBCOMMAND_USAGE[sub]) {
|
|
64
|
+
log(MEMORY_SUBCOMMAND_USAGE[sub]);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
log(DOC_TOPICS.memory);
|
|
68
|
+
log('');
|
|
69
|
+
log('Subcommands: ' + Object.keys(MEMORY_SUBCOMMAND_USAGE).join(', '));
|
|
70
|
+
}
|
|
71
|
+
|
|
12
72
|
async function withCliAnalyticsContext(context, fn) {
|
|
13
73
|
globalThis.__empiricalCliAnalyticsContext = context || {};
|
|
14
74
|
try {
|
|
@@ -96,6 +156,7 @@ async function runMemoryQuery(args) {
|
|
|
96
156
|
const tagsRaw = parseOption(args, '--tags', '');
|
|
97
157
|
const tags = tagsRaw ? tagsRaw.split(',').map((t) => t.trim()).filter(Boolean) : [];
|
|
98
158
|
const pretty = parseFlag(args, '--pretty');
|
|
159
|
+
const format = parseOption(args, '--format', 'json');
|
|
99
160
|
|
|
100
161
|
const accessToken = requireAccessToken();
|
|
101
162
|
const baseUrl = resolveApiBaseUrl();
|
|
@@ -119,6 +180,11 @@ async function runMemoryQuery(args) {
|
|
|
119
180
|
? response.data.results
|
|
120
181
|
: (Array.isArray(response) ? response : []);
|
|
121
182
|
|
|
183
|
+
if (format === 'table') {
|
|
184
|
+
log(renderMemoryResultsTable(results));
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
|
|
122
188
|
if (pretty) {
|
|
123
189
|
log(JSON.stringify(results, null, 2));
|
|
124
190
|
} else {
|
|
@@ -158,6 +224,7 @@ async function runMemoryList(args) {
|
|
|
158
224
|
const cursor = parseOption(args, '--cursor', '');
|
|
159
225
|
const sortOrder = parseFlag(args, '--oldest') ? 'asc' : 'desc';
|
|
160
226
|
const pretty = parseFlag(args, '--pretty');
|
|
227
|
+
const format = parseOption(args, '--format', 'json');
|
|
161
228
|
|
|
162
229
|
const params = new URLSearchParams();
|
|
163
230
|
if (category) params.set('category', category);
|
|
@@ -180,6 +247,13 @@ async function runMemoryList(args) {
|
|
|
180
247
|
process.exit(1);
|
|
181
248
|
}
|
|
182
249
|
const data = response && response.data ? response.data : response;
|
|
250
|
+
|
|
251
|
+
if (format === 'table') {
|
|
252
|
+
const items = Array.isArray(data && data.memories) ? data.memories : (Array.isArray(data) ? data : []);
|
|
253
|
+
log(renderMemoryResultsTable(items));
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
|
|
183
257
|
log(pretty ? JSON.stringify(data, null, 2) : JSON.stringify(data));
|
|
184
258
|
}
|
|
185
259
|
|
|
@@ -199,7 +273,7 @@ async function runMemoryRecord(args) {
|
|
|
199
273
|
const summary = parseOption(args, '--summary', '');
|
|
200
274
|
|
|
201
275
|
if (!category || !summary) {
|
|
202
|
-
warn('Usage: empirical memory record --category <cat> --summary <text> [--tags t1,t2] [--node-type observation]');
|
|
276
|
+
warn('Usage: empirical memory record --category <cat> --summary <text> [--tags t1,t2] [--node-type observation] [--no-dedupe] [--no-auto-link]');
|
|
203
277
|
warn('Valid categories: ' + MEMORY_CATEGORIES.join(', '));
|
|
204
278
|
process.exit(1);
|
|
205
279
|
}
|
|
@@ -230,6 +304,19 @@ async function runMemoryRecord(args) {
|
|
|
230
304
|
payload.data = { text: String(payload.summary) };
|
|
231
305
|
}
|
|
232
306
|
|
|
307
|
+
// C-002 (docs/prd-cli-and-plugin-fixes.md): the server now dedupes/auto-links every
|
|
308
|
+
// POST /api/v1/memories write by default, matching the MCP record_graph_memory tool's
|
|
309
|
+
// guarantee. Only send dedupeEnabled/autoLink at all when the caller explicitly opted
|
|
310
|
+
// out here -- omitting them entirely lets the server's own default-on behavior apply,
|
|
311
|
+
// rather than this CLI baking in a redundant "true" that could drift from the server's
|
|
312
|
+
// actual default over time.
|
|
313
|
+
if (parseFlag(args, '--no-dedupe')) {
|
|
314
|
+
payload.dedupeEnabled = false;
|
|
315
|
+
}
|
|
316
|
+
if (parseFlag(args, '--no-auto-link')) {
|
|
317
|
+
payload.autoLink = false;
|
|
318
|
+
}
|
|
319
|
+
|
|
233
320
|
const accessToken = requireAccessToken();
|
|
234
321
|
const baseUrl = resolveApiBaseUrl();
|
|
235
322
|
const recordUrl = new URL('/api/v1/memories', baseUrl).toString();
|
|
@@ -348,7 +435,49 @@ async function runMemoryUpdate(args) {
|
|
|
348
435
|
log(JSON.stringify(updated));
|
|
349
436
|
}
|
|
350
437
|
|
|
438
|
+
async function runMemoryDeleteBulk(args) {
|
|
439
|
+
const tag = parseOption(args, '--tag', '');
|
|
440
|
+
const category = parseOption(args, '--category', '');
|
|
441
|
+
const all = parseFlag(args, '--all');
|
|
442
|
+
const confirm = parseFlag(args, '--confirm');
|
|
443
|
+
|
|
444
|
+
const selectorCount = [Boolean(tag), Boolean(category), all].filter(Boolean).length;
|
|
445
|
+
if (selectorCount !== 1) {
|
|
446
|
+
warn('Usage: empirical memory delete (--tag <t> | --category <c> | --all) --confirm');
|
|
447
|
+
process.exit(1);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
const accessToken = requireAccessToken();
|
|
451
|
+
const baseUrl = resolveApiBaseUrl();
|
|
452
|
+
const url = new URL('/api/v1/memory-bulk-delete', baseUrl).toString();
|
|
453
|
+
const body = { confirm };
|
|
454
|
+
if (tag) body.tag = tag;
|
|
455
|
+
if (category) body.category = category;
|
|
456
|
+
if (all) body.all = true;
|
|
457
|
+
|
|
458
|
+
let response;
|
|
459
|
+
try {
|
|
460
|
+
response = await httpJson(url, 'POST', body, buildApiAuthHeaders(accessToken));
|
|
461
|
+
} catch (error) {
|
|
462
|
+
printApiError(error);
|
|
463
|
+
process.exit(1);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// Server always reports the count first: without --confirm it's a dry-run preview and no
|
|
467
|
+
// writes happened; with --confirm it reports what it just deleted. Either way the count is
|
|
468
|
+
// printed, matching the single-memory delete's dry-run-by-default convention above.
|
|
469
|
+
const data = (response && response.data) ? response.data : response;
|
|
470
|
+
log(JSON.stringify(data));
|
|
471
|
+
}
|
|
472
|
+
|
|
351
473
|
async function runMemoryDelete(args) {
|
|
474
|
+
const tag = parseOption(args, '--tag', '');
|
|
475
|
+
const category = parseOption(args, '--category', '');
|
|
476
|
+
const all = parseFlag(args, '--all');
|
|
477
|
+
if (tag || category || all) {
|
|
478
|
+
return runMemoryDeleteBulk(args);
|
|
479
|
+
}
|
|
480
|
+
|
|
352
481
|
const match = parseOption(args, '--match', '');
|
|
353
482
|
const memoryId = parseOption(args, '--memory-id', '');
|
|
354
483
|
const minScore = Number(parseOption(args, '--min-score', '0.35'));
|
|
@@ -356,6 +485,7 @@ async function runMemoryDelete(args) {
|
|
|
356
485
|
|
|
357
486
|
if (!match && !memoryId) {
|
|
358
487
|
warn('Usage: empirical memory delete (--match <query> | --memory-id <id>) [--confirm] [--min-score 0.35]');
|
|
488
|
+
warn(' or: empirical memory delete (--tag <t> | --category <c> | --all) --confirm');
|
|
359
489
|
process.exit(1);
|
|
360
490
|
}
|
|
361
491
|
|
|
@@ -479,14 +609,188 @@ async function runMemoryExport(args) {
|
|
|
479
609
|
process.exit(1);
|
|
480
610
|
}
|
|
481
611
|
|
|
612
|
+
// The raw server response is the export envelope itself (streamed as one JSON body, not the
|
|
613
|
+
// standard {status,data} wrapper): {schemaVersion, generatedAt, scope, workspaceId,
|
|
614
|
+
// memoryCount, truncated, memories: [...]}. Re-shape it into the versioned, round-trippable
|
|
615
|
+
// format `memory import` consumes: {schemaVersion: 1, exportedAt, memories: [{category,
|
|
616
|
+
// summary, tags, nodeType, data, mass, gravity, timeContext, notes, relationships}]}.
|
|
617
|
+
const rawMemories = Array.isArray(response && response.memories) ? response.memories : [];
|
|
618
|
+
const envelope = {
|
|
619
|
+
schemaVersion: 1,
|
|
620
|
+
exportedAt: new Date().toISOString(),
|
|
621
|
+
memories: rawMemories.map((m) => ({
|
|
622
|
+
category: m.category,
|
|
623
|
+
summary: m.summary,
|
|
624
|
+
tags: Array.isArray(m.tags) ? m.tags : [],
|
|
625
|
+
nodeType: m.nodeType,
|
|
626
|
+
data: m.data,
|
|
627
|
+
mass: m.mass,
|
|
628
|
+
gravity: m.gravity,
|
|
629
|
+
timeContext: m.timeContext || null,
|
|
630
|
+
notes: Array.isArray(m.notes) ? m.notes.map((n) => ({ text: n.text })) : [],
|
|
631
|
+
relationships: Array.isArray(m.relationships)
|
|
632
|
+
? m.relationships.map((r) => ({ type: r.type, targetId: r.targetId, weight: r.weight }))
|
|
633
|
+
: []
|
|
634
|
+
}))
|
|
635
|
+
};
|
|
636
|
+
|
|
482
637
|
const resolvedPath = path.resolve(outPath);
|
|
483
|
-
fs.writeFileSync(resolvedPath, JSON.stringify(
|
|
638
|
+
fs.writeFileSync(resolvedPath, JSON.stringify(envelope, null, 2) + '\n', 'utf8');
|
|
639
|
+
|
|
640
|
+
log(JSON.stringify({ exported: true, path: resolvedPath, schemaVersion: envelope.schemaVersion, memoryCount: envelope.memories.length }));
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
async function runMemoryImport(args) {
|
|
644
|
+
const filePath = args.find((a) => a && !a.startsWith('--')) || '';
|
|
645
|
+
const dryRun = parseFlag(args, '--dry-run');
|
|
646
|
+
|
|
647
|
+
if (!filePath) {
|
|
648
|
+
warn('Usage: empirical memory import <file> [--dry-run]');
|
|
649
|
+
process.exit(1);
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
let doc;
|
|
653
|
+
try {
|
|
654
|
+
doc = JSON.parse(fs.readFileSync(path.resolve(filePath), 'utf8'));
|
|
655
|
+
} catch (error) {
|
|
656
|
+
warn(JSON.stringify({ error: true, code: 'json_file_error', message: error.message }));
|
|
657
|
+
process.exit(1);
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
const memories = Array.isArray(doc && doc.memories) ? doc.memories : null;
|
|
661
|
+
if (!memories) {
|
|
662
|
+
warn(JSON.stringify({
|
|
663
|
+
error: true,
|
|
664
|
+
code: 'invalid_format',
|
|
665
|
+
message: 'File is not a valid Empirical memory export: missing a top-level "memories" array. Re-export with `empirical memory export`.'
|
|
666
|
+
}));
|
|
667
|
+
process.exit(1);
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
if (dryRun) {
|
|
671
|
+
log(JSON.stringify({ dryRun: true, wouldImport: memories.length, schemaVersion: doc.schemaVersion || null }));
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
const accessToken = requireAccessToken();
|
|
676
|
+
const baseUrl = resolveApiBaseUrl();
|
|
677
|
+
// Every row goes through the normal create endpoint, so imports get the same dedupe and
|
|
678
|
+
// auto-link treatment as any other write, no special-casing needed here.
|
|
679
|
+
const recordUrl = new URL('/api/v1/memories', baseUrl).toString();
|
|
680
|
+
|
|
681
|
+
let imported = 0;
|
|
682
|
+
let failed = 0;
|
|
683
|
+
const errors = [];
|
|
684
|
+
|
|
685
|
+
for (const row of memories) {
|
|
686
|
+
const payload = {
|
|
687
|
+
category: row.category,
|
|
688
|
+
summary: row.summary,
|
|
689
|
+
nodeType: row.nodeType || 'observation',
|
|
690
|
+
tags: Array.isArray(row.tags) ? row.tags : [],
|
|
691
|
+
data: row.data !== undefined
|
|
692
|
+
? row.data
|
|
693
|
+
: (row.summary !== undefined ? { text: String(row.summary) } : undefined)
|
|
694
|
+
};
|
|
695
|
+
if (Array.isArray(row.relationships) && row.relationships.length) payload.relationships = row.relationships;
|
|
696
|
+
if (row.mass !== undefined && row.mass !== null) payload.mass = row.mass;
|
|
697
|
+
if (row.gravity !== undefined && row.gravity !== null) payload.gravity = row.gravity;
|
|
698
|
+
if (row.timeContext) payload.timeContext = row.timeContext;
|
|
699
|
+
|
|
700
|
+
let created;
|
|
701
|
+
try {
|
|
702
|
+
const response = await httpJson(recordUrl, 'POST', payload, buildApiAuthHeaders(accessToken));
|
|
703
|
+
created = (response && response.data) ? response.data : response;
|
|
704
|
+
imported++;
|
|
705
|
+
} catch (error) {
|
|
706
|
+
failed++;
|
|
707
|
+
errors.push({ summary: row.summary, message: error.message });
|
|
708
|
+
continue;
|
|
709
|
+
}
|
|
484
710
|
|
|
485
|
-
|
|
711
|
+
const notes = Array.isArray(row.notes) ? row.notes : [];
|
|
712
|
+
const createdId = created && (created.id || created._id);
|
|
713
|
+
if (notes.length && createdId) {
|
|
714
|
+
const noteUrl = new URL(`/api/v1/memories/${encodeURIComponent(createdId)}/notes`, baseUrl).toString();
|
|
715
|
+
for (const note of notes) {
|
|
716
|
+
const text = typeof note === 'string' ? note : (note && note.text);
|
|
717
|
+
if (!text) continue;
|
|
718
|
+
try {
|
|
719
|
+
await httpJson(noteUrl, 'POST', { text }, buildApiAuthHeaders(accessToken));
|
|
720
|
+
} catch (_error) {
|
|
721
|
+
// Best-effort: a note failing to re-attach shouldn't fail the whole import.
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
log(JSON.stringify({ imported, failed, total: memories.length, errors: errors.slice(0, 20) }));
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
async function runMemoryStats(args) {
|
|
731
|
+
const format = parseOption(args, '--format', 'json');
|
|
732
|
+
|
|
733
|
+
const accessToken = requireAccessToken();
|
|
734
|
+
const baseUrl = resolveApiBaseUrl();
|
|
735
|
+
const url = new URL('/api/v1/memory-stats', baseUrl).toString();
|
|
736
|
+
|
|
737
|
+
let response;
|
|
738
|
+
try {
|
|
739
|
+
response = await httpJson(url, 'GET', undefined, buildApiAuthHeaders(accessToken));
|
|
740
|
+
} catch (error) {
|
|
741
|
+
printApiError(error);
|
|
742
|
+
process.exit(1);
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
const data = (response && response.data) ? response.data : response;
|
|
746
|
+
|
|
747
|
+
if (format === 'table') {
|
|
748
|
+
log(`Total memories: ${data.total}`);
|
|
749
|
+
log(`Oldest: ${data.oldest || '(none)'}`);
|
|
750
|
+
log(`Newest: ${data.newest || '(none)'}`);
|
|
751
|
+
log('');
|
|
752
|
+
log('By category:');
|
|
753
|
+
log(renderTable(data.byCategory || [], ['category', 'count']));
|
|
754
|
+
log('');
|
|
755
|
+
log('By tag:');
|
|
756
|
+
log(renderTable(data.byTag || [], ['tag', 'count']));
|
|
757
|
+
return;
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
log(JSON.stringify(data));
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
async function runMemoryCompact(args) {
|
|
764
|
+
const apply = parseFlag(args, '--apply');
|
|
765
|
+
const category = parseOption(args, '--category', '');
|
|
766
|
+
|
|
767
|
+
const accessToken = requireAccessToken();
|
|
768
|
+
const baseUrl = resolveApiBaseUrl();
|
|
769
|
+
const url = new URL('/api/v1/memory-compaction', baseUrl).toString();
|
|
770
|
+
const body = { apply };
|
|
771
|
+
if (category) body.category = category;
|
|
772
|
+
|
|
773
|
+
let response;
|
|
774
|
+
try {
|
|
775
|
+
response = await httpJson(url, 'POST', body, buildApiAuthHeaders(accessToken));
|
|
776
|
+
} catch (error) {
|
|
777
|
+
printApiError(error);
|
|
778
|
+
process.exit(1);
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
const data = (response && response.data) ? response.data : response;
|
|
782
|
+
log(JSON.stringify(data));
|
|
486
783
|
}
|
|
487
784
|
|
|
488
785
|
async function runMemoryCommand(subArgs) {
|
|
489
|
-
const
|
|
786
|
+
const helpRequested = subArgs.includes('--help') || subArgs.includes('-h');
|
|
787
|
+
const sub = subArgs[0] && !subArgs[0].startsWith('-') ? subArgs[0] : undefined;
|
|
788
|
+
|
|
789
|
+
if (helpRequested || !sub) {
|
|
790
|
+
printMemoryHelp(sub);
|
|
791
|
+
return;
|
|
792
|
+
}
|
|
793
|
+
|
|
490
794
|
const rest = subArgs.slice(1);
|
|
491
795
|
|
|
492
796
|
return withCliAnalyticsContext(
|
|
@@ -502,9 +806,12 @@ async function runMemoryCommand(subArgs) {
|
|
|
502
806
|
if (sub === 'duplicates') return runMemoryDuplicates(rest);
|
|
503
807
|
if (sub === 'merge') return runMemoryMerge(rest);
|
|
504
808
|
if (sub === 'export') return runMemoryExport(rest);
|
|
809
|
+
if (sub === 'import') return runMemoryImport(rest);
|
|
810
|
+
if (sub === 'stats') return runMemoryStats(rest);
|
|
811
|
+
if (sub === 'compact') return runMemoryCompact(rest);
|
|
505
812
|
|
|
506
813
|
warn('Unknown memory subcommand: ' + (sub || '(none)'));
|
|
507
|
-
warn('Usage: empirical memory <query|record|note|update|delete|duplicates|merge|export> [options]');
|
|
814
|
+
warn('Usage: empirical memory <query|record|note|update|delete|duplicates|merge|export|import|stats|compact> [options]');
|
|
508
815
|
process.exit(1);
|
|
509
816
|
})
|
|
510
817
|
);
|
|
@@ -524,5 +831,9 @@ module.exports = {
|
|
|
524
831
|
runMemoryDuplicates,
|
|
525
832
|
runMemoryMerge,
|
|
526
833
|
runMemoryExport,
|
|
834
|
+
runMemoryImport,
|
|
835
|
+
runMemoryStats,
|
|
836
|
+
runMemoryCompact,
|
|
837
|
+
runMemoryDeleteBulk,
|
|
527
838
|
runMemoryCommand
|
|
528
839
|
};
|
package/lib/pluginCommands.js
CHANGED
|
@@ -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
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
log(
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
log('
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
+
};
|