clawvault 3.0.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 (291) hide show
  1. package/README.md +352 -20
  2. package/bin/clawvault.js +8 -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 -10
  6. package/bin/register-maintenance-commands.js +39 -3
  7. package/bin/register-query-commands.js +58 -29
  8. package/bin/register-task-commands.js +18 -1
  9. package/bin/register-task-commands.test.js +16 -0
  10. package/bin/register-vault-operations-commands.js +29 -1
  11. package/bin/register-workgraph-commands.js +1368 -0
  12. package/dashboard/lib/graph-diff.js +104 -0
  13. package/dashboard/lib/graph-diff.test.js +75 -0
  14. package/dashboard/lib/vault-parser.js +556 -0
  15. package/dashboard/lib/vault-parser.test.js +254 -0
  16. package/dashboard/public/app.js +796 -0
  17. package/dashboard/public/index.html +52 -0
  18. package/dashboard/public/styles.css +221 -0
  19. package/dashboard/server.js +374 -0
  20. package/dist/{chunk-F2JEUD4J.js → chunk-23YDQ3QU.js} +6 -8
  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-2ZDO52B4.js +52 -0
  24. package/dist/{chunk-ZZA73MFY.js → chunk-33DOSHTA.js} +176 -36
  25. package/dist/chunk-33VSQP4J.js +37 -0
  26. package/dist/chunk-4BQTQMJP.js +93 -0
  27. package/dist/{chunk-GUKMRGM7.js → chunk-4OXMU5S2.js} +1 -1
  28. package/dist/{chunk-62YTUT6J.js → chunk-4PY655YM.js} +15 -3
  29. package/dist/chunk-6FH3IULF.js +352 -0
  30. package/dist/{chunk-3NSBOUT3.js → chunk-77Q5CSPJ.js} +404 -80
  31. package/dist/{chunk-4VQTUVH7.js → chunk-7YZWHM36.js} +52 -26
  32. package/dist/chunk-BSJ6RIT7.js +447 -0
  33. package/dist/chunk-BUEW6IIK.js +364 -0
  34. package/dist/{chunk-WGRQ6HDV.js → chunk-CLJTREDS.js} +74 -14
  35. package/dist/chunk-EK6S23ZB.js +469 -0
  36. package/dist/{chunk-LNJA2UGL.js → chunk-ESFLMDRB.js} +9 -86
  37. package/dist/{chunk-H34S76MB.js → chunk-ESVS6K2B.js} +6 -6
  38. package/dist/{chunk-WAZ3NLWL.js → chunk-F55HGNU4.js} +0 -47
  39. package/dist/{chunk-QK3UCXWL.js → chunk-FHFUXL6G.js} +2 -2
  40. package/dist/{chunk-YKTA5JOJ.js → chunk-GAOWA7GR.js} +212 -46
  41. package/dist/chunk-GGA32J2R.js +784 -0
  42. package/dist/chunk-GNJL4YGR.js +79 -0
  43. package/dist/chunk-MDIH26GC.js +183 -0
  44. package/dist/{chunk-LYHGEHXG.js → chunk-MFAWT5O5.js} +0 -1
  45. package/dist/chunk-MM6QGW3P.js +207 -0
  46. package/dist/{chunk-P5EPF6MB.js → chunk-MW5C6ZQA.js} +110 -13
  47. package/dist/chunk-NCKFNBHJ.js +257 -0
  48. package/dist/{chunk-QBLMXKF2.js → chunk-OIWVQYQF.js} +1 -1
  49. package/dist/{chunk-42MXU7A6.js → chunk-P62WHA27.js} +58 -47
  50. package/dist/chunk-PBACDKKP.js +66 -0
  51. package/dist/{chunk-VGLOTGAS.js → chunk-QSHD36LH.js} +2 -2
  52. package/dist/{chunk-OZ7RIXTO.js → chunk-QSRRMEYM.js} +2 -2
  53. package/dist/chunk-QVEERJSP.js +152 -0
  54. package/dist/{chunk-N2AXRYLC.js → chunk-QWQ3TIKS.js} +1 -1
  55. package/dist/{chunk-3DHXQHYG.js → chunk-R2MIW5G7.js} +1 -1
  56. package/dist/{chunk-SJSFRIYS.js → chunk-SLXOR3CC.js} +2 -2
  57. package/dist/chunk-SS4B7P7V.js +99 -0
  58. package/dist/{chunk-JY6FYXIT.js → chunk-STCQGCEQ.js} +6 -11
  59. package/dist/chunk-U4O6C46S.js +154 -0
  60. package/dist/{chunk-ITPEXLHA.js → chunk-URXDAUVH.js} +24 -5
  61. package/dist/chunk-VSL7KY3M.js +189 -0
  62. package/dist/{chunk-U55BGUAU.js → chunk-W4SPAEE7.js} +6 -6
  63. package/dist/chunk-WMGIIABP.js +15 -0
  64. package/dist/{chunk-3D6BCTP6.js → chunk-X3SPPUFG.js} +51 -39
  65. package/dist/{chunk-THRJVD4L.js → chunk-Y6VJKXGL.js} +1 -1
  66. package/dist/{chunk-ZVVFWOLW.js → chunk-ZN54U2OZ.js} +123 -10
  67. package/dist/cli/index.js +32 -25
  68. package/dist/commands/archive.js +3 -3
  69. package/dist/commands/backlog.js +3 -3
  70. package/dist/commands/blocked.js +3 -3
  71. package/dist/commands/canvas.d.ts +15 -0
  72. package/dist/commands/canvas.js +200 -0
  73. package/dist/commands/checkpoint.js +2 -2
  74. package/dist/commands/compat.js +2 -2
  75. package/dist/commands/context.js +8 -6
  76. package/dist/commands/doctor.d.ts +11 -7
  77. package/dist/commands/doctor.js +18 -16
  78. package/dist/commands/embed.js +5 -6
  79. package/dist/commands/entities.js +2 -2
  80. package/dist/commands/graph.js +4 -4
  81. package/dist/commands/inject.d.ts +1 -1
  82. package/dist/commands/inject.js +5 -6
  83. package/dist/commands/kanban.js +4 -4
  84. package/dist/commands/link.js +5 -5
  85. package/dist/commands/migrate-observations.js +4 -4
  86. package/dist/commands/observe.d.ts +0 -1
  87. package/dist/commands/observe.js +14 -13
  88. package/dist/commands/project.js +5 -5
  89. package/dist/commands/rebuild-embeddings.d.ts +21 -0
  90. package/dist/commands/rebuild-embeddings.js +91 -0
  91. package/dist/commands/rebuild.js +12 -11
  92. package/dist/commands/recover.js +3 -3
  93. package/dist/commands/reflect.js +6 -7
  94. package/dist/commands/repair-session.js +1 -1
  95. package/dist/commands/replay.js +14 -14
  96. package/dist/commands/session-recap.js +1 -1
  97. package/dist/commands/setup.d.ts +2 -90
  98. package/dist/commands/setup.js +3 -21
  99. package/dist/commands/shell-init.js +1 -1
  100. package/dist/commands/sleep.d.ts +1 -1
  101. package/dist/commands/sleep.js +20 -19
  102. package/dist/commands/status.d.ts +2 -0
  103. package/dist/commands/status.js +57 -35
  104. package/dist/commands/sync-bd.d.ts +10 -0
  105. package/dist/commands/sync-bd.js +10 -0
  106. package/dist/commands/tailscale.js +3 -3
  107. package/dist/commands/task.js +4 -4
  108. package/dist/commands/template.js +2 -2
  109. package/dist/commands/wake.d.ts +1 -1
  110. package/dist/commands/wake.js +11 -10
  111. package/dist/commands/workgraph.d.ts +124 -0
  112. package/dist/commands/workgraph.js +38 -0
  113. package/dist/index.d.ts +337 -191
  114. package/dist/index.js +387 -118
  115. package/dist/{inject-Bzi5E-By.d.cts → inject-DYUrDqQO.d.ts} +3 -3
  116. package/dist/ledger-B7g7jhqG.d.ts +44 -0
  117. package/dist/lib/auto-linker.js +2 -2
  118. package/dist/lib/canvas-layout.d.ts +100 -16
  119. package/dist/lib/canvas-layout.js +21 -78
  120. package/dist/lib/config.d.ts +27 -3
  121. package/dist/lib/config.js +4 -2
  122. package/dist/lib/entity-index.js +1 -1
  123. package/dist/lib/project-utils.js +4 -4
  124. package/dist/lib/session-repair.js +1 -1
  125. package/dist/lib/session-utils.js +1 -1
  126. package/dist/lib/tailscale.js +1 -1
  127. package/dist/lib/task-utils.js +3 -3
  128. package/dist/lib/template-engine.js +1 -1
  129. package/dist/lib/webdav.js +1 -1
  130. package/dist/onnxruntime_binding-5QEF3SUC.node +0 -0
  131. package/dist/onnxruntime_binding-BKPKNEGC.node +0 -0
  132. package/dist/onnxruntime_binding-FMOXGIUT.node +0 -0
  133. package/dist/onnxruntime_binding-OI2KMXC5.node +0 -0
  134. package/dist/onnxruntime_binding-UX44MLAZ.node +0 -0
  135. package/dist/onnxruntime_binding-Y2W7N7WY.node +0 -0
  136. package/dist/openclaw-plugin.d.ts +8 -0
  137. package/dist/openclaw-plugin.js +14 -0
  138. package/dist/registry-BR4326o0.d.ts +30 -0
  139. package/dist/store-CA-6sKCJ.d.ts +34 -0
  140. package/dist/thread-B9LhXNU0.d.ts +41 -0
  141. package/dist/transformers.node-A2ZRORSQ.js +46775 -0
  142. package/dist/{types-Y2_Um2Ls.d.cts → types-BbWJoC1c.d.ts} +1 -44
  143. package/dist/workgraph/index.d.ts +5 -0
  144. package/dist/workgraph/index.js +23 -0
  145. package/dist/workgraph/ledger.d.ts +2 -0
  146. package/dist/workgraph/ledger.js +25 -0
  147. package/dist/workgraph/registry.d.ts +2 -0
  148. package/dist/workgraph/registry.js +19 -0
  149. package/dist/workgraph/store.d.ts +2 -0
  150. package/dist/workgraph/store.js +25 -0
  151. package/dist/workgraph/thread.d.ts +2 -0
  152. package/dist/workgraph/thread.js +25 -0
  153. package/dist/workgraph/types.d.ts +54 -0
  154. package/dist/workgraph/types.js +7 -0
  155. package/hooks/clawvault/HOOK.md +34 -4
  156. package/hooks/clawvault/handler.js +760 -78
  157. package/hooks/clawvault/handler.test.js +235 -79
  158. package/hooks/clawvault/openclaw.plugin.json +72 -0
  159. package/openclaw.plugin.json +65 -38
  160. package/package.json +15 -18
  161. package/dist/chunk-3RG5ZIWI.js +0 -10
  162. package/dist/chunk-6U6MK36V.js +0 -205
  163. package/dist/chunk-7R7O6STJ.js +0 -88
  164. package/dist/chunk-CMB7UL7C.js +0 -327
  165. package/dist/chunk-DEFFDRVP.js +0 -938
  166. package/dist/chunk-E7MFQB6D.js +0 -163
  167. package/dist/chunk-GAJV4IGR.js +0 -82
  168. package/dist/chunk-GQSLDZTS.js +0 -560
  169. package/dist/chunk-K234IDRJ.js +0 -1073
  170. package/dist/chunk-MFM6K7PU.js +0 -374
  171. package/dist/chunk-MXSSG3QU.js +0 -42
  172. package/dist/chunk-PAH27GSN.js +0 -108
  173. package/dist/cli/index.cjs +0 -10033
  174. package/dist/cli/index.d.cts +0 -5
  175. package/dist/commands/archive.cjs +0 -287
  176. package/dist/commands/archive.d.cts +0 -11
  177. package/dist/commands/backlog.cjs +0 -721
  178. package/dist/commands/backlog.d.cts +0 -53
  179. package/dist/commands/blocked.cjs +0 -204
  180. package/dist/commands/blocked.d.cts +0 -26
  181. package/dist/commands/checkpoint.cjs +0 -244
  182. package/dist/commands/checkpoint.d.cts +0 -41
  183. package/dist/commands/compat.cjs +0 -369
  184. package/dist/commands/compat.d.cts +0 -28
  185. package/dist/commands/context.cjs +0 -2989
  186. package/dist/commands/context.d.cts +0 -2
  187. package/dist/commands/doctor.cjs +0 -3062
  188. package/dist/commands/doctor.d.cts +0 -21
  189. package/dist/commands/embed.cjs +0 -232
  190. package/dist/commands/embed.d.cts +0 -17
  191. package/dist/commands/entities.cjs +0 -141
  192. package/dist/commands/entities.d.cts +0 -7
  193. package/dist/commands/graph.cjs +0 -501
  194. package/dist/commands/graph.d.cts +0 -21
  195. package/dist/commands/inject.cjs +0 -1636
  196. package/dist/commands/inject.d.cts +0 -2
  197. package/dist/commands/kanban.cjs +0 -884
  198. package/dist/commands/kanban.d.cts +0 -63
  199. package/dist/commands/link.cjs +0 -965
  200. package/dist/commands/link.d.cts +0 -11
  201. package/dist/commands/migrate-observations.cjs +0 -362
  202. package/dist/commands/migrate-observations.d.cts +0 -19
  203. package/dist/commands/observe.cjs +0 -4099
  204. package/dist/commands/observe.d.cts +0 -23
  205. package/dist/commands/project.cjs +0 -1341
  206. package/dist/commands/project.d.cts +0 -85
  207. package/dist/commands/rebuild.cjs +0 -3136
  208. package/dist/commands/rebuild.d.cts +0 -11
  209. package/dist/commands/recover.cjs +0 -361
  210. package/dist/commands/recover.d.cts +0 -38
  211. package/dist/commands/reflect.cjs +0 -1008
  212. package/dist/commands/reflect.d.cts +0 -11
  213. package/dist/commands/repair-session.cjs +0 -457
  214. package/dist/commands/repair-session.d.cts +0 -38
  215. package/dist/commands/replay.cjs +0 -4103
  216. package/dist/commands/replay.d.cts +0 -16
  217. package/dist/commands/session-recap.cjs +0 -353
  218. package/dist/commands/session-recap.d.cts +0 -27
  219. package/dist/commands/setup.cjs +0 -1345
  220. package/dist/commands/setup.d.cts +0 -100
  221. package/dist/commands/shell-init.cjs +0 -75
  222. package/dist/commands/shell-init.d.cts +0 -7
  223. package/dist/commands/sleep.cjs +0 -6028
  224. package/dist/commands/sleep.d.cts +0 -36
  225. package/dist/commands/status.cjs +0 -2736
  226. package/dist/commands/status.d.cts +0 -52
  227. package/dist/commands/tailscale.cjs +0 -1532
  228. package/dist/commands/tailscale.d.cts +0 -52
  229. package/dist/commands/task.cjs +0 -1236
  230. package/dist/commands/task.d.cts +0 -97
  231. package/dist/commands/template.cjs +0 -457
  232. package/dist/commands/template.d.cts +0 -36
  233. package/dist/commands/wake.cjs +0 -2626
  234. package/dist/commands/wake.d.cts +0 -22
  235. package/dist/context-BUGaWpyL.d.cts +0 -46
  236. package/dist/index.cjs +0 -14526
  237. package/dist/index.d.cts +0 -858
  238. package/dist/inject-Bzi5E-By.d.ts +0 -137
  239. package/dist/lib/auto-linker.cjs +0 -176
  240. package/dist/lib/auto-linker.d.cts +0 -26
  241. package/dist/lib/canvas-layout.cjs +0 -136
  242. package/dist/lib/canvas-layout.d.cts +0 -31
  243. package/dist/lib/config.cjs +0 -78
  244. package/dist/lib/config.d.cts +0 -11
  245. package/dist/lib/entity-index.cjs +0 -84
  246. package/dist/lib/entity-index.d.cts +0 -26
  247. package/dist/lib/project-utils.cjs +0 -864
  248. package/dist/lib/project-utils.d.cts +0 -97
  249. package/dist/lib/session-repair.cjs +0 -239
  250. package/dist/lib/session-repair.d.cts +0 -110
  251. package/dist/lib/session-utils.cjs +0 -209
  252. package/dist/lib/session-utils.d.cts +0 -63
  253. package/dist/lib/tailscale.cjs +0 -1183
  254. package/dist/lib/tailscale.d.cts +0 -225
  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/lib/webdav.cjs +0 -568
  260. package/dist/lib/webdav.d.cts +0 -109
  261. package/dist/plugin/index.cjs +0 -1907
  262. package/dist/plugin/index.d.cts +0 -36
  263. package/dist/plugin/index.d.ts +0 -36
  264. package/dist/plugin/index.js +0 -572
  265. package/dist/plugin/inject.cjs +0 -356
  266. package/dist/plugin/inject.d.cts +0 -54
  267. package/dist/plugin/inject.d.ts +0 -54
  268. package/dist/plugin/inject.js +0 -17
  269. package/dist/plugin/observe.cjs +0 -631
  270. package/dist/plugin/observe.d.cts +0 -39
  271. package/dist/plugin/observe.d.ts +0 -39
  272. package/dist/plugin/observe.js +0 -18
  273. package/dist/plugin/templates.cjs +0 -593
  274. package/dist/plugin/templates.d.cts +0 -52
  275. package/dist/plugin/templates.d.ts +0 -52
  276. package/dist/plugin/templates.js +0 -25
  277. package/dist/plugin/types.cjs +0 -18
  278. package/dist/plugin/types.d.cts +0 -209
  279. package/dist/plugin/types.d.ts +0 -209
  280. package/dist/plugin/types.js +0 -0
  281. package/dist/plugin/vault.cjs +0 -927
  282. package/dist/plugin/vault.d.cts +0 -68
  283. package/dist/plugin/vault.d.ts +0 -68
  284. package/dist/plugin/vault.js +0 -22
  285. package/dist/types-Y2_Um2Ls.d.ts +0 -205
  286. package/templates/memory-event.md +0 -67
  287. package/templates/party.md +0 -63
  288. package/templates/primitive-registry.yaml +0 -551
  289. package/templates/run.md +0 -68
  290. package/templates/trigger.md +0 -68
  291. package/templates/workspace.md +0 -50
