@remnic/core 9.3.623 → 9.3.625

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.
Files changed (262) hide show
  1. package/dist/access-cli.js +18 -16
  2. package/dist/access-cli.js.map +1 -1
  3. package/dist/access-http.d.ts +12 -5
  4. package/dist/access-http.js +10 -9
  5. package/dist/access-mcp.d.ts +5 -5
  6. package/dist/access-mcp.js +8 -8
  7. package/dist/access-schema.d.ts +5 -5
  8. package/dist/{access-service-CBNEKjzN.d.ts → access-service-C_sfOHsX.d.ts} +26 -3
  9. package/dist/access-service.d.ts +5 -5
  10. package/dist/access-service.js +7 -7
  11. package/dist/action-confidence.d.ts +1 -1
  12. package/dist/active-memory-bridge.d.ts +1 -1
  13. package/dist/active-recall.d.ts +1 -1
  14. package/dist/active-recall.js +2 -1
  15. package/dist/active-recall.js.map +1 -1
  16. package/dist/behavior-learner.d.ts +1 -1
  17. package/dist/behavior-signals.d.ts +1 -1
  18. package/dist/bootstrap.d.ts +4 -4
  19. package/dist/briefing.d.ts +1 -1
  20. package/dist/briefing.js +3 -3
  21. package/dist/buffer-surprise-report.d.ts +1 -1
  22. package/dist/buffer.d.ts +1 -1
  23. package/dist/calibration.d.ts +1 -1
  24. package/dist/causal-behavior.d.ts +1 -1
  25. package/dist/causal-consolidation.d.ts +1 -1
  26. package/dist/causal-consolidation.js +4 -4
  27. package/dist/{chunk-C4PZTWTG.js → chunk-2RHI3FGV.js} +540 -17
  28. package/dist/chunk-2RHI3FGV.js.map +1 -0
  29. package/dist/{chunk-GYTVOLNX.js → chunk-3MNBW7R7.js} +2 -2
  30. package/dist/{chunk-QFQQFX2H.js → chunk-3R2UZV3U.js} +2 -2
  31. package/dist/{chunk-O4UNM6OR.js → chunk-532VCWYW.js} +2 -2
  32. package/dist/{chunk-2UFQYU5F.js → chunk-57QXN2CS.js} +2 -2
  33. package/dist/chunk-7WV3F5DQ.js +22 -0
  34. package/dist/chunk-7WV3F5DQ.js.map +1 -0
  35. package/dist/{chunk-RKW6QR7W.js → chunk-AZ4RI3QD.js} +1461 -78
  36. package/dist/chunk-AZ4RI3QD.js.map +1 -0
  37. package/dist/{chunk-KQFQ3IS5.js → chunk-F3FY3D3S.js} +43 -7
  38. package/dist/chunk-F3FY3D3S.js.map +1 -0
  39. package/dist/{chunk-4R4KTDIE.js → chunk-FPNQF475.js} +1 -1
  40. package/dist/chunk-FPNQF475.js.map +1 -0
  41. package/dist/{chunk-UGEBPVNI.js → chunk-GE7Q7KXP.js} +2 -2
  42. package/dist/{chunk-GLWW3EJQ.js → chunk-KB4MFBF5.js} +3 -3
  43. package/dist/{chunk-5GOMXHLC.js → chunk-KKTXCFD7.js} +255 -1
  44. package/dist/chunk-KKTXCFD7.js.map +1 -0
  45. package/dist/{chunk-FH3PPO42.js → chunk-KVFYTRMV.js} +2 -2
  46. package/dist/{chunk-BNW5NJJH.js → chunk-LQYTQCXM.js} +2 -2
  47. package/dist/{chunk-AYHXQR53.js → chunk-MVQN73GT.js} +2 -2
  48. package/dist/{chunk-ZZPIJPPD.js → chunk-N5RGXWLQ.js} +2 -2
  49. package/dist/chunk-NDAH7BJ5.js +213 -0
  50. package/dist/chunk-NDAH7BJ5.js.map +1 -0
  51. package/dist/{chunk-R3OQGYOU.js → chunk-P2D2MM47.js} +2 -2
  52. package/dist/{chunk-PSUB67YB.js → chunk-PW6GURU3.js} +2 -2
  53. package/dist/{chunk-W3BKVM64.js → chunk-QDV6VAD4.js} +2 -2
  54. package/dist/{chunk-3QSU4NFF.js → chunk-QHXW3LZV.js} +3 -3
  55. package/dist/{chunk-I6UCUHLK.js → chunk-SHV5Y2WU.js} +182 -3
  56. package/dist/chunk-SHV5Y2WU.js.map +1 -0
  57. package/dist/{chunk-OZXVGYGZ.js → chunk-STDAAGH7.js} +2 -2
  58. package/dist/{chunk-FMGWXIES.js → chunk-TZDSNIRO.js} +5 -5
  59. package/dist/{chunk-2L54V4ZO.js → chunk-UELS6WWF.js} +2 -2
  60. package/dist/{chunk-PJGB7XRR.js → chunk-UGHUNQ74.js} +502 -134
  61. package/dist/chunk-UGHUNQ74.js.map +1 -0
  62. package/dist/{chunk-ZJSZNTEI.js → chunk-Y3TMFC6I.js} +140 -10
  63. package/dist/chunk-Y3TMFC6I.js.map +1 -0
  64. package/dist/{chunk-BPSGLMQ4.js → chunk-YQNADJCT.js} +2 -2
  65. package/dist/{cli-Cw729yLf.d.ts → cli-EZv6YE6_.d.ts} +3 -3
  66. package/dist/cli.d.ts +6 -6
  67. package/dist/cli.js +23 -21
  68. package/dist/compounding/engine.d.ts +1 -1
  69. package/dist/compounding/engine.js +3 -3
  70. package/dist/compounding/preference-consolidator.d.ts +1 -1
  71. package/dist/compression-optimizer.d.ts +1 -1
  72. package/dist/config.d.ts +1 -1
  73. package/dist/config.js +2 -1
  74. package/dist/connectors/codex-materialize-runner.d.ts +1 -1
  75. package/dist/connectors/codex-materialize-runner.js +3 -3
  76. package/dist/connectors/codex-materialize.d.ts +1 -1
  77. package/dist/connectors/index.d.ts +1 -1
  78. package/dist/connectors/index.js +3 -3
  79. package/dist/consolidation-provenance-check.d.ts +1 -1
  80. package/dist/consolidation-undo.d.ts +1 -1
  81. package/dist/contradiction/index.d.ts +2 -2
  82. package/dist/conversation-index/backend.d.ts +1 -1
  83. package/dist/conversation-index/chunker.d.ts +1 -1
  84. package/dist/conversation-index/faiss-adapter.d.ts +1 -1
  85. package/dist/conversation-index/indexer.d.ts +1 -1
  86. package/dist/conversation-index/search.d.ts +1 -1
  87. package/dist/day-summary.d.ts +1 -1
  88. package/dist/delinearize.d.ts +1 -1
  89. package/dist/direct-answer-wiring.d.ts +1 -1
  90. package/dist/direct-answer.d.ts +1 -1
  91. package/dist/embedding-fallback.d.ts +1 -1
  92. package/dist/enrichment/index.d.ts +1 -1
  93. package/dist/entity-retrieval.d.ts +1 -1
  94. package/dist/entity-retrieval.js +3 -3
  95. package/dist/entity-schema.d.ts +1 -1
  96. package/dist/explicit-capture.d.ts +4 -4
  97. package/dist/extraction-judge-telemetry.d.ts +1 -1
  98. package/dist/extraction-judge-training.d.ts +1 -1
  99. package/dist/extraction-judge.d.ts +1 -1
  100. package/dist/extraction.d.ts +1 -1
  101. package/dist/fallback-llm.d.ts +1 -1
  102. package/dist/identity-continuity.d.ts +1 -1
  103. package/dist/importance.d.ts +1 -1
  104. package/dist/index.d.ts +307 -9
  105. package/dist/index.js +155 -29
  106. package/dist/index.js.map +1 -1
  107. package/dist/intent.d.ts +1 -1
  108. package/dist/lcm/engine.d.ts +1 -1
  109. package/dist/lcm/index.d.ts +1 -1
  110. package/dist/lcm/tools.d.ts +1 -1
  111. package/dist/lifecycle.d.ts +1 -1
  112. package/dist/live-connectors-runner.d.ts +1 -1
  113. package/dist/local-llm.d.ts +1 -1
  114. package/dist/maintenance/memory-governance.d.ts +1 -1
  115. package/dist/maintenance/memory-governance.js +3 -3
  116. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +3 -3
  117. package/dist/maintenance/rebuild-memory-projection.js +4 -4
  118. package/dist/mcp-memory-inspector-app.d.ts +5 -5
  119. package/dist/memory-action-policy.d.ts +1 -1
  120. package/dist/memory-cache.d.ts +1 -1
  121. package/dist/memory-lifecycle-ledger-utils.d.ts +1 -1
  122. package/dist/memory-projection-store.d.ts +1 -1
  123. package/dist/memory-provenance.d.ts +1 -1
  124. package/dist/memory-worth-outcomes.d.ts +1 -1
  125. package/dist/models-json.d.ts +1 -1
  126. package/dist/namespaces/migrate.d.ts +1 -1
  127. package/dist/namespaces/migrate.js +4 -4
  128. package/dist/namespaces/principal.d.ts +1 -1
  129. package/dist/namespaces/search.d.ts +1 -1
  130. package/dist/namespaces/storage.d.ts +1 -1
  131. package/dist/namespaces/storage.js +3 -3
  132. package/dist/native-knowledge.d.ts +1 -1
  133. package/dist/operator-toolkit.d.ts +1 -1
  134. package/dist/operator-toolkit.js +8 -7
  135. package/dist/{orchestrator-CqWOjfgl.d.ts → orchestrator-CEycaY3M.d.ts} +361 -4
  136. package/dist/orchestrator.d.ts +4 -4
  137. package/dist/orchestrator.js +13 -11
  138. package/dist/patterns-cli.d.ts +1 -1
  139. package/dist/policy-runtime.d.ts +1 -1
  140. package/dist/qmd-recall-cache.d.ts +1 -1
  141. package/dist/qmd.d.ts +1 -1
  142. package/dist/recall-disclosure-escalation.d.ts +1 -1
  143. package/dist/recall-explain-renderer.d.ts +1 -1
  144. package/dist/recall-explain-renderer.js +3 -3
  145. package/dist/recall-planner-llm.d.ts +1 -1
  146. package/dist/recall-state.d.ts +1 -1
  147. package/dist/recall-tag-filter.d.ts +1 -1
  148. package/dist/recall-xray-cli.d.ts +1 -1
  149. package/dist/recall-xray-cli.js +4 -4
  150. package/dist/recall-xray-renderer.d.ts +1 -1
  151. package/dist/recall-xray-renderer.js +3 -3
  152. package/dist/recall-xray.d.ts +1 -1
  153. package/dist/recall-xray.js +2 -2
  154. package/dist/resolve-auth-token.d.ts +1 -1
  155. package/dist/resume-bundles.js +3 -2
  156. package/dist/retrieval-agents.d.ts +1 -1
  157. package/dist/retrieval-tiers.d.ts +1 -1
  158. package/dist/routing/engine.d.ts +1 -1
  159. package/dist/routing/store.d.ts +1 -1
  160. package/dist/schemas.d.ts +32 -32
  161. package/dist/search/embed-helper.d.ts +1 -1
  162. package/dist/search/factory.d.ts +1 -1
  163. package/dist/search/index.d.ts +1 -1
  164. package/dist/search/lancedb-backend.d.ts +1 -1
  165. package/dist/search/meilisearch-backend.d.ts +1 -1
  166. package/dist/search/noop-backend.d.ts +1 -1
  167. package/dist/search/orama-backend.d.ts +1 -1
  168. package/dist/search/port.d.ts +1 -1
  169. package/dist/search/remote-backend.d.ts +1 -1
  170. package/dist/{semantic-SLAa_prH.d.ts → semantic-DJR8_DMQ.d.ts} +1 -1
  171. package/dist/{semantic-consolidation-4HkHWgeI.d.ts → semantic-consolidation-FbhPeJjB.d.ts} +1 -1
  172. package/dist/semantic-consolidation.d.ts +2 -2
  173. package/dist/semantic-consolidation.js +4 -4
  174. package/dist/semantic-rule-promotion.js +3 -3
  175. package/dist/semantic-rule-verifier.d.ts +1 -1
  176. package/dist/semantic-rule-verifier.js +3 -3
  177. package/dist/session-observer-bands.d.ts +1 -1
  178. package/dist/session-observer-state.d.ts +1 -1
  179. package/dist/shared-context/manager.d.ts +5 -5
  180. package/dist/signal.d.ts +1 -1
  181. package/dist/storage.d.ts +19 -1
  182. package/dist/storage.js +2 -2
  183. package/dist/summarizer.d.ts +1 -1
  184. package/dist/summary-snapshot.d.ts +1 -1
  185. package/dist/temporal-supersession.d.ts +1 -1
  186. package/dist/temporal-validity.d.ts +1 -1
  187. package/dist/threading.d.ts +1 -1
  188. package/dist/tier-migration.d.ts +1 -1
  189. package/dist/tier-routing.d.ts +1 -1
  190. package/dist/topics.d.ts +1 -1
  191. package/dist/transcript.d.ts +1 -1
  192. package/dist/transfer/types.d.ts +12 -12
  193. package/dist/types-D5VRAI04.d.ts +3134 -0
  194. package/dist/types.d.ts +3 -2862
  195. package/dist/types.js +1 -1
  196. package/dist/utility-runtime.d.ts +1 -1
  197. package/dist/verified-recall.js +3 -3
  198. package/package.json +1 -1
  199. package/src/access-http.ts +182 -8
  200. package/src/access-mcp.ts +198 -0
  201. package/src/access-service.ts +65 -0
  202. package/src/cli.ts +187 -0
  203. package/src/config.ts +7 -0
  204. package/src/index.ts +7 -0
  205. package/src/orchestrator.ts +42 -0
  206. package/src/storage.ts +106 -0
  207. package/src/types.ts +5 -0
  208. package/src/wearables/cleanup.test.ts +134 -0
  209. package/src/wearables/cleanup.ts +188 -0
  210. package/src/wearables/cli.test.ts +170 -0
  211. package/src/wearables/cli.ts +441 -0
  212. package/src/wearables/config.test.ts +143 -0
  213. package/src/wearables/config.ts +332 -0
  214. package/src/wearables/corrections.test.ts +118 -0
  215. package/src/wearables/corrections.ts +211 -0
  216. package/src/wearables/day-store.test.ts +143 -0
  217. package/src/wearables/day-store.ts +238 -0
  218. package/src/wearables/errors.test.ts +32 -0
  219. package/src/wearables/errors.ts +29 -0
  220. package/src/wearables/index.ts +114 -0
  221. package/src/wearables/memory-gen.test.ts +342 -0
  222. package/src/wearables/memory-gen.ts +413 -0
  223. package/src/wearables/pipeline.test.ts +608 -0
  224. package/src/wearables/pipeline.ts +519 -0
  225. package/src/wearables/redaction.test.ts +94 -0
  226. package/src/wearables/redaction.ts +156 -0
  227. package/src/wearables/registry.test.ts +62 -0
  228. package/src/wearables/registry.ts +133 -0
  229. package/src/wearables/service.test.ts +425 -0
  230. package/src/wearables/service.ts +691 -0
  231. package/src/wearables/speakers.test.ts +110 -0
  232. package/src/wearables/speakers.ts +174 -0
  233. package/src/wearables/storage-io.test.ts +105 -0
  234. package/src/wearables/sync-state.test.ts +134 -0
  235. package/src/wearables/sync-state.ts +186 -0
  236. package/src/wearables/types.ts +285 -0
  237. package/dist/chunk-4R4KTDIE.js.map +0 -1
  238. package/dist/chunk-5GOMXHLC.js.map +0 -1
  239. package/dist/chunk-C4PZTWTG.js.map +0 -1
  240. package/dist/chunk-I6UCUHLK.js.map +0 -1
  241. package/dist/chunk-KQFQ3IS5.js.map +0 -1
  242. package/dist/chunk-PJGB7XRR.js.map +0 -1
  243. package/dist/chunk-RKW6QR7W.js.map +0 -1
  244. package/dist/chunk-ZJSZNTEI.js.map +0 -1
  245. /package/dist/{chunk-GYTVOLNX.js.map → chunk-3MNBW7R7.js.map} +0 -0
  246. /package/dist/{chunk-QFQQFX2H.js.map → chunk-3R2UZV3U.js.map} +0 -0
  247. /package/dist/{chunk-O4UNM6OR.js.map → chunk-532VCWYW.js.map} +0 -0
  248. /package/dist/{chunk-2UFQYU5F.js.map → chunk-57QXN2CS.js.map} +0 -0
  249. /package/dist/{chunk-UGEBPVNI.js.map → chunk-GE7Q7KXP.js.map} +0 -0
  250. /package/dist/{chunk-GLWW3EJQ.js.map → chunk-KB4MFBF5.js.map} +0 -0
  251. /package/dist/{chunk-FH3PPO42.js.map → chunk-KVFYTRMV.js.map} +0 -0
  252. /package/dist/{chunk-BNW5NJJH.js.map → chunk-LQYTQCXM.js.map} +0 -0
  253. /package/dist/{chunk-AYHXQR53.js.map → chunk-MVQN73GT.js.map} +0 -0
  254. /package/dist/{chunk-ZZPIJPPD.js.map → chunk-N5RGXWLQ.js.map} +0 -0
  255. /package/dist/{chunk-R3OQGYOU.js.map → chunk-P2D2MM47.js.map} +0 -0
  256. /package/dist/{chunk-PSUB67YB.js.map → chunk-PW6GURU3.js.map} +0 -0
  257. /package/dist/{chunk-W3BKVM64.js.map → chunk-QDV6VAD4.js.map} +0 -0
  258. /package/dist/{chunk-3QSU4NFF.js.map → chunk-QHXW3LZV.js.map} +0 -0
  259. /package/dist/{chunk-OZXVGYGZ.js.map → chunk-STDAAGH7.js.map} +0 -0
  260. /package/dist/{chunk-FMGWXIES.js.map → chunk-TZDSNIRO.js.map} +0 -0
  261. /package/dist/{chunk-2L54V4ZO.js.map → chunk-UELS6WWF.js.map} +0 -0
  262. /package/dist/{chunk-BPSGLMQ4.js.map → chunk-YQNADJCT.js.map} +0 -0
