clawvault 3.1.0 → 3.2.0

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 (289) 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 +1368 -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-F2JEUD4J.js → chunk-23YDQ3QU.js} +6 -8
  22. package/dist/{chunk-C7OK5WKP.js → chunk-2JQ3O2YL.js} +4 -4
  23. package/dist/{chunk-VR5NE7PZ.js → chunk-2RAZ4ZFE.js} +1 -1
  24. package/dist/chunk-2ZDO52B4.js +52 -0
  25. package/dist/{chunk-ZZA73MFY.js → chunk-33DOSHTA.js} +176 -36
  26. package/dist/chunk-33VSQP4J.js +37 -0
  27. package/dist/chunk-4BQTQMJP.js +93 -0
  28. package/dist/{chunk-GUKMRGM7.js → chunk-4OXMU5S2.js} +1 -1
  29. package/dist/{chunk-62YTUT6J.js → chunk-4PY655YM.js} +15 -3
  30. package/dist/chunk-6FH3IULF.js +352 -0
  31. package/dist/{chunk-3NSBOUT3.js → chunk-77Q5CSPJ.js} +404 -80
  32. package/dist/{chunk-4VQTUVH7.js → chunk-7YZWHM36.js} +52 -26
  33. package/dist/chunk-BSJ6RIT7.js +447 -0
  34. package/dist/chunk-BUEW6IIK.js +364 -0
  35. package/dist/{chunk-LI4O6NVK.js → chunk-CLJTREDS.js} +74 -14
  36. package/dist/chunk-EK6S23ZB.js +469 -0
  37. package/dist/{chunk-LNJA2UGL.js → chunk-ESFLMDRB.js} +9 -86
  38. package/dist/{chunk-H34S76MB.js → chunk-ESVS6K2B.js} +6 -6
  39. package/dist/{chunk-WAZ3NLWL.js → chunk-F55HGNU4.js} +0 -47
  40. package/dist/{chunk-QK3UCXWL.js → chunk-FHFUXL6G.js} +2 -2
  41. package/dist/{chunk-H62BP7RI.js → chunk-GAOWA7GR.js} +212 -46
  42. package/dist/chunk-GGA32J2R.js +784 -0
  43. package/dist/chunk-GNJL4YGR.js +79 -0
  44. package/dist/chunk-IVRIKYFE.js +520 -0
  45. package/dist/chunk-MDIH26GC.js +183 -0
  46. package/dist/{chunk-LYHGEHXG.js → chunk-MFAWT5O5.js} +0 -1
  47. package/dist/chunk-MM6QGW3P.js +207 -0
  48. package/dist/{chunk-P5EPF6MB.js → chunk-MW5C6ZQA.js} +110 -13
  49. package/dist/chunk-NCKFNBHJ.js +257 -0
  50. package/dist/{chunk-QBLMXKF2.js → chunk-OIWVQYQF.js} +1 -1
  51. package/dist/{chunk-42MXU7A6.js → chunk-P62WHA27.js} +58 -47
  52. package/dist/chunk-PBACDKKP.js +66 -0
  53. package/dist/{chunk-VGLOTGAS.js → chunk-QSHD36LH.js} +2 -2
  54. package/dist/{chunk-OZ7RIXTO.js → chunk-QSRRMEYM.js} +2 -2
  55. package/dist/chunk-QVEERJSP.js +152 -0
  56. package/dist/{chunk-N2AXRYLC.js → chunk-QWQ3TIKS.js} +1 -1
  57. package/dist/{chunk-3DHXQHYG.js → chunk-R2MIW5G7.js} +1 -1
  58. package/dist/{chunk-SJSFRIYS.js → chunk-SLXOR3CC.js} +2 -2
  59. package/dist/chunk-SS4B7P7V.js +99 -0
  60. package/dist/{chunk-JY6FYXIT.js → chunk-STCQGCEQ.js} +6 -11
  61. package/dist/chunk-TIGW564L.js +628 -0
  62. package/dist/chunk-U4O6C46S.js +154 -0
  63. package/dist/{chunk-ITPEXLHA.js → chunk-URXDAUVH.js} +24 -5
  64. package/dist/chunk-VSL7KY3M.js +189 -0
  65. package/dist/{chunk-U55BGUAU.js → chunk-W4SPAEE7.js} +6 -6
  66. package/dist/chunk-WMGIIABP.js +15 -0
  67. package/dist/{chunk-33UGEQRT.js → chunk-X3SPPUFG.js} +151 -64
  68. package/dist/chunk-Y6VJKXGL.js +373 -0
  69. package/dist/{chunk-3WRJEKN4.js → chunk-ZN54U2OZ.js} +123 -10
  70. package/dist/cli/index.js +34 -24
  71. package/dist/commands/archive.js +3 -3
  72. package/dist/commands/backlog.js +3 -3
  73. package/dist/commands/blocked.js +3 -3
  74. package/dist/commands/canvas.d.ts +15 -0
  75. package/dist/commands/canvas.js +200 -0
  76. package/dist/commands/checkpoint.js +2 -2
  77. package/dist/commands/compat.js +2 -2
  78. package/dist/commands/context.js +8 -6
  79. package/dist/commands/doctor.d.ts +11 -7
  80. package/dist/commands/doctor.js +18 -16
  81. package/dist/commands/embed.js +5 -6
  82. package/dist/commands/entities.js +2 -2
  83. package/dist/commands/graph.js +4 -4
  84. package/dist/commands/inject.d.ts +1 -1
  85. package/dist/commands/inject.js +5 -6
  86. package/dist/commands/kanban.js +4 -4
  87. package/dist/commands/link.js +5 -5
  88. package/dist/commands/migrate-observations.js +4 -4
  89. package/dist/commands/observe.d.ts +0 -1
  90. package/dist/commands/observe.js +14 -13
  91. package/dist/commands/project.js +5 -5
  92. package/dist/commands/rebuild-embeddings.d.ts +21 -0
  93. package/dist/commands/rebuild-embeddings.js +91 -0
  94. package/dist/commands/rebuild.js +12 -11
  95. package/dist/commands/recover.js +3 -3
  96. package/dist/commands/reflect.js +6 -7
  97. package/dist/commands/repair-session.js +1 -1
  98. package/dist/commands/replay.js +14 -14
  99. package/dist/commands/session-recap.js +1 -1
  100. package/dist/commands/setup.d.ts +2 -89
  101. package/dist/commands/setup.js +3 -21
  102. package/dist/commands/shell-init.js +1 -1
  103. package/dist/commands/sleep.d.ts +1 -1
  104. package/dist/commands/sleep.js +20 -19
  105. package/dist/commands/status.d.ts +2 -0
  106. package/dist/commands/status.js +57 -35
  107. package/dist/commands/sync-bd.d.ts +10 -0
  108. package/dist/commands/sync-bd.js +10 -0
  109. package/dist/commands/tailscale.d.ts +52 -0
  110. package/dist/commands/tailscale.js +26 -0
  111. package/dist/commands/task.js +4 -4
  112. package/dist/commands/template.js +2 -2
  113. package/dist/commands/wake.d.ts +1 -1
  114. package/dist/commands/wake.js +11 -10
  115. package/dist/commands/workgraph.d.ts +124 -0
  116. package/dist/commands/workgraph.js +38 -0
  117. package/dist/index.d.ts +341 -191
  118. package/dist/index.js +446 -116
  119. package/dist/{inject-Bzi5E-By.d.ts → inject-DYUrDqQO.d.ts} +3 -3
  120. package/dist/ledger-B7g7jhqG.d.ts +44 -0
  121. package/dist/lib/auto-linker.js +2 -2
  122. package/dist/lib/canvas-layout.d.ts +115 -0
  123. package/dist/lib/canvas-layout.js +35 -0
  124. package/dist/lib/config.d.ts +27 -3
  125. package/dist/lib/config.js +4 -2
  126. package/dist/lib/entity-index.js +1 -1
  127. package/dist/lib/project-utils.js +4 -4
  128. package/dist/lib/session-repair.js +1 -1
  129. package/dist/lib/session-utils.js +1 -1
  130. package/dist/lib/tailscale.d.ts +225 -0
  131. package/dist/lib/tailscale.js +50 -0
  132. package/dist/lib/task-utils.js +3 -3
  133. package/dist/lib/template-engine.js +1 -1
  134. package/dist/lib/webdav.d.ts +109 -0
  135. package/dist/lib/webdav.js +35 -0
  136. package/dist/onnxruntime_binding-5QEF3SUC.node +0 -0
  137. package/dist/onnxruntime_binding-BKPKNEGC.node +0 -0
  138. package/dist/onnxruntime_binding-FMOXGIUT.node +0 -0
  139. package/dist/onnxruntime_binding-OI2KMXC5.node +0 -0
  140. package/dist/onnxruntime_binding-UX44MLAZ.node +0 -0
  141. package/dist/onnxruntime_binding-Y2W7N7WY.node +0 -0
  142. package/dist/openclaw-plugin.d.ts +8 -0
  143. package/dist/openclaw-plugin.js +14 -0
  144. package/dist/registry-BR4326o0.d.ts +30 -0
  145. package/dist/store-CA-6sKCJ.d.ts +34 -0
  146. package/dist/thread-B9LhXNU0.d.ts +41 -0
  147. package/dist/transformers.node-A2ZRORSQ.js +46775 -0
  148. package/dist/{types-Y2_Um2Ls.d.ts → types-BbWJoC1c.d.ts} +1 -44
  149. package/dist/workgraph/index.d.ts +5 -0
  150. package/dist/workgraph/index.js +23 -0
  151. package/dist/workgraph/ledger.d.ts +2 -0
  152. package/dist/workgraph/ledger.js +25 -0
  153. package/dist/workgraph/registry.d.ts +2 -0
  154. package/dist/workgraph/registry.js +19 -0
  155. package/dist/workgraph/store.d.ts +2 -0
  156. package/dist/workgraph/store.js +25 -0
  157. package/dist/workgraph/thread.d.ts +2 -0
  158. package/dist/workgraph/thread.js +25 -0
  159. package/dist/workgraph/types.d.ts +54 -0
  160. package/dist/workgraph/types.js +7 -0
  161. package/hooks/clawvault/HOOK.md +113 -0
  162. package/hooks/clawvault/handler.js +1561 -0
  163. package/hooks/clawvault/handler.test.js +510 -0
  164. package/hooks/clawvault/openclaw.plugin.json +72 -0
  165. package/openclaw.plugin.json +65 -38
  166. package/package.json +25 -22
  167. package/dist/chunk-3RG5ZIWI.js +0 -10
  168. package/dist/chunk-3ZIH425O.js +0 -871
  169. package/dist/chunk-6U6MK36V.js +0 -205
  170. package/dist/chunk-CMB7UL7C.js +0 -327
  171. package/dist/chunk-D2H45LON.js +0 -1074
  172. package/dist/chunk-E7MFQB6D.js +0 -163
  173. package/dist/chunk-GQSLDZTS.js +0 -560
  174. package/dist/chunk-MFM6K7PU.js +0 -374
  175. package/dist/chunk-MXSSG3QU.js +0 -42
  176. package/dist/chunk-OCGVIN3L.js +0 -88
  177. package/dist/chunk-PAH27GSN.js +0 -108
  178. package/dist/chunk-YCUNCH2I.js +0 -78
  179. package/dist/cli/index.cjs +0 -8584
  180. package/dist/cli/index.d.cts +0 -5
  181. package/dist/commands/archive.cjs +0 -287
  182. package/dist/commands/archive.d.cts +0 -11
  183. package/dist/commands/backlog.cjs +0 -721
  184. package/dist/commands/backlog.d.cts +0 -53
  185. package/dist/commands/blocked.cjs +0 -204
  186. package/dist/commands/blocked.d.cts +0 -26
  187. package/dist/commands/checkpoint.cjs +0 -244
  188. package/dist/commands/checkpoint.d.cts +0 -41
  189. package/dist/commands/compat.cjs +0 -294
  190. package/dist/commands/compat.d.cts +0 -28
  191. package/dist/commands/context.cjs +0 -2990
  192. package/dist/commands/context.d.cts +0 -2
  193. package/dist/commands/doctor.cjs +0 -2986
  194. package/dist/commands/doctor.d.cts +0 -21
  195. package/dist/commands/embed.cjs +0 -232
  196. package/dist/commands/embed.d.cts +0 -17
  197. package/dist/commands/entities.cjs +0 -141
  198. package/dist/commands/entities.d.cts +0 -7
  199. package/dist/commands/graph.cjs +0 -501
  200. package/dist/commands/graph.d.cts +0 -21
  201. package/dist/commands/inject.cjs +0 -1636
  202. package/dist/commands/inject.d.cts +0 -2
  203. package/dist/commands/kanban.cjs +0 -884
  204. package/dist/commands/kanban.d.cts +0 -63
  205. package/dist/commands/link.cjs +0 -965
  206. package/dist/commands/link.d.cts +0 -11
  207. package/dist/commands/migrate-observations.cjs +0 -362
  208. package/dist/commands/migrate-observations.d.cts +0 -19
  209. package/dist/commands/observe.cjs +0 -4099
  210. package/dist/commands/observe.d.cts +0 -23
  211. package/dist/commands/project.cjs +0 -1341
  212. package/dist/commands/project.d.cts +0 -85
  213. package/dist/commands/rebuild.cjs +0 -3136
  214. package/dist/commands/rebuild.d.cts +0 -11
  215. package/dist/commands/recover.cjs +0 -361
  216. package/dist/commands/recover.d.cts +0 -38
  217. package/dist/commands/reflect.cjs +0 -1008
  218. package/dist/commands/reflect.d.cts +0 -11
  219. package/dist/commands/repair-session.cjs +0 -457
  220. package/dist/commands/repair-session.d.cts +0 -38
  221. package/dist/commands/replay.cjs +0 -4103
  222. package/dist/commands/replay.d.cts +0 -16
  223. package/dist/commands/session-recap.cjs +0 -353
  224. package/dist/commands/session-recap.d.cts +0 -27
  225. package/dist/commands/setup.cjs +0 -1278
  226. package/dist/commands/setup.d.cts +0 -99
  227. package/dist/commands/shell-init.cjs +0 -75
  228. package/dist/commands/shell-init.d.cts +0 -7
  229. package/dist/commands/sleep.cjs +0 -6029
  230. package/dist/commands/sleep.d.cts +0 -36
  231. package/dist/commands/status.cjs +0 -2737
  232. package/dist/commands/status.d.cts +0 -52
  233. package/dist/commands/task.cjs +0 -1236
  234. package/dist/commands/task.d.cts +0 -97
  235. package/dist/commands/template.cjs +0 -457
  236. package/dist/commands/template.d.cts +0 -36
  237. package/dist/commands/wake.cjs +0 -2627
  238. package/dist/commands/wake.d.cts +0 -22
  239. package/dist/context-BUGaWpyL.d.cts +0 -46
  240. package/dist/index.cjs +0 -12373
  241. package/dist/index.d.cts +0 -854
  242. package/dist/inject-Bzi5E-By.d.cts +0 -137
  243. package/dist/lib/auto-linker.cjs +0 -176
  244. package/dist/lib/auto-linker.d.cts +0 -26
  245. package/dist/lib/config.cjs +0 -78
  246. package/dist/lib/config.d.cts +0 -11
  247. package/dist/lib/entity-index.cjs +0 -84
  248. package/dist/lib/entity-index.d.cts +0 -26
  249. package/dist/lib/project-utils.cjs +0 -864
  250. package/dist/lib/project-utils.d.cts +0 -97
  251. package/dist/lib/session-repair.cjs +0 -239
  252. package/dist/lib/session-repair.d.cts +0 -110
  253. package/dist/lib/session-utils.cjs +0 -209
  254. package/dist/lib/session-utils.d.cts +0 -63
  255. package/dist/lib/task-utils.cjs +0 -1137
  256. package/dist/lib/task-utils.d.cts +0 -208
  257. package/dist/lib/template-engine.cjs +0 -47
  258. package/dist/lib/template-engine.d.cts +0 -11
  259. package/dist/plugin/index.cjs +0 -1907
  260. package/dist/plugin/index.d.cts +0 -36
  261. package/dist/plugin/index.d.ts +0 -36
  262. package/dist/plugin/index.js +0 -572
  263. package/dist/plugin/inject.cjs +0 -356
  264. package/dist/plugin/inject.d.cts +0 -54
  265. package/dist/plugin/inject.d.ts +0 -54
  266. package/dist/plugin/inject.js +0 -17
  267. package/dist/plugin/observe.cjs +0 -631
  268. package/dist/plugin/observe.d.cts +0 -39
  269. package/dist/plugin/observe.d.ts +0 -39
  270. package/dist/plugin/observe.js +0 -18
  271. package/dist/plugin/templates.cjs +0 -593
  272. package/dist/plugin/templates.d.cts +0 -52
  273. package/dist/plugin/templates.d.ts +0 -52
  274. package/dist/plugin/templates.js +0 -25
  275. package/dist/plugin/types.cjs +0 -18
  276. package/dist/plugin/types.d.cts +0 -209
  277. package/dist/plugin/types.d.ts +0 -209
  278. package/dist/plugin/types.js +0 -0
  279. package/dist/plugin/vault.cjs +0 -927
  280. package/dist/plugin/vault.d.cts +0 -68
  281. package/dist/plugin/vault.d.ts +0 -68
  282. package/dist/plugin/vault.js +0 -22
  283. package/dist/types-Y2_Um2Ls.d.cts +0 -205
  284. package/templates/memory-event.md +0 -67
  285. package/templates/party.md +0 -63
  286. package/templates/primitive-registry.yaml +0 -551
  287. package/templates/run.md +0 -68
  288. package/templates/trigger.md +0 -68
  289. package/templates/workspace.md +0 -50