@@ -1,1183 +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/lib/tailscale.ts
31
- var tailscale_exports = {};
32
- __export(tailscale_exports, {
33
- CLAWVAULT_SERVE_PATH: () => CLAWVAULT_SERVE_PATH,
34
- DEFAULT_SERVE_PORT: () => DEFAULT_SERVE_PORT,
35
- FILE_ENDPOINT: () => FILE_ENDPOINT,
36
- MANIFEST_ENDPOINT: () => MANIFEST_ENDPOINT,
37
- SYNC_ENDPOINT: () => SYNC_ENDPOINT,
38
- checkPeerClawVault: () => checkPeerClawVault,
39
- compareManifests: () => compareManifests,
40
- configureTailscaleServe: () => configureTailscaleServe,
41
- discoverClawVaultPeers: () => discoverClawVaultPeers,
42
- fetchRemoteFile: () => fetchRemoteFile,
43
- fetchRemoteManifest: () => fetchRemoteManifest,
44
- findPeer: () => findPeer,
45
- generateVaultManifest: () => generateVaultManifest,
46
- getOnlinePeers: () => getOnlinePeers,
47
- getTailscaleStatus: () => getTailscaleStatus,
48
- getTailscaleVersion: () => getTailscaleVersion,
49
- hasTailscale: () => hasTailscale,
50
- pushFileToRemote: () => pushFileToRemote,
51
- resolvePeerIP: () => resolvePeerIP,
52
- serveVault: () => serveVault,
53
- stopTailscaleServe: () => stopTailscaleServe,
54
- syncWithPeer: () => syncWithPeer
55
- });
56
- module.exports = __toCommonJS(tailscale_exports);
57
- var import_child_process = require("child_process");
58
- var fs2 = __toESM(require("fs"), 1);
59
- var path2 = __toESM(require("path"), 1);
60
- var http = __toESM(require("http"), 1);
61
- var https = __toESM(require("https"), 1);
62
-
63
- // src/lib/webdav.ts
64
- var fs = __toESM(require("fs"), 1);
65
- var path = __toESM(require("path"), 1);
66
- var WEBDAV_PREFIX = "/webdav";
67
- var BLOCKED_PATHS = [
68
- ".clawvault",
69
- ".git",
70
- ".obsidian",
71
- "node_modules"
72
- ];
73
- var SUPPORTED_METHODS = ["GET", "PUT", "DELETE", "MKCOL", "PROPFIND", "OPTIONS", "HEAD", "MOVE", "COPY"];
74
- function toRequestSegments(requestPath) {
75
- return requestPath.replace(/\\/g, "/").split("/").filter(Boolean);
76
- }
77
- function isWithinRoot(fullPath, rootPath) {
78
- const resolvedRoot = path.resolve(rootPath);
79
- const relative2 = path.relative(resolvedRoot, fullPath);
80
- return !(relative2.startsWith("..") || path.isAbsolute(relative2));
81
- }
82
- function isPathSafe(requestPath, rootPath) {
83
- const pathParts = toRequestSegments(requestPath);
84
- if (pathParts.includes("..")) {
85
- return false;
86
- }
87
- const normalizedRelativePath = path.normalize(pathParts.join(path.sep));
88
- const fullPath = path.resolve(rootPath, normalizedRelativePath);
89
- if (!isWithinRoot(fullPath, rootPath)) {
90
- return false;
91
- }
92
- for (const part of pathParts) {
93
- if (BLOCKED_PATHS.includes(part)) {
94
- return false;
95
- }
96
- }
97
- return true;
98
- }
99
- function resolveWebDAVPath(requestPath, rootPath) {
100
- const pathParts = toRequestSegments(requestPath);
101
- if (pathParts.includes("..")) {
102
- return null;
103
- }
104
- const normalizedRelativePath = path.normalize(pathParts.join(path.sep));
105
- const fullPath = path.resolve(rootPath, normalizedRelativePath);
106
- if (!isWithinRoot(fullPath, rootPath)) {
107
- return null;
108
- }
109
- return fullPath;
110
- }
111
- function checkAuth(req, auth) {
112
- if (!auth) {
113
- return true;
114
- }
115
- const authHeader = req.headers.authorization;
116
- if (!authHeader || !authHeader.startsWith("Basic ")) {
117
- return false;
118
- }
119
- const base64Credentials = authHeader.slice(6);
120
- const credentials = Buffer.from(base64Credentials, "base64").toString("utf-8");
121
- const [username, password] = credentials.split(":");
122
- return username === auth.username && password === auth.password;
123
- }
124
- function escapeXml(str) {
125
- return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
126
- }
127
- function formatWebDAVDate(date) {
128
- return date.toUTCString();
129
- }
130
- function generatePropfindEntry(href, stats, isCollection) {
131
- const resourceType = isCollection ? "<D:resourcetype><D:collection/></D:resourcetype>" : "<D:resourcetype/>";
132
- const contentLength = stats && !isCollection ? `<D:getcontentlength>${stats.size}</D:getcontentlength>` : "";
133
- const lastModified = stats ? `<D:getlastmodified>${formatWebDAVDate(stats.mtime)}</D:getlastmodified>` : "";
134
- const etag = stats ? `<D:getetag>"${stats.mtime.getTime().toString(16)}-${stats.size.toString(16)}"</D:getetag>` : "";
135
- const contentType = !isCollection ? "<D:getcontenttype>application/octet-stream</D:getcontenttype>" : "";
136
- return ` <D:response>
137
- <D:href>${escapeXml(href)}</D:href>
138
- <D:propstat>
139
- <D:prop>
140
- ${resourceType}
141
- ${contentLength}
142
- ${lastModified}
143
- ${etag}
144
- ${contentType}
145
- </D:prop>
146
- <D:status>HTTP/1.1 200 OK</D:status>
147
- </D:propstat>
148
- </D:response>`;
149
- }
150
- function generatePropfindResponse(entries) {
151
- const responseEntries = entries.map(
152
- (e) => generatePropfindEntry(e.href, e.stats, e.isCollection)
153
- ).join("\n");
154
- return `<?xml version="1.0" encoding="utf-8"?>
155
- <D:multistatus xmlns:D="DAV:">
156
- ${responseEntries}
157
- </D:multistatus>`;
158
- }
159
- function handleOptions(res, prefix) {
160
- res.writeHead(200, {
161
- "Allow": SUPPORTED_METHODS.join(", "),
162
- "DAV": "1, 2",
163
- "Content-Length": "0",
164
- "Access-Control-Allow-Origin": "*",
165
- "Access-Control-Allow-Methods": SUPPORTED_METHODS.join(", "),
166
- "Access-Control-Allow-Headers": "Content-Type, Depth, Destination, Overwrite, Authorization",
167
- "MS-Author-Via": "DAV"
168
- });
169
- res.end();
170
- }
171
- function handleHead(res, filePath) {
172
- try {
173
- const stats = fs.statSync(filePath);
174
- if (stats.isDirectory()) {
175
- res.writeHead(200, {
176
- "Content-Type": "httpd/unix-directory",
177
- "Last-Modified": formatWebDAVDate(stats.mtime),
178
- "ETag": `"${stats.mtime.getTime().toString(16)}"`,
179
- "Access-Control-Allow-Origin": "*"
180
- });
181
- } else {
182
- res.writeHead(200, {
183
- "Content-Type": "application/octet-stream",
184
- "Content-Length": stats.size.toString(),
185
- "Last-Modified": formatWebDAVDate(stats.mtime),
186
- "ETag": `"${stats.mtime.getTime().toString(16)}-${stats.size.toString(16)}"`,
187
- "Access-Control-Allow-Origin": "*"
188
- });
189
- }
190
- res.end();
191
- } catch (err) {
192
- res.writeHead(404, { "Content-Type": "text/plain", "Access-Control-Allow-Origin": "*" });
193
- res.end("Not Found");
194
- }
195
- }
196
- function handleGet(res, filePath) {
197
- try {
198
- const stats = fs.statSync(filePath);
199
- if (stats.isDirectory()) {
200
- const entries = fs.readdirSync(filePath);
201
- const listing = entries.join("\n");
202
- res.writeHead(200, {
203
- "Content-Type": "text/plain",
204
- "Content-Length": Buffer.byteLength(listing).toString(),
205
- "Access-Control-Allow-Origin": "*"
206
- });
207
- res.end(listing);
208
- } else {
209
- const content = fs.readFileSync(filePath);
210
- res.writeHead(200, {
211
- "Content-Type": "application/octet-stream",
212
- "Content-Length": content.length.toString(),
213
- "Last-Modified": formatWebDAVDate(stats.mtime),
214
- "ETag": `"${stats.mtime.getTime().toString(16)}-${stats.size.toString(16)}"`,
215
- "Access-Control-Allow-Origin": "*"
216
- });
217
- res.end(content);
218
- }
219
- } catch (err) {
220
- res.writeHead(404, { "Content-Type": "text/plain", "Access-Control-Allow-Origin": "*" });
221
- res.end("Not Found");
222
- }
223
- }
224
- function handlePut(res, filePath, body) {
225
- try {
226
- const exists = fs.existsSync(filePath);
227
- const dir = path.dirname(filePath);
228
- if (!fs.existsSync(dir)) {
229
- fs.mkdirSync(dir, { recursive: true });
230
- }
231
- fs.writeFileSync(filePath, body);
232
- const status = exists ? 204 : 201;
233
- res.writeHead(status, {
234
- "Content-Length": "0",
235
- "Access-Control-Allow-Origin": "*"
236
- });
237
- res.end();
238
- } catch (err) {
239
- res.writeHead(500, { "Content-Type": "text/plain", "Access-Control-Allow-Origin": "*" });
240
- res.end(`Error: ${err}`);
241
- }
242
- }
243
- function handleDelete(res, filePath) {
244
- try {
245
- if (!fs.existsSync(filePath)) {
246
- res.writeHead(404, { "Content-Type": "text/plain", "Access-Control-Allow-Origin": "*" });
247
- res.end("Not Found");
248
- return;
249
- }
250
- const stats = fs.statSync(filePath);
251
- if (stats.isDirectory()) {
252
- fs.rmSync(filePath, { recursive: true });
253
- } else {
254
- fs.unlinkSync(filePath);
255
- }
256
- res.writeHead(204, {
257
- "Content-Length": "0",
258
- "Access-Control-Allow-Origin": "*"
259
- });
260
- res.end();
261
- } catch (err) {
262
- res.writeHead(500, { "Content-Type": "text/plain", "Access-Control-Allow-Origin": "*" });
263
- res.end(`Error: ${err}`);
264
- }
265
- }
266
- function handleMkcol(res, filePath) {
267
- try {
268
- if (fs.existsSync(filePath)) {
269
- res.writeHead(405, { "Content-Type": "text/plain", "Access-Control-Allow-Origin": "*" });
270
- res.end("Resource already exists");
271
- return;
272
- }
273
- const parent = path.dirname(filePath);
274
- if (!fs.existsSync(parent)) {
275
- res.writeHead(409, { "Content-Type": "text/plain", "Access-Control-Allow-Origin": "*" });
276
- res.end("Parent directory does not exist");
277
- return;
278
- }
279
- fs.mkdirSync(filePath);
280
- res.writeHead(201, {
281
- "Content-Length": "0",
282
- "Access-Control-Allow-Origin": "*"
283
- });
284
- res.end();
285
- } catch (err) {
286
- res.writeHead(500, { "Content-Type": "text/plain", "Access-Control-Allow-Origin": "*" });
287
- res.end(`Error: ${err}`);
288
- }
289
- }
290
- function handlePropfind(res, filePath, webdavPath, prefix, depth) {
291
- try {
292
- if (!fs.existsSync(filePath)) {
293
- res.writeHead(404, { "Content-Type": "text/plain", "Access-Control-Allow-Origin": "*" });
294
- res.end("Not Found");
295
- return;
296
- }
297
- const stats = fs.statSync(filePath);
298
- const entries = [];
299
- const normalizedWebdavPath = webdavPath.startsWith("/") ? webdavPath : "/" + webdavPath;
300
- const href = prefix + normalizedWebdavPath;
301
- entries.push({
302
- href: href.endsWith("/") || stats.isDirectory() ? href : href,
303
- stats,
304
- isCollection: stats.isDirectory()
305
- });
306
- if (stats.isDirectory() && depth !== "0") {
307
- try {
308
- const children = fs.readdirSync(filePath);
309
- for (const child of children) {
310
- if (BLOCKED_PATHS.includes(child)) {
311
- continue;
312
- }
313
- const childPath = path.join(filePath, child);
314
- const childWebdavPath = normalizedWebdavPath.endsWith("/") ? normalizedWebdavPath + child : normalizedWebdavPath + "/" + child;
315
- try {
316
- const childStats = fs.statSync(childPath);
317
- entries.push({
318
- href: prefix + childWebdavPath,
319
- stats: childStats,
320
- isCollection: childStats.isDirectory()
321
- });
322
- } catch {
323
- }
324
- }
325
- } catch {
326
- }
327
- }
328
- const xml = generatePropfindResponse(entries);
329
- res.writeHead(207, {
330
- "Content-Type": "application/xml; charset=utf-8",
331
- "Content-Length": Buffer.byteLength(xml).toString(),
332
- "Access-Control-Allow-Origin": "*"
333
- });
334
- res.end(xml);
335
- } catch (err) {
336
- res.writeHead(500, { "Content-Type": "text/plain", "Access-Control-Allow-Origin": "*" });
337
- res.end(`Error: ${err}`);
338
- }
339
- }
340
- function handleMove(res, sourcePath, destinationPath, overwrite) {
341
- try {
342
- if (!fs.existsSync(sourcePath)) {
343
- res.writeHead(404, { "Content-Type": "text/plain", "Access-Control-Allow-Origin": "*" });
344
- res.end("Source not found");
345
- return;
346
- }
347
- if (!destinationPath) {
348
- res.writeHead(400, { "Content-Type": "text/plain", "Access-Control-Allow-Origin": "*" });
349
- res.end("Destination header required");
350
- return;
351
- }
352
- const destExists = fs.existsSync(destinationPath);
353
- if (destExists && !overwrite) {
354
- res.writeHead(412, { "Content-Type": "text/plain", "Access-Control-Allow-Origin": "*" });
355
- res.end("Destination exists and Overwrite is F");
356
- return;
357
- }
358
- const destDir = path.dirname(destinationPath);
359
- if (!fs.existsSync(destDir)) {
360
- fs.mkdirSync(destDir, { recursive: true });
361
- }
362
- if (destExists) {
363
- const destStats = fs.statSync(destinationPath);
364
- if (destStats.isDirectory()) {
365
- fs.rmSync(destinationPath, { recursive: true });
366
- } else {
367
- fs.unlinkSync(destinationPath);
368
- }
369
- }
370
- fs.renameSync(sourcePath, destinationPath);
371
- const status = destExists ? 204 : 201;
372
- res.writeHead(status, {
373
- "Content-Length": "0",
374
- "Access-Control-Allow-Origin": "*"
375
- });
376
- res.end();
377
- } catch (err) {
378
- res.writeHead(500, { "Content-Type": "text/plain", "Access-Control-Allow-Origin": "*" });
379
- res.end(`Error: ${err}`);
380
- }
381
- }
382
- function handleCopy(res, sourcePath, destinationPath, overwrite) {
383
- try {
384
- if (!fs.existsSync(sourcePath)) {
385
- res.writeHead(404, { "Content-Type": "text/plain", "Access-Control-Allow-Origin": "*" });
386
- res.end("Source not found");
387
- return;
388
- }
389
- if (!destinationPath) {
390
- res.writeHead(400, { "Content-Type": "text/plain", "Access-Control-Allow-Origin": "*" });
391
- res.end("Destination header required");
392
- return;
393
- }
394
- const destExists = fs.existsSync(destinationPath);
395
- if (destExists && !overwrite) {
396
- res.writeHead(412, { "Content-Type": "text/plain", "Access-Control-Allow-Origin": "*" });
397
- res.end("Destination exists and Overwrite is F");
398
- return;
399
- }
400
- const destDir = path.dirname(destinationPath);
401
- if (!fs.existsSync(destDir)) {
402
- fs.mkdirSync(destDir, { recursive: true });
403
- }
404
- const sourceStats = fs.statSync(sourcePath);
405
- if (sourceStats.isDirectory()) {
406
- copyDirRecursive(sourcePath, destinationPath);
407
- } else {
408
- fs.copyFileSync(sourcePath, destinationPath);
409
- }
410
- const status = destExists ? 204 : 201;
411
- res.writeHead(status, {
412
- "Content-Length": "0",
413
- "Access-Control-Allow-Origin": "*"
414
- });
415
- res.end();
416
- } catch (err) {
417
- res.writeHead(500, { "Content-Type": "text/plain", "Access-Control-Allow-Origin": "*" });
418
- res.end(`Error: ${err}`);
419
- }
420
- }
421
- function copyDirRecursive(src, dest) {
422
- if (!fs.existsSync(dest)) {
423
- fs.mkdirSync(dest, { recursive: true });
424
- }
425
- const entries = fs.readdirSync(src, { withFileTypes: true });
426
- for (const entry of entries) {
427
- const srcPath = path.join(src, entry.name);
428
- const destPath = path.join(dest, entry.name);
429
- if (entry.isDirectory()) {
430
- copyDirRecursive(srcPath, destPath);
431
- } else {
432
- fs.copyFileSync(srcPath, destPath);
433
- }
434
- }
435
- }
436
- function parseDestinationHeader(destinationHeader, prefix, rootPath) {
437
- if (!destinationHeader) {
438
- return null;
439
- }
440
- try {
441
- let destPath;
442
- if (destinationHeader.startsWith("http://") || destinationHeader.startsWith("https://")) {
443
- const url = new URL(destinationHeader);
444
- destPath = decodeURIComponent(url.pathname);
445
- } else {
446
- destPath = decodeURIComponent(destinationHeader);
447
- }
448
- if (destPath.startsWith(prefix)) {
449
- destPath = destPath.slice(prefix.length);
450
- }
451
- return resolveWebDAVPath(destPath, rootPath);
452
- } catch {
453
- return null;
454
- }
455
- }
456
- function createWebDAVHandler(config) {
457
- const { rootPath, prefix = WEBDAV_PREFIX, auth } = config;
458
- return async (req, res) => {
459
- const rawUrl = req.url || "/";
460
- if (rawUrl.includes("..")) {
461
- if (rawUrl.startsWith(prefix)) {
462
- res.writeHead(403, { "Content-Type": "text/plain", "Access-Control-Allow-Origin": "*" });
463
- res.end("Forbidden");
464
- return true;
465
- }
466
- }
467
- const url = new URL(rawUrl, `http://${req.headers.host || "localhost"}`);
468
- const pathname = decodeURIComponent(url.pathname);
469
- if (!pathname.startsWith(prefix)) {
470
- return false;
471
- }
472
- let webdavPath = pathname.slice(prefix.length);
473
- if (!webdavPath.startsWith("/")) {
474
- webdavPath = "/" + webdavPath;
475
- }
476
- if (req.method === "OPTIONS") {
477
- handleOptions(res, prefix);
478
- return true;
479
- }
480
- if (!checkAuth(req, auth)) {
481
- res.writeHead(401, {
482
- "WWW-Authenticate": 'Basic realm="ClawVault WebDAV"',
483
- "Content-Type": "text/plain",
484
- "Access-Control-Allow-Origin": "*"
485
- });
486
- res.end("Unauthorized");
487
- return true;
488
- }
489
- if (!isPathSafe(webdavPath, rootPath)) {
490
- res.writeHead(403, { "Content-Type": "text/plain", "Access-Control-Allow-Origin": "*" });
491
- res.end("Forbidden");
492
- return true;
493
- }
494
- const filePath = resolveWebDAVPath(webdavPath, rootPath);
495
- if (!filePath) {
496
- res.writeHead(403, { "Content-Type": "text/plain", "Access-Control-Allow-Origin": "*" });
497
- res.end("Forbidden");
498
- return true;
499
- }
500
- const depth = req.headers.depth || "infinity";
501
- const overwrite = req.headers.overwrite?.toUpperCase() !== "F";
502
- const destinationHeader = req.headers.destination;
503
- switch (req.method) {
504
- case "HEAD":
505
- handleHead(res, filePath);
506
- return true;
507
- case "GET":
508
- handleGet(res, filePath);
509
- return true;
510
- case "PUT": {
511
- const chunks = [];
512
- for await (const chunk of req) {
513
- chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
514
- }
515
- const body = Buffer.concat(chunks);
516
- handlePut(res, filePath, body);
517
- return true;
518
- }
519
- case "DELETE":
520
- handleDelete(res, filePath);
521
- return true;
522
- case "MKCOL":
523
- handleMkcol(res, filePath);
524
- return true;
525
- case "PROPFIND":
526
- handlePropfind(res, filePath, webdavPath, prefix, depth);
527
- return true;
528
- case "MOVE": {
529
- const destPath = parseDestinationHeader(destinationHeader, prefix, rootPath);
530
- if (destPath && destinationHeader) {
531
- const destWebdavPath = destinationHeader.includes(prefix) ? destinationHeader.slice(destinationHeader.indexOf(prefix) + prefix.length) : destinationHeader;
532
- if (!isPathSafe(destWebdavPath, rootPath)) {
533
- res.writeHead(403, { "Content-Type": "text/plain", "Access-Control-Allow-Origin": "*" });
534
- res.end("Forbidden");
535
- return true;
536
- }
537
- }
538
- handleMove(res, filePath, destPath, overwrite);
539
- return true;
540
- }
541
- case "COPY": {
542
- const destPath = parseDestinationHeader(destinationHeader, prefix, rootPath);
543
- if (destPath && destinationHeader) {
544
- const destWebdavPath = destinationHeader.includes(prefix) ? destinationHeader.slice(destinationHeader.indexOf(prefix) + prefix.length) : destinationHeader;
545
- if (!isPathSafe(destWebdavPath, rootPath)) {
546
- res.writeHead(403, { "Content-Type": "text/plain", "Access-Control-Allow-Origin": "*" });
547
- res.end("Forbidden");
548
- return true;
549
- }
550
- }
551
- handleCopy(res, filePath, destPath, overwrite);
552
- return true;
553
- }
554
- default:
555
- res.writeHead(405, {
556
- "Allow": SUPPORTED_METHODS.join(", "),
557
- "Content-Type": "text/plain",
558
- "Access-Control-Allow-Origin": "*"
559
- });
560
- res.end("Method Not Allowed");
561
- return true;
562
- }
563
- };
564
- }
565
-
566
- // src/lib/tailscale.ts
567
- var crypto = __toESM(require("crypto"), 1);
568
- var DEFAULT_SERVE_PORT = 8384;
569
- var CLAWVAULT_SERVE_PATH = "/.clawvault";
570
- var MANIFEST_ENDPOINT = "/.clawvault/manifest";
571
- var SYNC_ENDPOINT = "/.clawvault/sync";
572
- var FILE_ENDPOINT = "/.clawvault/files";
573
- function hasTailscale() {
574
- const probe = (0, import_child_process.spawnSync)("tailscale", ["version"], {
575
- stdio: "pipe",
576
- encoding: "utf-8",
577
- timeout: 5e3
578
- });
579
- return !probe.error && probe.status === 0;
580
- }
581
- function getTailscaleVersion() {
582
- const result = (0, import_child_process.spawnSync)("tailscale", ["version"], {
583
- stdio: "pipe",
584
- encoding: "utf-8",
585
- timeout: 5e3
586
- });
587
- if (result.error || result.status !== 0) {
588
- return null;
589
- }
590
- const lines = result.stdout.trim().split("\n");
591
- return lines[0] || null;
592
- }
593
- function getTailscaleStatus() {
594
- const status = {
595
- installed: false,
596
- running: false,
597
- connected: false,
598
- peers: []
599
- };
600
- if (!hasTailscale()) {
601
- status.error = "Tailscale CLI not found. Install from https://tailscale.com/download";
602
- return status;
603
- }
604
- status.installed = true;
605
- const result = (0, import_child_process.spawnSync)("tailscale", ["status", "--json"], {
606
- stdio: "pipe",
607
- encoding: "utf-8",
608
- timeout: 1e4
609
- });
610
- if (result.error) {
611
- status.error = `Failed to get Tailscale status: ${result.error.message}`;
612
- return status;
613
- }
614
- if (result.status !== 0) {
615
- status.error = result.stderr?.trim() || "Tailscale daemon not running";
616
- return status;
617
- }
618
- try {
619
- const data = JSON.parse(result.stdout);
620
- status.running = true;
621
- status.backendState = data.BackendState;
622
- status.connected = data.BackendState === "Running";
623
- status.tailnetName = data.CurrentTailnet?.Name;
624
- if (data.Self) {
625
- status.selfIP = data.Self.TailscaleIPs?.[0];
626
- status.selfHostname = data.Self.HostName;
627
- status.selfDNSName = data.Self.DNSName;
628
- }
629
- if (data.Peer) {
630
- for (const [_, peerData] of Object.entries(data.Peer)) {
631
- const peer = {
632
- hostname: peerData.HostName || "",
633
- dnsName: peerData.DNSName || "",
634
- tailscaleIPs: peerData.TailscaleIPs || [],
635
- online: peerData.Online || false,
636
- os: peerData.OS,
637
- exitNode: peerData.ExitNode,
638
- tags: peerData.Tags,
639
- lastSeen: peerData.LastSeen
640
- };
641
- status.peers.push(peer);
642
- }
643
- }
644
- } catch (err) {
645
- status.error = `Failed to parse Tailscale status: ${err}`;
646
- }
647
- return status;
648
- }
649
- function findPeer(hostname) {
650
- const status = getTailscaleStatus();
651
- if (!status.connected) {
652
- return null;
653
- }
654
- const normalizedSearch = hostname.toLowerCase();
655
- let peer = status.peers.find(
656
- (p) => p.hostname.toLowerCase() === normalizedSearch
657
- );
658
- if (peer) return peer;
659
- peer = status.peers.find(
660
- (p) => p.dnsName.toLowerCase().startsWith(normalizedSearch)
661
- );
662
- if (peer) return peer;
663
- peer = status.peers.find(
664
- (p) => p.hostname.toLowerCase().includes(normalizedSearch)
665
- );
666
- return peer || null;
667
- }
668
- function getOnlinePeers() {
669
- const status = getTailscaleStatus();
670
- return status.peers.filter((p) => p.online);
671
- }
672
- function resolvePeerIP(hostname) {
673
- const peer = findPeer(hostname);
674
- return peer?.tailscaleIPs[0] || null;
675
- }
676
- function calculateChecksum(filePath) {
677
- const content = fs2.readFileSync(filePath);
678
- return crypto.createHash("sha256").update(content).digest("hex");
679
- }
680
- function generateVaultManifest(vaultPath) {
681
- const configPath = path2.join(vaultPath, ".clawvault.json");
682
- if (!fs2.existsSync(configPath)) {
683
- throw new Error(`Not a ClawVault: ${vaultPath}`);
684
- }
685
- const config = JSON.parse(fs2.readFileSync(configPath, "utf-8"));
686
- const files = [];
687
- function walkDir(dir, relativePath = "") {
688
- const entries = fs2.readdirSync(dir, { withFileTypes: true });
689
- for (const entry of entries) {
690
- const fullPath = path2.join(dir, entry.name);
691
- const relPath = path2.join(relativePath, entry.name);
692
- if (entry.name.startsWith(".") && entry.name !== ".clawvault.json") {
693
- continue;
694
- }
695
- if (entry.name === "node_modules") {
696
- continue;
697
- }
698
- if (entry.isDirectory()) {
699
- walkDir(fullPath, relPath);
700
- } else if (entry.isFile() && (entry.name.endsWith(".md") || entry.name === ".clawvault.json")) {
701
- const stats = fs2.statSync(fullPath);
702
- const category = relativePath.split(path2.sep)[0] || "root";
703
- files.push({
704
- path: relPath,
705
- size: stats.size,
706
- modified: stats.mtime.toISOString(),
707
- checksum: calculateChecksum(fullPath),
708
- category
709
- });
710
- }
711
- }
712
- }
713
- walkDir(vaultPath);
714
- return {
715
- name: config.name,
716
- version: config.version || "1.0.0",
717
- lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
718
- files
719
- };
720
- }
721
- function compareManifests(local, remote) {
722
- const localFiles = new Map(local.files.map((f) => [f.path, f]));
723
- const remoteFiles = new Map(remote.files.map((f) => [f.path, f]));
724
- const toPush = [];
725
- const toPull = [];
726
- const conflicts = [];
727
- const unchanged = [];
728
- for (const [filePath, localFile] of localFiles) {
729
- const remoteFile = remoteFiles.get(filePath);
730
- if (!remoteFile) {
731
- toPush.push(localFile);
732
- } else if (localFile.checksum === remoteFile.checksum) {
733
- unchanged.push(filePath);
734
- } else {
735
- const localTime = new Date(localFile.modified).getTime();
736
- const remoteTime = new Date(remoteFile.modified).getTime();
737
- if (localTime > remoteTime) {
738
- toPush.push(localFile);
739
- } else if (remoteTime > localTime) {
740
- toPull.push(remoteFile);
741
- } else {
742
- conflicts.push({ path: filePath, local: localFile, remote: remoteFile });
743
- }
744
- }
745
- }
746
- for (const [filePath, remoteFile] of remoteFiles) {
747
- if (!localFiles.has(filePath)) {
748
- toPull.push(remoteFile);
749
- }
750
- }
751
- return { toPush, toPull, conflicts, unchanged };
752
- }
753
- function serveVault(vaultPath, options = {}) {
754
- const port = options.port || DEFAULT_SERVE_PORT;
755
- const pathPrefix = options.pathPrefix || CLAWVAULT_SERVE_PATH;
756
- if (!fs2.existsSync(path2.join(vaultPath, ".clawvault.json"))) {
757
- throw new Error(`Not a ClawVault: ${vaultPath}`);
758
- }
759
- const webdavHandler = createWebDAVHandler({
760
- rootPath: vaultPath,
761
- prefix: WEBDAV_PREFIX,
762
- auth: options.webdavAuth
763
- });
764
- const server = http.createServer(async (req, res) => {
765
- const url = new URL(req.url || "/", `http://localhost:${port}`);
766
- const pathname = url.pathname;
767
- if (pathname.startsWith(WEBDAV_PREFIX)) {
768
- try {
769
- const handled = await webdavHandler(req, res);
770
- if (handled) return;
771
- } catch (err) {
772
- res.writeHead(500, { "Content-Type": "text/plain", "Access-Control-Allow-Origin": "*" });
773
- res.end(`WebDAV Error: ${err}`);
774
- return;
775
- }
776
- }
777
- res.setHeader("Access-Control-Allow-Origin", "*");
778
- res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
779
- res.setHeader("Access-Control-Allow-Headers", "Content-Type");
780
- if (req.method === "OPTIONS") {
781
- res.writeHead(200);
782
- res.end();
783
- return;
784
- }
785
- if (pathname === `${pathPrefix}/health`) {
786
- res.writeHead(200, { "Content-Type": "application/json" });
787
- res.end(JSON.stringify({ status: "ok", vault: path2.basename(vaultPath) }));
788
- return;
789
- }
790
- if (pathname === `${pathPrefix}/manifest`) {
791
- try {
792
- const manifest = generateVaultManifest(vaultPath);
793
- res.writeHead(200, { "Content-Type": "application/json" });
794
- res.end(JSON.stringify(manifest));
795
- } catch (err) {
796
- res.writeHead(500, { "Content-Type": "application/json" });
797
- res.end(JSON.stringify({ error: String(err) }));
798
- }
799
- return;
800
- }
801
- if (pathname.startsWith(`${pathPrefix}/files/`)) {
802
- const relativePath = decodeURIComponent(pathname.slice(`${pathPrefix}/files/`.length));
803
- const filePath = path2.join(vaultPath, relativePath);
804
- const resolvedPath = path2.resolve(filePath);
805
- const resolvedVault = path2.resolve(vaultPath);
806
- if (!resolvedPath.startsWith(resolvedVault)) {
807
- res.writeHead(403, { "Content-Type": "application/json" });
808
- res.end(JSON.stringify({ error: "Access denied" }));
809
- return;
810
- }
811
- if (!fs2.existsSync(filePath)) {
812
- res.writeHead(404, { "Content-Type": "application/json" });
813
- res.end(JSON.stringify({ error: "File not found" }));
814
- return;
815
- }
816
- try {
817
- const content = fs2.readFileSync(filePath, "utf-8");
818
- const stats = fs2.statSync(filePath);
819
- res.writeHead(200, {
820
- "Content-Type": "text/markdown",
821
- "Content-Length": Buffer.byteLength(content),
822
- "Last-Modified": stats.mtime.toUTCString()
823
- });
824
- res.end(content);
825
- } catch (err) {
826
- res.writeHead(500, { "Content-Type": "application/json" });
827
- res.end(JSON.stringify({ error: String(err) }));
828
- }
829
- return;
830
- }
831
- if (pathname.startsWith(`${pathPrefix}/upload/`) && req.method === "POST") {
832
- const relativePath = decodeURIComponent(pathname.slice(`${pathPrefix}/upload/`.length));
833
- const filePath = path2.join(vaultPath, relativePath);
834
- const resolvedPath = path2.resolve(filePath);
835
- const resolvedVault = path2.resolve(vaultPath);
836
- if (!resolvedPath.startsWith(resolvedVault)) {
837
- res.writeHead(403, { "Content-Type": "application/json" });
838
- res.end(JSON.stringify({ error: "Access denied" }));
839
- return;
840
- }
841
- let body = "";
842
- req.on("data", (chunk) => {
843
- body += chunk;
844
- });
845
- req.on("end", () => {
846
- try {
847
- const dir = path2.dirname(filePath);
848
- if (!fs2.existsSync(dir)) {
849
- fs2.mkdirSync(dir, { recursive: true });
850
- }
851
- fs2.writeFileSync(filePath, body, "utf-8");
852
- res.writeHead(200, { "Content-Type": "application/json" });
853
- res.end(JSON.stringify({ success: true, path: relativePath }));
854
- } catch (err) {
855
- res.writeHead(500, { "Content-Type": "application/json" });
856
- res.end(JSON.stringify({ error: String(err) }));
857
- }
858
- });
859
- return;
860
- }
861
- if (pathname === pathPrefix || pathname === `${pathPrefix}/`) {
862
- res.writeHead(200, { "Content-Type": "application/json" });
863
- res.end(JSON.stringify({
864
- service: "clawvault-sync",
865
- version: "1.0.0",
866
- vault: path2.basename(vaultPath),
867
- endpoints: {
868
- health: `${pathPrefix}/health`,
869
- manifest: `${pathPrefix}/manifest`,
870
- files: `${pathPrefix}/files/<path>`,
871
- upload: `${pathPrefix}/upload/<path>`,
872
- webdav: `${WEBDAV_PREFIX}/`
873
- }
874
- }));
875
- return;
876
- }
877
- res.writeHead(404, { "Content-Type": "application/json" });
878
- res.end(JSON.stringify({ error: "Not found" }));
879
- });
880
- server.listen(port, "0.0.0.0");
881
- return {
882
- server,
883
- port,
884
- stop: () => new Promise((resolve3, reject) => {
885
- server.close((err) => {
886
- if (err) reject(err);
887
- else resolve3();
888
- });
889
- })
890
- };
891
- }
892
- async function fetchRemoteManifest(host, port = DEFAULT_SERVE_PORT, useHttps = false) {
893
- return new Promise((resolve3, reject) => {
894
- const protocol = useHttps ? https : http;
895
- const url = `${useHttps ? "https" : "http"}://${host}:${port}${CLAWVAULT_SERVE_PATH}/manifest`;
896
- const req = protocol.get(url, { timeout: 1e4 }, (res) => {
897
- let data = "";
898
- res.on("data", (chunk) => {
899
- data += chunk;
900
- });
901
- res.on("end", () => {
902
- if (res.statusCode !== 200) {
903
- reject(new Error(`Failed to fetch manifest: HTTP ${res.statusCode}`));
904
- return;
905
- }
906
- try {
907
- resolve3(JSON.parse(data));
908
- } catch (err) {
909
- reject(new Error(`Invalid manifest response: ${err}`));
910
- }
911
- });
912
- });
913
- req.on("error", reject);
914
- req.on("timeout", () => {
915
- req.destroy();
916
- reject(new Error("Request timed out"));
917
- });
918
- });
919
- }
920
- async function fetchRemoteFile(host, filePath, port = DEFAULT_SERVE_PORT, useHttps = false) {
921
- return new Promise((resolve3, reject) => {
922
- const protocol = useHttps ? https : http;
923
- const encodedPath = encodeURIComponent(filePath).replace(/%2F/g, "/");
924
- const url = `${useHttps ? "https" : "http"}://${host}:${port}${CLAWVAULT_SERVE_PATH}/files/${encodedPath}`;
925
- const req = protocol.get(url, { timeout: 3e4 }, (res) => {
926
- let data = "";
927
- res.on("data", (chunk) => {
928
- data += chunk;
929
- });
930
- res.on("end", () => {
931
- if (res.statusCode !== 200) {
932
- reject(new Error(`Failed to fetch file: HTTP ${res.statusCode}`));
933
- return;
934
- }
935
- resolve3(data);
936
- });
937
- });
938
- req.on("error", reject);
939
- req.on("timeout", () => {
940
- req.destroy();
941
- reject(new Error("Request timed out"));
942
- });
943
- });
944
- }
945
- async function pushFileToRemote(host, filePath, content, port = DEFAULT_SERVE_PORT, useHttps = false) {
946
- return new Promise((resolve3, reject) => {
947
- const protocol = useHttps ? https : http;
948
- const encodedPath = encodeURIComponent(filePath).replace(/%2F/g, "/");
949
- const url = new URL(`${useHttps ? "https" : "http"}://${host}:${port}${CLAWVAULT_SERVE_PATH}/upload/${encodedPath}`);
950
- const options = {
951
- hostname: url.hostname,
952
- port: url.port,
953
- path: url.pathname,
954
- method: "POST",
955
- headers: {
956
- "Content-Type": "text/markdown",
957
- "Content-Length": Buffer.byteLength(content)
958
- },
959
- timeout: 3e4
960
- };
961
- const req = protocol.request(options, (res) => {
962
- let data = "";
963
- res.on("data", (chunk) => {
964
- data += chunk;
965
- });
966
- res.on("end", () => {
967
- if (res.statusCode !== 200) {
968
- reject(new Error(`Failed to push file: HTTP ${res.statusCode}`));
969
- return;
970
- }
971
- resolve3();
972
- });
973
- });
974
- req.on("error", reject);
975
- req.on("timeout", () => {
976
- req.destroy();
977
- reject(new Error("Request timed out"));
978
- });
979
- req.write(content);
980
- req.end();
981
- });
982
- }
983
- async function syncWithPeer(vaultPath, options) {
984
- const startTime = Date.now();
985
- const result = {
986
- pushed: [],
987
- pulled: [],
988
- deleted: [],
989
- unchanged: [],
990
- errors: [],
991
- stats: {
992
- bytesTransferred: 0,
993
- filesProcessed: 0,
994
- duration: 0
995
- }
996
- };
997
- const {
998
- peer,
999
- port = DEFAULT_SERVE_PORT,
1000
- direction = "bidirectional",
1001
- dryRun = false,
1002
- deleteOrphans = false,
1003
- categories,
1004
- https: useHttps = false
1005
- } = options;
1006
- let host = peer;
1007
- if (!peer.match(/^\d+\.\d+\.\d+\.\d+$/)) {
1008
- const resolvedIP = resolvePeerIP(peer);
1009
- if (!resolvedIP) {
1010
- result.errors.push(`Could not resolve peer: ${peer}`);
1011
- result.stats.duration = Date.now() - startTime;
1012
- return result;
1013
- }
1014
- host = resolvedIP;
1015
- }
1016
- try {
1017
- const localManifest = generateVaultManifest(vaultPath);
1018
- const remoteManifest = await fetchRemoteManifest(host, port, useHttps);
1019
- let { toPush, toPull, conflicts, unchanged } = compareManifests(localManifest, remoteManifest);
1020
- if (categories && categories.length > 0) {
1021
- const categorySet = new Set(categories);
1022
- toPush = toPush.filter((f) => categorySet.has(f.category));
1023
- toPull = toPull.filter((f) => categorySet.has(f.category));
1024
- }
1025
- result.unchanged = unchanged;
1026
- for (const conflict of conflicts) {
1027
- result.errors.push(`Conflict: ${conflict.path} (local and remote have same timestamp but different content)`);
1028
- }
1029
- if (direction === "push" || direction === "bidirectional") {
1030
- for (const file of toPush) {
1031
- try {
1032
- if (!dryRun) {
1033
- const content = fs2.readFileSync(path2.join(vaultPath, file.path), "utf-8");
1034
- await pushFileToRemote(host, file.path, content, port, useHttps);
1035
- result.stats.bytesTransferred += file.size;
1036
- }
1037
- result.pushed.push(file.path);
1038
- result.stats.filesProcessed++;
1039
- } catch (err) {
1040
- result.errors.push(`Failed to push ${file.path}: ${err}`);
1041
- }
1042
- }
1043
- }
1044
- if (direction === "pull" || direction === "bidirectional") {
1045
- for (const file of toPull) {
1046
- try {
1047
- if (!dryRun) {
1048
- const content = await fetchRemoteFile(host, file.path, port, useHttps);
1049
- const filePath = path2.join(vaultPath, file.path);
1050
- const dir = path2.dirname(filePath);
1051
- if (!fs2.existsSync(dir)) {
1052
- fs2.mkdirSync(dir, { recursive: true });
1053
- }
1054
- fs2.writeFileSync(filePath, content, "utf-8");
1055
- result.stats.bytesTransferred += file.size;
1056
- }
1057
- result.pulled.push(file.path);
1058
- result.stats.filesProcessed++;
1059
- } catch (err) {
1060
- result.errors.push(`Failed to pull ${file.path}: ${err}`);
1061
- }
1062
- }
1063
- }
1064
- if (deleteOrphans && direction === "pull") {
1065
- const remoteFiles = new Set(remoteManifest.files.map((f) => f.path));
1066
- for (const file of localManifest.files) {
1067
- if (!remoteFiles.has(file.path)) {
1068
- if (!categories || categories.includes(file.category)) {
1069
- try {
1070
- if (!dryRun) {
1071
- fs2.unlinkSync(path2.join(vaultPath, file.path));
1072
- }
1073
- result.deleted.push(file.path);
1074
- } catch (err) {
1075
- result.errors.push(`Failed to delete ${file.path}: ${err}`);
1076
- }
1077
- }
1078
- }
1079
- }
1080
- }
1081
- } catch (err) {
1082
- result.errors.push(`Sync failed: ${err}`);
1083
- }
1084
- result.stats.duration = Date.now() - startTime;
1085
- return result;
1086
- }
1087
- function configureTailscaleServe(localPort, options = {}) {
1088
- if (!hasTailscale()) {
1089
- return null;
1090
- }
1091
- const args = ["serve"];
1092
- if (options.funnel) {
1093
- args.push("--bg");
1094
- args.push("funnel");
1095
- } else if (options.background) {
1096
- args.push("--bg");
1097
- }
1098
- args.push(`localhost:${localPort}`);
1099
- const proc = (0, import_child_process.spawn)("tailscale", args, {
1100
- stdio: "inherit",
1101
- detached: options.background
1102
- });
1103
- if (options.background) {
1104
- proc.unref();
1105
- }
1106
- return proc;
1107
- }
1108
- function stopTailscaleServe() {
1109
- if (!hasTailscale()) {
1110
- return false;
1111
- }
1112
- const result = (0, import_child_process.spawnSync)("tailscale", ["serve", "off"], {
1113
- stdio: "pipe",
1114
- encoding: "utf-8",
1115
- timeout: 5e3
1116
- });
1117
- return result.status === 0;
1118
- }
1119
- async function checkPeerClawVault(host, port = DEFAULT_SERVE_PORT) {
1120
- try {
1121
- const response = await new Promise((resolve3) => {
1122
- const req = http.get(
1123
- `http://${host}:${port}${CLAWVAULT_SERVE_PATH}/health`,
1124
- { timeout: 5e3 },
1125
- (res) => {
1126
- resolve3(res.statusCode === 200);
1127
- }
1128
- );
1129
- req.on("error", () => resolve3(false));
1130
- req.on("timeout", () => {
1131
- req.destroy();
1132
- resolve3(false);
1133
- });
1134
- });
1135
- return response;
1136
- } catch {
1137
- return false;
1138
- }
1139
- }
1140
- async function discoverClawVaultPeers(port = DEFAULT_SERVE_PORT) {
1141
- const status = getTailscaleStatus();
1142
- if (!status.connected) {
1143
- return [];
1144
- }
1145
- const clawvaultPeers = [];
1146
- const checkPromises = status.peers.filter((p) => p.online).map(async (peer) => {
1147
- const ip = peer.tailscaleIPs[0];
1148
- if (!ip) return;
1149
- const isServing = await checkPeerClawVault(ip, port);
1150
- if (isServing) {
1151
- peer.clawvaultServing = true;
1152
- peer.clawvaultPort = port;
1153
- clawvaultPeers.push(peer);
1154
- }
1155
- });
1156
- await Promise.all(checkPromises);
1157
- return clawvaultPeers;
1158
- }
1159
- // Annotate the CommonJS export names for ESM import in node:
1160
- 0 && (module.exports = {
1161
- CLAWVAULT_SERVE_PATH,
1162
- DEFAULT_SERVE_PORT,
1163
- FILE_ENDPOINT,
1164
- MANIFEST_ENDPOINT,
1165
- SYNC_ENDPOINT,
1166
- checkPeerClawVault,
1167
- compareManifests,
1168
- configureTailscaleServe,
1169
- discoverClawVaultPeers,
1170
- fetchRemoteFile,
1171
- fetchRemoteManifest,
1172
- findPeer,
1173
- generateVaultManifest,
1174
- getOnlinePeers,
1175
- getTailscaleStatus,
1176
- getTailscaleVersion,
1177
- hasTailscale,
1178
- pushFileToRemote,
1179
- resolvePeerIP,
1180
- serveVault,
1181
- stopTailscaleServe,
1182
- syncWithPeer
1183
- });