@@ -0,0 +1,441 @@
1
+ /**
2
+ * Wearables CLI runner — one implementation shared by every CLI host
3
+ * (`remnic wearables ...` and `openclaw engram wearables ...`), so the
4
+ * surfaces never fork (same rule as the recall-explain renderers).
5
+ *
6
+ * Flag validation is strict: every value-taking flag requires a value,
7
+ * unknown flags error with the valid list, and invalid values reject
8
+ * loudly (CLAUDE.md rules 14 + 51).
9
+ */
10
+
11
+ import { WearablesInputError } from "./errors.js";
12
+ import type { WearablesService } from "./service.js";
13
+ import type { WearableSyncSummary } from "./types.js";
14
+
15
+ export interface WearablesCliIo {
16
+ stdout: { write(chunk: string): unknown };
17
+ stderr: { write(chunk: string): unknown };
18
+ }
19
+
20
+ const USAGE = `Usage: wearables <command> [options]
21
+
22
+ Commands:
23
+ status Show configured sources, connectors, last sync
24
+ check <source> Verify credentials/connectivity for a source
25
+ sync [options] Pull + clean + store transcripts (and memories)
26
+ --source <id> Only this source (default: all enabled)
27
+ --date <YYYY-MM-DD> Exactly this day
28
+ --days <n> Lookback window ending today (default 2)
29
+ --force-memories Re-extract memories for unchanged days
30
+ transcript --date <YYYY-MM-DD> [--source <id>]
31
+ Print the stored day transcript(s)
32
+ search <query> [options] Search stored transcripts
33
+ --source <id> --from <date> --to <date> --limit <n>
34
+ memories [options] List memories created from transcripts
35
+ --source <id> --date <date> --limit <n>
36
+ speakers list Show the speaker registry
37
+ speakers self <name> Set the wearer's display name
38
+ speakers set <source> <key> <name> [--self]
39
+ Map a provider speaker label to a name
40
+ speakers remove <source> <key> Remove a speaker mapping
41
+ corrections list Show correction rules (config + state)
42
+ corrections add <match> <replace> [--regex] [--case-sensitive] [--source <id>]
43
+ Add a transcript correction rule
44
+ corrections remove <index> Remove a state correction rule by index
45
+
46
+ Add --json to status/sync/search/memories for machine-readable output.
47
+ `;
48
+
49
+ interface ParsedFlags {
50
+ flags: Map<string, string | true>;
51
+ positional: string[];
52
+ }
53
+
54
+ /** Flags that take a value (everything else is boolean). */
55
+ const VALUE_FLAGS = new Set([
56
+ "--source",
57
+ "--date",
58
+ "--days",
59
+ "--from",
60
+ "--to",
61
+ "--limit",
62
+ ]);
63
+ const BOOLEAN_FLAGS = new Set([
64
+ "--json",
65
+ "--force-memories",
66
+ "--regex",
67
+ "--case-sensitive",
68
+ "--self",
69
+ ]);
70
+
71
+ function parseFlags(args: string[]): ParsedFlags {
72
+ const flags = new Map<string, string | true>();
73
+ const positional: string[] = [];
74
+ for (let index = 0; index < args.length; index++) {
75
+ const arg = args[index];
76
+ if (!arg.startsWith("--")) {
77
+ positional.push(arg);
78
+ continue;
79
+ }
80
+ if (BOOLEAN_FLAGS.has(arg)) {
81
+ flags.set(arg, true);
82
+ continue;
83
+ }
84
+ if (VALUE_FLAGS.has(arg)) {
85
+ const value = args[index + 1];
86
+ if (value === undefined || value.startsWith("--")) {
87
+ throw new WearablesInputError(`${arg} requires a value`);
88
+ }
89
+ flags.set(arg, value);
90
+ index++;
91
+ continue;
92
+ }
93
+ throw new WearablesInputError(
94
+ `unknown flag '${arg}' — valid flags: ${[...VALUE_FLAGS, ...BOOLEAN_FLAGS].join(", ")}`,
95
+ );
96
+ }
97
+ return { flags, positional };
98
+ }
99
+
100
+ function flagString(parsed: ParsedFlags, name: string): string | undefined {
101
+ const value = parsed.flags.get(name);
102
+ return typeof value === "string" ? value : undefined;
103
+ }
104
+
105
+ function flagInt(parsed: ParsedFlags, name: string): number | undefined {
106
+ const value = flagString(parsed, name);
107
+ if (value === undefined) return undefined;
108
+ const parsedValue = Number(value);
109
+ if (
110
+ !Number.isFinite(parsedValue) ||
111
+ !Number.isInteger(parsedValue) ||
112
+ parsedValue < 1
113
+ ) {
114
+ throw new WearablesInputError(`${name} expects a positive integer (got '${value}')`);
115
+ }
116
+ return parsedValue;
117
+ }
118
+
119
+ function renderSyncSummary(summary: WearableSyncSummary): string {
120
+ const lines = [
121
+ `${summary.source}: ${summary.conversations} conversation${summary.conversations === 1 ? "" : "s"} across ${summary.days.length} day${summary.days.length === 1 ? "" : "s"} (${summary.days.join(", ")})`,
122
+ ` segments kept/dropped: ${summary.segmentsKept}/${summary.segmentsDropped}`,
123
+ ` redactions applied: ${summary.redactions}`,
124
+ ` corrections applied: ${summary.correctionsApplied}`,
125
+ ` transcripts written: ${summary.transcriptsWritten.length > 0 ? summary.transcriptsWritten.join(", ") : "(none — unchanged)"}`,
126
+ ` memories created: ${summary.memoriesCreated} (skipped ${summary.memoriesSkipped})`,
127
+ ];
128
+ if (summary.nativeMemoriesImported > 0) {
129
+ lines.push(` native memories queued: ${summary.nativeMemoriesImported}`);
130
+ }
131
+ for (const warning of summary.warnings) {
132
+ lines.push(` warning: ${warning}`);
133
+ }
134
+ return lines.join("\n");
135
+ }
136
+
137
+ /**
138
+ * Run a wearables CLI command. Returns a process exit code; all output
139
+ * goes through `io`.
140
+ */
141
+ export async function runWearablesCliCommand(
142
+ service: WearablesService,
143
+ args: string[],
144
+ io: WearablesCliIo,
145
+ ): Promise<number> {
146
+ const [command, ...rest] = args;
147
+ try {
148
+ switch (command) {
149
+ case undefined:
150
+ case "help":
151
+ case "--help": {
152
+ io.stdout.write(USAGE);
153
+ return command === undefined ? 1 : 0;
154
+ }
155
+ case "status": {
156
+ const parsed = parseFlags(rest);
157
+ const status = await service.status();
158
+ if (parsed.flags.has("--json")) {
159
+ io.stdout.write(`${JSON.stringify(status, null, 2)}\n`);
160
+ return 0;
161
+ }
162
+ io.stdout.write(
163
+ `Wearables: ${status.enabled ? "enabled" : "disabled"} (timezone ${status.timezone})\n`,
164
+ );
165
+ if (status.sources.length === 0) {
166
+ io.stdout.write(
167
+ "No sources configured. Add wearables.sources.<id> to the plugin config.\n",
168
+ );
169
+ return 0;
170
+ }
171
+ for (const source of status.sources) {
172
+ io.stdout.write(
173
+ ` ${source.source} (${source.displayName}): ${source.enabled ? "enabled" : "disabled"}, ` +
174
+ `connector ${source.connectorInstalled ? "installed" : `MISSING — npm install @remnic/connector-${source.source}`}, ` +
175
+ `memoryMode ${source.memoryMode}, ` +
176
+ `${source.transcriptDays} transcript day${source.transcriptDays === 1 ? "" : "s"}, ` +
177
+ `last sync ${source.lastSyncAt ?? "never"}\n`,
178
+ );
179
+ }
180
+ return 0;
181
+ }
182
+ case "check": {
183
+ const [sourceId] = rest;
184
+ if (!sourceId) {
185
+ throw new WearablesInputError("check requires a source id (e.g. wearables check limitless)");
186
+ }
187
+ const result = await service.checkAuth(sourceId);
188
+ io.stdout.write(
189
+ result.ok
190
+ ? `${sourceId}: OK${result.detail ? ` — ${result.detail}` : ""}\n`
191
+ : `${sourceId}: FAILED${result.detail ? ` — ${result.detail}` : ""}\n`,
192
+ );
193
+ return result.ok ? 0 : 1;
194
+ }
195
+ case "sync": {
196
+ const parsed = parseFlags(rest);
197
+ if (parsed.positional.length > 0) {
198
+ throw new WearablesInputError(
199
+ `unexpected argument '${parsed.positional[0]}' — sync takes flags only`,
200
+ );
201
+ }
202
+ const summaries = await service.sync({
203
+ source: flagString(parsed, "--source"),
204
+ date: flagString(parsed, "--date"),
205
+ days: flagInt(parsed, "--days"),
206
+ forceMemories: parsed.flags.has("--force-memories"),
207
+ });
208
+ if (parsed.flags.has("--json")) {
209
+ io.stdout.write(`${JSON.stringify({ summaries }, null, 2)}\n`);
210
+ return 0;
211
+ }
212
+ for (const summary of summaries) {
213
+ io.stdout.write(`${renderSyncSummary(summary)}\n`);
214
+ }
215
+ io.stdout.write("OK\n");
216
+ return 0;
217
+ }
218
+ case "transcript": {
219
+ const parsed = parseFlags(rest);
220
+ const date = flagString(parsed, "--date");
221
+ if (!date) {
222
+ throw new WearablesInputError("transcript requires --date <YYYY-MM-DD>");
223
+ }
224
+ const views = await service.dayTranscript(date, flagString(parsed, "--source"));
225
+ if (views.length === 0) {
226
+ io.stderr.write(`No stored transcripts for ${date}.\n`);
227
+ return 1;
228
+ }
229
+ for (const view of views) {
230
+ if (views.length > 1) {
231
+ io.stdout.write(`\n===== ${view.source} — ${view.date} =====\n\n`);
232
+ }
233
+ if (view.overlapsWith.length > 0) {
234
+ io.stdout.write(
235
+ `(also recorded by: ${view.overlapsWith.join(", ")})\n\n`,
236
+ );
237
+ }
238
+ io.stdout.write(`${view.body}\n`);
239
+ }
240
+ return 0;
241
+ }
242
+ case "search": {
243
+ const parsed = parseFlags(rest);
244
+ const query = parsed.positional.join(" ").trim();
245
+ if (query.length === 0) {
246
+ throw new WearablesInputError("search requires a query");
247
+ }
248
+ const results = await service.searchTranscripts(query, {
249
+ source: flagString(parsed, "--source"),
250
+ from: flagString(parsed, "--from"),
251
+ to: flagString(parsed, "--to"),
252
+ limit: flagInt(parsed, "--limit"),
253
+ });
254
+ if (parsed.flags.has("--json")) {
255
+ io.stdout.write(`${JSON.stringify({ results }, null, 2)}\n`);
256
+ return 0;
257
+ }
258
+ if (results.length === 0) {
259
+ io.stdout.write("No matches.\n");
260
+ return 0;
261
+ }
262
+ for (const result of results) {
263
+ io.stdout.write(
264
+ `${result.source} ${result.date} ${result.snippet}\n`,
265
+ );
266
+ }
267
+ if (results.some((result) => result.backend === "scan")) {
268
+ io.stdout.write(
269
+ "(search index unavailable — results from bounded text scan)\n",
270
+ );
271
+ }
272
+ return 0;
273
+ }
274
+ case "memories": {
275
+ const parsed = parseFlags(rest);
276
+ const memories = await service.transcriptMemories({
277
+ source: flagString(parsed, "--source"),
278
+ date: flagString(parsed, "--date"),
279
+ limit: flagInt(parsed, "--limit"),
280
+ });
281
+ if (parsed.flags.has("--json")) {
282
+ io.stdout.write(`${JSON.stringify({ memories }, null, 2)}\n`);
283
+ return 0;
284
+ }
285
+ if (memories.length === 0) {
286
+ io.stdout.write("No wearable-derived memories found.\n");
287
+ return 0;
288
+ }
289
+ for (const memory of memories) {
290
+ const status = memory.status === "pending_review" ? " [pending review]" : "";
291
+ io.stdout.write(
292
+ `${memory.id} (${memory.source}${memory.date ? ` ${memory.date}` : ""})${status}\n ${memory.content.split("\n")[0]}\n`,
293
+ );
294
+ }
295
+ return 0;
296
+ }
297
+ case "speakers": {
298
+ const [action, ...speakerArgs] = rest;
299
+ if (action === "list" || action === undefined) {
300
+ const registry = await service.listSpeakers();
301
+ io.stdout.write(`Self: ${registry.selfName}\n`);
302
+ const entries = Object.entries(registry.speakers);
303
+ if (entries.length === 0) {
304
+ io.stdout.write("No speaker overrides stored.\n");
305
+ return 0;
306
+ }
307
+ for (const [key, override] of entries) {
308
+ io.stdout.write(
309
+ ` ${key} -> ${override.name}${override.isSelf ? " (you)" : ""}\n`,
310
+ );
311
+ }
312
+ return 0;
313
+ }
314
+ if (action === "self") {
315
+ const name = speakerArgs.join(" ").trim();
316
+ if (name.length === 0) {
317
+ throw new WearablesInputError("speakers self requires a name");
318
+ }
319
+ await service.setSelfName(name);
320
+ io.stdout.write(`Self name set to '${name}'.\n`);
321
+ return 0;
322
+ }
323
+ if (action === "set") {
324
+ const parsed = parseFlags(speakerArgs);
325
+ const [sourceId, speakerKey, ...nameParts] = parsed.positional;
326
+ const name = nameParts.join(" ").trim();
327
+ if (!sourceId || !speakerKey || name.length === 0) {
328
+ throw new WearablesInputError(
329
+ "speakers set requires: <source> <speakerKey> <name>",
330
+ );
331
+ }
332
+ await service.setSpeaker(sourceId, speakerKey, name, {
333
+ isSelf: parsed.flags.has("--self"),
334
+ });
335
+ io.stdout.write(`Mapped ${sourceId}:${speakerKey} -> ${name}.\n`);
336
+ io.stdout.write(
337
+ "(re-run `wearables sync --force-memories` to rebuild stored transcripts with the new label)\n",
338
+ );
339
+ return 0;
340
+ }
341
+ if (action === "remove") {
342
+ const [sourceId, speakerKey] = speakerArgs;
343
+ if (!sourceId || !speakerKey) {
344
+ throw new WearablesInputError("speakers remove requires: <source> <speakerKey>");
345
+ }
346
+ await service.removeSpeaker(sourceId, speakerKey);
347
+ io.stdout.write(`Removed mapping for ${sourceId}:${speakerKey}.\n`);
348
+ return 0;
349
+ }
350
+ throw new WearablesInputError(
351
+ `unknown speakers action '${action}' — expected list, self, set, or remove`,
352
+ );
353
+ }
354
+ case "corrections": {
355
+ const [action, ...correctionArgs] = rest;
356
+ if (action === "list" || action === undefined) {
357
+ const { fromConfig, fromState, stateFilePath } = await service.listCorrections();
358
+ if (fromConfig.length === 0 && fromState.length === 0) {
359
+ io.stdout.write("No correction rules configured.\n");
360
+ return 0;
361
+ }
362
+ if (fromConfig.length > 0) {
363
+ io.stdout.write("From config (wearables.corrections):\n");
364
+ fromConfig.forEach((rule, index) => {
365
+ io.stdout.write(` [config ${index}] ${formatRule(rule)}\n`);
366
+ });
367
+ }
368
+ if (fromState.length > 0) {
369
+ io.stdout.write(`From state (${stateFilePath}):\n`);
370
+ fromState.forEach((rule, index) => {
371
+ io.stdout.write(` [${index}] ${formatRule(rule)}\n`);
372
+ });
373
+ }
374
+ return 0;
375
+ }
376
+ if (action === "add") {
377
+ const parsed = parseFlags(correctionArgs);
378
+ const [match, replace] = parsed.positional;
379
+ if (match === undefined || replace === undefined) {
380
+ throw new WearablesInputError(
381
+ 'corrections add requires: <match> <replace> (quote multi-word values)',
382
+ );
383
+ }
384
+ const sourceFlag = flagString(parsed, "--source");
385
+ await service.addCorrection({
386
+ match,
387
+ replace,
388
+ ...(parsed.flags.has("--regex") ? { regex: true } : {}),
389
+ ...(parsed.flags.has("--case-sensitive") ? { caseInsensitive: false } : {}),
390
+ ...(sourceFlag !== undefined ? { sources: [sourceFlag] } : {}),
391
+ });
392
+ io.stdout.write(`Added correction: ${JSON.stringify(match)} -> ${JSON.stringify(replace)}.\n`);
393
+ return 0;
394
+ }
395
+ if (action === "remove") {
396
+ const [indexRaw] = correctionArgs;
397
+ const index = Number(indexRaw);
398
+ if (
399
+ indexRaw === undefined ||
400
+ !Number.isInteger(index) ||
401
+ index < 0
402
+ ) {
403
+ throw new WearablesInputError("corrections remove requires a non-negative index");
404
+ }
405
+ const removed = await service.removeCorrection(index);
406
+ io.stdout.write(`Removed correction ${formatRule(removed)}.\n`);
407
+ return 0;
408
+ }
409
+ throw new WearablesInputError(
410
+ `unknown corrections action '${action}' — expected list, add, or remove`,
411
+ );
412
+ }
413
+ default:
414
+ throw new WearablesInputError(
415
+ `unknown wearables command '${command}'\n\n${USAGE}`,
416
+ );
417
+ }
418
+ } catch (err) {
419
+ if (err instanceof WearablesInputError) {
420
+ io.stderr.write(`wearables: ${err.message}\n`);
421
+ return 1;
422
+ }
423
+ throw err;
424
+ }
425
+ }
426
+
427
+ function formatRule(rule: {
428
+ match: string;
429
+ replace: string;
430
+ regex?: boolean;
431
+ caseInsensitive?: boolean;
432
+ sources?: string[];
433
+ }): string {
434
+ const parts = [`${JSON.stringify(rule.match)} -> ${JSON.stringify(rule.replace)}`];
435
+ if (rule.regex === true) parts.push("(regex)");
436
+ if (rule.caseInsensitive === false) parts.push("(case-sensitive)");
437
+ if (rule.sources && rule.sources.length > 0) {
438
+ parts.push(`(sources: ${rule.sources.join(", ")})`);
439
+ }
440
+ return parts.join(" ");
441
+ }
@@ -0,0 +1,143 @@
1
+ import assert from "node:assert/strict";
2
+ import { test } from "node:test";
3
+
4
+ import {
5
+ defaultWearablesConfig,
6
+ parseWearablesConfig,
7
+ } from "./config.js";
8
+
9
+ test("undefined yields the disabled default config", () => {
10
+ const parsed = parseWearablesConfig(undefined);
11
+ assert.deepEqual(parsed, defaultWearablesConfig());
12
+ assert.equal(parsed.enabled, false);
13
+ assert.equal(parsed.redactionEnabled, true);
14
+ });
15
+
16
+ test("non-object shapes are rejected loudly", () => {
17
+ assert.throws(() => parseWearablesConfig(false), /must be an object/);
18
+ assert.throws(() => parseWearablesConfig(null), /must be an object/);
19
+ assert.throws(() => parseWearablesConfig([]), /must be an object/);
20
+ });
21
+
22
+ test("boolean-ish strings coerce; garbage booleans throw", () => {
23
+ assert.equal(parseWearablesConfig({ enabled: "true" }).enabled, true);
24
+ assert.equal(parseWearablesConfig({ enabled: "off" }).enabled, false);
25
+ assert.throws(() => parseWearablesConfig({ enabled: "fales" }), /wearables.enabled/);
26
+ });
27
+
28
+ test("source settings default to the least-privileged memory mode", () => {
29
+ const parsed = parseWearablesConfig({
30
+ enabled: true,
31
+ sources: { limitless: { enabled: true } },
32
+ });
33
+ const source = parsed.sources.limitless;
34
+ assert.equal(source.memoryMode, "review");
35
+ assert.equal(source.minConfidence, 0.6);
36
+ assert.equal(source.minImportance, "low");
37
+ assert.equal(source.maxMemoriesPerDay, 20);
38
+ assert.equal(source.importNativeMemories, "off");
39
+ assert.deepEqual(source.cleanup, {
40
+ mergeSameSpeaker: true,
41
+ stripFillers: true,
42
+ collapseRepeats: true,
43
+ dropLowQuality: true,
44
+ });
45
+ });
46
+
47
+ test("invalid enum values list the valid options", () => {
48
+ assert.throws(
49
+ () =>
50
+ parseWearablesConfig({
51
+ sources: { limitless: { memoryMode: "yolo" } },
52
+ }),
53
+ /memoryMode must be one of "off", "review", "auto"/,
54
+ );
55
+ assert.throws(
56
+ () =>
57
+ parseWearablesConfig({
58
+ sources: { limitless: { minImportance: "huge" } },
59
+ }),
60
+ /minImportance/,
61
+ );
62
+ });
63
+
64
+ test("maxMemoriesPerDay honors the documented 0-disables value and bounds", () => {
65
+ const parsed = parseWearablesConfig({
66
+ sources: { limitless: { maxMemoriesPerDay: 0 } },
67
+ });
68
+ assert.equal(parsed.sources.limitless.maxMemoriesPerDay, 0);
69
+ // Over-ceiling values reject instead of silently clamping to 500.
70
+ assert.throws(
71
+ () => parseWearablesConfig({ sources: { limitless: { maxMemoriesPerDay: 99999 } } }),
72
+ /maxMemoriesPerDay must be an integer between 0 and 500/,
73
+ );
74
+ assert.equal(
75
+ parseWearablesConfig({ sources: { limitless: { maxMemoriesPerDay: 500 } } })
76
+ .sources.limitless.maxMemoriesPerDay,
77
+ 500,
78
+ );
79
+ assert.throws(
80
+ () => parseWearablesConfig({ sources: { limitless: { maxMemoriesPerDay: "lots" } } }),
81
+ /maxMemoriesPerDay/,
82
+ );
83
+ // Fractional values must reject, not floor — 0.5 flooring to 0 would
84
+ // silently disable the cap.
85
+ assert.throws(
86
+ () => parseWearablesConfig({ sources: { limitless: { maxMemoriesPerDay: 0.5 } } }),
87
+ /maxMemoriesPerDay/,
88
+ );
89
+ assert.throws(
90
+ () => parseWearablesConfig({ sources: { limitless: { maxMemoriesPerDay: -3 } } }),
91
+ /maxMemoriesPerDay/,
92
+ );
93
+ });
94
+
95
+ test("source ids are validated against the path-safe pattern", () => {
96
+ assert.throws(
97
+ () => parseWearablesConfig({ sources: { "Bad Source!": {} } }),
98
+ /lowercase source ids/,
99
+ );
100
+ // Custom (non-built-in) ids are allowed — third-party connectors.
101
+ const parsed = parseWearablesConfig({ sources: { "my-recorder": {} } });
102
+ assert.ok(parsed.sources["my-recorder"]);
103
+ });
104
+
105
+ test("timezone is validated as a real IANA identifier", () => {
106
+ assert.equal(
107
+ parseWearablesConfig({ timezone: "America/Chicago" }).timezone,
108
+ "America/Chicago",
109
+ );
110
+ assert.throws(() => parseWearablesConfig({ timezone: "Mars/Olympus" }), /IANA/);
111
+ });
112
+
113
+ test("correction rules and redaction patterns are compiled at parse time", () => {
114
+ assert.throws(
115
+ () => parseWearablesConfig({ corrections: [{ match: "(", replace: "x", regex: true }] }),
116
+ /not a valid regular expression/,
117
+ );
118
+ assert.throws(
119
+ () => parseWearablesConfig({ redactionPatterns: ["("] }),
120
+ /redactionPatterns/,
121
+ );
122
+ const parsed = parseWearablesConfig({
123
+ corrections: [{ match: "remnick", replace: "Remnic", sources: ["limitless"] }],
124
+ redactionPatterns: ["internal-codename-\\w+"],
125
+ });
126
+ assert.equal(parsed.corrections.length, 1);
127
+ assert.equal(parsed.redactionPatterns.length, 1);
128
+ });
129
+
130
+ test("minConfidence rejects out-of-range values instead of clamping", () => {
131
+ assert.throws(
132
+ () => parseWearablesConfig({ sources: { bee: { minConfidence: 7 } } }),
133
+ /minConfidence must be a number between 0 and 1/,
134
+ );
135
+ assert.throws(
136
+ () => parseWearablesConfig({ sources: { bee: { minConfidence: -1 } } }),
137
+ /minConfidence/,
138
+ );
139
+ assert.equal(
140
+ parseWearablesConfig({ sources: { bee: { minConfidence: 0.85 } } }).sources.bee.minConfidence,
141
+ 0.85,
142
+ );
143
+ });