agenr 0.7.5 → 0.7.6

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/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.7.6] - 2026-02-20
4
+
5
+ ### Fixed
6
+ - fix(plugin): `agenr_recall` now passes query as a positional argument instead of unsupported `--query`
7
+ - fix(plugin): `agenr_recall` now uses `--type` (singular) instead of invalid `--types`
8
+ - fix(plugin): removed unsupported `--threshold` forwarding from `agenr_recall`; threshold has no direct CLI equivalent
9
+ - fix(plugin): `agenr_store` now sends entries array directly on stdin and passes `platform`/`project` as CLI flags
10
+ - fix(plugin): `agenr_store` now infers missing `subject` from `content` before CLI spawn, matching MCP server behavior
11
+ - fix(plugin): `agenr_retire` now calls `agenr retire --id <entry_id>` instead of subject matching with UUIDs
12
+ - fix(cli): `agenr retire` now supports `--id <id>` and enforces exactly one of subject or `--id`
13
+ - fix(plugin): `agenr_extract` now uses a two-step flow for `store=true` (`extract --json` then `store`) and injects source metadata before storing
14
+ - fix(cli): `agenr store` now accepts the `--aggressive` flag used by plugin dedup config forwarding
15
+
3
16
  ## [0.7.5] - 2026-02-20
4
17
 
5
18
  ### Changed
package/README.md CHANGED
@@ -202,7 +202,7 @@ MCP settings.
202
202
  | `agenr extract <files...>` | Extract knowledge entries from text files |
203
203
  | `agenr store [files...]` | Store entries with semantic dedup |
204
204
  | `agenr recall [query]` | Semantic + memory-aware recall |
205
- | `agenr retire <subject>` | Retire a stale entry (hidden, not deleted) |
205
+ | `agenr retire [subject]` | Retire a stale entry (hidden, not deleted). Match by subject text or use --id <id> to target by entry ID. |
206
206
  | `agenr watch [file]` | Live-watch files/directories, auto-extract knowledge |
207
207
  | `agenr daemon install` | Install background watch daemon (macOS launchd) |
208
208
  | `agenr daemon status` | Show daemon status (running/stopped, pid, watched file, recent logs) |
@@ -227,7 +227,7 @@ Deep dive: [docs/ARCHITECTURE.md](./docs/ARCHITECTURE.md)
227
227
 
228
228
  ## Status
229
229
 
230
- The core pipeline is stable and tested (782 tests). We use it daily managing
230
+ The core pipeline is stable and tested (797 tests). We use it daily managing
231
231
  thousands of knowledge entries across OpenClaw sessions.
232
232
 
233
233
  What works: extraction, storage, recall, MCP integration, online dedup, consolidation, smart filtering, live watching, daemon mode.
package/dist/cli-main.js CHANGED
@@ -14968,6 +14968,23 @@ async function queryCandidates(db, subject, contains) {
14968
14968
  content: toStringValue5(row.content)
14969
14969
  }));
14970
14970
  }
