@empirical-gauzza/cli 1.0.29 → 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/lib/alertBanner.js +17 -0
- package/lib/constants.js +657 -640
- package/lib/http.js +2 -2
- 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/http.js
CHANGED
|
@@ -84,7 +84,7 @@ function httpJson(urlString, method, payload, headers = {}, timeoutMs = 0) {
|
|
|
84
84
|
headers: {
|
|
85
85
|
Accept: 'application/json',
|
|
86
86
|
// Identifies the client so the server can tell a CLI write from a portal write
|
|
87
|
-
// (
|
|
87
|
+
// (@empirical/core's memoryAuthorship). Writes also send source: 'cli' in the body,
|
|
88
88
|
// which is authoritative; this covers every other endpoint.
|
|
89
89
|
'User-Agent': 'empirical-cli/1.0',
|
|
90
90
|
...(body ? { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) } : {}),
|
|
@@ -144,7 +144,7 @@ function httpForm(urlString, method, params, headers = {}) {
|
|
|
144
144
|
headers: {
|
|
145
145
|
Accept: 'application/json',
|
|
146
146
|
// Identifies the client so the server can tell a CLI write from a portal write
|
|
147
|
-
// (
|
|
147
|
+
// (@empirical/core's memoryAuthorship). Writes also send source: 'cli' in the body,
|
|
148
148
|
// which is authoritative; this covers every other endpoint.
|
|
149
149
|
'User-Agent': 'empirical-cli/1.0',
|
|
150
150
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|
@@ -195,6 +195,27 @@ Use these when connected: `get_empirical_policy`, `query_memories`, `list_memori
|
|
|
195
195
|
graph work, use `get_memory_neighbors` and `update_memory_relationships` when available instead of
|
|
196
196
|
reconstructing relationships through many unrelated calls.
|
|
197
197
|
|
|
198
|
+
## Reminders — set them, relay them, close them
|
|
199
|
+
|
|
200
|
+
Any memory can carry a due date. Empirical then brings it back: an email with a calendar entry
|
|
201
|
+
when the reminder is set, a nudge when it is due, and a notice attached to the next Empirical
|
|
202
|
+
response each of the user's agents receives.
|
|
203
|
+
|
|
204
|
+
- **Set one** when the user says "remind me", names a deadline, or a todo has a real due date.
|
|
205
|
+
Use the `dueAt` parameter on `record_graph_memory` / `update_memory_by_query` (ISO 8601), or
|
|
206
|
+
`empirical memory record ... --due 2026-10-14T09:00:00Z` on the CLI. `dueAt` is a real field the
|
|
207
|
+
server filters and fires on; a date written inside `data` is text and nothing will ever fire.
|
|
208
|
+
- **Relay it.** If any Empirical result starts with `REMINDER DUE`, or a CLI command prints a
|
|
209
|
+
banner that starts with `REMINDER DUE`, tell the user about it in your own words BEFORE the task
|
|
210
|
+
you were doing, then ask whether to mark it done. Each agent is shown a reminder once; if you do
|
|
211
|
+
not relay it, the user does not hear it from you.
|
|
212
|
+
- **Close it** when the user says it is handled: `acknowledge_reminder` with the `memoryId` from
|
|
213
|
+
the notice (or `state: "cancelled"` to drop it), or `empirical memory acknowledge --memory-id <id>`
|
|
214
|
+
(`--cancel` to drop). Closing frees the slot; plans cap concurrent open reminders.
|
|
215
|
+
- **What is due?** `query_memories` with `dueBefore` set to now and `remindState: ["pending"]`, or
|
|
216
|
+
`empirical memory query --match "what is due" --due-before <now> --remind-state pending`.
|
|
217
|
+
Dueness is a filter; semantic search will not rank a memory higher because it is due.
|
|
218
|
+
|
|
198
219
|
## Authentication and failures
|
|
199
220
|
|
|
200
221
|
- If a memory call fails, retry the same call once.
|
|
@@ -179,6 +179,27 @@ token error, run `empirical oauth bootstrap headless --write-env`. When in doubt
|
|
|
179
179
|
subcommand's flags, run `empirical doc memory`, `empirical doc kit`, or `empirical doc skill`
|
|
180
180
|
rather than guessing.
|
|
181
181
|
|
|
182
|
+
## Reminders — set them, relay them, close them
|
|
183
|
+
|
|
184
|
+
Any memory can carry a due date. Empirical then brings it back: an email with a calendar entry
|
|
185
|
+
when the reminder is set, a nudge when it is due, and a notice attached to the next Empirical
|
|
186
|
+
response each of the user's agents receives.
|
|
187
|
+
|
|
188
|
+
- **Set one** when the user says "remind me", names a deadline, or a todo has a real due date.
|
|
189
|
+
Use the `dueAt` parameter on `record_graph_memory` / `update_memory_by_query` (ISO 8601), or
|
|
190
|
+
`empirical memory record ... --due 2026-10-14T09:00:00Z` on the CLI. `dueAt` is a real field the
|
|
191
|
+
server filters and fires on; a date written inside `data` is text and nothing will ever fire.
|
|
192
|
+
- **Relay it.** If any Empirical result starts with `REMINDER DUE`, or a CLI command prints a
|
|
193
|
+
banner that starts with `REMINDER DUE`, tell the user about it in your own words BEFORE the task
|
|
194
|
+
you were doing, then ask whether to mark it done. Each agent is shown a reminder once; if you do
|
|
195
|
+
not relay it, the user does not hear it from you.
|
|
196
|
+
- **Close it** when the user says it is handled: `acknowledge_reminder` with the `memoryId` from
|
|
197
|
+
the notice (or `state: "cancelled"` to drop it), or `empirical memory acknowledge --memory-id <id>`
|
|
198
|
+
(`--cancel` to drop). Closing frees the slot; plans cap concurrent open reminders.
|
|
199
|
+
- **What is due?** `query_memories` with `dueBefore` set to now and `remindState: ["pending"]`, or
|
|
200
|
+
`empirical memory query --match "what is due" --due-before <now> --remind-state pending`.
|
|
201
|
+
Dueness is a filter; semantic search will not rank a memory higher because it is due.
|
|
202
|
+
|
|
182
203
|
## Authentication and failures
|
|
183
204
|
|
|
184
205
|
- If a memory call fails, retry the same call once.
|
|
@@ -167,6 +167,27 @@ An `empirical` CLI may also be installed on this machine for diagnostics (`empir
|
|
|
167
167
|
manual account setup, but it is not the point of this variant — do not lead with CLI commands for
|
|
168
168
|
query or record workflows here.
|
|
169
169
|
|
|
170
|
+
## Reminders — set them, relay them, close them
|
|
171
|
+
|
|
172
|
+
Any memory can carry a due date. Empirical then brings it back: an email with a calendar entry
|
|
173
|
+
when the reminder is set, a nudge when it is due, and a notice attached to the next Empirical
|
|
174
|
+
response each of the user's agents receives.
|
|
175
|
+
|
|
176
|
+
- **Set one** when the user says "remind me", names a deadline, or a todo has a real due date.
|
|
177
|
+
Use the `dueAt` parameter on `record_graph_memory` / `update_memory_by_query` (ISO 8601), or
|
|
178
|
+
`empirical memory record ... --due 2026-10-14T09:00:00Z` on the CLI. `dueAt` is a real field the
|
|
179
|
+
server filters and fires on; a date written inside `data` is text and nothing will ever fire.
|
|
180
|
+
- **Relay it.** If any Empirical result starts with `REMINDER DUE`, or a CLI command prints a
|
|
181
|
+
banner that starts with `REMINDER DUE`, tell the user about it in your own words BEFORE the task
|
|
182
|
+
you were doing, then ask whether to mark it done. Each agent is shown a reminder once; if you do
|
|
183
|
+
not relay it, the user does not hear it from you.
|
|
184
|
+
- **Close it** when the user says it is handled: `acknowledge_reminder` with the `memoryId` from
|
|
185
|
+
the notice (or `state: "cancelled"` to drop it), or `empirical memory acknowledge --memory-id <id>`
|
|
186
|
+
(`--cancel` to drop). Closing frees the slot; plans cap concurrent open reminders.
|
|
187
|
+
- **What is due?** `query_memories` with `dueBefore` set to now and `remindState: ["pending"]`, or
|
|
188
|
+
`empirical memory query --match "what is due" --due-before <now> --remind-state pending`.
|
|
189
|
+
Dueness is a filter; semantic search will not rank a memory higher because it is due.
|
|
190
|
+
|
|
170
191
|
## Authentication and failures
|
|
171
192
|
|
|
172
193
|
- If an MCP tool call fails, retry the same call once.
|
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",
|