clawvault 3.1.0 → 3.2.1

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 (273) hide show
  1. package/README.md +422 -141
  2. package/bin/clawvault.js +10 -2
  3. package/bin/command-registration.test.js +3 -1
  4. package/bin/command-runtime.js +9 -1
  5. package/bin/register-core-commands.js +23 -28
  6. package/bin/register-maintenance-commands.js +39 -3
  7. package/bin/register-query-commands.js +58 -29
  8. package/bin/register-tailscale-commands.js +106 -0
  9. package/bin/register-task-commands.js +18 -1
  10. package/bin/register-task-commands.test.js +16 -0
  11. package/bin/register-vault-operations-commands.js +29 -1
  12. package/bin/register-workgraph-commands.js +451 -0
  13. package/dashboard/lib/graph-diff.js +104 -0
  14. package/dashboard/lib/graph-diff.test.js +75 -0
  15. package/dashboard/lib/vault-parser.js +556 -0
  16. package/dashboard/lib/vault-parser.test.js +254 -0
  17. package/dashboard/public/app.js +796 -0
  18. package/dashboard/public/index.html +52 -0
  19. package/dashboard/public/styles.css +221 -0
  20. package/dashboard/server.js +374 -0
  21. package/dist/{chunk-C7OK5WKP.js → chunk-2JQ3O2YL.js} +4 -4
  22. package/dist/{chunk-VR5NE7PZ.js → chunk-2RAZ4ZFE.js} +1 -1
  23. package/dist/{chunk-F2JEUD4J.js → chunk-4ITRXIVT.js} +5 -7
  24. package/dist/{chunk-GUKMRGM7.js → chunk-4OXMU5S2.js} +1 -1
  25. package/dist/chunk-5PJ4STIC.js +465 -0
  26. package/dist/{chunk-62YTUT6J.js → chunk-AZYOKJYC.js} +2 -2
  27. package/dist/chunk-BSJ6RIT7.js +447 -0
  28. package/dist/chunk-ECRZL5XR.js +50 -0
  29. package/dist/chunk-ERNE2FZ5.js +189 -0
  30. package/dist/{chunk-WAZ3NLWL.js → chunk-F55HGNU4.js} +0 -47
  31. package/dist/{chunk-VGLOTGAS.js → chunk-FAKNOB7Y.js} +2 -2
  32. package/dist/{chunk-QK3UCXWL.js → chunk-FHFUXL6G.js} +2 -2
  33. package/dist/chunk-GNJL4YGR.js +79 -0
  34. package/dist/chunk-HR4KN6S2.js +152 -0
  35. package/dist/{chunk-OZ7RIXTO.js → chunk-IIOU45CK.js} +1 -1
  36. package/dist/chunk-IJBFGPCS.js +33 -0
  37. package/dist/chunk-IVRIKYFE.js +520 -0
  38. package/dist/chunk-K7PNYS45.js +93 -0
  39. package/dist/chunk-MDIH26GC.js +183 -0
  40. package/dist/{chunk-LYHGEHXG.js → chunk-MFAWT5O5.js} +0 -1
  41. package/dist/{chunk-H34S76MB.js → chunk-MNPUYCHQ.js} +6 -6
  42. package/dist/chunk-NTOPJI7W.js +207 -0
  43. package/dist/{chunk-QBLMXKF2.js → chunk-OIWVQYQF.js} +1 -1
  44. package/dist/chunk-PG56HX5T.js +154 -0
  45. package/dist/{chunk-LNJA2UGL.js → chunk-PI4WMLMG.js} +7 -84
  46. package/dist/chunk-QMHPQYUV.js +363 -0
  47. package/dist/{chunk-H62BP7RI.js → chunk-QPDDIHXE.js} +209 -43
  48. package/dist/{chunk-N2AXRYLC.js → chunk-QWQ3TIKS.js} +1 -1
  49. package/dist/{chunk-3DHXQHYG.js → chunk-R2MIW5G7.js} +1 -1
  50. package/dist/{chunk-SJSFRIYS.js → chunk-S5OJEGFG.js} +2 -2
  51. package/dist/chunk-SS4B7P7V.js +99 -0
  52. package/dist/chunk-TIGW564L.js +628 -0
  53. package/dist/chunk-U67V476Y.js +35 -0
  54. package/dist/{chunk-JY6FYXIT.js → chunk-UCQAOZHW.js} +6 -11
  55. package/dist/{chunk-ITPEXLHA.js → chunk-URXDAUVH.js} +24 -5
  56. package/dist/chunk-WIOLLGAD.js +190 -0
  57. package/dist/{chunk-3WRJEKN4.js → chunk-WJVWINEM.js} +72 -8
  58. package/dist/chunk-WMGIIABP.js +15 -0
  59. package/dist/{chunk-33UGEQRT.js → chunk-X3SPPUFG.js} +151 -64
  60. package/dist/{chunk-3NSBOUT3.js → chunk-Y3TIJEBP.js} +314 -79
  61. package/dist/chunk-Y6VJKXGL.js +373 -0
  62. package/dist/{chunk-LI4O6NVK.js → chunk-YDWHS4LJ.js} +49 -9
  63. package/dist/{chunk-U55BGUAU.js → chunk-YNIPYN4F.js} +5 -5
  64. package/dist/chunk-YXQCA6B7.js +226 -0
  65. package/dist/cli/index.js +26 -22
  66. package/dist/commands/archive.js +3 -3
  67. package/dist/commands/backlog.js +3 -3
  68. package/dist/commands/blocked.js +3 -3
  69. package/dist/commands/canvas.d.ts +15 -0
  70. package/dist/commands/canvas.js +200 -0
  71. package/dist/commands/checkpoint.js +2 -2
  72. package/dist/commands/compat.js +2 -2
  73. package/dist/commands/context.js +7 -5
  74. package/dist/commands/doctor.d.ts +11 -7
  75. package/dist/commands/doctor.js +16 -14
  76. package/dist/commands/embed.js +5 -6
  77. package/dist/commands/entities.js +2 -2
  78. package/dist/commands/graph.js +3 -3
  79. package/dist/commands/inject.d.ts +1 -1
  80. package/dist/commands/inject.js +4 -5
  81. package/dist/commands/kanban.js +4 -4
  82. package/dist/commands/link.js +2 -2
  83. package/dist/commands/migrate-observations.js +4 -4
  84. package/dist/commands/observe.d.ts +0 -1
  85. package/dist/commands/observe.js +13 -12
  86. package/dist/commands/project.js +5 -5
  87. package/dist/commands/rebuild-embeddings.d.ts +21 -0
  88. package/dist/commands/rebuild-embeddings.js +91 -0
  89. package/dist/commands/rebuild.js +12 -11
  90. package/dist/commands/recover.js +3 -3
  91. package/dist/commands/reflect.js +6 -7
  92. package/dist/commands/repair-session.js +1 -1
  93. package/dist/commands/replay.js +14 -14
  94. package/dist/commands/session-recap.js +1 -1
  95. package/dist/commands/setup.d.ts +2 -89
  96. package/dist/commands/setup.js +3 -21
  97. package/dist/commands/shell-init.js +1 -1
  98. package/dist/commands/sleep.d.ts +1 -1
  99. package/dist/commands/sleep.js +18 -17
  100. package/dist/commands/status.d.ts +2 -0
  101. package/dist/commands/status.js +40 -30
  102. package/dist/commands/sync-bd.d.ts +10 -0
  103. package/dist/commands/sync-bd.js +10 -0
  104. package/dist/commands/tailscale.d.ts +52 -0
  105. package/dist/commands/tailscale.js +26 -0
  106. package/dist/commands/task.js +4 -4
  107. package/dist/commands/template.js +2 -2
  108. package/dist/commands/wake.d.ts +1 -1
  109. package/dist/commands/wake.js +11 -10
  110. package/dist/index.d.ts +334 -191
  111. package/dist/index.js +432 -108
  112. package/dist/{inject-Bzi5E-By.d.ts → inject-DYUrDqQO.d.ts} +3 -3
  113. package/dist/ledger-B7g7jhqG.d.ts +44 -0
  114. package/dist/lib/auto-linker.js +1 -1
  115. package/dist/lib/canvas-layout.d.ts +115 -0
  116. package/dist/lib/canvas-layout.js +35 -0
  117. package/dist/lib/config.d.ts +27 -3
  118. package/dist/lib/config.js +4 -2
  119. package/dist/lib/entity-index.js +1 -1
  120. package/dist/lib/project-utils.js +4 -4
  121. package/dist/lib/session-repair.js +1 -1
  122. package/dist/lib/session-utils.js +1 -1
  123. package/dist/lib/tailscale.d.ts +225 -0
  124. package/dist/lib/tailscale.js +50 -0
  125. package/dist/lib/task-utils.js +3 -3
  126. package/dist/lib/template-engine.js +1 -1
  127. package/dist/lib/webdav.d.ts +109 -0
  128. package/dist/lib/webdav.js +35 -0
  129. package/dist/plugin/index.d.ts +344 -28
  130. package/dist/plugin/index.js +3919 -227
  131. package/dist/registry-BR4326o0.d.ts +30 -0
  132. package/dist/store-CA-6sKCJ.d.ts +34 -0
  133. package/dist/thread-B9LhXNU0.d.ts +41 -0
  134. package/dist/{types-Y2_Um2Ls.d.ts → types-BbWJoC1c.d.ts} +1 -44
  135. package/dist/workgraph/index.d.ts +5 -0
  136. package/dist/workgraph/index.js +23 -0
  137. package/dist/workgraph/ledger.d.ts +2 -0
  138. package/dist/workgraph/ledger.js +25 -0
  139. package/dist/workgraph/registry.d.ts +2 -0
  140. package/dist/workgraph/registry.js +19 -0
  141. package/dist/workgraph/store.d.ts +2 -0
  142. package/dist/workgraph/store.js +25 -0
  143. package/dist/workgraph/thread.d.ts +2 -0
  144. package/dist/workgraph/thread.js +25 -0
  145. package/dist/workgraph/types.d.ts +54 -0
  146. package/dist/workgraph/types.js +7 -0
  147. package/hooks/clawvault/HOOK.md +113 -0
  148. package/hooks/clawvault/handler.js +1559 -0
  149. package/hooks/clawvault/handler.test.js +510 -0
  150. package/hooks/clawvault/openclaw.plugin.json +72 -0
  151. package/openclaw.plugin.json +235 -30
  152. package/package.json +20 -20
  153. package/dist/chunk-3RG5ZIWI.js +0 -10
  154. package/dist/chunk-3ZIH425O.js +0 -871
  155. package/dist/chunk-6U6MK36V.js +0 -205
  156. package/dist/chunk-CMB7UL7C.js +0 -327
  157. package/dist/chunk-D2H45LON.js +0 -1074
  158. package/dist/chunk-E7MFQB6D.js +0 -163
  159. package/dist/chunk-GQSLDZTS.js +0 -560
  160. package/dist/chunk-MFM6K7PU.js +0 -374
  161. package/dist/chunk-MXSSG3QU.js +0 -42
  162. package/dist/chunk-OCGVIN3L.js +0 -88
  163. package/dist/chunk-PAH27GSN.js +0 -108
  164. package/dist/chunk-YCUNCH2I.js +0 -78
  165. package/dist/cli/index.cjs +0 -8584
  166. package/dist/cli/index.d.cts +0 -5
  167. package/dist/commands/archive.cjs +0 -287
  168. package/dist/commands/archive.d.cts +0 -11
  169. package/dist/commands/backlog.cjs +0 -721
  170. package/dist/commands/backlog.d.cts +0 -53
  171. package/dist/commands/blocked.cjs +0 -204
  172. package/dist/commands/blocked.d.cts +0 -26
  173. package/dist/commands/checkpoint.cjs +0 -244
  174. package/dist/commands/checkpoint.d.cts +0 -41
  175. package/dist/commands/compat.cjs +0 -294
  176. package/dist/commands/compat.d.cts +0 -28
  177. package/dist/commands/context.cjs +0 -2990
  178. package/dist/commands/context.d.cts +0 -2
  179. package/dist/commands/doctor.cjs +0 -2986
  180. package/dist/commands/doctor.d.cts +0 -21
  181. package/dist/commands/embed.cjs +0 -232
  182. package/dist/commands/embed.d.cts +0 -17
  183. package/dist/commands/entities.cjs +0 -141
  184. package/dist/commands/entities.d.cts +0 -7
  185. package/dist/commands/graph.cjs +0 -501
  186. package/dist/commands/graph.d.cts +0 -21
  187. package/dist/commands/inject.cjs +0 -1636
  188. package/dist/commands/inject.d.cts +0 -2
  189. package/dist/commands/kanban.cjs +0 -884
  190. package/dist/commands/kanban.d.cts +0 -63
  191. package/dist/commands/link.cjs +0 -965
  192. package/dist/commands/link.d.cts +0 -11
  193. package/dist/commands/migrate-observations.cjs +0 -362
  194. package/dist/commands/migrate-observations.d.cts +0 -19
  195. package/dist/commands/observe.cjs +0 -4099
  196. package/dist/commands/observe.d.cts +0 -23
  197. package/dist/commands/project.cjs +0 -1341
  198. package/dist/commands/project.d.cts +0 -85
  199. package/dist/commands/rebuild.cjs +0 -3136
  200. package/dist/commands/rebuild.d.cts +0 -11
  201. package/dist/commands/recover.cjs +0 -361
  202. package/dist/commands/recover.d.cts +0 -38
  203. package/dist/commands/reflect.cjs +0 -1008
  204. package/dist/commands/reflect.d.cts +0 -11
  205. package/dist/commands/repair-session.cjs +0 -457
  206. package/dist/commands/repair-session.d.cts +0 -38
  207. package/dist/commands/replay.cjs +0 -4103
  208. package/dist/commands/replay.d.cts +0 -16
  209. package/dist/commands/session-recap.cjs +0 -353
  210. package/dist/commands/session-recap.d.cts +0 -27
  211. package/dist/commands/setup.cjs +0 -1278
  212. package/dist/commands/setup.d.cts +0 -99
  213. package/dist/commands/shell-init.cjs +0 -75
  214. package/dist/commands/shell-init.d.cts +0 -7
  215. package/dist/commands/sleep.cjs +0 -6029
  216. package/dist/commands/sleep.d.cts +0 -36
  217. package/dist/commands/status.cjs +0 -2737
  218. package/dist/commands/status.d.cts +0 -52
  219. package/dist/commands/task.cjs +0 -1236
  220. package/dist/commands/task.d.cts +0 -97
  221. package/dist/commands/template.cjs +0 -457
  222. package/dist/commands/template.d.cts +0 -36
  223. package/dist/commands/wake.cjs +0 -2627
  224. package/dist/commands/wake.d.cts +0 -22
  225. package/dist/context-BUGaWpyL.d.cts +0 -46
  226. package/dist/index.cjs +0 -12373
  227. package/dist/index.d.cts +0 -854
  228. package/dist/inject-Bzi5E-By.d.cts +0 -137
  229. package/dist/lib/auto-linker.cjs +0 -176
  230. package/dist/lib/auto-linker.d.cts +0 -26
  231. package/dist/lib/config.cjs +0 -78
  232. package/dist/lib/config.d.cts +0 -11
  233. package/dist/lib/entity-index.cjs +0 -84
  234. package/dist/lib/entity-index.d.cts +0 -26
  235. package/dist/lib/project-utils.cjs +0 -864
  236. package/dist/lib/project-utils.d.cts +0 -97
  237. package/dist/lib/session-repair.cjs +0 -239
  238. package/dist/lib/session-repair.d.cts +0 -110
  239. package/dist/lib/session-utils.cjs +0 -209
  240. package/dist/lib/session-utils.d.cts +0 -63
  241. package/dist/lib/task-utils.cjs +0 -1137
  242. package/dist/lib/task-utils.d.cts +0 -208
  243. package/dist/lib/template-engine.cjs +0 -47
  244. package/dist/lib/template-engine.d.cts +0 -11
  245. package/dist/plugin/index.cjs +0 -1907
  246. package/dist/plugin/index.d.cts +0 -36
  247. package/dist/plugin/inject.cjs +0 -356
  248. package/dist/plugin/inject.d.cts +0 -54
  249. package/dist/plugin/inject.d.ts +0 -54
  250. package/dist/plugin/inject.js +0 -17
  251. package/dist/plugin/observe.cjs +0 -631
  252. package/dist/plugin/observe.d.cts +0 -39
  253. package/dist/plugin/observe.d.ts +0 -39
  254. package/dist/plugin/observe.js +0 -18
  255. package/dist/plugin/templates.cjs +0 -593
  256. package/dist/plugin/templates.d.cts +0 -52
  257. package/dist/plugin/templates.d.ts +0 -52
  258. package/dist/plugin/templates.js +0 -25
  259. package/dist/plugin/types.cjs +0 -18
  260. package/dist/plugin/types.d.cts +0 -209
  261. package/dist/plugin/types.d.ts +0 -209
  262. package/dist/plugin/types.js +0 -0
  263. package/dist/plugin/vault.cjs +0 -927
  264. package/dist/plugin/vault.d.cts +0 -68
  265. package/dist/plugin/vault.d.ts +0 -68
  266. package/dist/plugin/vault.js +0 -22
  267. package/dist/types-Y2_Um2Ls.d.cts +0 -205
  268. package/templates/memory-event.md +0 -67
  269. package/templates/party.md +0 -63
  270. package/templates/primitive-registry.yaml +0 -551
  271. package/templates/run.md +0 -68
  272. package/templates/trigger.md +0 -68
  273. package/templates/workspace.md +0 -50