@@ -0,0 +1,79 @@
1
+ // src/lib/config.ts
2
+ import * as fs from "fs";
3
+ import * as path from "path";
4
+ function getVaultPath() {
5
+ const vaultPath = process.env.CLAWVAULT_PATH;
6
+ if (!vaultPath) {
7
+ throw new Error("CLAWVAULT_PATH environment variable not set");
8
+ }
9
+ return path.resolve(vaultPath);
10
+ }
11
+ function findNearestVaultPath(startPath = process.cwd()) {
12
+ let current = path.resolve(startPath);
13
+ while (true) {
14
+ if (fs.existsSync(path.join(current, ".clawvault.json"))) {
15
+ return current;
16
+ }
17
+ const parent = path.dirname(current);
18
+ if (parent === current) {
19
+ return null;
20
+ }
21
+ current = parent;
22
+ }
23
+ }
24
+ function validateVaultPath(vaultPath) {
25
+ if (!vaultPath || typeof vaultPath !== "string") return null;
26
+ const resolved = path.resolve(vaultPath);
27
+ if (!path.isAbsolute(resolved)) return null;
28
+ try {
29
+ const stat = fs.statSync(resolved);
30
+ if (!stat.isDirectory()) return null;
31
+ } catch {
32
+ return null;
33
+ }
34
+ const configPath = path.join(resolved, ".clawvault.json");
35
+ if (!fs.existsSync(configPath)) return null;
36
+ return resolved;
37
+ }
38
+ function resolveAgentVaultPath(agentVaults, agentId) {
39
+ if (!agentId || typeof agentId !== "string") return null;
40
+ if (!agentVaults || typeof agentVaults !== "object" || Array.isArray(agentVaults)) {
41
+ return null;
42
+ }
43
+ const agentPath = agentVaults[agentId];
44
+ if (!agentPath || typeof agentPath !== "string") return null;
45
+ return validateVaultPath(agentPath);
46
+ }
47
+ function resolveVaultPath(options = {}) {
48
+ if (options.explicitPath) {
49
+ return path.resolve(options.explicitPath);
50
+ }
51
+ if (options.agentId && options.pluginConfig?.agentVaults) {
52
+ const agentVaultPath = resolveAgentVaultPath(
53
+ options.pluginConfig.agentVaults,
54
+ options.agentId
55
+ );
56
+ if (agentVaultPath) {
57
+ return agentVaultPath;
58
+ }
59
+ }
60
+ if (options.pluginConfig?.vaultPath) {
61
+ const validated = validateVaultPath(options.pluginConfig.vaultPath);
62
+ if (validated) return validated;
63
+ }
64
+ if (process.env.CLAWVAULT_PATH) {
65
+ return path.resolve(process.env.CLAWVAULT_PATH);
66
+ }
67
+ const discovered = findNearestVaultPath(options.cwd ?? process.cwd());
68
+ if (discovered) {
69
+ return discovered;
70
+ }
71
+ throw new Error("No vault path found. Set CLAWVAULT_PATH, use --vault, or run inside a vault.");
72
+ }
73
+
74
+ export {
75
+ getVaultPath,
76
+ findNearestVaultPath,
77
+ resolveAgentVaultPath,
78
+ resolveVaultPath
79
+ };
@@ -0,0 +1,520 @@
1
+ // src/lib/webdav.ts
2
+ import * as fs from "fs";
3
+ import * as path from "path";
4
+ var WEBDAV_PREFIX = "/webdav";
5
+ var BLOCKED_PATHS = [
6
+ ".clawvault",
7
+ ".git",
8
+ ".obsidian",
9
+ "node_modules"
10
+ ];
11
+ var SUPPORTED_METHODS = ["GET", "PUT", "DELETE", "MKCOL", "PROPFIND", "OPTIONS", "HEAD", "MOVE", "COPY"];
12
+ function toRequestSegments(requestPath) {
13
+ return requestPath.replace(/\\/g, "/").split("/").filter(Boolean);
14
+ }
15
+ function isWithinRoot(fullPath, rootPath) {
16
+ const resolvedRoot = path.resolve(rootPath);
17
+ const relative2 = path.relative(resolvedRoot, fullPath);
18
+ return !(relative2.startsWith("..") || path.isAbsolute(relative2));
19
+ }
20
+ function isPathSafe(requestPath, rootPath) {
21
+ const pathParts = toRequestSegments(requestPath);
22
+ if (pathParts.includes("..")) {
23
+ return false;
24
+ }
25
+ const normalizedRelativePath = path.normalize(pathParts.join(path.sep));
26
+ const fullPath = path.resolve(rootPath, normalizedRelativePath);
27
+ if (!isWithinRoot(fullPath, rootPath)) {
28
+ return false;
29
+ }
30
+ for (const part of pathParts) {
31
+ if (BLOCKED_PATHS.includes(part)) {
32
+ return false;
33
+ }
34
+ }
35
+ return true;
36
+ }
37
+ function resolveWebDAVPath(requestPath, rootPath) {
38
+ const pathParts = toRequestSegments(requestPath);
39
+ if (pathParts.includes("..")) {
40
+ return null;
41
+ }
42
+ const normalizedRelativePath = path.normalize(pathParts.join(path.sep));
43
+ const fullPath = path.resolve(rootPath, normalizedRelativePath);
44
+ if (!isWithinRoot(fullPath, rootPath)) {
45
+ return null;
46
+ }
47
+ return fullPath;
48
+ }
49
+ function checkAuth(req, auth) {
50
+ if (!auth) {
51
+ return true;
52
+ }
53
+ const authHeader = req.headers.authorization;
54
+ if (!authHeader || !authHeader.startsWith("Basic ")) {
55
+ return false;
56
+ }
57
+ const base64Credentials = authHeader.slice(6);
58
+ const credentials = Buffer.from(base64Credentials, "base64").toString("utf-8");
59
+ const [username, password] = credentials.split(":");
60
+ return username === auth.username && password === auth.password;
61
+ }
62
+ function escapeXml(str) {
63
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
64
+ }
65
+ function formatWebDAVDate(date) {
66
+ return date.toUTCString();
67
+ }
68
+ function generatePropfindEntry(href, stats, isCollection) {
69
+ const resourceType = isCollection ? "<D:resourcetype><D:collection/></D:resourcetype>" : "<D:resourcetype/>";
70
+ const contentLength = stats && !isCollection ? `<D:getcontentlength>${stats.size}</D:getcontentlength>` : "";
71
+ const lastModified = stats ? `<D:getlastmodified>${formatWebDAVDate(stats.mtime)}</D:getlastmodified>` : "";
72
+ const etag = stats ? `<D:getetag>"${stats.mtime.getTime().toString(16)}-${stats.size.toString(16)}"</D:getetag>` : "";
73
+ const contentType = !isCollection ? "<D:getcontenttype>application/octet-stream</D:getcontenttype>" : "";
74
+ return ` <D:response>
75
+ <D:href>${escapeXml(href)}</D:href>
76
+ <D:propstat>
77
+ <D:prop>
78
+ ${resourceType}
79
+ ${contentLength}
80
+ ${lastModified}
81
+ ${etag}
82
+ ${contentType}
83
+ </D:prop>
84
+ <D:status>HTTP/1.1 200 OK</D:status>
85
+ </D:propstat>
86
+ </D:response>`;
87
+ }
88
+ function generatePropfindResponse(entries) {
89
+ const responseEntries = entries.map(
90
+ (e) => generatePropfindEntry(e.href, e.stats, e.isCollection)
91
+ ).join("\n");
92
+ return `<?xml version="1.0" encoding="utf-8"?>
93
+ <D:multistatus xmlns:D="DAV:">
94
+ ${responseEntries}
95
+ </D:multistatus>`;
96
+ }
97
+ function handleOptions(res, prefix) {
98
+ res.writeHead(200, {
99
+ "Allow": SUPPORTED_METHODS.join(", "),
100
+ "DAV": "1, 2",
101
+ "Content-Length": "0",
102
+ "Access-Control-Allow-Origin": "*",
103
+ "Access-Control-Allow-Methods": SUPPORTED_METHODS.join(", "),
104
+ "Access-Control-Allow-Headers": "Content-Type, Depth, Destination, Overwrite, Authorization",
105
+ "MS-Author-Via": "DAV"
106
+ });
107
+ res.end();
108
+ }
109
+ function handleHead(res, filePath) {
110
+ try {
111
+ const stats = fs.statSync(filePath);
112
+ if (stats.isDirectory()) {
113
+ res.writeHead(200, {
114
+ "Content-Type": "httpd/unix-directory",
115
+ "Last-Modified": formatWebDAVDate(stats.mtime),
116
+ "ETag": `"${stats.mtime.getTime().toString(16)}"`,
117
+ "Access-Control-Allow-Origin": "*"
118
+ });
119
+ } else {
120
+ res.writeHead(200, {
121
+ "Content-Type": "application/octet-stream",
122
+ "Content-Length": stats.size.toString(),
123
+ "Last-Modified": formatWebDAVDate(stats.mtime),
124
+ "ETag": `"${stats.mtime.getTime().toString(16)}-${stats.size.toString(16)}"`,
125
+ "Access-Control-Allow-Origin": "*"
126
+ });
127
+ }
128
+ res.end();
129
+ } catch (err) {
130
+ res.writeHead(404, { "Content-Type": "text/plain", "Access-Control-Allow-Origin": "*" });
131
+ res.end("Not Found");
132
+ }
133
+ }
134
+ function handleGet(res, filePath) {
135
+ try {
136
+ const stats = fs.statSync(filePath);
137
+ if (stats.isDirectory()) {
138
+ const entries = fs.readdirSync(filePath);
139
+ const listing = entries.join("\n");
140
+ res.writeHead(200, {
141
+ "Content-Type": "text/plain",
142
+ "Content-Length": Buffer.byteLength(listing).toString(),
143
+ "Access-Control-Allow-Origin": "*"
144
+ });
145
+ res.end(listing);
146
+ } else {
147
+ const content = fs.readFileSync(filePath);
148
+ res.writeHead(200, {
149
+ "Content-Type": "application/octet-stream",
150
+ "Content-Length": content.length.toString(),
151
+ "Last-Modified": formatWebDAVDate(stats.mtime),
152
+ "ETag": `"${stats.mtime.getTime().toString(16)}-${stats.size.toString(16)}"`,
153
+ "Access-Control-Allow-Origin": "*"
154
+ });
155
+ res.end(content);
156
+ }
157
+ } catch (err) {
158
+ res.writeHead(404, { "Content-Type": "text/plain", "Access-Control-Allow-Origin": "*" });
159
+ res.end("Not Found");
160
+ }
161
+ }
162
+ function handlePut(res, filePath, body) {
163
+ try {
164
+ const exists = fs.existsSync(filePath);
165
+ const dir = path.dirname(filePath);
166
+ if (!fs.existsSync(dir)) {
167
+ fs.mkdirSync(dir, { recursive: true });
168
+ }
169
+ fs.writeFileSync(filePath, body);
170
+ const status = exists ? 204 : 201;
171
+ res.writeHead(status, {
172
+ "Content-Length": "0",
173
+ "Access-Control-Allow-Origin": "*"
174
+ });
175
+ res.end();
176
+ } catch (err) {
177
+ res.writeHead(500, { "Content-Type": "text/plain", "Access-Control-Allow-Origin": "*" });
178
+ res.end(`Error: ${err}`);
179
+ }
180
+ }
181
+ function handleDelete(res, filePath) {
182
+ try {
183
+ if (!fs.existsSync(filePath)) {
184
+ res.writeHead(404, { "Content-Type": "text/plain", "Access-Control-Allow-Origin": "*" });
185
+ res.end("Not Found");
186
+ return;
187
+ }
188
+ const stats = fs.statSync(filePath);
189
+ if (stats.isDirectory()) {
190
+ fs.rmSync(filePath, { recursive: true });
191
+ } else {
192
+ fs.unlinkSync(filePath);
193
+ }
194
+ res.writeHead(204, {
195
+ "Content-Length": "0",
196
+ "Access-Control-Allow-Origin": "*"
197
+ });
198
+ res.end();
199
+ } catch (err) {
200
+ res.writeHead(500, { "Content-Type": "text/plain", "Access-Control-Allow-Origin": "*" });
201
+ res.end(`Error: ${err}`);
202
+ }
203
+ }
204
+ function handleMkcol(res, filePath) {
205
+ try {
206
+ if (fs.existsSync(filePath)) {
207
+ res.writeHead(405, { "Content-Type": "text/plain", "Access-Control-Allow-Origin": "*" });
208
+ res.end("Resource already exists");
209
+ return;
210
+ }
211
+ const parent = path.dirname(filePath);
212
+ if (!fs.existsSync(parent)) {
213
+ res.writeHead(409, { "Content-Type": "text/plain", "Access-Control-Allow-Origin": "*" });
214
+ res.end("Parent directory does not exist");
215
+ return;
216
+ }
217
+ fs.mkdirSync(filePath);
218
+ res.writeHead(201, {
219
+ "Content-Length": "0",
220
+ "Access-Control-Allow-Origin": "*"
221
+ });
222
+ res.end();
223
+ } catch (err) {
224
+ res.writeHead(500, { "Content-Type": "text/plain", "Access-Control-Allow-Origin": "*" });
225
+ res.end(`Error: ${err}`);
226
+ }
227
+ }
228
+ function handlePropfind(res, filePath, webdavPath, prefix, depth) {
229
+ try {
230
+ if (!fs.existsSync(filePath)) {
231
+ res.writeHead(404, { "Content-Type": "text/plain", "Access-Control-Allow-Origin": "*" });
232
+ res.end("Not Found");
233
+ return;
234
+ }
235
+ const stats = fs.statSync(filePath);
236
+ const entries = [];
237
+ const normalizedWebdavPath = webdavPath.startsWith("/") ? webdavPath : "/" + webdavPath;
238
+ const href = prefix + normalizedWebdavPath;
239
+ entries.push({
240
+ href: href.endsWith("/") || stats.isDirectory() ? href : href,
241
+ stats,
242
+ isCollection: stats.isDirectory()
243
+ });
244
+ if (stats.isDirectory() && depth !== "0") {
245
+ try {
246
+ const children = fs.readdirSync(filePath);
247
+ for (const child of children) {
248
+ if (BLOCKED_PATHS.includes(child)) {
249
+ continue;
250
+ }
251
+ const childPath = path.join(filePath, child);
252
+ const childWebdavPath = normalizedWebdavPath.endsWith("/") ? normalizedWebdavPath + child : normalizedWebdavPath + "/" + child;
253
+ try {
254
+ const childStats = fs.statSync(childPath);
255
+ entries.push({
256
+ href: prefix + childWebdavPath,
257
+ stats: childStats,
258
+ isCollection: childStats.isDirectory()
259
+ });
260
+ } catch {
261
+ }
262
+ }
263
+ } catch {
264
+ }
265
+ }
266
+ const xml = generatePropfindResponse(entries);
267
+ res.writeHead(207, {
268
+ "Content-Type": "application/xml; charset=utf-8",
269
+ "Content-Length": Buffer.byteLength(xml).toString(),
270
+ "Access-Control-Allow-Origin": "*"
271
+ });
272
+ res.end(xml);
273
+ } catch (err) {
274
+ res.writeHead(500, { "Content-Type": "text/plain", "Access-Control-Allow-Origin": "*" });
275
+ res.end(`Error: ${err}`);
276
+ }
277
+ }
278
+ function handleMove(res, sourcePath, destinationPath, overwrite) {
279
+ try {
280
+ if (!fs.existsSync(sourcePath)) {
281
+ res.writeHead(404, { "Content-Type": "text/plain", "Access-Control-Allow-Origin": "*" });
282
+ res.end("Source not found");
283
+ return;
284
+ }
285
+ if (!destinationPath) {
286
+ res.writeHead(400, { "Content-Type": "text/plain", "Access-Control-Allow-Origin": "*" });
287
+ res.end("Destination header required");
288
+ return;
289
+ }
290
+ const destExists = fs.existsSync(destinationPath);
291
+ if (destExists && !overwrite) {
292
+ res.writeHead(412, { "Content-Type": "text/plain", "Access-Control-Allow-Origin": "*" });
293
+ res.end("Destination exists and Overwrite is F");
294
+ return;
295
+ }
296
+ const destDir = path.dirname(destinationPath);
297
+ if (!fs.existsSync(destDir)) {
298
+ fs.mkdirSync(destDir, { recursive: true });
299
+ }
300
+ if (destExists) {
301
+ const destStats = fs.statSync(destinationPath);
302
+ if (destStats.isDirectory()) {
303
+ fs.rmSync(destinationPath, { recursive: true });
304
+ } else {
305
+ fs.unlinkSync(destinationPath);
306
+ }
307
+ }
308
+ fs.renameSync(sourcePath, destinationPath);
309
+ const status = destExists ? 204 : 201;
310
+ res.writeHead(status, {
311
+ "Content-Length": "0",
312
+ "Access-Control-Allow-Origin": "*"
313
+ });
314
+ res.end();
315
+ } catch (err) {
316
+ res.writeHead(500, { "Content-Type": "text/plain", "Access-Control-Allow-Origin": "*" });
317
+ res.end(`Error: ${err}`);
318
+ }
319
+ }
320
+ function handleCopy(res, sourcePath, destinationPath, overwrite) {
321
+ try {
322
+ if (!fs.existsSync(sourcePath)) {
323
+ res.writeHead(404, { "Content-Type": "text/plain", "Access-Control-Allow-Origin": "*" });
324
+ res.end("Source not found");
325
+ return;
326
+ }
327
+ if (!destinationPath) {
328
+ res.writeHead(400, { "Content-Type": "text/plain", "Access-Control-Allow-Origin": "*" });
329
+ res.end("Destination header required");
330
+ return;
331
+ }
332
+ const destExists = fs.existsSync(destinationPath);
333
+ if (destExists && !overwrite) {
334
+ res.writeHead(412, { "Content-Type": "text/plain", "Access-Control-Allow-Origin": "*" });
335
+ res.end("Destination exists and Overwrite is F");
336
+ return;
337
+ }
338
+ const destDir = path.dirname(destinationPath);
339
+ if (!fs.existsSync(destDir)) {
340
+ fs.mkdirSync(destDir, { recursive: true });
341
+ }
342
+ const sourceStats = fs.statSync(sourcePath);
343
+ if (sourceStats.isDirectory()) {
344
+ copyDirRecursive(sourcePath, destinationPath);
345
+ } else {
346
+ fs.copyFileSync(sourcePath, destinationPath);
347
+ }
348
+ const status = destExists ? 204 : 201;
349
+ res.writeHead(status, {
350
+ "Content-Length": "0",
351
+ "Access-Control-Allow-Origin": "*"
352
+ });
353
+ res.end();
354
+ } catch (err) {
355
+ res.writeHead(500, { "Content-Type": "text/plain", "Access-Control-Allow-Origin": "*" });
356
+ res.end(`Error: ${err}`);
357
+ }
358
+ }
359
+ function copyDirRecursive(src, dest) {
360
+ if (!fs.existsSync(dest)) {
361
+ fs.mkdirSync(dest, { recursive: true });
362
+ }
363
+ const entries = fs.readdirSync(src, { withFileTypes: true });
364
+ for (const entry of entries) {
365
+ const srcPath = path.join(src, entry.name);
366
+ const destPath = path.join(dest, entry.name);
367
+ if (entry.isDirectory()) {
368
+ copyDirRecursive(srcPath, destPath);
369
+ } else {
370
+ fs.copyFileSync(srcPath, destPath);
371
+ }
372
+ }
373
+ }
374
+ function parseDestinationHeader(destinationHeader, prefix, rootPath) {
375
+ if (!destinationHeader) {
376
+ return null;
377
+ }
378
+ try {
379
+ let destPath;
380
+ if (destinationHeader.startsWith("http://") || destinationHeader.startsWith("https://")) {
381
+ const url = new URL(destinationHeader);
382
+ destPath = decodeURIComponent(url.pathname);
383
+ } else {
384
+ destPath = decodeURIComponent(destinationHeader);
385
+ }
386
+ if (destPath.startsWith(prefix)) {
387
+ destPath = destPath.slice(prefix.length);
388
+ }
389
+ return resolveWebDAVPath(destPath, rootPath);
390
+ } catch {
391
+ return null;
392
+ }
393
+ }
394
+ function createWebDAVHandler(config) {
395
+ const { rootPath, prefix = WEBDAV_PREFIX, auth } = config;
396
+ return async (req, res) => {
397
+ const rawUrl = req.url || "/";
398
+ if (rawUrl.includes("..")) {
399
+ if (rawUrl.startsWith(prefix)) {
400
+ res.writeHead(403, { "Content-Type": "text/plain", "Access-Control-Allow-Origin": "*" });
401
+ res.end("Forbidden");
402
+ return true;
403
+ }
404
+ }
405
+ const url = new URL(rawUrl, `http://${req.headers.host || "localhost"}`);
406
+ const pathname = decodeURIComponent(url.pathname);
407
+ if (!pathname.startsWith(prefix)) {
408
+ return false;
409
+ }
410
+ let webdavPath = pathname.slice(prefix.length);
411
+ if (!webdavPath.startsWith("/")) {
412
+ webdavPath = "/" + webdavPath;
413
+ }
414
+ if (req.method === "OPTIONS") {
415
+ handleOptions(res, prefix);
416
+ return true;
417
+ }
418
+ if (!checkAuth(req, auth)) {
419
+ res.writeHead(401, {
420
+ "WWW-Authenticate": 'Basic realm="ClawVault WebDAV"',
421
+ "Content-Type": "text/plain",
422
+ "Access-Control-Allow-Origin": "*"
423
+ });
424
+ res.end("Unauthorized");
425
+ return true;
426
+ }
427
+ if (!isPathSafe(webdavPath, rootPath)) {
428
+ res.writeHead(403, { "Content-Type": "text/plain", "Access-Control-Allow-Origin": "*" });
429
+ res.end("Forbidden");
430
+ return true;
431
+ }
432
+ const filePath = resolveWebDAVPath(webdavPath, rootPath);
433
+ if (!filePath) {
434
+ res.writeHead(403, { "Content-Type": "text/plain", "Access-Control-Allow-Origin": "*" });
435
+ res.end("Forbidden");
436
+ return true;
437
+ }
438
+ const depth = req.headers.depth || "infinity";
439
+ const overwrite = req.headers.overwrite?.toUpperCase() !== "F";
440
+ const destinationHeader = req.headers.destination;
441
+ switch (req.method) {
442
+ case "HEAD":
443
+ handleHead(res, filePath);
444
+ return true;
445
+ case "GET":
446
+ handleGet(res, filePath);
447
+ return true;
448
+ case "PUT": {
449
+ const chunks = [];
450
+ for await (const chunk of req) {
451
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
452
+ }
453
+ const body = Buffer.concat(chunks);
454
+ handlePut(res, filePath, body);
455
+ return true;
456
+ }
457
+ case "DELETE":
458
+ handleDelete(res, filePath);
459
+ return true;
460
+ case "MKCOL":
461
+ handleMkcol(res, filePath);
462
+ return true;
463
+ case "PROPFIND":
464
+ handlePropfind(res, filePath, webdavPath, prefix, depth);
465
+ return true;
466
+ case "MOVE": {
467
+ const destPath = parseDestinationHeader(destinationHeader, prefix, rootPath);
468
+ if (destPath && destinationHeader) {
469
+ const destWebdavPath = destinationHeader.includes(prefix) ? destinationHeader.slice(destinationHeader.indexOf(prefix) + prefix.length) : destinationHeader;
470
+ if (!isPathSafe(destWebdavPath, rootPath)) {
471
+ res.writeHead(403, { "Content-Type": "text/plain", "Access-Control-Allow-Origin": "*" });
472
+ res.end("Forbidden");
473
+ return true;
474
+ }
475
+ }
476
+ handleMove(res, filePath, destPath, overwrite);
477
+ return true;
478
+ }
479
+ case "COPY": {
480
+ const destPath = parseDestinationHeader(destinationHeader, prefix, rootPath);
481
+ if (destPath && destinationHeader) {
482
+ const destWebdavPath = destinationHeader.includes(prefix) ? destinationHeader.slice(destinationHeader.indexOf(prefix) + prefix.length) : destinationHeader;
483
+ if (!isPathSafe(destWebdavPath, rootPath)) {
484
+ res.writeHead(403, { "Content-Type": "text/plain", "Access-Control-Allow-Origin": "*" });
485
+ res.end("Forbidden");
486
+ return true;
487
+ }
488
+ }
489
+ handleCopy(res, filePath, destPath, overwrite);
490
+ return true;
491
+ }
492
+ default:
493
+ res.writeHead(405, {
494
+ "Allow": SUPPORTED_METHODS.join(", "),
495
+ "Content-Type": "text/plain",
496
+ "Access-Control-Allow-Origin": "*"
497
+ });
498
+ res.end("Method Not Allowed");
499
+ return true;
500
+ }
501
+ };
502
+ }
503
+
504
+ export {
505
+ WEBDAV_PREFIX,
506
+ isPathSafe,
507
+ resolveWebDAVPath,
508
+ checkAuth,
509
+ generatePropfindResponse,
510
+ handleOptions,
511
+ handleHead,
512
+ handleGet,
513
+ handlePut,
514
+ handleDelete,
515
+ handleMkcol,
516
+ handlePropfind,
517
+ handleMove,
518
+ handleCopy,
519
+ createWebDAVHandler
520
+ };