@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/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
- // (services/memoryAuthorship.ts). Writes also send source: 'cli' in the body,
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
- // (services/memoryAuthorship.ts). Writes also send source: 'cli' in the body,
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.
@@ -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 response = await httpJson(bulkUrl, 'POST', { memories, relationships }, buildApiAuthHeaders(accessToken));
945
- const data = (response && response.data) ? response.data : response;
946
- log(JSON.stringify(data));
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.29",
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",