@empirical-gauzza/cli 1.0.28 → 1.0.30
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 +1 -1
- package/lib/alertBanner.js +17 -0
- package/lib/constants.js +657 -640
- package/lib/http.js +2 -2
- package/lib/installers/hooks.js +321 -61
- package/lib/installers/skill-variants/SKILL.both.md +21 -0
- package/lib/installers/skill-variants/SKILL.cli.md +21 -0
- package/lib/installers/skill-variants/SKILL.mcp.md +21 -0
- package/lib/memoryCommands.js +88 -4
- package/package.json +1 -1
package/lib/memoryCommands.js
CHANGED
|
@@ -50,18 +50,22 @@ const RECALL_PATHS = ['semantic', 'list', 'graph', 'hebbian'];
|
|
|
50
50
|
const RECALL_HOPS = [1, 2];
|
|
51
51
|
// Hops only mean something on a pathway that walks relationships.
|
|
52
52
|
const RECALL_HOP_PATHS = ['graph', 'hebbian'];
|
|
53
|
+
// Reminder states `memory query --remind-state` may keep. Same enum the server's shared
|
|
54
|
+
// normalizer and Memory.remindState declare.
|
|
55
|
+
const REMIND_STATES = ['pending', 'notified', 'acknowledged', 'cancelled'];
|
|
53
56
|
|
|
54
57
|
// ─── Scoped `--help` (C-005 item 5) ───────────────────────────────────────────
|
|
55
58
|
// `empirical memory --help` / `empirical memory <sub> --help` print scoped usage instead of
|
|
56
59
|
// the 90-line global help wall bin/empirical.js otherwise short-circuits every --help to.
|
|
57
60
|
|
|
58
61
|
const MEMORY_SUBCOMMAND_USAGE = {
|
|
59
|
-
query: 'Usage: empirical memory query --match <query> [--top-k 3] [--category <cat>] [--tags <t1,t2>] [--tags-all <t1,t2>] [--status <s1,s2>] [--path semantic|list|graph|hebbian] [--hops 1|2] [--pretty] [--format table]',
|
|
62
|
+
query: 'Usage: empirical memory query --match <query> [--top-k 3] [--category <cat>] [--tags <t1,t2>] [--tags-all <t1,t2>] [--status <s1,s2>] [--due-before <ISO date>] [--due-after <ISO date>] [--remind-state pending,notified] [--path semantic|list|graph|hebbian] [--hops 1|2] [--pretty] [--format table]',
|
|
60
63
|
get: 'Usage: empirical memory get --memory-id <id> [--pretty]',
|
|
61
64
|
list: 'Usage: empirical memory list [--category <cat>] [--tags <t1,t2>] [--start-date <ISO>] [--end-date <ISO>] [--limit 20] [--cursor <cursor>] [--oldest] [--pretty] [--format table]',
|
|
62
65
|
record: 'Usage: empirical memory record --category <cat> (--summary <text> | --summary - | --summary-file <path>) [--tags t1,t2] [--node-type observation] [--json-file <path>] [--no-dedupe] [--no-auto-link] [--author-type user|agent]',
|
|
63
66
|
note: 'Usage: empirical memory note --text <note> (--match <query> | --memory-id <id>) [--min-score 0.35]',
|
|
64
67
|
update: 'Usage: empirical memory update (--match <query> | --memory-id <id>) (--patch-json <json> | --json-file <path>) [--min-score 0.35]',
|
|
68
|
+
acknowledge: 'Usage: empirical memory acknowledge --memory-id <id> [--cancel] (marks a due reminder done, or dropped with --cancel; frees its slot and clears the alert)',
|
|
65
69
|
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',
|
|
66
70
|
duplicates: 'Usage: empirical memory duplicates --memory-id <id>',
|
|
67
71
|
merge: 'Usage: empirical memory merge --canonical-id <id> --duplicate-id <id> [--no-merge-tags] [--no-merge-relationships]',
|
|
@@ -209,6 +213,29 @@ async function runMemoryQuery(args) {
|
|
|
209
213
|
const pretty = parseFlag(args, '--pretty');
|
|
210
214
|
const format = parseOption(args, '--format', 'json');
|
|
211
215
|
|
|
216
|
+
// Reminder filters. Dates are parsed HERE as well as server-side, for the same reason --due
|
|
217
|
+
// is on record: a typo fails at the terminal, not as a 400 after the round trip. Anything
|
|
218
|
+
// Date can parse is accepted and sent as the caller typed it.
|
|
219
|
+
const dueFilters = {};
|
|
220
|
+
for (const [flag, key] of [['--due-before', 'dueBefore'], ['--due-after', 'dueAfter']]) {
|
|
221
|
+
const raw = parseOption(args, flag, '');
|
|
222
|
+
if (!raw) continue;
|
|
223
|
+
if (Number.isNaN(new Date(raw).getTime())) {
|
|
224
|
+
warn(`Invalid ${flag} "${raw}". Use an ISO 8601 date, for example 2026-10-14 or 2026-10-14T09:00:00Z.`);
|
|
225
|
+
process.exit(1);
|
|
226
|
+
}
|
|
227
|
+
dueFilters[key] = String(raw).trim();
|
|
228
|
+
}
|
|
229
|
+
const remindStateRaw = parseOption(args, '--remind-state', '');
|
|
230
|
+
const remindState = remindStateRaw
|
|
231
|
+
? remindStateRaw.split(',').map((s) => s.trim().toLowerCase()).filter(Boolean)
|
|
232
|
+
: [];
|
|
233
|
+
const unknownStates = remindState.filter((s) => !REMIND_STATES.includes(s));
|
|
234
|
+
if (unknownStates.length) {
|
|
235
|
+
warn(`--remind-state must be one of: ${REMIND_STATES.join(', ')} (received ${JSON.stringify(unknownStates)})`);
|
|
236
|
+
process.exit(1);
|
|
237
|
+
}
|
|
238
|
+
|
|
212
239
|
// --path / --hops are validated HERE, where the value is decided, so a typo costs no round
|
|
213
240
|
// trip and the message names the four pathways. The server validates the same way (the shared
|
|
214
241
|
// normalizer in packages/server/src/services/recallParams.service.ts); this is a fast local
|
|
@@ -252,6 +279,9 @@ async function runMemoryQuery(args) {
|
|
|
252
279
|
if (tags.length) body.tags = tags;
|
|
253
280
|
if (tagsAll.length) body.tagsAll = tagsAll;
|
|
254
281
|
if (status.length) body.status = status;
|
|
282
|
+
if (dueFilters.dueBefore) body.dueBefore = dueFilters.dueBefore;
|
|
283
|
+
if (dueFilters.dueAfter) body.dueAfter = dueFilters.dueAfter;
|
|
284
|
+
if (remindState.length) body.remindState = remindState;
|
|
255
285
|
if (path) body.path = path;
|
|
256
286
|
if (hops !== null) body.hops = hops;
|
|
257
287
|
|
|
@@ -490,6 +520,23 @@ async function runMemoryRecord(args) {
|
|
|
490
520
|
payload.authorType = authorType;
|
|
491
521
|
}
|
|
492
522
|
|
|
523
|
+
// --due turns the memory into a reminder. Parsed and rejected HERE as well as server-side, so
|
|
524
|
+
// a typo fails immediately at the terminal instead of coming back as a 400 after the write was
|
|
525
|
+
// composed. Accepts anything Date can parse, so '2026-10-14' and a full ISO timestamp both work.
|
|
526
|
+
const dueRaw = parseOption(args, '--due', '');
|
|
527
|
+
if (dueRaw) {
|
|
528
|
+
const parsedDue = new Date(dueRaw);
|
|
529
|
+
if (Number.isNaN(parsedDue.getTime())) {
|
|
530
|
+
warn(JSON.stringify({
|
|
531
|
+
error: true,
|
|
532
|
+
code: 'invalid_due',
|
|
533
|
+
message: `Invalid --due "${dueRaw}". Use an ISO 8601 date, for example 2026-10-14 or 2026-10-14T09:00:00Z.`
|
|
534
|
+
}));
|
|
535
|
+
process.exit(1);
|
|
536
|
+
}
|
|
537
|
+
payload.dueAt = parsedDue.toISOString();
|
|
538
|
+
}
|
|
539
|
+
|
|
493
540
|
const accessToken = requireAccessToken();
|
|
494
541
|
const baseUrl = resolveApiBaseUrl();
|
|
495
542
|
const recordUrl = new URL('/api/v1/memories', baseUrl).toString();
|
|
@@ -549,6 +596,29 @@ async function runMemoryNote(args) {
|
|
|
549
596
|
log(JSON.stringify(created));
|
|
550
597
|
}
|
|
551
598
|
|
|
599
|
+
// Closing a reminder from the terminal or from an agent reading a CLI banner. One PUT, the same
|
|
600
|
+
// transition the portal's Mark done / Cancel make; the server retires the alert on every surface.
|
|
601
|
+
async function runMemoryAcknowledge(args) {
|
|
602
|
+
const memoryId = parseOption(args, '--memory-id', '');
|
|
603
|
+
const cancel = parseFlag(args, '--cancel');
|
|
604
|
+
if (!memoryId) {
|
|
605
|
+
warn(MEMORY_SUBCOMMAND_USAGE.acknowledge);
|
|
606
|
+
process.exit(1);
|
|
607
|
+
}
|
|
608
|
+
const accessToken = requireAccessToken();
|
|
609
|
+
const baseUrl = resolveApiBaseUrl();
|
|
610
|
+
const url = new URL(`/api/v1/memories/${encodeURIComponent(memoryId)}`, baseUrl).toString();
|
|
611
|
+
let response;
|
|
612
|
+
try {
|
|
613
|
+
response = await httpJson(url, 'PUT', { remindState: cancel ? 'cancelled' : 'acknowledged' }, buildApiAuthHeaders(accessToken));
|
|
614
|
+
} catch (error) {
|
|
615
|
+
printApiError(error);
|
|
616
|
+
process.exit(1);
|
|
617
|
+
}
|
|
618
|
+
const data = response && response.data ? response.data : response;
|
|
619
|
+
log(JSON.stringify({ id: data.id, remindState: data.remindState, dueAt: data.dueAt || null, message: cancel ? 'Reminder cancelled. Its slot is free.' : 'Reminder marked done. Its slot is free.' }, null, 2));
|
|
620
|
+
}
|
|
621
|
+
|
|
552
622
|
async function runMemoryUpdate(args) {
|
|
553
623
|
const match = parseOption(args, '--match', '');
|
|
554
624
|
const patchJson = parseOption(args, '--patch-json', '');
|
|
@@ -941,9 +1011,21 @@ async function runMemoryBulkImport(args) {
|
|
|
941
1011
|
const bulkUrl = new URL('/api/v1/memories/bulk', baseUrl).toString();
|
|
942
1012
|
|
|
943
1013
|
try {
|
|
944
|
-
const
|
|
945
|
-
const
|
|
946
|
-
|
|
1014
|
+
const enqueueResponse = await httpJson(bulkUrl, 'POST', { memories, relationships }, buildApiAuthHeaders(accessToken));
|
|
1015
|
+
const { jobId } = (enqueueResponse && enqueueResponse.data) ? enqueueResponse.data : enqueueResponse;
|
|
1016
|
+
const statusUrl = new URL(`/api/v1/memories/bulk/${jobId}`, baseUrl).toString();
|
|
1017
|
+
|
|
1018
|
+
const pollIntervalMs = 3000;
|
|
1019
|
+
const maxPolls = 200;
|
|
1020
|
+
for (let i = 0; i < maxPolls; i += 1) {
|
|
1021
|
+
await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
|
|
1022
|
+
const statusResponse = await httpJson(statusUrl, 'GET', undefined, buildApiAuthHeaders(accessToken));
|
|
1023
|
+
const job = (statusResponse && statusResponse.data) ? statusResponse.data : statusResponse;
|
|
1024
|
+
if (job.status === 'succeeded') { log(JSON.stringify(job)); return; }
|
|
1025
|
+
if (job.status === 'failed') { warn(JSON.stringify(job)); process.exit(1); }
|
|
1026
|
+
}
|
|
1027
|
+
warn(JSON.stringify({ error: true, code: 'timeout', message: `Job ${jobId} did not complete within ${(maxPolls * pollIntervalMs) / 1000}s` }));
|
|
1028
|
+
process.exit(1);
|
|
947
1029
|
} catch (error) {
|
|
948
1030
|
printApiError(error);
|
|
949
1031
|
process.exit(1);
|
|
@@ -1025,6 +1107,7 @@ async function runMemoryCommand(subArgs) {
|
|
|
1025
1107
|
if (sub === 'record') return runMemoryRecord(rest);
|
|
1026
1108
|
if (sub === 'note') return runMemoryNote(rest);
|
|
1027
1109
|
if (sub === 'update') return runMemoryUpdate(rest);
|
|
1110
|
+
if (sub === 'acknowledge' || sub === 'ack' || sub === 'done') return runMemoryAcknowledge(rest);
|
|
1028
1111
|
if (sub === 'delete') return runMemoryDelete(rest);
|
|
1029
1112
|
if (sub === 'duplicates') return runMemoryDuplicates(rest);
|
|
1030
1113
|
if (sub === 'merge') return runMemoryMerge(rest);
|
|
@@ -1053,6 +1136,7 @@ module.exports = {
|
|
|
1053
1136
|
runMemoryRecord,
|
|
1054
1137
|
runMemoryNote,
|
|
1055
1138
|
runMemoryUpdate,
|
|
1139
|
+
runMemoryAcknowledge,
|
|
1056
1140
|
runMemoryDelete,
|
|
1057
1141
|
runMemoryDuplicates,
|
|
1058
1142
|
runMemoryMerge,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@empirical-gauzza/cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.30",
|
|
4
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
5
|
"keywords": [
|
|
6
6
|
"memory",
|