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,628 @@
1
+ import {
2
+ WEBDAV_PREFIX,
3
+ createWebDAVHandler
4
+ } from "./chunk-IVRIKYFE.js";
5
+
6
+ // src/lib/tailscale.ts
7
+ import { spawnSync, spawn } from "child_process";
8
+ import * as fs from "fs";
9
+ import * as path from "path";
10
+ import * as http from "http";
11
+ import * as https from "https";
12
+ import * as crypto from "crypto";
13
+ var DEFAULT_SERVE_PORT = 8384;
14
+ var CLAWVAULT_SERVE_PATH = "/.clawvault";
15
+ var MANIFEST_ENDPOINT = "/.clawvault/manifest";
16
+ var SYNC_ENDPOINT = "/.clawvault/sync";
17
+ var FILE_ENDPOINT = "/.clawvault/files";
18
+ function hasTailscale() {
19
+ const probe = spawnSync("tailscale", ["version"], {
20
+ stdio: "pipe",
21
+ encoding: "utf-8",
22
+ timeout: 5e3
23
+ });
24
+ return !probe.error && probe.status === 0;
25
+ }
26
+ function getTailscaleVersion() {
27
+ const result = spawnSync("tailscale", ["version"], {
28
+ stdio: "pipe",
29
+ encoding: "utf-8",
30
+ timeout: 5e3
31
+ });
32
+ if (result.error || result.status !== 0) {
33
+ return null;
34
+ }
35
+ const lines = result.stdout.trim().split("\n");
36
+ return lines[0] || null;
37
+ }
38
+ function getTailscaleStatus() {
39
+ const status = {
40
+ installed: false,
41
+ running: false,
42
+ connected: false,
43
+ peers: []
44
+ };
45
+ if (!hasTailscale()) {
46
+ status.error = "Tailscale CLI not found. Install from https://tailscale.com/download";
47
+ return status;
48
+ }
49
+ status.installed = true;
50
+ const result = spawnSync("tailscale", ["status", "--json"], {
51
+ stdio: "pipe",
52
+ encoding: "utf-8",
53
+ timeout: 1e4
54
+ });
55
+ if (result.error) {
56
+ status.error = `Failed to get Tailscale status: ${result.error.message}`;
57
+ return status;
58
+ }
59
+ if (result.status !== 0) {
60
+ status.error = result.stderr?.trim() || "Tailscale daemon not running";
61
+ return status;
62
+ }
63
+ try {
64
+ const data = JSON.parse(result.stdout);
65
+ status.running = true;
66
+ status.backendState = data.BackendState;
67
+ status.connected = data.BackendState === "Running";
68
+ status.tailnetName = data.CurrentTailnet?.Name;
69
+ if (data.Self) {
70
+ status.selfIP = data.Self.TailscaleIPs?.[0];
71
+ status.selfHostname = data.Self.HostName;
72
+ status.selfDNSName = data.Self.DNSName;
73
+ }
74
+ if (data.Peer) {
75
+ for (const [_, peerData] of Object.entries(data.Peer)) {
76
+ const peer = {
77
+ hostname: peerData.HostName || "",
78
+ dnsName: peerData.DNSName || "",
79
+ tailscaleIPs: peerData.TailscaleIPs || [],
80
+ online: peerData.Online || false,
81
+ os: peerData.OS,
82
+ exitNode: peerData.ExitNode,
83
+ tags: peerData.Tags,
84
+ lastSeen: peerData.LastSeen
85
+ };
86
+ status.peers.push(peer);
87
+ }
88
+ }
89
+ } catch (err) {
90
+ status.error = `Failed to parse Tailscale status: ${err}`;
91
+ }
92
+ return status;
93
+ }
94
+ function findPeer(hostname) {
95
+ const status = getTailscaleStatus();
96
+ if (!status.connected) {
97
+ return null;
98
+ }
99
+ const normalizedSearch = hostname.toLowerCase();
100
+ let peer = status.peers.find(
101
+ (p) => p.hostname.toLowerCase() === normalizedSearch
102
+ );
103
+ if (peer) return peer;
104
+ peer = status.peers.find(
105
+ (p) => p.dnsName.toLowerCase().startsWith(normalizedSearch)
106
+ );
107
+ if (peer) return peer;
108
+ peer = status.peers.find(
109
+ (p) => p.hostname.toLowerCase().includes(normalizedSearch)
110
+ );
111
+ return peer || null;
112
+ }
113
+ function getOnlinePeers() {
114
+ const status = getTailscaleStatus();
115
+ return status.peers.filter((p) => p.online);
116
+ }
117
+ function resolvePeerIP(hostname) {
118
+ const peer = findPeer(hostname);
119
+ return peer?.tailscaleIPs[0] || null;
120
+ }
121
+ function calculateChecksum(filePath) {
122
+ const content = fs.readFileSync(filePath);
123
+ return crypto.createHash("sha256").update(content).digest("hex");
124
+ }
125
+ function generateVaultManifest(vaultPath) {
126
+ const configPath = path.join(vaultPath, ".clawvault.json");
127
+ if (!fs.existsSync(configPath)) {
128
+ throw new Error(`Not a ClawVault: ${vaultPath}`);
129
+ }
130
+ const config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
131
+ const files = [];
132
+ function walkDir(dir, relativePath = "") {
133
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
134
+ for (const entry of entries) {
135
+ const fullPath = path.join(dir, entry.name);
136
+ const relPath = path.join(relativePath, entry.name);
137
+ if (entry.name.startsWith(".") && entry.name !== ".clawvault.json") {
138
+ continue;
139
+ }
140
+ if (entry.name === "node_modules") {
141
+ continue;
142
+ }
143
+ if (entry.isDirectory()) {
144
+ walkDir(fullPath, relPath);
145
+ } else if (entry.isFile() && (entry.name.endsWith(".md") || entry.name === ".clawvault.json")) {
146
+ const stats = fs.statSync(fullPath);
147
+ const category = relativePath.split(path.sep)[0] || "root";
148
+ files.push({
149
+ path: relPath,
150
+ size: stats.size,
151
+ modified: stats.mtime.toISOString(),
152
+ checksum: calculateChecksum(fullPath),
153
+ category
154
+ });
155
+ }
156
+ }
157
+ }
158
+ walkDir(vaultPath);
159
+ return {
160
+ name: config.name,
161
+ version: config.version || "1.0.0",
162
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
163
+ files
164
+ };
165
+ }
166
+ function compareManifests(local, remote) {
167
+ const localFiles = new Map(local.files.map((f) => [f.path, f]));
168
+ const remoteFiles = new Map(remote.files.map((f) => [f.path, f]));
169
+ const toPush = [];
170
+ const toPull = [];
171
+ const conflicts = [];
172
+ const unchanged = [];
173
+ for (const [filePath, localFile] of localFiles) {
174
+ const remoteFile = remoteFiles.get(filePath);
175
+ if (!remoteFile) {
176
+ toPush.push(localFile);
177
+ } else if (localFile.checksum === remoteFile.checksum) {
178
+ unchanged.push(filePath);
179
+ } else {
180
+ const localTime = new Date(localFile.modified).getTime();
181
+ const remoteTime = new Date(remoteFile.modified).getTime();
182
+ if (localTime > remoteTime) {
183
+ toPush.push(localFile);
184
+ } else if (remoteTime > localTime) {
185
+ toPull.push(remoteFile);
186
+ } else {
187
+ conflicts.push({ path: filePath, local: localFile, remote: remoteFile });
188
+ }
189
+ }
190
+ }
191
+ for (const [filePath, remoteFile] of remoteFiles) {
192
+ if (!localFiles.has(filePath)) {
193
+ toPull.push(remoteFile);
194
+ }
195
+ }
196
+ return { toPush, toPull, conflicts, unchanged };
197
+ }
198
+ function serveVault(vaultPath, options = {}) {
199
+ const port = options.port || DEFAULT_SERVE_PORT;
200
+ const pathPrefix = options.pathPrefix || CLAWVAULT_SERVE_PATH;
201
+ if (!fs.existsSync(path.join(vaultPath, ".clawvault.json"))) {
202
+ throw new Error(`Not a ClawVault: ${vaultPath}`);
203
+ }
204
+ const webdavHandler = createWebDAVHandler({
205
+ rootPath: vaultPath,
206
+ prefix: WEBDAV_PREFIX,
207
+ auth: options.webdavAuth
208
+ });
209
+ const server = http.createServer(async (req, res) => {
210
+ const url = new URL(req.url || "/", `http://localhost:${port}`);
211
+ const pathname = url.pathname;
212
+ if (pathname.startsWith(WEBDAV_PREFIX)) {
213
+ try {
214
+ const handled = await webdavHandler(req, res);
215
+ if (handled) return;
216
+ } catch (err) {
217
+ res.writeHead(500, { "Content-Type": "text/plain", "Access-Control-Allow-Origin": "*" });
218
+ res.end(`WebDAV Error: ${err}`);
219
+ return;
220
+ }
221
+ }
222
+ res.setHeader("Access-Control-Allow-Origin", "*");
223
+ res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
224
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type");
225
+ if (req.method === "OPTIONS") {
226
+ res.writeHead(200);
227
+ res.end();
228
+ return;
229
+ }
230
+ if (pathname === `${pathPrefix}/health`) {
231
+ res.writeHead(200, { "Content-Type": "application/json" });
232
+ res.end(JSON.stringify({ status: "ok", vault: path.basename(vaultPath) }));
233
+ return;
234
+ }
235
+ if (pathname === `${pathPrefix}/manifest`) {
236
+ try {
237
+ const manifest = generateVaultManifest(vaultPath);
238
+ res.writeHead(200, { "Content-Type": "application/json" });
239
+ res.end(JSON.stringify(manifest));
240
+ } catch (err) {
241
+ res.writeHead(500, { "Content-Type": "application/json" });
242
+ res.end(JSON.stringify({ error: String(err) }));
243
+ }
244
+ return;
245
+ }
246
+ if (pathname.startsWith(`${pathPrefix}/files/`)) {
247
+ const relativePath = decodeURIComponent(pathname.slice(`${pathPrefix}/files/`.length));
248
+ const filePath = path.join(vaultPath, relativePath);
249
+ const resolvedPath = path.resolve(filePath);
250
+ const resolvedVault = path.resolve(vaultPath);
251
+ if (!resolvedPath.startsWith(resolvedVault)) {
252
+ res.writeHead(403, { "Content-Type": "application/json" });
253
+ res.end(JSON.stringify({ error: "Access denied" }));
254
+ return;
255
+ }
256
+ if (!fs.existsSync(filePath)) {
257
+ res.writeHead(404, { "Content-Type": "application/json" });
258
+ res.end(JSON.stringify({ error: "File not found" }));
259
+ return;
260
+ }
261
+ try {
262
+ const content = fs.readFileSync(filePath, "utf-8");
263
+ const stats = fs.statSync(filePath);
264
+ res.writeHead(200, {
265
+ "Content-Type": "text/markdown",
266
+ "Content-Length": Buffer.byteLength(content),
267
+ "Last-Modified": stats.mtime.toUTCString()
268
+ });
269
+ res.end(content);
270
+ } catch (err) {
271
+ res.writeHead(500, { "Content-Type": "application/json" });
272
+ res.end(JSON.stringify({ error: String(err) }));
273
+ }
274
+ return;
275
+ }
276
+ if (pathname.startsWith(`${pathPrefix}/upload/`) && req.method === "POST") {
277
+ const relativePath = decodeURIComponent(pathname.slice(`${pathPrefix}/upload/`.length));
278
+ const filePath = path.join(vaultPath, relativePath);
279
+ const resolvedPath = path.resolve(filePath);
280
+ const resolvedVault = path.resolve(vaultPath);
281
+ if (!resolvedPath.startsWith(resolvedVault)) {
282
+ res.writeHead(403, { "Content-Type": "application/json" });
283
+ res.end(JSON.stringify({ error: "Access denied" }));
284
+ return;
285
+ }
286
+ let body = "";
287
+ req.on("data", (chunk) => {
288
+ body += chunk;
289
+ });
290
+ req.on("end", () => {
291
+ try {
292
+ const dir = path.dirname(filePath);
293
+ if (!fs.existsSync(dir)) {
294
+ fs.mkdirSync(dir, { recursive: true });
295
+ }
296
+ fs.writeFileSync(filePath, body, "utf-8");
297
+ res.writeHead(200, { "Content-Type": "application/json" });
298
+ res.end(JSON.stringify({ success: true, path: relativePath }));
299
+ } catch (err) {
300
+ res.writeHead(500, { "Content-Type": "application/json" });
301
+ res.end(JSON.stringify({ error: String(err) }));
302
+ }
303
+ });
304
+ return;
305
+ }
306
+ if (pathname === pathPrefix || pathname === `${pathPrefix}/`) {
307
+ res.writeHead(200, { "Content-Type": "application/json" });
308
+ res.end(JSON.stringify({
309
+ service: "clawvault-sync",
310
+ version: "1.0.0",
311
+ vault: path.basename(vaultPath),
312
+ endpoints: {
313
+ health: `${pathPrefix}/health`,
314
+ manifest: `${pathPrefix}/manifest`,
315
+ files: `${pathPrefix}/files/<path>`,
316
+ upload: `${pathPrefix}/upload/<path>`,
317
+ webdav: `${WEBDAV_PREFIX}/`
318
+ }
319
+ }));
320
+ return;
321
+ }
322
+ res.writeHead(404, { "Content-Type": "application/json" });
323
+ res.end(JSON.stringify({ error: "Not found" }));
324
+ });
325
+ server.listen(port, "0.0.0.0");
326
+ return {
327
+ server,
328
+ port,
329
+ stop: () => new Promise((resolve2, reject) => {
330
+ server.close((err) => {
331
+ if (err) reject(err);
332
+ else resolve2();
333
+ });
334
+ })
335
+ };
336
+ }
337
+ async function fetchRemoteManifest(host, port = DEFAULT_SERVE_PORT, useHttps = false) {
338
+ return new Promise((resolve2, reject) => {
339
+ const protocol = useHttps ? https : http;
340
+ const url = `${useHttps ? "https" : "http"}://${host}:${port}${CLAWVAULT_SERVE_PATH}/manifest`;
341
+ const req = protocol.get(url, { timeout: 1e4 }, (res) => {
342
+ let data = "";
343
+ res.on("data", (chunk) => {
344
+ data += chunk;
345
+ });
346
+ res.on("end", () => {
347
+ if (res.statusCode !== 200) {
348
+ reject(new Error(`Failed to fetch manifest: HTTP ${res.statusCode}`));
349
+ return;
350
+ }
351
+ try {
352
+ resolve2(JSON.parse(data));
353
+ } catch (err) {
354
+ reject(new Error(`Invalid manifest response: ${err}`));
355
+ }
356
+ });
357
+ });
358
+ req.on("error", reject);
359
+ req.on("timeout", () => {
360
+ req.destroy();
361
+ reject(new Error("Request timed out"));
362
+ });
363
+ });
364
+ }
365
+ async function fetchRemoteFile(host, filePath, port = DEFAULT_SERVE_PORT, useHttps = false) {
366
+ return new Promise((resolve2, reject) => {
367
+ const protocol = useHttps ? https : http;
368
+ const encodedPath = encodeURIComponent(filePath).replace(/%2F/g, "/");
369
+ const url = `${useHttps ? "https" : "http"}://${host}:${port}${CLAWVAULT_SERVE_PATH}/files/${encodedPath}`;
370
+ const req = protocol.get(url, { timeout: 3e4 }, (res) => {
371
+ let data = "";
372
+ res.on("data", (chunk) => {
373
+ data += chunk;
374
+ });
375
+ res.on("end", () => {
376
+ if (res.statusCode !== 200) {
377
+ reject(new Error(`Failed to fetch file: HTTP ${res.statusCode}`));
378
+ return;
379
+ }
380
+ resolve2(data);
381
+ });
382
+ });
383
+ req.on("error", reject);
384
+ req.on("timeout", () => {
385
+ req.destroy();
386
+ reject(new Error("Request timed out"));
387
+ });
388
+ });
389
+ }
390
+ async function pushFileToRemote(host, filePath, content, port = DEFAULT_SERVE_PORT, useHttps = false) {
391
+ return new Promise((resolve2, reject) => {
392
+ const protocol = useHttps ? https : http;
393
+ const encodedPath = encodeURIComponent(filePath).replace(/%2F/g, "/");
394
+ const url = new URL(`${useHttps ? "https" : "http"}://${host}:${port}${CLAWVAULT_SERVE_PATH}/upload/${encodedPath}`);
395
+ const options = {
396
+ hostname: url.hostname,
397
+ port: url.port,
398
+ path: url.pathname,
399
+ method: "POST",
400
+ headers: {
401
+ "Content-Type": "text/markdown",
402
+ "Content-Length": Buffer.byteLength(content)
403
+ },
404
+ timeout: 3e4
405
+ };
406
+ const req = protocol.request(options, (res) => {
407
+ let data = "";
408
+ res.on("data", (chunk) => {
409
+ data += chunk;
410
+ });
411
+ res.on("end", () => {
412
+ if (res.statusCode !== 200) {
413
+ reject(new Error(`Failed to push file: HTTP ${res.statusCode}`));
414
+ return;
415
+ }
416
+ resolve2();
417
+ });
418
+ });
419
+ req.on("error", reject);
420
+ req.on("timeout", () => {
421
+ req.destroy();
422
+ reject(new Error("Request timed out"));
423
+ });
424
+ req.write(content);
425
+ req.end();
426
+ });
427
+ }
428
+ async function syncWithPeer(vaultPath, options) {
429
+ const startTime = Date.now();
430
+ const result = {
431
+ pushed: [],
432
+ pulled: [],
433
+ deleted: [],
434
+ unchanged: [],
435
+ errors: [],
436
+ stats: {
437
+ bytesTransferred: 0,
438
+ filesProcessed: 0,
439
+ duration: 0
440
+ }
441
+ };
442
+ const {
443
+ peer,
444
+ port = DEFAULT_SERVE_PORT,
445
+ direction = "bidirectional",
446
+ dryRun = false,
447
+ deleteOrphans = false,
448
+ categories,
449
+ https: useHttps = false
450
+ } = options;
451
+ let host = peer;
452
+ if (!peer.match(/^\d+\.\d+\.\d+\.\d+$/)) {
453
+ const resolvedIP = resolvePeerIP(peer);
454
+ if (!resolvedIP) {
455
+ result.errors.push(`Could not resolve peer: ${peer}`);
456
+ result.stats.duration = Date.now() - startTime;
457
+ return result;
458
+ }
459
+ host = resolvedIP;
460
+ }
461
+ try {
462
+ const localManifest = generateVaultManifest(vaultPath);
463
+ const remoteManifest = await fetchRemoteManifest(host, port, useHttps);
464
+ let { toPush, toPull, conflicts, unchanged } = compareManifests(localManifest, remoteManifest);
465
+ if (categories && categories.length > 0) {
466
+ const categorySet = new Set(categories);
467
+ toPush = toPush.filter((f) => categorySet.has(f.category));
468
+ toPull = toPull.filter((f) => categorySet.has(f.category));
469
+ }
470
+ result.unchanged = unchanged;
471
+ for (const conflict of conflicts) {
472
+ result.errors.push(`Conflict: ${conflict.path} (local and remote have same timestamp but different content)`);
473
+ }
474
+ if (direction === "push" || direction === "bidirectional") {
475
+ for (const file of toPush) {
476
+ try {
477
+ if (!dryRun) {
478
+ const content = fs.readFileSync(path.join(vaultPath, file.path), "utf-8");
479
+ await pushFileToRemote(host, file.path, content, port, useHttps);
480
+ result.stats.bytesTransferred += file.size;
481
+ }
482
+ result.pushed.push(file.path);
483
+ result.stats.filesProcessed++;
484
+ } catch (err) {
485
+ result.errors.push(`Failed to push ${file.path}: ${err}`);
486
+ }
487
+ }
488
+ }
489
+ if (direction === "pull" || direction === "bidirectional") {
490
+ for (const file of toPull) {
491
+ try {
492
+ if (!dryRun) {
493
+ const content = await fetchRemoteFile(host, file.path, port, useHttps);
494
+ const filePath = path.join(vaultPath, file.path);
495
+ const dir = path.dirname(filePath);
496
+ if (!fs.existsSync(dir)) {
497
+ fs.mkdirSync(dir, { recursive: true });
498
+ }
499
+ fs.writeFileSync(filePath, content, "utf-8");
500
+ result.stats.bytesTransferred += file.size;
501
+ }
502
+ result.pulled.push(file.path);
503
+ result.stats.filesProcessed++;
504
+ } catch (err) {
505
+ result.errors.push(`Failed to pull ${file.path}: ${err}`);
506
+ }
507
+ }
508
+ }
509
+ if (deleteOrphans && direction === "pull") {
510
+ const remoteFiles = new Set(remoteManifest.files.map((f) => f.path));
511
+ for (const file of localManifest.files) {
512
+ if (!remoteFiles.has(file.path)) {
513
+ if (!categories || categories.includes(file.category)) {
514
+ try {
515
+ if (!dryRun) {
516
+ fs.unlinkSync(path.join(vaultPath, file.path));
517
+ }
518
+ result.deleted.push(file.path);
519
+ } catch (err) {
520
+ result.errors.push(`Failed to delete ${file.path}: ${err}`);
521
+ }
522
+ }
523
+ }
524
+ }
525
+ }
526
+ } catch (err) {
527
+ result.errors.push(`Sync failed: ${err}`);
528
+ }
529
+ result.stats.duration = Date.now() - startTime;
530
+ return result;
531
+ }
532
+ function configureTailscaleServe(localPort, options = {}) {
533
+ if (!hasTailscale()) {
534
+ return null;
535
+ }
536
+ const args = ["serve"];
537
+ if (options.funnel) {
538
+ args.push("--bg");
539
+ args.push("funnel");
540
+ } else if (options.background) {
541
+ args.push("--bg");
542
+ }
543
+ args.push(`localhost:${localPort}`);
544
+ const proc = spawn("tailscale", args, {
545
+ stdio: "inherit",
546
+ detached: options.background
547
+ });
548
+ if (options.background) {
549
+ proc.unref();
550
+ }
551
+ return proc;
552
+ }
553
+ function stopTailscaleServe() {
554
+ if (!hasTailscale()) {
555
+ return false;
556
+ }
557
+ const result = spawnSync("tailscale", ["serve", "off"], {
558
+ stdio: "pipe",
559
+ encoding: "utf-8",
560
+ timeout: 5e3
561
+ });
562
+ return result.status === 0;
563
+ }
564
+ async function checkPeerClawVault(host, port = DEFAULT_SERVE_PORT) {
565
+ try {
566
+ const response = await new Promise((resolve2) => {
567
+ const req = http.get(
568
+ `http://${host}:${port}${CLAWVAULT_SERVE_PATH}/health`,
569
+ { timeout: 5e3 },
570
+ (res) => {
571
+ resolve2(res.statusCode === 200);
572
+ }
573
+ );
574
+ req.on("error", () => resolve2(false));
575
+ req.on("timeout", () => {
576
+ req.destroy();
577
+ resolve2(false);
578
+ });
579
+ });
580
+ return response;
581
+ } catch {
582
+ return false;
583
+ }
584
+ }
585
+ async function discoverClawVaultPeers(port = DEFAULT_SERVE_PORT) {
586
+ const status = getTailscaleStatus();
587
+ if (!status.connected) {
588
+ return [];
589
+ }
590
+ const clawvaultPeers = [];
591
+ const checkPromises = status.peers.filter((p) => p.online).map(async (peer) => {
592
+ const ip = peer.tailscaleIPs[0];
593
+ if (!ip) return;
594
+ const isServing = await checkPeerClawVault(ip, port);
595
+ if (isServing) {
596
+ peer.clawvaultServing = true;
597
+ peer.clawvaultPort = port;
598
+ clawvaultPeers.push(peer);
599
+ }
600
+ });
601
+ await Promise.all(checkPromises);
602
+ return clawvaultPeers;
603
+ }
604
+
605
+ export {
606
+ DEFAULT_SERVE_PORT,
607
+ CLAWVAULT_SERVE_PATH,
608
+ MANIFEST_ENDPOINT,
609
+ SYNC_ENDPOINT,
610
+ FILE_ENDPOINT,
611
+ hasTailscale,
612
+ getTailscaleVersion,
613
+ getTailscaleStatus,
614
+ findPeer,
615
+ getOnlinePeers,
616
+ resolvePeerIP,
617
+ generateVaultManifest,
618
+ compareManifests,
619
+ serveVault,
620
+ fetchRemoteManifest,
621
+ fetchRemoteFile,
622
+ pushFileToRemote,
623
+ syncWithPeer,
624
+ configureTailscaleServe,
625
+ stopTailscaleServe,
626
+ checkPeerClawVault,
627
+ discoverClawVaultPeers
628
+ };