@empirical-gauzza/cli 1.0.20 → 1.0.22
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/README.md +63 -63
- package/bin/empirical.js +13 -1
- package/lib/auth.js +83 -2
- package/lib/blueprintCommands.js +128 -0
- package/lib/constants.js +27 -0
- package/lib/doc.js +1 -1
- package/lib/env.js +49 -49
- package/lib/help.js +6 -1
- package/lib/installers/coreMemoryBlock.js +91 -91
- package/lib/installers/detect.js +47 -47
- package/lib/installers/hooks.js +12 -12
- package/lib/installers/skill-variants/SKILL.both.md +154 -154
- package/lib/installers/skill-variants/SKILL.cli.md +150 -150
- package/lib/installers/skill-variants/SKILL.mcp.md +134 -134
- package/lib/installers/skillVariants.js +59 -59
- package/lib/memoryCommands.js +95 -10
- package/lib/oauthClient.js +222 -222
- package/lib/policyCommands.js +144 -144
- package/lib/setupCommands.js +123 -0
- package/lib/skillPaths.js +25 -25
- package/lib/ui.js +7 -1
- package/lib/workspaceContext.js +47 -47
- package/package.json +1 -1
- package/tests/ui.test.js +42 -0
|
@@ -1,59 +1,59 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
// C-001 (docs/prd-cli-and-plugin-fixes.md): mode-aware empirical-memory Skill content.
|
|
4
|
-
//
|
|
5
|
-
// The plugin marketplace (mattgauzza/empirical-memory-plugin) is the source of truth for Skill
|
|
6
|
-
// content, but its manifest schema (see meta/manifests.json / build-manifests.mjs in that repo)
|
|
7
|
-
// has no field that selects among sibling SKILL.*.md files at install time -- every host's native
|
|
8
|
-
// plugin mechanism resolves a plugin's skill from the literal filename `SKILL.md`. So the
|
|
9
|
-
// marketplace always ships the "both" variant as SKILL.md, and this CLI is what actually makes
|
|
10
|
-
// the user's chosen integration mode matter: it vendors its own copy of all three variants and
|
|
11
|
-
// overwrites the on-disk SKILL.md for each host after a native plugin install/update (see
|
|
12
|
-
// nativePlugin.js's applySkillModeVariant) and after a Kit sync (see skillSync.js).
|
|
13
|
-
//
|
|
14
|
-
// These files are copied from, and must be kept in sync with,
|
|
15
|
-
// M:\Projects\empirical-memory-plugin\skills\empirical-memory\SKILL.{mcp,cli,both}.md. There is no
|
|
16
|
-
// automated sync between the two repos; a human updates both when skill content changes.
|
|
17
|
-
const fs = require('fs');
|
|
18
|
-
const path = require('path');
|
|
19
|
-
|
|
20
|
-
const VARIANTS_DIR = path.join(__dirname, 'skill-variants');
|
|
21
|
-
|
|
22
|
-
const VARIANT_FILES = {
|
|
23
|
-
mcp: 'SKILL.mcp.md',
|
|
24
|
-
cli: 'SKILL.cli.md',
|
|
25
|
-
both: 'SKILL.both.md'
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
// installWizard.js's `integrationMode`: 1 = MCP, 2 = CLI-direct, 3 = both.
|
|
29
|
-
function variantNameForIntegrationMode(integrationMode) {
|
|
30
|
-
if (integrationMode === 2) return 'cli';
|
|
31
|
-
if (integrationMode === 3) return 'both';
|
|
32
|
-
return 'mcp';
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// Returns the variant's markdown content, or null if the vendored file is missing (e.g. a stale
|
|
36
|
-
// CLI release published before a variant existed). Never throws -- callers must treat a missing
|
|
37
|
-
// variant as "leave whatever is already installed" per the PRD's own risk mitigation, not as a
|
|
38
|
-
// fatal error.
|
|
39
|
-
function readSkillVariantContent(variantName) {
|
|
40
|
-
const fileName = VARIANT_FILES[variantName];
|
|
41
|
-
if (!fileName) return null;
|
|
42
|
-
const filePath = path.join(VARIANTS_DIR, fileName);
|
|
43
|
-
try {
|
|
44
|
-
return fs.readFileSync(filePath, 'utf8');
|
|
45
|
-
} catch (_error) {
|
|
46
|
-
return null;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function readSkillVariantForIntegrationMode(integrationMode) {
|
|
51
|
-
return readSkillVariantContent(variantNameForIntegrationMode(integrationMode));
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
module.exports = {
|
|
55
|
-
VARIANT_FILES,
|
|
56
|
-
variantNameForIntegrationMode,
|
|
57
|
-
readSkillVariantContent,
|
|
58
|
-
readSkillVariantForIntegrationMode
|
|
59
|
-
};
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// C-001 (docs/prd-cli-and-plugin-fixes.md): mode-aware empirical-memory Skill content.
|
|
4
|
+
//
|
|
5
|
+
// The plugin marketplace (mattgauzza/empirical-memory-plugin) is the source of truth for Skill
|
|
6
|
+
// content, but its manifest schema (see meta/manifests.json / build-manifests.mjs in that repo)
|
|
7
|
+
// has no field that selects among sibling SKILL.*.md files at install time -- every host's native
|
|
8
|
+
// plugin mechanism resolves a plugin's skill from the literal filename `SKILL.md`. So the
|
|
9
|
+
// marketplace always ships the "both" variant as SKILL.md, and this CLI is what actually makes
|
|
10
|
+
// the user's chosen integration mode matter: it vendors its own copy of all three variants and
|
|
11
|
+
// overwrites the on-disk SKILL.md for each host after a native plugin install/update (see
|
|
12
|
+
// nativePlugin.js's applySkillModeVariant) and after a Kit sync (see skillSync.js).
|
|
13
|
+
//
|
|
14
|
+
// These files are copied from, and must be kept in sync with,
|
|
15
|
+
// M:\Projects\empirical-memory-plugin\skills\empirical-memory\SKILL.{mcp,cli,both}.md. There is no
|
|
16
|
+
// automated sync between the two repos; a human updates both when skill content changes.
|
|
17
|
+
const fs = require('fs');
|
|
18
|
+
const path = require('path');
|
|
19
|
+
|
|
20
|
+
const VARIANTS_DIR = path.join(__dirname, 'skill-variants');
|
|
21
|
+
|
|
22
|
+
const VARIANT_FILES = {
|
|
23
|
+
mcp: 'SKILL.mcp.md',
|
|
24
|
+
cli: 'SKILL.cli.md',
|
|
25
|
+
both: 'SKILL.both.md'
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// installWizard.js's `integrationMode`: 1 = MCP, 2 = CLI-direct, 3 = both.
|
|
29
|
+
function variantNameForIntegrationMode(integrationMode) {
|
|
30
|
+
if (integrationMode === 2) return 'cli';
|
|
31
|
+
if (integrationMode === 3) return 'both';
|
|
32
|
+
return 'mcp';
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Returns the variant's markdown content, or null if the vendored file is missing (e.g. a stale
|
|
36
|
+
// CLI release published before a variant existed). Never throws -- callers must treat a missing
|
|
37
|
+
// variant as "leave whatever is already installed" per the PRD's own risk mitigation, not as a
|
|
38
|
+
// fatal error.
|
|
39
|
+
function readSkillVariantContent(variantName) {
|
|
40
|
+
const fileName = VARIANT_FILES[variantName];
|
|
41
|
+
if (!fileName) return null;
|
|
42
|
+
const filePath = path.join(VARIANTS_DIR, fileName);
|
|
43
|
+
try {
|
|
44
|
+
return fs.readFileSync(filePath, 'utf8');
|
|
45
|
+
} catch (_error) {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function readSkillVariantForIntegrationMode(integrationMode) {
|
|
51
|
+
return readSkillVariantContent(variantNameForIntegrationMode(integrationMode));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
module.exports = {
|
|
55
|
+
VARIANT_FILES,
|
|
56
|
+
variantNameForIntegrationMode,
|
|
57
|
+
readSkillVariantContent,
|
|
58
|
+
readSkillVariantForIntegrationMode
|
|
59
|
+
};
|
package/lib/memoryCommands.js
CHANGED
|
@@ -55,6 +55,7 @@ const MEMORY_SUBCOMMAND_USAGE = {
|
|
|
55
55
|
merge: 'Usage: empirical memory merge --canonical-id <id> --duplicate-id <id> [--no-merge-tags] [--no-merge-relationships]',
|
|
56
56
|
export: 'Usage: empirical memory export --scope <personal|workspace> [--out <path>] [--workspace-id <id>]',
|
|
57
57
|
import: 'Usage: empirical memory import <file> [--dry-run]',
|
|
58
|
+
'bulk-import': 'Usage: empirical memory bulk-import <file> [--dry-run]\nFile format: { "memories": [{ "localKey": "...", "category": "...", "nodeType": "...", "summary": "...", "data": {...} }], "relationships": [{ "fromLocalKey": "...", "toLocalKey": "...", "type": "..." }] }',
|
|
58
59
|
stats: 'Usage: empirical memory stats [--format table]',
|
|
59
60
|
compact: 'Usage: empirical memory compact [--apply] [--category <cat>]'
|
|
60
61
|
};
|
|
@@ -78,11 +79,13 @@ async function withCliAnalyticsContext(context, fn) {
|
|
|
78
79
|
}
|
|
79
80
|
}
|
|
80
81
|
|
|
81
|
-
async function resolveMemoryByQuery(match, minScore, accessToken, baseUrl) {
|
|
82
|
+
async function resolveMemoryByQuery(match, minScore, accessToken, baseUrl, filters) {
|
|
82
83
|
const queryUrl = new URL('/api/v1/memories/query', baseUrl).toString();
|
|
84
|
+
const body = { query: match, topK: 5 };
|
|
85
|
+
if (filters && Object.keys(filters).length) body.filters = filters;
|
|
83
86
|
let response;
|
|
84
87
|
try {
|
|
85
|
-
response = await httpJson(queryUrl, 'POST',
|
|
88
|
+
response = await httpJson(queryUrl, 'POST', body, buildApiAuthHeaders(accessToken));
|
|
86
89
|
} catch (error) {
|
|
87
90
|
return { found: false, reason: 'error', message: error.message };
|
|
88
91
|
}
|
|
@@ -103,6 +106,19 @@ async function resolveMemoryByQuery(match, minScore, accessToken, baseUrl) {
|
|
|
103
106
|
|
|
104
107
|
scored.sort((a, b) => b.score - a.score);
|
|
105
108
|
|
|
109
|
+
// A caller-supplied filter (e.g. --tags) that narrows the server-side result set to exactly
|
|
110
|
+
// one memory is already a stronger disambiguation signal than the semantic score - trust it
|
|
111
|
+
// outright rather than falling through to the score check below. This matters because Atlas
|
|
112
|
+
// vector search has a real async indexing lag: a memory created moments ago can come back
|
|
113
|
+
// with score 0 (unindexed, not "not this one") even when a tag filter already proves it is
|
|
114
|
+
// the only candidate. Fixes a real bug found 2026-08-25 where delete --match failed on a
|
|
115
|
+
// memory `query` found instantly, because delete had no way to trust a filtered, unambiguous
|
|
116
|
+
// result over a stale/zero semantic score.
|
|
117
|
+
if (filters && Object.keys(filters).length && results.length === 1) {
|
|
118
|
+
const only = scored[0];
|
|
119
|
+
return { found: true, memory: { id: only.id, summary: only.summary, score: only.score } };
|
|
120
|
+
}
|
|
121
|
+
|
|
106
122
|
const top = scored[0];
|
|
107
123
|
const second = scored[1];
|
|
108
124
|
|
|
@@ -117,6 +133,23 @@ async function resolveMemoryByQuery(match, minScore, accessToken, baseUrl) {
|
|
|
117
133
|
return { found: true, memory: { id: top.id, summary: top.summary, score: top.score } };
|
|
118
134
|
}
|
|
119
135
|
|
|
136
|
+
// --match resolution depends on Atlas vector search, which has a real async indexing lag:
|
|
137
|
+
// a memory created moments ago can be invisible to $vectorSearch even though it is already
|
|
138
|
+
// findable by tag. Letting --match commands pass --tags narrows the candidate pool directly
|
|
139
|
+
// instead of hoping semantic ranking surfaces the right one - the fix for a real bug found
|
|
140
|
+
// 2026-08-25 where delete --match returned unrelated score-0 candidates for a memory that
|
|
141
|
+
// `query` found instantly. Deliberately --tags (plural) only, not --category: `delete` already
|
|
142
|
+
// uses singular --tag/--category to mean "bulk delete everything in this category", so adding
|
|
143
|
+
// --category here would silently reroute a --match delete into a bulk delete instead of
|
|
144
|
+
// narrowing it - --tags never collides with that bulk-mode gate.
|
|
145
|
+
function parseMatchFilters(args) {
|
|
146
|
+
const tagsRaw = parseOption(args, '--tags', '');
|
|
147
|
+
const tags = tagsRaw ? tagsRaw.split(',').map((t) => t.trim()).filter(Boolean) : [];
|
|
148
|
+
const filters = {};
|
|
149
|
+
if (tags.length) filters.tags = tags;
|
|
150
|
+
return filters;
|
|
151
|
+
}
|
|
152
|
+
|
|
120
153
|
function printMemoryResolveError(result) {
|
|
121
154
|
if (result.reason === 'no_results') {
|
|
122
155
|
warn(JSON.stringify({ error: true, code: 'no_results', message: 'No memories matched the query.' }));
|
|
@@ -340,12 +373,12 @@ async function runMemoryNote(args) {
|
|
|
340
373
|
const minScore = Number(parseOption(args, '--min-score', '0.35'));
|
|
341
374
|
|
|
342
375
|
if (!text) {
|
|
343
|
-
warn('Usage: empirical memory note --text <note> (--match <query> | --memory-id <id>) [--min-score 0.35]');
|
|
376
|
+
warn('Usage: empirical memory note --text <note> (--match <query> | --memory-id <id>) [--tags <t1,t2>] [--min-score 0.35]');
|
|
344
377
|
process.exit(1);
|
|
345
378
|
}
|
|
346
379
|
|
|
347
380
|
if (!match && !memoryId) {
|
|
348
|
-
warn('Usage: empirical memory note --text <note> (--match <query> | --memory-id <id>)');
|
|
381
|
+
warn('Usage: empirical memory note --text <note> (--match <query> | --memory-id <id>) [--tags <t1,t2>]');
|
|
349
382
|
process.exit(1);
|
|
350
383
|
}
|
|
351
384
|
|
|
@@ -354,7 +387,8 @@ async function runMemoryNote(args) {
|
|
|
354
387
|
|
|
355
388
|
let targetId = memoryId;
|
|
356
389
|
if (!targetId) {
|
|
357
|
-
const
|
|
390
|
+
const filters = parseMatchFilters(args);
|
|
391
|
+
const result = await resolveMemoryByQuery(match, minScore, accessToken, baseUrl, filters);
|
|
358
392
|
if (!result.found) {
|
|
359
393
|
printMemoryResolveError(result);
|
|
360
394
|
process.exit(1);
|
|
@@ -383,12 +417,12 @@ async function runMemoryUpdate(args) {
|
|
|
383
417
|
const minScore = Number(parseOption(args, '--min-score', '0.35'));
|
|
384
418
|
|
|
385
419
|
if (!patchJson && !jsonFile) {
|
|
386
|
-
warn('Usage: empirical memory update (--match <query> | --memory-id <id>) (--patch-json <json> | --json-file <path>)');
|
|
420
|
+
warn('Usage: empirical memory update (--match <query> | --memory-id <id>) (--patch-json <json> | --json-file <path>) [--tags <t1,t2>]');
|
|
387
421
|
process.exit(1);
|
|
388
422
|
}
|
|
389
423
|
|
|
390
424
|
if (!match && !memoryId) {
|
|
391
|
-
warn('Usage: empirical memory update (--match <query> | --memory-id <id>) (--patch-json <json> | --json-file <path>)');
|
|
425
|
+
warn('Usage: empirical memory update (--match <query> | --memory-id <id>) (--patch-json <json> | --json-file <path>) [--tags <t1,t2>]');
|
|
392
426
|
process.exit(1);
|
|
393
427
|
}
|
|
394
428
|
|
|
@@ -414,7 +448,8 @@ async function runMemoryUpdate(args) {
|
|
|
414
448
|
|
|
415
449
|
let targetId = memoryId;
|
|
416
450
|
if (!targetId) {
|
|
417
|
-
const
|
|
451
|
+
const filters = parseMatchFilters(args);
|
|
452
|
+
const result = await resolveMemoryByQuery(match, minScore, accessToken, baseUrl, filters);
|
|
418
453
|
if (!result.found) {
|
|
419
454
|
printMemoryResolveError(result);
|
|
420
455
|
process.exit(1);
|
|
@@ -484,7 +519,7 @@ async function runMemoryDelete(args) {
|
|
|
484
519
|
const confirm = parseFlag(args, '--confirm');
|
|
485
520
|
|
|
486
521
|
if (!match && !memoryId) {
|
|
487
|
-
warn('Usage: empirical memory delete (--match <query> | --memory-id <id>) [--confirm] [--min-score 0.35]');
|
|
522
|
+
warn('Usage: empirical memory delete (--match <query> | --memory-id <id>) [--tags <t1,t2>] [--confirm] [--min-score 0.35]');
|
|
488
523
|
warn(' or: empirical memory delete (--tag <t> | --category <c> | --all) --confirm');
|
|
489
524
|
process.exit(1);
|
|
490
525
|
}
|
|
@@ -496,7 +531,8 @@ async function runMemoryDelete(args) {
|
|
|
496
531
|
let targetSummary = '';
|
|
497
532
|
|
|
498
533
|
if (!targetId) {
|
|
499
|
-
const
|
|
534
|
+
const filters = parseMatchFilters(args);
|
|
535
|
+
const result = await resolveMemoryByQuery(match, minScore, accessToken, baseUrl, filters);
|
|
500
536
|
if (!result.found) {
|
|
501
537
|
if (!confirm && (result.reason === 'ambiguous' || result.reason === 'low_score')) {
|
|
502
538
|
// Dry-run: return candidates info
|
|
@@ -727,6 +763,53 @@ async function runMemoryImport(args) {
|
|
|
727
763
|
log(JSON.stringify({ imported, failed, total: memories.length, errors: errors.slice(0, 20) }));
|
|
728
764
|
}
|
|
729
765
|
|
|
766
|
+
async function runMemoryBulkImport(args) {
|
|
767
|
+
const filePath = args.find((a) => a && !a.startsWith('--')) || '';
|
|
768
|
+
const dryRun = parseFlag(args, '--dry-run');
|
|
769
|
+
|
|
770
|
+
if (!filePath) {
|
|
771
|
+
warn('Usage: empirical memory bulk-import <file> [--dry-run]');
|
|
772
|
+
process.exit(1);
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
let plan;
|
|
776
|
+
try {
|
|
777
|
+
plan = JSON.parse(fs.readFileSync(path.resolve(filePath), 'utf8'));
|
|
778
|
+
} catch (error) {
|
|
779
|
+
warn(JSON.stringify({ error: true, code: 'json_file_error', message: error.message }));
|
|
780
|
+
process.exit(1);
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
const memories = Array.isArray(plan && plan.memories) ? plan.memories : null;
|
|
784
|
+
if (!memories) {
|
|
785
|
+
warn(JSON.stringify({
|
|
786
|
+
error: true,
|
|
787
|
+
code: 'invalid_format',
|
|
788
|
+
message: 'File must have a top-level "memories" array. See `empirical memory bulk-import --help`.'
|
|
789
|
+
}));
|
|
790
|
+
process.exit(1);
|
|
791
|
+
}
|
|
792
|
+
const relationships = Array.isArray(plan.relationships) ? plan.relationships : [];
|
|
793
|
+
|
|
794
|
+
if (dryRun) {
|
|
795
|
+
log(JSON.stringify({ dryRun: true, wouldImportMemories: memories.length, wouldImportRelationships: relationships.length }));
|
|
796
|
+
return;
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
const accessToken = requireAccessToken();
|
|
800
|
+
const baseUrl = resolveApiBaseUrl();
|
|
801
|
+
const bulkUrl = new URL('/api/v1/memories/bulk', baseUrl).toString();
|
|
802
|
+
|
|
803
|
+
try {
|
|
804
|
+
const response = await httpJson(bulkUrl, 'POST', { memories, relationships }, buildApiAuthHeaders(accessToken));
|
|
805
|
+
const data = (response && response.data) ? response.data : response;
|
|
806
|
+
log(JSON.stringify(data));
|
|
807
|
+
} catch (error) {
|
|
808
|
+
printApiError(error);
|
|
809
|
+
process.exit(1);
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
|
|
730
813
|
async function runMemoryStats(args) {
|
|
731
814
|
const format = parseOption(args, '--format', 'json');
|
|
732
815
|
|
|
@@ -807,6 +890,7 @@ async function runMemoryCommand(subArgs) {
|
|
|
807
890
|
if (sub === 'merge') return runMemoryMerge(rest);
|
|
808
891
|
if (sub === 'export') return runMemoryExport(rest);
|
|
809
892
|
if (sub === 'import') return runMemoryImport(rest);
|
|
893
|
+
if (sub === 'bulk-import') return runMemoryBulkImport(rest);
|
|
810
894
|
if (sub === 'stats') return runMemoryStats(rest);
|
|
811
895
|
if (sub === 'compact') return runMemoryCompact(rest);
|
|
812
896
|
|
|
@@ -832,6 +916,7 @@ module.exports = {
|
|
|
832
916
|
runMemoryMerge,
|
|
833
917
|
runMemoryExport,
|
|
834
918
|
runMemoryImport,
|
|
919
|
+
runMemoryBulkImport,
|
|
835
920
|
runMemoryStats,
|
|
836
921
|
runMemoryCompact,
|
|
837
922
|
runMemoryDeleteBulk,
|