@@ -1,1636 +0,0 @@
1
- "use strict";
2
- var __create = Object.create;
3
- var __defProp = Object.defineProperty;
4
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
- var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __getProtoOf = Object.getPrototypeOf;
7
- var __hasOwnProp = Object.prototype.hasOwnProperty;
8
- var __export = (target, all) => {
9
- for (var name in all)
10
- __defProp(target, name, { get: all[name], enumerable: true });
11
- };
12
- var __copyProps = (to, from, except, desc) => {
13
- if (from && typeof from === "object" || typeof from === "function") {
14
- for (let key of __getOwnPropNames(from))
15
- if (!__hasOwnProp.call(to, key) && key !== except)
16
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
- }
18
- return to;
19
- };
20
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
- // If the importer is in node compatibility mode or this is not an ESM
22
- // file that has been converted to a CommonJS file using a Babel-
23
- // compatible transform (i.e. "__esModule" has not been set), then set
24
- // "default" to the CommonJS "module.exports" for node compatibility.
25
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
- mod
27
- ));
28
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
-
30
- // src/commands/inject.ts
31
- var inject_exports = {};
32
- __export(inject_exports, {
33
- buildInjectionResult: () => buildInjectionResult,
34
- injectCommand: () => injectCommand,
35
- registerInjectCommand: () => registerInjectCommand
36
- });
37
- module.exports = __toCommonJS(inject_exports);
38
- var path4 = __toESM(require("path"), 1);
39
-
40
- // src/lib/config-manager.ts
41
- var fs = __toESM(require("fs"), 1);
42
- var path = __toESM(require("path"), 1);
43
-
44
- // src/types.ts
45
- var DEFAULT_CATEGORIES = [
46
- "rules",
47
- "preferences",
48
- "decisions",
49
- "patterns",
50
- "people",
51
- "projects",
52
- "goals",
53
- "transcripts",
54
- "inbox",
55
- "templates",
56
- "lessons",
57
- "agents",
58
- "commitments",
59
- "handoffs",
60
- "research",
61
- "tasks",
62
- "backlog"
63
- ];
64
-
65
- // src/lib/config-manager.ts
66
- var CONFIG_FILE = ".clawvault.json";
67
- var OBSERVE_PROVIDERS = ["anthropic", "openai", "gemini"];
68
- var OBSERVER_COMPRESSION_PROVIDERS = [
69
- "anthropic",
70
- "openai",
71
- "gemini",
72
- "openai-compatible",
73
- "ollama"
74
- ];
75
- var THEMES = ["neural", "minimal", "none"];
76
- var CONTEXT_PROFILES = ["default", "planning", "incident", "handoff", "auto"];
77
- var DEFAULT_THEME = "none";
78
- var DEFAULT_OBSERVE_MODEL = "gemini-2.0-flash";
79
- var DEFAULT_OBSERVE_PROVIDER = "gemini";
80
- var DEFAULT_CONTEXT_MAX_RESULTS = 5;
81
- var DEFAULT_CONTEXT_PROFILE = "default";
82
- var DEFAULT_GRAPH_MAX_HOPS = 2;
83
- var DEFAULT_INJECT_MAX_RESULTS = 8;
84
- var DEFAULT_INJECT_USE_LLM = true;
85
- var DEFAULT_INJECT_SCOPE = ["global"];
86
- function configPathFor(vaultPath) {
87
- return path.join(path.resolve(vaultPath), CONFIG_FILE);
88
- }
89
- function readConfigDocument(vaultPath) {
90
- const configPath = configPathFor(vaultPath);
91
- if (!fs.existsSync(configPath)) {
92
- throw new Error(`No ClawVault config found at ${configPath}`);
93
- }
94
- try {
95
- const parsed = JSON.parse(fs.readFileSync(configPath, "utf-8"));
96
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
97
- throw new Error("Config root must be a JSON object.");
98
- }
99
- return { ...parsed };
100
- } catch (error) {
101
- if (error instanceof Error) {
102
- throw new Error(`Failed to parse ${CONFIG_FILE}: ${error.message}`);
103
- }
104
- throw new Error(`Failed to parse ${CONFIG_FILE}.`);
105
- }
106
- }
107
- function asStringArray(value) {
108
- if (!Array.isArray(value)) {
109
- return null;
110
- }
111
- const output = value.map((entry) => typeof entry === "string" ? entry.trim() : "").filter(Boolean);
112
- return output.length > 0 ? output : null;
113
- }
114
- function asPositiveInteger(value) {
115
- if (typeof value === "number" && Number.isInteger(value) && value > 0) {
116
- return value;
117
- }
118
- if (typeof value === "string") {
119
- const parsed = Number.parseInt(value, 10);
120
- if (Number.isInteger(parsed) && parsed > 0) {
121
- return parsed;
122
- }
123
- }
124
- return null;
125
- }
126
- function asBoolean(value) {
127
- if (typeof value === "boolean") {
128
- return value;
129
- }
130
- if (typeof value === "string") {
131
- const normalized = value.trim().toLowerCase();
132
- if (["true", "1", "yes", "on"].includes(normalized)) {
133
- return true;
134
- }
135
- if (["false", "0", "no", "off"].includes(normalized)) {
136
- return false;
137
- }
138
- }
139
- return null;
140
- }
141
- function isObserveProvider(value) {
142
- return typeof value === "string" && OBSERVE_PROVIDERS.includes(value);
143
- }
144
- function isObserverCompressionProvider(value) {
145
- return typeof value === "string" && OBSERVER_COMPRESSION_PROVIDERS.includes(value);
146
- }
147
- function isTheme(value) {
148
- return typeof value === "string" && THEMES.includes(value);
149
- }
150
- function isContextProfile(value) {
151
- return typeof value === "string" && CONTEXT_PROFILES.includes(value);
152
- }
153
- function normalizeRouteTarget(target) {
154
- const trimmed = target.trim().replace(/^\/+/, "").replace(/\/+$/, "");
155
- if (!trimmed) {
156
- throw new Error("Route target cannot be empty.");
157
- }
158
- const segments = trimmed.split("/").map((segment) => segment.trim()).filter(Boolean);
159
- if (segments.length === 0) {
160
- throw new Error("Route target cannot be empty.");
161
- }
162
- if (segments.some((segment) => segment === "." || segment === "..")) {
163
- throw new Error(`Route target cannot contain relative path segments: ${target}`);
164
- }
165
- return segments.join("/");
166
- }
167
- function normalizeRouteRule(raw) {
168
- if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
169
- return null;
170
- }
171
- const record = raw;
172
- const pattern = typeof record.pattern === "string" ? record.pattern.trim() : "";
173
- const target = typeof record.target === "string" ? record.target.trim() : "";
174
- const priority = asPositiveInteger(record.priority);
175
- if (!pattern || !target || priority === null) {
176
- return null;
177
- }
178
- return {
179
- pattern,
180
- target: normalizeRouteTarget(target),
181
- priority
182
- };
183
- }
184
- function normalizeRoutes(value) {
185
- if (!Array.isArray(value)) {
186
- return [];
187
- }
188
- return value.map((entry) => normalizeRouteRule(entry)).filter((entry) => entry !== null).sort((left, right) => right.priority - left.priority || left.pattern.localeCompare(right.pattern));
189
- }
190
- function withDefaults(vaultPath, config) {
191
- const resolvedPath = path.resolve(vaultPath);
192
- const defaults = {
193
- name: path.basename(resolvedPath),
194
- categories: [...DEFAULT_CATEGORIES],
195
- theme: DEFAULT_THEME,
196
- observe: {
197
- model: DEFAULT_OBSERVE_MODEL,
198
- provider: DEFAULT_OBSERVE_PROVIDER
199
- },
200
- observer: {
201
- compression: {}
202
- },
203
- context: {
204
- maxResults: DEFAULT_CONTEXT_MAX_RESULTS,
205
- defaultProfile: DEFAULT_CONTEXT_PROFILE
206
- },
207
- graph: {
208
- maxHops: DEFAULT_GRAPH_MAX_HOPS
209
- },
210
- inject: {
211
- maxResults: DEFAULT_INJECT_MAX_RESULTS,
212
- useLlm: DEFAULT_INJECT_USE_LLM,
213
- scope: [...DEFAULT_INJECT_SCOPE]
214
- },
215
- routes: []
216
- };
217
- const observeRecord = config.observe && typeof config.observe === "object" && !Array.isArray(config.observe) ? config.observe : {};
218
- const contextRecord = config.context && typeof config.context === "object" && !Array.isArray(config.context) ? config.context : {};
219
- const observerRecord = config.observer && typeof config.observer === "object" && !Array.isArray(config.observer) ? config.observer : {};
220
- const compressionRecord = observerRecord.compression && typeof observerRecord.compression === "object" && !Array.isArray(observerRecord.compression) ? observerRecord.compression : {};
221
- const graphRecord = config.graph && typeof config.graph === "object" && !Array.isArray(config.graph) ? config.graph : {};
222
- const compressionProvider = isObserverCompressionProvider(compressionRecord.provider) ? compressionRecord.provider : void 0;
223
- const compressionModel = typeof compressionRecord.model === "string" && compressionRecord.model.trim() ? compressionRecord.model.trim() : void 0;
224
- const compressionBaseUrl = typeof compressionRecord.baseUrl === "string" && compressionRecord.baseUrl.trim() ? compressionRecord.baseUrl.trim() : void 0;
225
- const compressionApiKey = typeof compressionRecord.apiKey === "string" && compressionRecord.apiKey.trim() ? compressionRecord.apiKey.trim() : void 0;
226
- const normalizedCompression = {};
227
- if (compressionProvider) {
228
- normalizedCompression.provider = compressionProvider;
229
- }
230
- if (compressionModel) {
231
- normalizedCompression.model = compressionModel;
232
- }
233
- if (compressionBaseUrl) {
234
- normalizedCompression.baseUrl = compressionBaseUrl;
235
- }
236
- if (compressionApiKey) {
237
- normalizedCompression.apiKey = compressionApiKey;
238
- }
239
- const injectRecord = config.inject && typeof config.inject === "object" && !Array.isArray(config.inject) ? config.inject : {};
240
- return {
241
- ...config,
242
- name: typeof config.name === "string" && config.name.trim() ? config.name.trim() : defaults.name,
243
- categories: asStringArray(config.categories) ?? defaults.categories,
244
- theme: isTheme(config.theme) ? config.theme : defaults.theme,
245
- observe: {
246
- ...observeRecord,
247
- model: typeof observeRecord.model === "string" && observeRecord.model.trim() ? observeRecord.model.trim() : defaults.observe.model,
248
- provider: isObserveProvider(observeRecord.provider) ? observeRecord.provider : defaults.observe.provider
249
- },
250
- observer: {
251
- ...observerRecord,
252
- compression: normalizedCompression
253
- },
254
- context: {
255
- ...contextRecord,
256
- maxResults: asPositiveInteger(contextRecord.maxResults) ?? defaults.context.maxResults,
257
- defaultProfile: isContextProfile(contextRecord.defaultProfile) ? contextRecord.defaultProfile : defaults.context.defaultProfile
258
- },
259
- graph: {
260
- ...graphRecord,
261
- maxHops: asPositiveInteger(graphRecord.maxHops) ?? defaults.graph.maxHops
262
- },
263
- inject: {
264
- ...injectRecord,
265
- maxResults: asPositiveInteger(injectRecord.maxResults) ?? defaults.inject.maxResults,
266
- useLlm: asBoolean(injectRecord.useLlm) ?? defaults.inject.useLlm,
267
- scope: asStringArray(injectRecord.scope) ?? (typeof injectRecord.scope === "string" ? injectRecord.scope.split(",").map((entry) => entry.trim()).filter(Boolean) : null) ?? [...defaults.inject.scope]
268
- },
269
- routes: normalizeRoutes(config.routes)
270
- };
271
- }
272
- function listConfig(vaultPath) {
273
- const config = readConfigDocument(vaultPath);
274
- return withDefaults(vaultPath, config);
275
- }
276
-
277
- // src/lib/inject-utils.ts
278
- var fs3 = __toESM(require("fs"), 1);
279
- var path3 = __toESM(require("path"), 1);
280
- var import_gray_matter2 = __toESM(require("gray-matter"), 1);
281
-
282
- // src/lib/memory-graph.ts
283
- var fs2 = __toESM(require("fs"), 1);
284
- var path2 = __toESM(require("path"), 1);
285
- var import_gray_matter = __toESM(require("gray-matter"), 1);
286
- var import_glob = require("glob");
287
- var MEMORY_GRAPH_SCHEMA_VERSION = 1;
288
- var GRAPH_INDEX_RELATIVE_PATH = path2.join(".clawvault", "graph-index.json");
289
- var WIKI_LINK_RE = /\[\[([^\]]+)\]\]/g;
290
- var HASH_TAG_RE = /(^|\s)#([\w-]+)/g;
291
- var FRONTMATTER_RELATION_FIELDS = [
292
- "related",
293
- "depends_on",
294
- "dependsOn",
295
- "blocked_by",
296
- "blocks",
297
- "owner",
298
- "project",
299
- "people",
300
- "links"
301
- ];
302
- function normalizeRelativePath(value) {
303
- return value.split(path2.sep).join("/").replace(/^\.\//, "").replace(/^\/+/, "");
304
- }
305
- function toNoteKey(relativePath) {
306
- const normalized = normalizeRelativePath(relativePath);
307
- return normalized.toLowerCase().endsWith(".md") ? normalized.slice(0, -3) : normalized;
308
- }
309
- function toNoteNodeId(noteKey) {
310
- return `note:${noteKey}`;
311
- }
312
- function toTagNodeId(tag) {
313
- return `tag:${tag.toLowerCase()}`;
314
- }
315
- function normalizeUnresolvedKey(raw) {
316
- const normalized = raw.trim().toLowerCase().replace(/\\/g, "/").replace(/^\.\//, "").replace(/^\/+/, "").replace(/\.md$/, "").replace(/[^a-z0-9/_-]+/g, "-").replace(/\/+/g, "/").replace(/-+/g, "-").replace(/^[-/]+|[-/]+$/g, "");
317
- return normalized || "unknown";
318
- }
319
- function toUnresolvedNodeId(raw) {
320
- return `unresolved:${normalizeUnresolvedKey(raw)}`;
321
- }
322
- function titleFromNoteKey(noteKey) {
323
- const basename3 = noteKey.split("/").pop() ?? noteKey;
324
- return basename3.replace(/[-_]+/g, " ").replace(/\s+/g, " ").trim().replace(/\b\w/g, (char) => char.toUpperCase());
325
- }
326
- function inferNodeType(relativePath, frontmatter) {
327
- const normalized = normalizeRelativePath(relativePath).toLowerCase();
328
- const category = normalized.split("/")[0] ?? "note";
329
- const explicitType = typeof frontmatter.type === "string" ? frontmatter.type.toLowerCase() : "";
330
- if (category.includes("daily") || explicitType === "daily") return "daily";
331
- if (category === "observations" || explicitType === "observation") return "observation";
332
- if (category === "handoffs" || explicitType === "handoff") return "handoff";
333
- if (category === "decisions" || explicitType === "decision") return "decision";
334
- if (category === "lessons" || explicitType === "lesson") return "lesson";
335
- if (category === "projects" || explicitType === "project") return "project";
336
- if (category === "people" || explicitType === "person") return "person";
337
- if (category === "commitments" || explicitType === "commitment") return "commitment";
338
- return "note";
339
- }
340
- function ensureClawvaultDir(vaultPath) {
341
- const dirPath = path2.join(vaultPath, ".clawvault");
342
- if (!fs2.existsSync(dirPath)) {
343
- fs2.mkdirSync(dirPath, { recursive: true });
344
- }
345
- return dirPath;
346
- }
347
- function getGraphIndexPath(vaultPath) {
348
- return path2.join(vaultPath, GRAPH_INDEX_RELATIVE_PATH);
349
- }
350
- function normalizeWikiTarget(target) {
351
- let value = target.trim();
352
- if (!value) return "";
353
- const pipeIndex = value.indexOf("|");
354
- if (pipeIndex >= 0) {
355
- value = value.slice(0, pipeIndex);
356
- }
357
- const hashIndex = value.indexOf("#");
358
- if (hashIndex >= 0) {
359
- value = value.slice(0, hashIndex);
360
- }
361
- value = value.trim().replace(/\\/g, "/").replace(/^\.\//, "").replace(/^\/+/, "");
362
- if (value.toLowerCase().endsWith(".md")) {
363
- value = value.slice(0, -3);
364
- }
365
- return value.trim();
366
- }
367
- function collectTags(frontmatter, markdownContent) {
368
- const tags = /* @__PURE__ */ new Set();
369
- const fmTags = frontmatter.tags;
370
- if (Array.isArray(fmTags)) {
371
- for (const tag of fmTags) {
372
- if (typeof tag === "string" && tag.trim()) tags.add(tag.trim().toLowerCase());
373
- }
374
- } else if (typeof fmTags === "string") {
375
- for (const token of fmTags.split(",")) {
376
- const normalized = token.trim().toLowerCase();
377
- if (normalized) tags.add(normalized);
378
- }
379
- }
380
- const markdownMatches = markdownContent.matchAll(HASH_TAG_RE);
381
- for (const match of markdownMatches) {
382
- const tag = match[2]?.trim().toLowerCase();
383
- if (tag) tags.add(tag);
384
- }
385
- return [...tags].sort((a, b) => a.localeCompare(b));
386
- }
387
- function extractWikiTargets(markdownContent) {
388
- const targets = /* @__PURE__ */ new Set();
389
- for (const match of markdownContent.matchAll(WIKI_LINK_RE)) {
390
- const candidate = match[1];
391
- if (!candidate) continue;
392
- const normalized = normalizeWikiTarget(candidate);
393
- if (normalized) targets.add(normalized);
394
- }
395
- return [...targets];
396
- }
397
- function toStringArray(value) {
398
- if (typeof value === "string") {
399
- return value.split(",").map((entry) => entry.trim()).filter(Boolean);
400
- }
401
- if (Array.isArray(value)) {
402
- return value.flatMap((entry) => typeof entry === "string" ? entry.split(",") : []).map((entry) => entry.trim()).filter(Boolean);
403
- }
404
- return [];
405
- }
406
- function extractFrontmatterRelations(frontmatter) {
407
- const relations = [];
408
- for (const field of FRONTMATTER_RELATION_FIELDS) {
409
- const raw = frontmatter[field];
410
- for (const value of toStringArray(raw)) {
411
- const normalized = normalizeWikiTarget(value);
412
- if (normalized) relations.push({ field, target: normalized });
413
- }
414
- }
415
- return relations;
416
- }
417
- function buildNoteRegistry(relativePaths) {
418
- const byLowerPath = /* @__PURE__ */ new Map();
419
- const byLowerBasename = /* @__PURE__ */ new Map();
420
- for (const relativePath of relativePaths) {
421
- const noteKey = toNoteKey(relativePath);
422
- const lowerKey = noteKey.toLowerCase();
423
- if (!byLowerPath.has(lowerKey)) {
424
- byLowerPath.set(lowerKey, noteKey);
425
- }
426
- const base = noteKey.split("/").pop() ?? noteKey;
427
- const lowerBase = base.toLowerCase();
428
- const existing = byLowerBasename.get(lowerBase) ?? [];
429
- existing.push(noteKey);
430
- byLowerBasename.set(lowerBase, existing);
431
- }
432
- return { byLowerPath, byLowerBasename };
433
- }
434
- function resolveTargetNodeId(rawTarget, registry) {
435
- const normalized = normalizeWikiTarget(rawTarget);
436
- if (!normalized) {
437
- return toUnresolvedNodeId(rawTarget);
438
- }
439
- const lowerTarget = normalized.toLowerCase();
440
- const direct = registry.byLowerPath.get(lowerTarget);
441
- if (direct) {
442
- return toNoteNodeId(direct);
443
- }
444
- if (!normalized.includes("/")) {
445
- const basenameMatches = registry.byLowerBasename.get(lowerTarget) ?? [];
446
- if (basenameMatches.length === 1) {
447
- return toNoteNodeId(basenameMatches[0]);
448
- }
449
- }
450
- return toUnresolvedNodeId(normalized);
451
- }
452
- function createEdgeId(type, source, target, label) {
453
- const suffix = label ? `:${label}` : "";
454
- return `${type}:${source}->${target}${suffix}`;
455
- }
456
- function buildFragmentNode(id, title, type, category, pathValue, tags, missing, modifiedAt) {
457
- return {
458
- id,
459
- title,
460
- type,
461
- category,
462
- path: pathValue,
463
- tags,
464
- missing,
465
- degree: 0,
466
- modifiedAt
467
- };
468
- }
469
- function parseFileFragment(vaultPath, relativePath, mtimeMs, registry) {
470
- const absolutePath = path2.join(vaultPath, relativePath);
471
- const raw = fs2.readFileSync(absolutePath, "utf-8");
472
- const parsed = (0, import_gray_matter.default)(raw);
473
- const frontmatter = parsed.data ?? {};
474
- const noteKey = toNoteKey(relativePath);
475
- const noteNodeId = toNoteNodeId(noteKey);
476
- const noteType = inferNodeType(relativePath, frontmatter);
477
- const tags = collectTags(frontmatter, parsed.content);
478
- const modifiedAt = new Date(mtimeMs).toISOString();
479
- const nodes = /* @__PURE__ */ new Map();
480
- const edges = /* @__PURE__ */ new Map();
481
- nodes.set(
482
- noteNodeId,
483
- buildFragmentNode(
484
- noteNodeId,
485
- typeof frontmatter.title === "string" && frontmatter.title.trim() ? frontmatter.title.trim() : titleFromNoteKey(noteKey),
486
- noteType,
487
- noteType,
488
- normalizeRelativePath(relativePath),
489
- tags,
490
- false,
491
- modifiedAt
492
- )
493
- );
494
- for (const tag of tags) {
495
- const tagNodeId = toTagNodeId(tag);
496
- if (!nodes.has(tagNodeId)) {
497
- nodes.set(tagNodeId, buildFragmentNode(tagNodeId, `#${tag}`, "tag", "tag", null, [], false, null));
498
- }
499
- const edgeId = createEdgeId("tag", noteNodeId, tagNodeId);
500
- edges.set(edgeId, {
501
- id: edgeId,
502
- source: noteNodeId,
503
- target: tagNodeId,
504
- type: "tag"
505
- });
506
- }
507
- const wikiTargets = extractWikiTargets(parsed.content);
508
- for (const target of wikiTargets) {
509
- const targetNodeId = resolveTargetNodeId(target, registry);
510
- if (targetNodeId.startsWith("unresolved:") && !nodes.has(targetNodeId)) {
511
- nodes.set(
512
- targetNodeId,
513
- buildFragmentNode(targetNodeId, titleFromNoteKey(normalizeUnresolvedKey(target)), "unresolved", "unresolved", null, [], true, null)
514
- );
515
- }
516
- const edgeId = createEdgeId("wiki_link", noteNodeId, targetNodeId);
517
- edges.set(edgeId, {
518
- id: edgeId,
519
- source: noteNodeId,
520
- target: targetNodeId,
521
- type: "wiki_link"
522
- });
523
- }
524
- for (const relation of extractFrontmatterRelations(frontmatter)) {
525
- const targetNodeId = resolveTargetNodeId(relation.target, registry);
526
- if (targetNodeId.startsWith("unresolved:") && !nodes.has(targetNodeId)) {
527
- nodes.set(
528
- targetNodeId,
529
- buildFragmentNode(
530
- targetNodeId,
531
- titleFromNoteKey(normalizeUnresolvedKey(relation.target)),
532
- "unresolved",
533
- "unresolved",
534
- null,
535
- [],
536
- true,
537
- null
538
- )
539
- );
540
- }
541
- const edgeId = createEdgeId("frontmatter_relation", noteNodeId, targetNodeId, relation.field);
542
- edges.set(edgeId, {
543
- id: edgeId,
544
- source: noteNodeId,
545
- target: targetNodeId,
546
- type: "frontmatter_relation",
547
- label: relation.field
548
- });
549
- }
550
- return {
551
- relativePath: normalizeRelativePath(relativePath),
552
- mtimeMs,
553
- nodes: [...nodes.values()],
554
- edges: [...edges.values()]
555
- };
556
- }
557
- function combineFragments(fragments, generatedAt) {
558
- const nodes = /* @__PURE__ */ new Map();
559
- const edges = /* @__PURE__ */ new Map();
560
- for (const fragment of Object.values(fragments)) {
561
- for (const node of fragment.nodes) {
562
- const existing = nodes.get(node.id);
563
- if (!existing) {
564
- nodes.set(node.id, { ...node, degree: 0 });
565
- } else if (node.modifiedAt && (!existing.modifiedAt || node.modifiedAt > existing.modifiedAt)) {
566
- nodes.set(node.id, { ...existing, ...node, degree: 0 });
567
- }
568
- }
569
- for (const edge of fragment.edges) {
570
- edges.set(edge.id, edge);
571
- }
572
- }
573
- const degreeByNode = /* @__PURE__ */ new Map();
574
- for (const edge of edges.values()) {
575
- degreeByNode.set(edge.source, (degreeByNode.get(edge.source) ?? 0) + 1);
576
- degreeByNode.set(edge.target, (degreeByNode.get(edge.target) ?? 0) + 1);
577
- }
578
- for (const node of nodes.values()) {
579
- node.degree = degreeByNode.get(node.id) ?? 0;
580
- }
581
- const nodeTypeCounts = {};
582
- for (const node of nodes.values()) {
583
- nodeTypeCounts[node.type] = (nodeTypeCounts[node.type] ?? 0) + 1;
584
- }
585
- const edgeTypeCounts = {};
586
- for (const edge of edges.values()) {
587
- edgeTypeCounts[edge.type] = (edgeTypeCounts[edge.type] ?? 0) + 1;
588
- }
589
- const sortedNodes = [...nodes.values()].sort((a, b) => a.id.localeCompare(b.id));
590
- const sortedEdges = [...edges.values()].sort((a, b) => a.id.localeCompare(b.id));
591
- return {
592
- schemaVersion: MEMORY_GRAPH_SCHEMA_VERSION,
593
- nodes: sortedNodes,
594
- edges: sortedEdges,
595
- stats: {
596
- generatedAt,
597
- nodeCount: sortedNodes.length,
598
- edgeCount: sortedEdges.length,
599
- nodeTypeCounts,
600
- edgeTypeCounts
601
- }
602
- };
603
- }
604
- function isValidIndex(index) {
605
- if (!index || typeof index !== "object") return false;
606
- const typed = index;
607
- return typed.schemaVersion === MEMORY_GRAPH_SCHEMA_VERSION && typeof typed.vaultPath === "string" && typeof typed.generatedAt === "string" && Boolean(typed.files && typeof typed.files === "object") && Boolean(typed.graph && typeof typed.graph === "object");
608
- }
609
- function loadMemoryGraphIndex(vaultPath) {
610
- const indexPath = getGraphIndexPath(path2.resolve(vaultPath));
611
- if (!fs2.existsSync(indexPath)) {
612
- return null;
613
- }
614
- try {
615
- const parsed = JSON.parse(fs2.readFileSync(indexPath, "utf-8"));
616
- if (!isValidIndex(parsed)) {
617
- return null;
618
- }
619
- return parsed;
620
- } catch {
621
- return null;
622
- }
623
- }
624
- async function buildOrUpdateMemoryGraphIndex(vaultPathInput, options = {}) {
625
- const vaultPath = path2.resolve(vaultPathInput);
626
- ensureClawvaultDir(vaultPath);
627
- const existing = options.forceFull ? null : loadMemoryGraphIndex(vaultPath);
628
- const markdownFiles = await (0, import_glob.glob)("**/*.md", {
629
- cwd: vaultPath,
630
- ignore: ["**/node_modules/**", "**/.git/**", "**/.obsidian/**", "**/.trash/**", "**/ledger/archive/**"]
631
- });
632
- const normalizedFiles = markdownFiles.map(normalizeRelativePath).sort((a, b) => a.localeCompare(b));
633
- const registry = buildNoteRegistry(normalizedFiles);
634
- const nextFragments = {};
635
- const existingFragments = existing?.files ?? {};
636
- const currentFileSet = new Set(normalizedFiles);
637
- for (const relativePath of normalizedFiles) {
638
- const absolutePath = path2.join(vaultPath, relativePath);
639
- const stat = fs2.statSync(absolutePath);
640
- const existingFragment = existingFragments[relativePath];
641
- if (!options.forceFull && existingFragment && existingFragment.mtimeMs === stat.mtimeMs) {
642
- nextFragments[relativePath] = existingFragment;
643
- continue;
644
- }
645
- nextFragments[relativePath] = parseFileFragment(vaultPath, relativePath, stat.mtimeMs, registry);
646
- }
647
- for (const [relativePath, fragment] of Object.entries(existingFragments)) {
648
- if (!currentFileSet.has(relativePath)) {
649
- continue;
650
- }
651
- if (!nextFragments[relativePath]) {
652
- nextFragments[relativePath] = fragment;
653
- }
654
- }
655
- const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
656
- const graph = combineFragments(nextFragments, generatedAt);
657
- const nextIndex = {
658
- schemaVersion: MEMORY_GRAPH_SCHEMA_VERSION,
659
- vaultPath,
660
- generatedAt,
661
- files: nextFragments,
662
- graph
663
- };
664
- fs2.writeFileSync(getGraphIndexPath(vaultPath), JSON.stringify(nextIndex, null, 2));
665
- return nextIndex;
666
- }
667
- async function getMemoryGraph(vaultPath, options = {}) {
668
- if (options.refresh === true) {
669
- return (await buildOrUpdateMemoryGraphIndex(vaultPath, { forceFull: true })).graph;
670
- }
671
- return (await buildOrUpdateMemoryGraphIndex(vaultPath)).graph;
672
- }
673
-
674
- // src/lib/claude-credentials.ts
675
- var import_child_process = require("child_process");
676
- var import_fs = require("fs");
677
- var import_os = require("os");
678
- var import_path = require("path");
679
- var CLAUDE_CODE_SERVICE = "Claude Code-credentials";
680
- var CLAUDE_CODE_ACCOUNT = "Claude Code";
681
- var OAUTH_CLIENT_ID = "9d1c250a-e61b-44d9-88ed-5944d1962f5e";
682
- var TOKEN_REFRESH_URL = "https://console.anthropic.com/v1/oauth/token";
683
- var EXPIRY_BUFFER_MS = 5 * 60 * 1e3;
684
- function readClaudeCliCredentials(opts) {
685
- if (process.platform === "darwin") {
686
- try {
687
- const raw = (0, import_child_process.execFileSync)(
688
- "security",
689
- ["find-generic-password", "-s", CLAUDE_CODE_SERVICE, "-w"],
690
- { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }
691
- ).trim();
692
- const parsed = parseCredentialsJson(raw);
693
- if (parsed) return parsed;
694
- } catch {
695
- }
696
- }
697
- const home = opts?.homeDir ?? (0, import_os.homedir)();
698
- const credFile = (0, import_path.join)(home, ".claude", ".credentials.json");
699
- if (!(0, import_fs.existsSync)(credFile)) {
700
- return null;
701
- }
702
- try {
703
- const raw = (0, import_fs.readFileSync)(credFile, "utf8");
704
- return parseCredentialsJson(raw);
705
- } catch {
706
- return null;
707
- }
708
- }
709
- function parseCredentialsJson(raw) {
710
- try {
711
- const parsed = JSON.parse(raw);
712
- const oauth = parsed.claudeAiOauth;
713
- if (oauth && typeof oauth.accessToken === "string" && typeof oauth.refreshToken === "string" && typeof oauth.expiresAt === "number") {
714
- return {
715
- accessToken: oauth.accessToken,
716
- refreshToken: oauth.refreshToken,
717
- expiresAt: oauth.expiresAt
718
- };
719
- }
720
- } catch {
721
- }
722
- return null;
723
- }
724
- async function refreshClaudeOAuthToken(refreshToken, fetchImpl) {
725
- const f = fetchImpl ?? fetch;
726
- const response = await f(TOKEN_REFRESH_URL, {
727
- method: "POST",
728
- headers: { "content-type": "application/json" },
729
- body: JSON.stringify({
730
- grant_type: "refresh_token",
731
- client_id: OAUTH_CLIENT_ID,
732
- refresh_token: refreshToken
733
- })
734
- });
735
- if (!response.ok) {
736
- throw new Error(`OAuth token refresh failed (${response.status})`);
737
- }
738
- const data = await response.json();
739
- return {
740
- accessToken: data.access_token,
741
- refreshToken: data.refresh_token,
742
- expiresAt: Date.now() + data.expires_in * 1e3
743
- };
744
- }
745
- function writeClaudeCliCredentials(cred, opts) {
746
- const payload = JSON.stringify({ claudeAiOauth: cred });
747
- if (process.platform === "darwin") {
748
- try {
749
- (0, import_child_process.execFileSync)(
750
- "security",
751
- ["add-generic-password", "-U", "-s", CLAUDE_CODE_SERVICE, "-a", CLAUDE_CODE_ACCOUNT, "-w", payload],
752
- { stdio: "ignore" }
753
- );
754
- return;
755
- } catch {
756
- }
757
- }
758
- const home = opts?.homeDir ?? (0, import_os.homedir)();
759
- const credFile = (0, import_path.join)(home, ".claude", ".credentials.json");
760
- (0, import_fs.writeFileSync)(credFile, payload, "utf8");
761
- }
762
- async function resolveClaudeOAuthToken(opts) {
763
- const cred = readClaudeCliCredentials(opts);
764
- if (!cred) {
765
- return null;
766
- }
767
- if (cred.expiresAt < Date.now() + EXPIRY_BUFFER_MS) {
768
- try {
769
- const refreshed = await refreshClaudeOAuthToken(cred.refreshToken, opts?.fetchImpl);
770
- writeClaudeCliCredentials(refreshed, opts);
771
- return refreshed.accessToken;
772
- } catch {
773
- return cred.accessToken;
774
- }
775
- }
776
- return cred.accessToken;
777
- }
778
-
779
- // src/lib/llm-provider.ts
780
- var DEFAULT_MODELS = {
781
- anthropic: "claude-haiku-4-5",
782
- openai: "gpt-4o-mini",
783
- gemini: "gemini-2.0-flash"
784
- };
785
- async function resolveAnthropicAuth(fetchImpl) {
786
- const oauthEnvToken = process.env.ANTHROPIC_OAUTH_TOKEN?.trim();
787
- if (oauthEnvToken) {
788
- return { token: oauthEnvToken, isOAuth: true };
789
- }
790
- const apiKey = process.env.ANTHROPIC_API_KEY?.trim();
791
- if (apiKey) {
792
- return { token: apiKey, isOAuth: false };
793
- }
794
- if (process.env.CLAWVAULT_CLAUDE_AUTH) {
795
- const oauthToken = await resolveClaudeOAuthToken({ fetchImpl });
796
- if (oauthToken) {
797
- return { token: oauthToken, isOAuth: true };
798
- }
799
- }
800
- return null;
801
- }
802
- async function resolveLlmProvider(fetchImpl) {
803
- if (process.env.CLAWVAULT_NO_LLM) {
804
- return null;
805
- }
806
- const anthropicAuth = await resolveAnthropicAuth(fetchImpl);
807
- if (anthropicAuth) {
808
- return "anthropic";
809
- }
810
- if (process.env.OPENAI_API_KEY) {
811
- return "openai";
812
- }
813
- if (process.env.GEMINI_API_KEY) {
814
- return "gemini";
815
- }
816
- return null;
817
- }
818
- async function requestLlmCompletion(options) {
819
- const provider = options.provider ?? await resolveLlmProvider(options.fetchImpl);
820
- if (!provider) {
821
- return "";
822
- }
823
- if (provider === "anthropic") {
824
- return callAnthropic(options, provider);
825
- }
826
- if (provider === "gemini") {
827
- return callGemini(options, provider);
828
- }
829
- return callOpenAI(options, provider);
830
- }
831
- async function callAnthropic(options, provider) {
832
- const fetchImpl = options.fetchImpl ?? fetch;
833
- const auth = await resolveAnthropicAuth(fetchImpl);
834
- if (!auth) {
835
- return "";
836
- }
837
- const headers = auth.isOAuth ? {
838
- "content-type": "application/json",
839
- "authorization": `Bearer ${auth.token}`,
840
- "anthropic-version": "2023-06-01",
841
- "anthropic-beta": "claude-code-20250219,oauth-2025-04-20",
842
- "x-app": "cli",
843
- "user-agent": "claude-cli/1.0.0 (external, cli)"
844
- } : {
845
- "content-type": "application/json",
846
- "x-api-key": auth.token,
847
- "anthropic-version": "2023-06-01"
848
- };
849
- const systemMessages = [];
850
- if (auth.isOAuth) {
851
- systemMessages.push({ type: "text", text: "You are Claude Code, Anthropic's official CLI for Claude." });
852
- }
853
- if (options.systemPrompt?.trim()) {
854
- systemMessages.push({ type: "text", text: options.systemPrompt.trim() });
855
- }
856
- const body = {
857
- model: options.model ?? DEFAULT_MODELS[provider],
858
- temperature: options.temperature ?? 0.1,
859
- max_tokens: options.maxTokens ?? 1200,
860
- messages: [{ role: "user", content: options.prompt }]
861
- };
862
- if (systemMessages.length > 0) {
863
- body.system = systemMessages;
864
- }
865
- const response = await fetchImpl("https://api.anthropic.com/v1/messages", {
866
- method: "POST",
867
- headers,
868
- body: JSON.stringify(body)
869
- });
870
- if (!response.ok) {
871
- throw new Error(`Anthropic request failed (${response.status})`);
872
- }
873
- const payload = await response.json();
874
- return payload.content?.filter((entry) => entry.type === "text" && entry.text).map((entry) => entry.text).join("\n").trim() ?? "";
875
- }
876
- async function callOpenAI(options, provider) {
877
- const apiKey = process.env.OPENAI_API_KEY;
878
- if (!apiKey) {
879
- return "";
880
- }
881
- const fetchImpl = options.fetchImpl ?? fetch;
882
- const messages = [];
883
- if (options.systemPrompt?.trim()) {
884
- messages.push({ role: "system", content: options.systemPrompt.trim() });
885
- }
886
- messages.push({ role: "user", content: options.prompt });
887
- const response = await fetchImpl("https://api.openai.com/v1/chat/completions", {
888
- method: "POST",
889
- headers: {
890
- "content-type": "application/json",
891
- authorization: `Bearer ${apiKey}`
892
- },
893
- body: JSON.stringify({
894
- model: options.model ?? DEFAULT_MODELS[provider],
895
- temperature: options.temperature ?? 0.1,
896
- max_tokens: options.maxTokens ?? 1200,
897
- messages
898
- })
899
- });
900
- if (!response.ok) {
901
- throw new Error(`OpenAI request failed (${response.status})`);
902
- }
903
- const payload = await response.json();
904
- return payload.choices?.[0]?.message?.content?.trim() ?? "";
905
- }
906
- async function callGemini(options, provider) {
907
- const apiKey = process.env.GEMINI_API_KEY;
908
- if (!apiKey) {
909
- return "";
910
- }
911
- const fetchImpl = options.fetchImpl ?? fetch;
912
- const model = options.model ?? DEFAULT_MODELS[provider];
913
- const response = await fetchImpl(
914
- `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent`,
915
- {
916
- method: "POST",
917
- headers: { "content-type": "application/json", "x-goog-api-key": apiKey },
918
- body: JSON.stringify({
919
- contents: [{ parts: [{ text: options.prompt }] }],
920
- generationConfig: {
921
- temperature: options.temperature ?? 0.1,
922
- maxOutputTokens: options.maxTokens ?? 1200
923
- }
924
- })
925
- }
926
- );
927
- if (!response.ok) {
928
- throw new Error(`Gemini request failed (${response.status})`);
929
- }
930
- const payload = await response.json();
931
- return payload.candidates?.[0]?.content?.parts?.[0]?.text?.trim() ?? "";
932
- }
933
-
934
- // src/lib/inject-utils.ts
935
- var INJECTABLE_CATEGORIES = ["rules", "decisions", "preferences"];
936
- var DEFAULT_CATEGORY_PRIORITY = {
937
- rules: 100,
938
- decisions: 80,
939
- preferences: 60
940
- };
941
- var STOP_WORDS = /* @__PURE__ */ new Set([
942
- "a",
943
- "an",
944
- "and",
945
- "are",
946
- "as",
947
- "at",
948
- "be",
949
- "by",
950
- "for",
951
- "from",
952
- "in",
953
- "is",
954
- "it",
955
- "of",
956
- "on",
957
- "or",
958
- "that",
959
- "the",
960
- "this",
961
- "to",
962
- "with",
963
- "you",
964
- "your",
965
- "we",
966
- "our",
967
- "their",
968
- "they",
969
- "them",
970
- "i"
971
- ]);
972
- function normalizeText(value) {
973
- return value.toLowerCase().replace(/[^a-z0-9]+/g, " ").replace(/\s+/g, " ").trim();
974
- }
975
- function normalizeScopeValue(value) {
976
- return value.trim().toLowerCase().replace(/\s+/g, "-");
977
- }
978
- function toRelativePath(vaultPath, absolutePath) {
979
- return path3.relative(vaultPath, absolutePath).split(path3.sep).join("/");
980
- }
981
- function toNoteNodeId2(relativePath) {
982
- const normalized = relativePath.toLowerCase().endsWith(".md") ? relativePath.slice(0, -3) : relativePath;
983
- return `note:${normalized}`;
984
- }
985
- function extractHeadingTitle(content) {
986
- const match = content.match(/^#\s+(.+)$/m);
987
- return match?.[1]?.trim() || null;
988
- }
989
- function toStringArray2(value) {
990
- if (typeof value === "string") {
991
- return value.split(",").map((entry) => entry.trim()).filter(Boolean);
992
- }
993
- if (Array.isArray(value)) {
994
- return value.flatMap((entry) => typeof entry === "string" ? entry.split(",") : []).map((entry) => entry.trim()).filter(Boolean);
995
- }
996
- return [];
997
- }
998
- function parsePriority(frontmatter, category) {
999
- const explicit = frontmatter.priority;
1000
- if (typeof explicit === "number" && Number.isFinite(explicit)) {
1001
- return Math.max(1, Math.round(explicit));
1002
- }
1003
- if (typeof explicit === "string") {
1004
- const parsed = Number.parseFloat(explicit);
1005
- if (Number.isFinite(parsed)) {
1006
- return Math.max(1, Math.round(parsed));
1007
- }
1008
- }
1009
- const importance = frontmatter.importance;
1010
- if (typeof importance === "number" && Number.isFinite(importance) && importance >= 0 && importance <= 1) {
1011
- return Math.max(1, Math.round(importance * 100));
1012
- }
1013
- return DEFAULT_CATEGORY_PRIORITY[category];
1014
- }
1015
- function deriveTitle(frontmatter, markdownContent, fallbackPath) {
1016
- if (typeof frontmatter.title === "string" && frontmatter.title.trim()) {
1017
- return frontmatter.title.trim();
1018
- }
1019
- const heading = extractHeadingTitle(markdownContent);
1020
- if (heading) {
1021
- return heading;
1022
- }
1023
- return path3.basename(fallbackPath, ".md").replace(/[-_]+/g, " ").trim();
1024
- }
1025
- function deriveTriggers(params) {
1026
- const { category, frontmatter, title, relativePath } = params;
1027
- const explicitTriggers = toStringArray2(frontmatter.triggers);
1028
- const tags = toStringArray2(frontmatter.tags);
1029
- const aliases = toStringArray2(frontmatter.aliases);
1030
- const baseName = path3.basename(relativePath, ".md").replace(/[-_]+/g, " ").trim();
1031
- const normalizedTitle = title.trim();
1032
- const triggerSet = /* @__PURE__ */ new Set();
1033
- for (const value of [...explicitTriggers, ...tags, ...aliases]) {
1034
- if (value.trim()) {
1035
- triggerSet.add(value.trim());
1036
- }
1037
- }
1038
- if (triggerSet.size === 0 || category !== "rules") {
1039
- if (normalizedTitle) {
1040
- triggerSet.add(normalizedTitle);
1041
- }
1042
- if (baseName) {
1043
- triggerSet.add(baseName);
1044
- }
1045
- }
1046
- for (const keyword of tokenizeKeywords(normalizedTitle)) {
1047
- if (keyword.length >= 4) {
1048
- triggerSet.add(keyword);
1049
- }
1050
- }
1051
- return [...triggerSet];
1052
- }
1053
- function deriveScope(frontmatter) {
1054
- const raw = [
1055
- ...toStringArray2(frontmatter.scope),
1056
- ...toStringArray2(frontmatter.scopes)
1057
- ];
1058
- return [...new Set(raw.map(normalizeScopeValue).filter(Boolean))];
1059
- }
1060
- function tokenizeKeywords(value) {
1061
- const normalized = normalizeText(value);
1062
- if (!normalized) {
1063
- return [];
1064
- }
1065
- const tokens = normalized.split(" ").filter(Boolean);
1066
- const unique = [];
1067
- const seen = /* @__PURE__ */ new Set();
1068
- for (const token of tokens) {
1069
- if (token.length < 3 || STOP_WORDS.has(token) || seen.has(token)) {
1070
- continue;
1071
- }
1072
- seen.add(token);
1073
- unique.push(token);
1074
- }
1075
- return unique;
1076
- }
1077
- function collectMarkdownFiles(rootPath, currentPath, collected) {
1078
- if (!fs3.existsSync(currentPath)) {
1079
- return;
1080
- }
1081
- const entries = fs3.readdirSync(currentPath, { withFileTypes: true });
1082
- for (const entry of entries) {
1083
- const absolutePath = path3.join(currentPath, entry.name);
1084
- if (entry.isDirectory()) {
1085
- collectMarkdownFiles(rootPath, absolutePath, collected);
1086
- continue;
1087
- }
1088
- if (entry.isFile() && entry.name.endsWith(".md")) {
1089
- collected.push(toRelativePath(rootPath, absolutePath));
1090
- }
1091
- }
1092
- }
1093
- function parseInjectableFile(vaultPath, category, relativePath) {
1094
- const absolutePath = path3.join(vaultPath, relativePath);
1095
- if (!fs3.existsSync(absolutePath)) {
1096
- return null;
1097
- }
1098
- try {
1099
- const raw = fs3.readFileSync(absolutePath, "utf-8");
1100
- const parsed = (0, import_gray_matter2.default)(raw);
1101
- const frontmatter = parsed.data ?? {};
1102
- const title = deriveTitle(frontmatter, parsed.content, relativePath);
1103
- const triggers = deriveTriggers({ category, frontmatter, title, relativePath });
1104
- const scope = deriveScope(frontmatter);
1105
- const priority = parsePriority(frontmatter, category);
1106
- const searchKeywordSet = /* @__PURE__ */ new Set();
1107
- for (const trigger of triggers) {
1108
- for (const keyword of tokenizeKeywords(trigger)) {
1109
- searchKeywordSet.add(keyword);
1110
- }
1111
- }
1112
- for (const keyword of tokenizeKeywords(title)) {
1113
- searchKeywordSet.add(keyword);
1114
- }
1115
- return {
1116
- id: relativePath,
1117
- category,
1118
- relativePath,
1119
- title,
1120
- content: parsed.content.trim(),
1121
- triggers,
1122
- scope,
1123
- priority,
1124
- searchKeywords: [...searchKeywordSet],
1125
- noteNodeId: toNoteNodeId2(relativePath)
1126
- };
1127
- } catch {
1128
- return null;
1129
- }
1130
- }
1131
- function indexInjectableItems(vaultPathInput) {
1132
- const vaultPath = path3.resolve(vaultPathInput);
1133
- const items = [];
1134
- for (const category of INJECTABLE_CATEGORIES) {
1135
- const categoryRoot = path3.join(vaultPath, category);
1136
- const markdownFiles = [];
1137
- collectMarkdownFiles(vaultPath, categoryRoot, markdownFiles);
1138
- for (const relativePath of markdownFiles.sort((left, right) => left.localeCompare(right))) {
1139
- const parsed = parseInjectableFile(vaultPath, category, relativePath);
1140
- if (parsed) {
1141
- items.push(parsed);
1142
- }
1143
- }
1144
- }
1145
- return items;
1146
- }
1147
- function containsPhrase(normalizedHaystack, phrase) {
1148
- const normalizedPhrase = normalizeText(phrase);
1149
- if (!normalizedPhrase) {
1150
- return false;
1151
- }
1152
- const haystack = ` ${normalizedHaystack} `;
1153
- const needle = ` ${normalizedPhrase} `;
1154
- return haystack.includes(needle);
1155
- }
1156
- function collectNodeAliases(node) {
1157
- const aliases = /* @__PURE__ */ new Set();
1158
- if (node.title) {
1159
- aliases.add(node.title);
1160
- }
1161
- if (node.path) {
1162
- const basename3 = path3.basename(node.path, ".md");
1163
- aliases.add(basename3.replace(/[-_]+/g, " "));
1164
- aliases.add(basename3);
1165
- }
1166
- return [...aliases].map((alias) => normalizeText(alias)).filter((alias) => alias.length >= 3);
1167
- }
1168
- function isEligibleGraphNode(node) {
1169
- if (!node) return false;
1170
- if (node.missing) return false;
1171
- if (node.type === "tag" || node.type === "unresolved") return false;
1172
- return Boolean(node.path);
1173
- }
1174
- function buildDeterministicEntityMatches(message, graph) {
1175
- const normalizedMessage = normalizeText(message);
1176
- const nodeById = new Map(graph.nodes.map((node) => [node.id, node]));
1177
- const directAliasesByNode = /* @__PURE__ */ new Map();
1178
- for (const node of graph.nodes) {
1179
- if (!isEligibleGraphNode(node)) {
1180
- continue;
1181
- }
1182
- const aliases = collectNodeAliases(node);
1183
- for (const alias of aliases) {
1184
- if (containsPhrase(normalizedMessage, alias)) {
1185
- const bucket = directAliasesByNode.get(node.id) ?? /* @__PURE__ */ new Set();
1186
- bucket.add(alias);
1187
- directAliasesByNode.set(node.id, bucket);
1188
- }
1189
- }
1190
- }
1191
- const oneHopSourcesByNode = /* @__PURE__ */ new Map();
1192
- if (directAliasesByNode.size === 0) {
1193
- return { directAliasesByNode, oneHopSourcesByNode };
1194
- }
1195
- for (const edge of graph.edges) {
1196
- const leftDirect = directAliasesByNode.has(edge.source);
1197
- const rightDirect = directAliasesByNode.has(edge.target);
1198
- if (!leftDirect && !rightDirect) {
1199
- continue;
1200
- }
1201
- if (leftDirect && !rightDirect) {
1202
- const target = nodeById.get(edge.target);
1203
- const source = nodeById.get(edge.source);
1204
- if (!isEligibleGraphNode(target) || !isEligibleGraphNode(source)) {
1205
- continue;
1206
- }
1207
- const bucket = oneHopSourcesByNode.get(target.id) ?? /* @__PURE__ */ new Set();
1208
- bucket.add(source.title || source.id);
1209
- oneHopSourcesByNode.set(target.id, bucket);
1210
- continue;
1211
- }
1212
- if (rightDirect && !leftDirect) {
1213
- const source = nodeById.get(edge.source);
1214
- const target = nodeById.get(edge.target);
1215
- if (!isEligibleGraphNode(source) || !isEligibleGraphNode(target)) {
1216
- continue;
1217
- }
1218
- const bucket = oneHopSourcesByNode.get(source.id) ?? /* @__PURE__ */ new Set();
1219
- bucket.add(target.title || target.id);
1220
- oneHopSourcesByNode.set(source.id, bucket);
1221
- }
1222
- }
1223
- return { directAliasesByNode, oneHopSourcesByNode };
1224
- }
1225
- function normalizeScopeInput(scope) {
1226
- if (!scope) {
1227
- return [];
1228
- }
1229
- if (Array.isArray(scope)) {
1230
- return [...new Set(scope.map(normalizeScopeValue).filter(Boolean))];
1231
- }
1232
- return [...new Set(scope.split(",").map(normalizeScopeValue).filter(Boolean))];
1233
- }
1234
- function scopeMatches(itemScope, requestedScope) {
1235
- if (requestedScope.length === 0 || requestedScope.includes("global") || requestedScope.includes("*")) {
1236
- return true;
1237
- }
1238
- if (itemScope.length === 0 || itemScope.includes("global") || itemScope.includes("*")) {
1239
- return true;
1240
- }
1241
- return itemScope.some((scope) => requestedScope.includes(scope));
1242
- }
1243
- function deterministicInjectMatches(params) {
1244
- const message = params.message.trim();
1245
- if (!message) {
1246
- return [];
1247
- }
1248
- const requestedScope = normalizeScopeInput(params.scope);
1249
- const messageKeywords = new Set(tokenizeKeywords(message));
1250
- const { directAliasesByNode, oneHopSourcesByNode } = buildDeterministicEntityMatches(message, params.graph);
1251
- const normalizedMessage = normalizeText(message);
1252
- const matches = [];
1253
- for (const item of params.items) {
1254
- if (!scopeMatches(item.scope, requestedScope)) {
1255
- continue;
1256
- }
1257
- const reasons = [];
1258
- let signalScore = 0;
1259
- const triggerHits = [];
1260
- for (const trigger of item.triggers) {
1261
- if (containsPhrase(normalizedMessage, trigger)) {
1262
- triggerHits.push(trigger);
1263
- }
1264
- }
1265
- if (triggerHits.length > 0) {
1266
- const weight = Math.min(30, 12 + triggerHits.length * 6);
1267
- signalScore += weight;
1268
- reasons.push({
1269
- source: "trigger",
1270
- value: triggerHits.slice(0, 3).join(", "),
1271
- weight
1272
- });
1273
- }
1274
- const keywordHits = item.searchKeywords.filter((keyword) => messageKeywords.has(keyword));
1275
- if (keywordHits.length > 0) {
1276
- const weight = Math.min(18, keywordHits.length * 4);
1277
- signalScore += weight;
1278
- reasons.push({
1279
- source: "keyword",
1280
- value: keywordHits.slice(0, 4).join(", "),
1281
- weight
1282
- });
1283
- }
1284
- const directAliases = directAliasesByNode.get(item.noteNodeId);
1285
- if (directAliases && directAliases.size > 0) {
1286
- const weight = 18;
1287
- signalScore += weight;
1288
- reasons.push({
1289
- source: "entity",
1290
- value: [...directAliases].slice(0, 3).join(", "),
1291
- weight
1292
- });
1293
- }
1294
- const oneHopSources = oneHopSourcesByNode.get(item.noteNodeId);
1295
- if (oneHopSources && oneHopSources.size > 0) {
1296
- const weight = 10;
1297
- signalScore += weight;
1298
- reasons.push({
1299
- source: "graph_1hop",
1300
- value: `via ${[...oneHopSources].slice(0, 2).join(", ")}`,
1301
- weight
1302
- });
1303
- }
1304
- if (reasons.length === 0) {
1305
- continue;
1306
- }
1307
- const deterministicScore = item.priority + signalScore;
1308
- matches.push({
1309
- item,
1310
- score: deterministicScore,
1311
- deterministicScore,
1312
- llmScore: null,
1313
- reasons
1314
- });
1315
- }
1316
- return matches.sort((left, right) => {
1317
- if (right.score !== left.score) {
1318
- return right.score - left.score;
1319
- }
1320
- if (right.item.priority !== left.item.priority) {
1321
- return right.item.priority - left.item.priority;
1322
- }
1323
- return left.item.relativePath.localeCompare(right.item.relativePath);
1324
- });
1325
- }
1326
- function parseLlmMatches(rawOutput) {
1327
- const cleaned = rawOutput.replace(/^```(?:json)?\s*/i, "").replace(/\s*```$/i, "").trim();
1328
- if (!cleaned) {
1329
- return [];
1330
- }
1331
- let parsed;
1332
- try {
1333
- parsed = JSON.parse(cleaned);
1334
- } catch {
1335
- return [];
1336
- }
1337
- const rows = Array.isArray(parsed) ? parsed : parsed && typeof parsed === "object" && Array.isArray(parsed.matches) ? parsed.matches : [];
1338
- return rows.flatMap((row) => {
1339
- if (!row || typeof row !== "object" || Array.isArray(row)) {
1340
- return [];
1341
- }
1342
- const id = typeof row.id === "string" ? row.id.trim() : "";
1343
- const scoreRaw = row.score;
1344
- const score = typeof scoreRaw === "number" ? scoreRaw : Number.NaN;
1345
- const reason = typeof row.reason === "string" ? row.reason.trim() : "";
1346
- if (!id || !Number.isFinite(score)) {
1347
- return [];
1348
- }
1349
- return [{
1350
- id,
1351
- score: Math.max(0, Math.min(1, score)),
1352
- reason
1353
- }];
1354
- });
1355
- }
1356
- async function addLlmIntentMatches(params) {
1357
- if (params.items.length === 0) {
1358
- return params.matches;
1359
- }
1360
- const existingById = new Map(params.matches.map((match) => [match.item.id, match]));
1361
- const candidates = [...params.items].sort((left, right) => right.priority - left.priority || left.relativePath.localeCompare(right.relativePath)).slice(0, 40);
1362
- const prompt = [
1363
- "You rank ClawVault injectable memory items for an agent prompt.",
1364
- "Return strict JSON only in this shape:",
1365
- '{"matches":[{"id":"<candidate id>","score":0.0-1.0,"reason":"short why"}]}',
1366
- "Only include candidate IDs from the list below.",
1367
- "Use higher scores for items that are relevant to the user message intent even if wording differs.",
1368
- "",
1369
- `User message: ${params.message}`,
1370
- "",
1371
- "Candidates:",
1372
- ...candidates.map((item) => {
1373
- const preview = item.content.replace(/\s+/g, " ").trim().slice(0, 220);
1374
- return [
1375
- `- id: ${item.id}`,
1376
- ` category: ${item.category}`,
1377
- ` title: ${item.title}`,
1378
- ` triggers: ${item.triggers.join(", ") || "(none)"}`,
1379
- ` scope: ${item.scope.join(", ") || "(none)"}`,
1380
- ` content: ${preview || "(empty)"}`
1381
- ].join("\n");
1382
- })
1383
- ].join("\n");
1384
- const output = await requestLlmCompletion({
1385
- provider: params.provider,
1386
- prompt,
1387
- model: params.model,
1388
- temperature: 0.1,
1389
- maxTokens: 1200,
1390
- fetchImpl: params.fetchImpl,
1391
- systemPrompt: "You are an intent ranking engine. Respond with valid JSON only."
1392
- });
1393
- const parsed = parseLlmMatches(output);
1394
- if (parsed.length === 0) {
1395
- return params.matches;
1396
- }
1397
- const itemById = new Map(params.items.map((item) => [item.id, item]));
1398
- for (const row of parsed) {
1399
- const candidate = itemById.get(row.id);
1400
- if (!candidate) {
1401
- continue;
1402
- }
1403
- const llmWeight = row.score * 24;
1404
- const reason = row.reason || `intent score ${row.score.toFixed(2)}`;
1405
- const existing = existingById.get(row.id);
1406
- if (existing) {
1407
- existing.llmScore = row.score;
1408
- existing.score += llmWeight;
1409
- existing.reasons.push({
1410
- source: "llm_intent",
1411
- value: reason,
1412
- weight: llmWeight
1413
- });
1414
- continue;
1415
- }
1416
- if (row.score < 0.45) {
1417
- continue;
1418
- }
1419
- const seededDeterministic = candidate.priority * 0.2;
1420
- existingById.set(row.id, {
1421
- item: candidate,
1422
- deterministicScore: seededDeterministic,
1423
- llmScore: row.score,
1424
- score: seededDeterministic + llmWeight,
1425
- reasons: [{
1426
- source: "llm_intent",
1427
- value: reason,
1428
- weight: llmWeight
1429
- }]
1430
- });
1431
- }
1432
- return [...existingById.values()].sort((left, right) => {
1433
- if (right.score !== left.score) {
1434
- return right.score - left.score;
1435
- }
1436
- if (right.item.priority !== left.item.priority) {
1437
- return right.item.priority - left.item.priority;
1438
- }
1439
- return left.item.relativePath.localeCompare(right.item.relativePath);
1440
- });
1441
- }
1442
- async function readGraph(vaultPath) {
1443
- const resolvedPath = path3.resolve(vaultPath);
1444
- const loaded = loadMemoryGraphIndex(resolvedPath);
1445
- if (loaded?.graph) {
1446
- return loaded.graph;
1447
- }
1448
- try {
1449
- return await getMemoryGraph(resolvedPath);
1450
- } catch {
1451
- return {
1452
- schemaVersion: 1,
1453
- nodes: [],
1454
- edges: [],
1455
- stats: {
1456
- generatedAt: (/* @__PURE__ */ new Date(0)).toISOString(),
1457
- nodeCount: 0,
1458
- edgeCount: 0,
1459
- nodeTypeCounts: {},
1460
- edgeTypeCounts: {}
1461
- }
1462
- };
1463
- }
1464
- }
1465
- async function runPromptInjection(vaultPathInput, message, options = {}) {
1466
- const vaultPath = path3.resolve(vaultPathInput);
1467
- const maxResults = Math.max(1, Math.floor(options.maxResults ?? 8));
1468
- const useLlm = options.useLlm ?? false;
1469
- const startDeterministic = Date.now();
1470
- const items = indexInjectableItems(vaultPath);
1471
- const graph = await readGraph(vaultPath);
1472
- let matches = deterministicInjectMatches({
1473
- message,
1474
- items,
1475
- graph,
1476
- scope: options.scope
1477
- });
1478
- const deterministicMs = Date.now() - startDeterministic;
1479
- let llmProvider = null;
1480
- if (useLlm) {
1481
- llmProvider = await resolveLlmProvider();
1482
- if (llmProvider) {
1483
- try {
1484
- matches = await addLlmIntentMatches({
1485
- message,
1486
- items,
1487
- matches,
1488
- provider: llmProvider,
1489
- model: options.model,
1490
- fetchImpl: options.fetchImpl
1491
- });
1492
- } catch {
1493
- }
1494
- }
1495
- }
1496
- return {
1497
- message,
1498
- generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
1499
- deterministicMs,
1500
- llmProvider,
1501
- usedLlm: Boolean(useLlm && llmProvider),
1502
- matches: matches.slice(0, maxResults)
1503
- };
1504
- }
1505
-
1506
- // src/commands/inject.ts
1507
- function asPositiveInteger2(value, fallback) {
1508
- if (typeof value === "number" && Number.isInteger(value) && value > 0) {
1509
- return value;
1510
- }
1511
- if (typeof value === "string") {
1512
- const parsed = Number.parseInt(value, 10);
1513
- if (Number.isInteger(parsed) && parsed > 0) {
1514
- return parsed;
1515
- }
1516
- }
1517
- return fallback;
1518
- }
1519
- function asBoolean2(value, fallback) {
1520
- if (typeof value === "boolean") {
1521
- return value;
1522
- }
1523
- if (typeof value === "string") {
1524
- const normalized = value.trim().toLowerCase();
1525
- if (["true", "1", "yes", "on"].includes(normalized)) {
1526
- return true;
1527
- }
1528
- if (["false", "0", "no", "off"].includes(normalized)) {
1529
- return false;
1530
- }
1531
- }
1532
- return fallback;
1533
- }
1534
- function asStringArray2(value, fallback) {
1535
- if (Array.isArray(value)) {
1536
- const normalized = value.flatMap((entry) => typeof entry === "string" ? entry.split(",") : []).map((entry) => entry.trim()).filter(Boolean);
1537
- if (normalized.length > 0) {
1538
- return normalized;
1539
- }
1540
- }
1541
- if (typeof value === "string") {
1542
- const normalized = value.split(",").map((entry) => entry.trim()).filter(Boolean);
1543
- if (normalized.length > 0) {
1544
- return normalized;
1545
- }
1546
- }
1547
- return fallback;
1548
- }
1549
- function readInjectConfig(vaultPath) {
1550
- const config = listConfig(vaultPath);
1551
- const inject = config.inject ?? {};
1552
- return {
1553
- maxResults: asPositiveInteger2(inject.maxResults, 8),
1554
- useLlm: asBoolean2(inject.useLlm, true),
1555
- scope: asStringArray2(inject.scope, ["global"])
1556
- };
1557
- }
1558
- function formatReasons(match) {
1559
- return match.reasons.map((reason) => `${reason.source}:${reason.value}`).join(" | ");
1560
- }
1561
- function formatInjectMarkdown(result) {
1562
- const lines = [];
1563
- lines.push(`## Prompt Injection for: ${result.message}`);
1564
- lines.push("");
1565
- lines.push(`- Deterministic matching: ${result.deterministicMs}ms`);
1566
- lines.push(`- LLM fuzzy matching: ${result.usedLlm ? `enabled (${result.llmProvider})` : "disabled"}`);
1567
- lines.push("");
1568
- if (result.matches.length === 0) {
1569
- lines.push("_No injectable rules matched this message._");
1570
- return lines.join("\n");
1571
- }
1572
- for (const [index, match] of result.matches.entries()) {
1573
- lines.push(`### ${index + 1}. ${match.item.title}`);
1574
- lines.push(`- Path: ${match.item.relativePath}`);
1575
- lines.push(`- Category: ${match.item.category}`);
1576
- lines.push(`- Priority: ${match.item.priority}`);
1577
- lines.push(`- Score: ${match.score.toFixed(2)}`);
1578
- lines.push(`- Match sources: ${formatReasons(match)}`);
1579
- lines.push("");
1580
- lines.push(match.item.content || "_No content._");
1581
- lines.push("");
1582
- }
1583
- return lines.join("\n").trimEnd();
1584
- }
1585
- async function buildInjectionResult(message, options) {
1586
- const normalizedMessage = message.trim();
1587
- if (!normalizedMessage) {
1588
- throw new Error("Message is required for inject.");
1589
- }
1590
- const vaultPath = path4.resolve(options.vaultPath);
1591
- const config = readInjectConfig(vaultPath);
1592
- const maxResults = options.maxResults ?? config.maxResults;
1593
- const useLlm = options.useLlm ?? config.useLlm;
1594
- const scope = options.scope ?? config.scope;
1595
- return runPromptInjection(vaultPath, normalizedMessage, {
1596
- maxResults,
1597
- useLlm,
1598
- scope,
1599
- model: options.model
1600
- });
1601
- }
1602
- async function injectCommand(message, options) {
1603
- const result = await buildInjectionResult(message, options);
1604
- if ((options.format ?? "markdown") === "json") {
1605
- console.log(JSON.stringify(result, null, 2));
1606
- return;
1607
- }
1608
- console.log(formatInjectMarkdown(result));
1609
- }
1610
- function parsePositiveInteger(raw, label) {
1611
- const parsed = Number.parseInt(raw, 10);
1612
- if (!Number.isFinite(parsed) || parsed <= 0) {
1613
- throw new Error(`Invalid ${label}: ${raw}`);
1614
- }
1615
- return parsed;
1616
- }
1617
- function registerInjectCommand(program) {
1618
- program.command("inject <message>").description("Inject rules, decisions, and preferences into your prompt context").option("-v, --vault <path>", "Vault path").option("-n, --max-results <n>", "Maximum number of injected items").option("--scope <scope>", "Comma-separated scope filter override").option("--enable-llm", "Enable optional LLM fuzzy intent matching").option("--disable-llm", "Disable optional LLM fuzzy intent matching").option("--format <format>", "Output format (markdown|json)", "markdown").option("--model <model>", "Override LLM model when fuzzy matching is enabled").action(async (message, rawOptions) => {
1619
- const format = rawOptions.format === "json" ? "json" : "markdown";
1620
- const useLlm = rawOptions.enableLlm ? true : rawOptions.disableLlm ? false : void 0;
1621
- await injectCommand(message, {
1622
- vaultPath: rawOptions.vault ?? process.env.CLAWVAULT_PATH ?? process.cwd(),
1623
- maxResults: rawOptions.maxResults ? parsePositiveInteger(rawOptions.maxResults, "max-results") : void 0,
1624
- useLlm,
1625
- scope: rawOptions.scope,
1626
- format,
1627
- model: rawOptions.model
1628
- });
1629
- });
1630
- }
1631
- // Annotate the CommonJS export names for ESM import in node:
1632
- 0 && (module.exports = {
1633
- buildInjectionResult,
1634
- injectCommand,
1635
- registerInjectCommand
1636
- });