14971
+ async function queryById(db, id) {
14972
+ const result = await db.execute({
14973
+ sql: `
14974
+ SELECT id, type, subject, importance, content
14975
+ FROM entries
14976
+ WHERE retired = 0 AND id = ?
14977
+ `,
14978
+ args: [id]
14979
+ });
14980
+ return result.rows.map((row) => ({
14981
+ id: toStringValue5(row.id),
14982
+ type: toStringValue5(row.type),
14983
+ subject: toStringValue5(row.subject),
14984
+ importance: toNumber7(row.importance),
14985
+ content: toStringValue5(row.content)
14986
+ }));
14987
+ }
14971
14988
  async function runRetireCommand(subject, options, deps) {
14972
14989
  const resolvedDeps = {
14973
14990
  readConfigFn: deps?.readConfigFn ?? readConfig,
@@ -14989,8 +15006,12 @@ async function runRetireCommand(subject, options, deps) {
14989
15006
  logFn: deps?.logFn ?? ((line) => clack7.log.info(line, { output: process.stderr }))
14990
15007
  };
14991
15008
  const querySubject = subject.trim();
14992
- if (!querySubject) {
14993
- throw new Error("subject is required");
15009
+ const queryId = options.id?.trim() ?? "";
15010
+ if (!queryId && !querySubject) {
15011
+ throw new Error("subject or id is required");
15012
+ }
15013
+ if (queryId && querySubject) {
15014
+ throw new Error("subject and id are mutually exclusive");
14994
15015
  }
14995
15016
  const clackOutput = { output: process.stderr };
14996
15017
  clack7.intro(banner(), clackOutput);
@@ -15000,9 +15021,15 @@ async function runRetireCommand(subject, options, deps) {
15000
15021
  try {
15001
15022
  await resolvedDeps.initDbFn(db);
15002
15023
  const contains = options.contains === true;
15003
- const candidates = await queryCandidates(db, querySubject, contains);
15024
+ let candidates;
15025
+ if (queryId) {
15026
+ candidates = await queryById(db, queryId);
15027
+ } else {
15028
+ candidates = await queryCandidates(db, querySubject, contains);
15029
+ }
15030
+ const queryDisplay = queryId || querySubject;
15004
15031
  if (candidates.length === 0) {
15005
- clack7.log.warn(`No active entries matching: ${querySubject}`, clackOutput);
15032
+ clack7.log.warn(`No active entries matching: ${queryDisplay}`, clackOutput);
15006
15033
  clack7.outro(void 0, clackOutput);
15007
15034
  return { exitCode: 1 };
15008
15035
  }
@@ -15038,16 +15065,17 @@ async function runRetireCommand(subject, options, deps) {
15038
15065
  return { exitCode: 1 };
15039
15066
  }
15040
15067
  }
15068
+ const retireSubject = queryId ? candidates[0]?.subject ?? queryId : querySubject;
15041
15069
  const retired = await resolvedDeps.retireEntriesFn({
15042
- subjectPattern: querySubject,
15043
- matchType: contains ? "contains" : "exact",
15070
+ subjectPattern: retireSubject,
15071
+ matchType: queryId ? "exact" : contains ? "contains" : "exact",
15044
15072
  reason: options.reason?.trim() || void 0,
15045
15073
  writeLedger: options.persist === true,
15046
15074
  db,
15047
15075
  dbPath
15048
15076
  });
15049
15077
  if (retired.count === 0) {
15050
- clack7.log.warn(`No active entries matching: ${querySubject}`, clackOutput);
15078
+ clack7.log.warn(`No active entries matching: ${queryDisplay}`, clackOutput);
15051
15079
  clack7.outro(void 0, clackOutput);
15052
15080
  return { exitCode: 1 };
15053
15081
  }
@@ -17249,7 +17277,7 @@ function createProgram() {
17249
17277
  });
17250
17278
  process.exitCode = result.exitCode;
17251
17279
  });
17252
- program.command("store").description("Store extracted knowledge entries in the local database").argument("[files...]", "One or more extraction JSON files").option("--db <path>", "Database path override").option("--dry-run", "Show what would be stored without writing", false).option("--verbose", "Show per-entry dedup decisions", false).option("--force", "Skip dedup and store all entries as new", false).option("--platform <name>", "Platform tag: openclaw, claude-code, codex").option("--project <name>", "Project tag (lowercase).", (val, prev) => [...prev, val], []).option("--online-dedup", "Enable online LLM dedup at write time", true).option("--no-online-dedup", "Disable online LLM dedup at write time").option("--dedup-threshold <n>", "Similarity threshold for online dedup (0.0-1.0)").action(
17280
+ program.command("store").description("Store extracted knowledge entries in the local database").argument("[files...]", "One or more extraction JSON files").option("--db <path>", "Database path override").option("--dry-run", "Show what would be stored without writing", false).option("--verbose", "Show per-entry dedup decisions", false).option("--force", "Skip dedup and store all entries as new", false).option("--aggressive", "Enable aggressive dedup (lower similarity threshold)", false).option("--platform <name>", "Platform tag: openclaw, claude-code, codex").option("--project <name>", "Project tag (lowercase).", (val, prev) => [...prev, val], []).option("--online-dedup", "Enable online LLM dedup at write time", true).option("--no-online-dedup", "Disable online LLM dedup at write time").option("--dedup-threshold <n>", "Similarity threshold for online dedup (0.0-1.0)").action(
17253
17281
  async (files, opts) => {
17254
17282
  const result = await runStoreCommand(files ?? [], opts);
17255
17283
  process.exitCode = result.exitCode;
@@ -17277,13 +17305,25 @@ function createProgram() {
17277
17305
  });
17278
17306
  process.exitCode = result.exitCode;
17279
17307
  });
17280
- program.command("retire <subject>").description("Retire a stale entry from active recall (entry is hidden, not deleted)").option("--persist", "Write to retirements ledger so retirement survives re-ingest").option("--contains", "Use substring matching instead of exact match").option("--dry-run", "Preview matches without retiring").option("--reason <text>", "Reason for retirement").option("--db <path>", "Path to database file").action(async (subject, opts) => {
17281
- const result = await runRetireCommand(subject, {
17308
+ program.command("retire [subject]").description("Retire a stale entry from active recall (entry is hidden, not deleted)").option("--persist", "Write to retirements ledger so retirement survives re-ingest").option("--contains", "Use substring matching instead of exact match").option("--dry-run", "Preview matches without retiring").option("--reason <text>", "Reason for retirement").option("--db <path>", "Path to database file").option("--id <id>", "Retire a specific entry by its ID (overrides subject matching)").action(async (subject, opts) => {
17309
+ const entryId = opts.id;
17310
+ if (!entryId && !subject) {
17311
+ console.error("Error: provide a subject or --id <id>");
17312
+ process.exitCode = 1;
17313
+ return;
17314
+ }
17315
+ if (entryId && subject) {
17316
+ console.error("Error: --id and subject are mutually exclusive");
17317
+ process.exitCode = 1;
17318
+ return;
17319
+ }
17320
+ const result = await runRetireCommand(subject ?? "", {
17282
17321
  persist: opts.persist === true,
17283
17322
  contains: opts.contains === true,
17284
17323
  dryRun: opts.dryRun === true,
17285
17324
  reason: opts.reason,
17286
- db: opts.db
17325
+ db: opts.db,
17326
+ id: entryId
17287
17327
  });
17288
17328
  process.exitCode = result.exitCode;
17289
17329
  });
@@ -313,11 +313,10 @@ async function runRecallTool(agenrPath, params) {
313
313
  const limit = asNumber(params.limit);
314
314
  const types = asString(params.types);
315
315
  const since = asString(params.since);
316
- const threshold = asNumber(params.threshold);
317
316
  const platform = asString(params.platform);
318
317
  const project = asString(params.project);
319
318
  if (query) {
320
- args.push("--query", query);
319
+ args.push(query);
321
320
  }
322
321
  if (context) {
323
322
  args.push("--context", context);
@@ -326,14 +325,11 @@ async function runRecallTool(agenrPath, params) {
326
325
  args.push("--limit", String(limit));
327
326
  }
328
327
  if (types) {
329
- args.push("--types", types);
328
+ args.push("--type", types);
330
329
  }
331
330
  if (since) {
332
331
  args.push("--since", since);
333
332
  }
334
- if (threshold !== void 0) {
335
- args.push("--threshold", String(threshold));
336
- }
337
333
  if (platform) {
338
334
  args.push("--platform", platform);
339
335
  }
@@ -381,14 +377,20 @@ async function runStoreTool(agenrPath, params, pluginConfig) {
381
377
  const entries = Array.isArray(params.entries) ? params.entries : [];
382
378
  const platform = asString(params.platform);
383
379
  const project = asString(params.project);
384
- const payload = { entries };
380
+ const storeArgs = ["store"];
385
381
  if (platform) {
386
- payload.platform = platform;
382
+ storeArgs.push("--platform", platform);
387
383
  }
388
384
  if (project) {
389
- payload.project = project;
385
+ storeArgs.push("--project", project);
390
386
  }
391
- const storeArgs = ["store"];
387
+ const processedEntries = entries.map((e) => {
388
+ const entry = e && typeof e === "object" ? e : {};
389
+ if (!entry.subject && typeof entry.content === "string") {
390
+ entry.subject = entry.content.slice(0, 60).replace(/[.!?][\s\S]*$/, "").trim() || entry.content.slice(0, 40);
391
+ }
392
+ return entry;
393
+ });
392
394
  const dedupConfig = pluginConfig?.dedup;
393
395
  if (dedupConfig?.aggressive === true) {
394
396
  storeArgs.push("--aggressive");
@@ -396,7 +398,7 @@ async function runStoreTool(agenrPath, params, pluginConfig) {
396
398
  if (typeof dedupConfig?.threshold === "number") {
397
399
  storeArgs.push("--dedup-threshold", String(dedupConfig.threshold));
398
400
  }
399
- const result = await runAgenrCommand(agenrPath, storeArgs, JSON.stringify(payload));
401
+ const result = await runAgenrCommand(agenrPath, storeArgs, JSON.stringify(processedEntries));
400
402
  if (result.timedOut) {
401
403
  return {
402
404
  content: [{ type: "text", text: "agenr_store timed out" }]
@@ -426,13 +428,6 @@ async function runExtractTool(agenrPath, params) {
426
428
  };
427
429
  }
428
430
  const args = ["extract", "--json"];
429
- if (params.store === true) {
430
- args.push("--store");
431
- }
432
- const source = asString(params.source);
433
- if (source) {
434
- args.push("--source", source);
435
- }
436
431
  const tempFile = join(
437
432
  tmpdir(),
438
433
  `agenr-extract-${Date.now()}-${Math.random().toString(36).slice(2)}.txt`
@@ -440,37 +435,74 @@ async function runExtractTool(agenrPath, params) {
440
435
  try {
441
436
  writeFileSync(tempFile, text, "utf8");
442
437
  args.push(tempFile);
443
- const result = await runAgenrCommand(agenrPath, args, void 0, EXTRACT_TIMEOUT_MS);
444
- if (result.timedOut) {
438
+ const extractResult = await runAgenrCommand(agenrPath, args, void 0, EXTRACT_TIMEOUT_MS);
439
+ if (extractResult.timedOut) {
445
440
  return {
446
441
  content: [{ type: "text", text: "agenr_extract failed: command timed out" }]
447
442
  };
448
443
  }
449
- if (result.error) {
444
+ if (extractResult.error) {
450
445
  return {
451
- content: [{ type: "text", text: `agenr_extract failed: ${result.error.message}` }]
446
+ content: [{ type: "text", text: `agenr_extract failed: ${extractResult.error.message}` }]
452
447
  };
453
448
  }
454
- if (result.code !== 0) {
455
- const message = result.stderr.trim() || result.stdout.trim() || "unknown error";
449
+ if (extractResult.code !== 0) {
450
+ const message = extractResult.stderr.trim() || extractResult.stdout.trim() || "unknown error";
456
451
  return {
457
452
  content: [{ type: "text", text: `agenr_extract failed: ${message}` }]
458
453
  };
459
454
  }
455
+ if (params.store === true) {
456
+ let entries;
457
+ try {
458
+ entries = JSON.parse(extractResult.stdout);
459
+ if (!Array.isArray(entries)) {
460
+ throw new Error("extract did not return an array");
461
+ }
462
+ } catch (err) {
463
+ return {
464
+ content: [
465
+ {
466
+ type: "text",
467
+ text: `agenr_extract failed: could not parse extract output for store: ${err instanceof Error ? err.message : String(err)}`
468
+ }
469
+ ]
470
+ };
471
+ }
472
+ const source = asString(params.source);
473
+ if (source) {
474
+ for (const e of entries) {
475
+ if (e && typeof e === "object") {
476
+ const entry = e;
477
+ if (!entry.source) {
478
+ entry.source = { file: source, context: "extracted via agenr_extract" };
479
+ }
480
+ }
481
+ }
482
+ }
483
+ const storeResult = await runAgenrCommand(agenrPath, ["store"], JSON.stringify(entries));
484
+ if (storeResult.timedOut) {
485
+ return { content: [{ type: "text", text: "agenr_extract store step timed out" }] };
486
+ }
487
+ if (storeResult.error) {
488
+ return { content: [{ type: "text", text: `agenr_extract store step failed: ${storeResult.error.message}` }] };
489
+ }
490
+ if (storeResult.code !== 0) {
491
+ const message = storeResult.stderr.trim() || storeResult.stdout.trim() || "unknown error";
492
+ return { content: [{ type: "text", text: `agenr_extract store step failed: ${message}` }] };
493
+ }
494
+ return { content: [{ type: "text", text: `Extracted and stored ${entries.length} entries.` }] };
495
+ }
460
496
  try {
461
- const parsed = JSON.parse(result.stdout);
497
+ const parsed = JSON.parse(extractResult.stdout);
462
498
  return {
463
499
  content: [{ type: "text", text: JSON.stringify(parsed, null, 2) }],
464
500
  details: parsed
465
501
  };
466
- } catch (error) {
502
+ } catch {
467
503
  return {
468
- content: [
469
- {
470
- type: "text",
471
- text: `agenr_extract failed: ${error instanceof Error ? error.message : String(error)}`
472
- }
473
- ]
504
+ content: [{ type: "text", text: "No results found." }],
505
+ details: { count: 0 }
474
506
  };
475
507
  }
476
508
  } finally {
@@ -489,7 +521,7 @@ async function runRetireTool(agenrPath, params) {
489
521
  }
490
522
  const reason = asString(params.reason);
491
523
  const persist = params.persist === true;
492
- const args = ["retire", entryId];
524
+ const args = ["retire", "--id", entryId];
493
525
  if (reason) {
494
526
  args.push("--reason", reason);
495
527
  }
@@ -700,7 +732,6 @@ var plugin = {
700
732
  since: Type.Optional(
701
733
  Type.String({ description: "Only entries newer than this (ISO date or relative, e.g. 7d)." })
702
734
  ),
703
- threshold: Type.Optional(Type.Number({ description: "Minimum relevance score 0.0-1.0." })),
704
735
  platform: Type.Optional(Type.String({ description: "Platform filter: openclaw, claude-code, codex." })),
705
736
  project: Type.Optional(Type.String({ description: "Project scope. Pass * for all projects." }))
706
737
  }),
package/package.json CHANGED
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "name": "agenr",
3
- "version": "0.7.5",
4
-
3
+ "version": "0.7.6",
5
4
  "openclaw": {
6
5
  "extensions": [
7
6
  "dist/openclaw-plugin/index.js"
@@ -12,13 +11,6 @@
12
11
  "bin": {
13
12
  "agenr": "dist/cli.js"
14
13
  },
15
- "scripts": {
16
- "build": "tsup src/cli.ts src/cli-main.ts src/openclaw-plugin/index.ts --format esm --dts",
17
- "dev": "tsup src/cli.ts src/cli-main.ts --format esm --watch",
18
- "test": "vitest run",
19
- "test:watch": "vitest",
20
- "typecheck": "tsc --noEmit"
21
- },
22
14
  "dependencies": {
23
15
  "@clack/prompts": "^1.0.1",
24
16
  "@libsql/client": "^0.17.0",
@@ -62,9 +54,11 @@
62
54
  "README.md"
63
55
  ],
64
56
  "author": "agenr-ai",
65
- "pnpm": {
66
- "overrides": {
67
- "fast-xml-parser": "^5.3.6"
68
- }
57
+ "scripts": {
58
+ "build": "tsup src/cli.ts src/cli-main.ts src/openclaw-plugin/index.ts --format esm --dts",
59
+ "dev": "tsup src/cli.ts src/cli-main.ts --format esm --watch",
60
+ "test": "vitest run",
61
+ "test:watch": "vitest",
62
+ "typecheck": "tsc --noEmit"
69
63
  }
70
- }
64
+ }