clawvault 2.3.0 → 2.4.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.
- package/bin/clawvault.js +6 -0
- package/bin/register-core-commands.js +25 -2
- package/bin/register-tailscale-commands.js +106 -0
- package/bin/register-task-commands.js +18 -2
- package/bin/register-task-commands.test.js +24 -0
- package/dist/{chunk-SOTWYGH7.js → chunk-33GW63WK.js} +4 -35
- package/dist/chunk-4GBPTBFJ.js +628 -0
- package/dist/{chunk-GQVYQCY5.js → chunk-AHGUJG76.js} +3 -3
- package/dist/{chunk-GJEGPO7U.js → chunk-BI6SGGZP.js} +1 -1
- package/dist/chunk-CLE2HHNT.js +513 -0
- package/dist/{chunk-NGVAEFT2.js → chunk-DEFBIVQ3.js} +21 -0
- package/dist/{chunk-DPS7NYIU.js → chunk-DHJPXGC7.js} +2 -2
- package/dist/{chunk-2HM7ZI4X.js → chunk-FEFPBHH4.js} +287 -12
- package/dist/{chunk-K6XHCUFL.js → chunk-FHFUXL6G.js} +8 -1
- package/dist/{chunk-6BBTI7NV.js → chunk-GBIDDDSL.js} +2 -2
- package/dist/{chunk-VR5NE7PZ.js → chunk-HVTTYDCJ.js} +1 -1
- package/dist/chunk-IFTEGE4D.js +361 -0
- package/dist/{chunk-5WR6RRPX.js → chunk-JXY6T5R7.js} +2 -2
- package/dist/chunk-L3DJ36BZ.js +40 -0
- package/dist/{chunk-Z2XBWN7A.js → chunk-NAMFB7ZA.js} +2 -0
- package/dist/chunk-NZ4ZZNSR.js +373 -0
- package/dist/{chunk-OTQW3OMC.js → chunk-Q3WBH4P4.js} +97 -0
- package/dist/{chunk-MQUJNOHK.js → chunk-QALB2V3E.js} +1 -1
- package/dist/{chunk-PTSEIWXZ.js → chunk-SNEMCQP7.js} +13 -6
- package/dist/commands/archive.js +3 -3
- package/dist/commands/backlog.js +9 -2
- package/dist/commands/blocked.js +9 -2
- package/dist/commands/canvas.d.ts +11 -3
- package/dist/commands/canvas.js +1333 -25
- package/dist/commands/context.js +5 -4
- package/dist/commands/doctor.js +1 -1
- package/dist/commands/migrate-observations.js +3 -3
- package/dist/commands/observe.d.ts +1 -0
- package/dist/commands/observe.js +5 -4
- package/dist/commands/rebuild.js +5 -4
- package/dist/commands/reflect.js +5 -5
- package/dist/commands/replay.js +7 -6
- package/dist/commands/setup.d.ts +10 -2
- package/dist/commands/setup.js +1 -1
- package/dist/commands/sleep.js +7 -6
- package/dist/commands/status.js +1 -1
- package/dist/commands/tailscale.d.ts +52 -0
- package/dist/commands/tailscale.js +25 -0
- package/dist/commands/task.js +1 -1
- package/dist/commands/wake.js +4 -4
- package/dist/index.d.ts +9 -0
- package/dist/index.js +79 -15
- package/dist/lib/tailscale.d.ts +225 -0
- package/dist/lib/tailscale.js +49 -0
- package/dist/lib/task-utils.d.ts +13 -1
- package/dist/lib/task-utils.js +3 -1
- package/dist/lib/webdav.d.ts +109 -0
- package/dist/lib/webdav.js +34 -0
- package/package.json +2 -2
- package/dist/chunk-W463YRED.js +0 -97
|
@@ -0,0 +1,513 @@
|
|
|
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 isPathSafe(requestPath, rootPath) {
|
|
13
|
+
if (requestPath.includes("..")) {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
const normalizedPath = path.normalize(requestPath).replace(/^\/+/, "");
|
|
17
|
+
const fullPath = path.resolve(rootPath, normalizedPath);
|
|
18
|
+
const resolvedRoot = path.resolve(rootPath);
|
|
19
|
+
if (!fullPath.startsWith(resolvedRoot + path.sep) && fullPath !== resolvedRoot) {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
const pathParts = normalizedPath.split(path.sep);
|
|
23
|
+
for (const part of pathParts) {
|
|
24
|
+
if (BLOCKED_PATHS.includes(part)) {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
function resolveWebDAVPath(requestPath, rootPath) {
|
|
31
|
+
if (requestPath.includes("..")) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
const normalizedPath = path.normalize(requestPath).replace(/^\/+/, "");
|
|
35
|
+
const fullPath = path.resolve(rootPath, normalizedPath);
|
|
36
|
+
const resolvedRoot = path.resolve(rootPath);
|
|
37
|
+
if (!fullPath.startsWith(resolvedRoot + path.sep) && fullPath !== resolvedRoot) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
return fullPath;
|
|
41
|
+
}
|
|
42
|
+
function checkAuth(req, auth) {
|
|
43
|
+
if (!auth) {
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
const authHeader = req.headers.authorization;
|
|
47
|
+
if (!authHeader || !authHeader.startsWith("Basic ")) {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
const base64Credentials = authHeader.slice(6);
|
|
51
|
+
const credentials = Buffer.from(base64Credentials, "base64").toString("utf-8");
|
|
52
|
+
const [username, password] = credentials.split(":");
|
|
53
|
+
return username === auth.username && password === auth.password;
|
|
54
|
+
}
|
|
55
|
+
function escapeXml(str) {
|
|
56
|
+
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
57
|
+
}
|
|
58
|
+
function formatWebDAVDate(date) {
|
|
59
|
+
return date.toUTCString();
|
|
60
|
+
}
|
|
61
|
+
function generatePropfindEntry(href, stats, isCollection) {
|
|
62
|
+
const resourceType = isCollection ? "<D:resourcetype><D:collection/></D:resourcetype>" : "<D:resourcetype/>";
|
|
63
|
+
const contentLength = stats && !isCollection ? `<D:getcontentlength>${stats.size}</D:getcontentlength>` : "";
|
|
64
|
+
const lastModified = stats ? `<D:getlastmodified>${formatWebDAVDate(stats.mtime)}</D:getlastmodified>` : "";
|
|
65
|
+
const etag = stats ? `<D:getetag>"${stats.mtime.getTime().toString(16)}-${stats.size.toString(16)}"</D:getetag>` : "";
|
|
66
|
+
const contentType = !isCollection ? "<D:getcontenttype>application/octet-stream</D:getcontenttype>" : "";
|
|
67
|
+
return ` <D:response>
|
|
68
|
+
<D:href>${escapeXml(href)}</D:href>
|
|
69
|
+
<D:propstat>
|
|
70
|
+
<D:prop>
|
|
71
|
+
${resourceType}
|
|
72
|
+
${contentLength}
|
|
73
|
+
${lastModified}
|
|
74
|
+
${etag}
|
|
75
|
+
${contentType}
|
|
76
|
+
</D:prop>
|
|
77
|
+
<D:status>HTTP/1.1 200 OK</D:status>
|
|
78
|
+
</D:propstat>
|
|
79
|
+
</D:response>`;
|
|
80
|
+
}
|
|
81
|
+
function generatePropfindResponse(entries) {
|
|
82
|
+
const responseEntries = entries.map(
|
|
83
|
+
(e) => generatePropfindEntry(e.href, e.stats, e.isCollection)
|
|
84
|
+
).join("\n");
|
|
85
|
+
return `<?xml version="1.0" encoding="utf-8"?>
|
|
86
|
+
<D:multistatus xmlns:D="DAV:">
|
|
87
|
+
${responseEntries}
|
|
88
|
+
</D:multistatus>`;
|
|
89
|
+
}
|
|
90
|
+
function handleOptions(res, prefix) {
|
|
91
|
+
res.writeHead(200, {
|
|
92
|
+
"Allow": SUPPORTED_METHODS.join(", "),
|
|
93
|
+
"DAV": "1, 2",
|
|
94
|
+
"Content-Length": "0",
|
|
95
|
+
"Access-Control-Allow-Origin": "*",
|
|
96
|
+
"Access-Control-Allow-Methods": SUPPORTED_METHODS.join(", "),
|
|
97
|
+
"Access-Control-Allow-Headers": "Content-Type, Depth, Destination, Overwrite, Authorization",
|
|
98
|
+
"MS-Author-Via": "DAV"
|
|
99
|
+
});
|
|
100
|
+
res.end();
|
|
101
|
+
}
|
|
102
|
+
function handleHead(res, filePath) {
|
|
103
|
+
try {
|
|
104
|
+
const stats = fs.statSync(filePath);
|
|
105
|
+
if (stats.isDirectory()) {
|
|
106
|
+
res.writeHead(200, {
|
|
107
|
+
"Content-Type": "httpd/unix-directory",
|
|
108
|
+
"Last-Modified": formatWebDAVDate(stats.mtime),
|
|
109
|
+
"ETag": `"${stats.mtime.getTime().toString(16)}"`,
|
|
110
|
+
"Access-Control-Allow-Origin": "*"
|
|
111
|
+
});
|
|
112
|
+
} else {
|
|
113
|
+
res.writeHead(200, {
|
|
114
|
+
"Content-Type": "application/octet-stream",
|
|
115
|
+
"Content-Length": stats.size.toString(),
|
|
116
|
+
"Last-Modified": formatWebDAVDate(stats.mtime),
|
|
117
|
+
"ETag": `"${stats.mtime.getTime().toString(16)}-${stats.size.toString(16)}"`,
|
|
118
|
+
"Access-Control-Allow-Origin": "*"
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
res.end();
|
|
122
|
+
} catch (err) {
|
|
123
|
+
res.writeHead(404, { "Content-Type": "text/plain", "Access-Control-Allow-Origin": "*" });
|
|
124
|
+
res.end("Not Found");
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
function handleGet(res, filePath) {
|
|
128
|
+
try {
|
|
129
|
+
const stats = fs.statSync(filePath);
|
|
130
|
+
if (stats.isDirectory()) {
|
|
131
|
+
const entries = fs.readdirSync(filePath);
|
|
132
|
+
const listing = entries.join("\n");
|
|
133
|
+
res.writeHead(200, {
|
|
134
|
+
"Content-Type": "text/plain",
|
|
135
|
+
"Content-Length": Buffer.byteLength(listing).toString(),
|
|
136
|
+
"Access-Control-Allow-Origin": "*"
|
|
137
|
+
});
|
|
138
|
+
res.end(listing);
|
|
139
|
+
} else {
|
|
140
|
+
const content = fs.readFileSync(filePath);
|
|
141
|
+
res.writeHead(200, {
|
|
142
|
+
"Content-Type": "application/octet-stream",
|
|
143
|
+
"Content-Length": content.length.toString(),
|
|
144
|
+
"Last-Modified": formatWebDAVDate(stats.mtime),
|
|
145
|
+
"ETag": `"${stats.mtime.getTime().toString(16)}-${stats.size.toString(16)}"`,
|
|
146
|
+
"Access-Control-Allow-Origin": "*"
|
|
147
|
+
});
|
|
148
|
+
res.end(content);
|
|
149
|
+
}
|
|
150
|
+
} catch (err) {
|
|
151
|
+
res.writeHead(404, { "Content-Type": "text/plain", "Access-Control-Allow-Origin": "*" });
|
|
152
|
+
res.end("Not Found");
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
function handlePut(res, filePath, body) {
|
|
156
|
+
try {
|
|
157
|
+
const exists = fs.existsSync(filePath);
|
|
158
|
+
const dir = path.dirname(filePath);
|
|
159
|
+
if (!fs.existsSync(dir)) {
|
|
160
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
161
|
+
}
|
|
162
|
+
fs.writeFileSync(filePath, body);
|
|
163
|
+
const status = exists ? 204 : 201;
|
|
164
|
+
res.writeHead(status, {
|
|
165
|
+
"Content-Length": "0",
|
|
166
|
+
"Access-Control-Allow-Origin": "*"
|
|
167
|
+
});
|
|
168
|
+
res.end();
|
|
169
|
+
} catch (err) {
|
|
170
|
+
res.writeHead(500, { "Content-Type": "text/plain", "Access-Control-Allow-Origin": "*" });
|
|
171
|
+
res.end(`Error: ${err}`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
function handleDelete(res, filePath) {
|
|
175
|
+
try {
|
|
176
|
+
if (!fs.existsSync(filePath)) {
|
|
177
|
+
res.writeHead(404, { "Content-Type": "text/plain", "Access-Control-Allow-Origin": "*" });
|
|
178
|
+
res.end("Not Found");
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
const stats = fs.statSync(filePath);
|
|
182
|
+
if (stats.isDirectory()) {
|
|
183
|
+
fs.rmSync(filePath, { recursive: true });
|
|
184
|
+
} else {
|
|
185
|
+
fs.unlinkSync(filePath);
|
|
186
|
+
}
|
|
187
|
+
res.writeHead(204, {
|
|
188
|
+
"Content-Length": "0",
|
|
189
|
+
"Access-Control-Allow-Origin": "*"
|
|
190
|
+
});
|
|
191
|
+
res.end();
|
|
192
|
+
} catch (err) {
|
|
193
|
+
res.writeHead(500, { "Content-Type": "text/plain", "Access-Control-Allow-Origin": "*" });
|
|
194
|
+
res.end(`Error: ${err}`);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
function handleMkcol(res, filePath) {
|
|
198
|
+
try {
|
|
199
|
+
if (fs.existsSync(filePath)) {
|
|
200
|
+
res.writeHead(405, { "Content-Type": "text/plain", "Access-Control-Allow-Origin": "*" });
|
|
201
|
+
res.end("Resource already exists");
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
const parent = path.dirname(filePath);
|
|
205
|
+
if (!fs.existsSync(parent)) {
|
|
206
|
+
res.writeHead(409, { "Content-Type": "text/plain", "Access-Control-Allow-Origin": "*" });
|
|
207
|
+
res.end("Parent directory does not exist");
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
fs.mkdirSync(filePath);
|
|
211
|
+
res.writeHead(201, {
|
|
212
|
+
"Content-Length": "0",
|
|
213
|
+
"Access-Control-Allow-Origin": "*"
|
|
214
|
+
});
|
|
215
|
+
res.end();
|
|
216
|
+
} catch (err) {
|
|
217
|
+
res.writeHead(500, { "Content-Type": "text/plain", "Access-Control-Allow-Origin": "*" });
|
|
218
|
+
res.end(`Error: ${err}`);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
function handlePropfind(res, filePath, webdavPath, prefix, depth) {
|
|
222
|
+
try {
|
|
223
|
+
if (!fs.existsSync(filePath)) {
|
|
224
|
+
res.writeHead(404, { "Content-Type": "text/plain", "Access-Control-Allow-Origin": "*" });
|
|
225
|
+
res.end("Not Found");
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
const stats = fs.statSync(filePath);
|
|
229
|
+
const entries = [];
|
|
230
|
+
const normalizedWebdavPath = webdavPath.startsWith("/") ? webdavPath : "/" + webdavPath;
|
|
231
|
+
const href = prefix + normalizedWebdavPath;
|
|
232
|
+
entries.push({
|
|
233
|
+
href: href.endsWith("/") || stats.isDirectory() ? href : href,
|
|
234
|
+
stats,
|
|
235
|
+
isCollection: stats.isDirectory()
|
|
236
|
+
});
|
|
237
|
+
if (stats.isDirectory() && depth !== "0") {
|
|
238
|
+
try {
|
|
239
|
+
const children = fs.readdirSync(filePath);
|
|
240
|
+
for (const child of children) {
|
|
241
|
+
if (BLOCKED_PATHS.includes(child)) {
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
const childPath = path.join(filePath, child);
|
|
245
|
+
const childWebdavPath = normalizedWebdavPath.endsWith("/") ? normalizedWebdavPath + child : normalizedWebdavPath + "/" + child;
|
|
246
|
+
try {
|
|
247
|
+
const childStats = fs.statSync(childPath);
|
|
248
|
+
entries.push({
|
|
249
|
+
href: prefix + childWebdavPath,
|
|
250
|
+
stats: childStats,
|
|
251
|
+
isCollection: childStats.isDirectory()
|
|
252
|
+
});
|
|
253
|
+
} catch {
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
} catch {
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
const xml = generatePropfindResponse(entries);
|
|
260
|
+
res.writeHead(207, {
|
|
261
|
+
"Content-Type": "application/xml; charset=utf-8",
|
|
262
|
+
"Content-Length": Buffer.byteLength(xml).toString(),
|
|
263
|
+
"Access-Control-Allow-Origin": "*"
|
|
264
|
+
});
|
|
265
|
+
res.end(xml);
|
|
266
|
+
} catch (err) {
|
|
267
|
+
res.writeHead(500, { "Content-Type": "text/plain", "Access-Control-Allow-Origin": "*" });
|
|
268
|
+
res.end(`Error: ${err}`);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
function handleMove(res, sourcePath, destinationPath, overwrite) {
|
|
272
|
+
try {
|
|
273
|
+
if (!fs.existsSync(sourcePath)) {
|
|
274
|
+
res.writeHead(404, { "Content-Type": "text/plain", "Access-Control-Allow-Origin": "*" });
|
|
275
|
+
res.end("Source not found");
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
if (!destinationPath) {
|
|
279
|
+
res.writeHead(400, { "Content-Type": "text/plain", "Access-Control-Allow-Origin": "*" });
|
|
280
|
+
res.end("Destination header required");
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
const destExists = fs.existsSync(destinationPath);
|
|
284
|
+
if (destExists && !overwrite) {
|
|
285
|
+
res.writeHead(412, { "Content-Type": "text/plain", "Access-Control-Allow-Origin": "*" });
|
|
286
|
+
res.end("Destination exists and Overwrite is F");
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
const destDir = path.dirname(destinationPath);
|
|
290
|
+
if (!fs.existsSync(destDir)) {
|
|
291
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
292
|
+
}
|
|
293
|
+
if (destExists) {
|
|
294
|
+
const destStats = fs.statSync(destinationPath);
|
|
295
|
+
if (destStats.isDirectory()) {
|
|
296
|
+
fs.rmSync(destinationPath, { recursive: true });
|
|
297
|
+
} else {
|
|
298
|
+
fs.unlinkSync(destinationPath);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
fs.renameSync(sourcePath, destinationPath);
|
|
302
|
+
const status = destExists ? 204 : 201;
|
|
303
|
+
res.writeHead(status, {
|
|
304
|
+
"Content-Length": "0",
|
|
305
|
+
"Access-Control-Allow-Origin": "*"
|
|
306
|
+
});
|
|
307
|
+
res.end();
|
|
308
|
+
} catch (err) {
|
|
309
|
+
res.writeHead(500, { "Content-Type": "text/plain", "Access-Control-Allow-Origin": "*" });
|
|
310
|
+
res.end(`Error: ${err}`);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
function handleCopy(res, sourcePath, destinationPath, overwrite) {
|
|
314
|
+
try {
|
|
315
|
+
if (!fs.existsSync(sourcePath)) {
|
|
316
|
+
res.writeHead(404, { "Content-Type": "text/plain", "Access-Control-Allow-Origin": "*" });
|
|
317
|
+
res.end("Source not found");
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
if (!destinationPath) {
|
|
321
|
+
res.writeHead(400, { "Content-Type": "text/plain", "Access-Control-Allow-Origin": "*" });
|
|
322
|
+
res.end("Destination header required");
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
const destExists = fs.existsSync(destinationPath);
|
|
326
|
+
if (destExists && !overwrite) {
|
|
327
|
+
res.writeHead(412, { "Content-Type": "text/plain", "Access-Control-Allow-Origin": "*" });
|
|
328
|
+
res.end("Destination exists and Overwrite is F");
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
const destDir = path.dirname(destinationPath);
|
|
332
|
+
if (!fs.existsSync(destDir)) {
|
|
333
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
334
|
+
}
|
|
335
|
+
const sourceStats = fs.statSync(sourcePath);
|
|
336
|
+
if (sourceStats.isDirectory()) {
|
|
337
|
+
copyDirRecursive(sourcePath, destinationPath);
|
|
338
|
+
} else {
|
|
339
|
+
fs.copyFileSync(sourcePath, destinationPath);
|
|
340
|
+
}
|
|
341
|
+
const status = destExists ? 204 : 201;
|
|
342
|
+
res.writeHead(status, {
|
|
343
|
+
"Content-Length": "0",
|
|
344
|
+
"Access-Control-Allow-Origin": "*"
|
|
345
|
+
});
|
|
346
|
+
res.end();
|
|
347
|
+
} catch (err) {
|
|
348
|
+
res.writeHead(500, { "Content-Type": "text/plain", "Access-Control-Allow-Origin": "*" });
|
|
349
|
+
res.end(`Error: ${err}`);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
function copyDirRecursive(src, dest) {
|
|
353
|
+
if (!fs.existsSync(dest)) {
|
|
354
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
355
|
+
}
|
|
356
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
357
|
+
for (const entry of entries) {
|
|
358
|
+
const srcPath = path.join(src, entry.name);
|
|
359
|
+
const destPath = path.join(dest, entry.name);
|
|
360
|
+
if (entry.isDirectory()) {
|
|
361
|
+
copyDirRecursive(srcPath, destPath);
|
|
362
|
+
} else {
|
|
363
|
+
fs.copyFileSync(srcPath, destPath);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
function parseDestinationHeader(destinationHeader, prefix, rootPath) {
|
|
368
|
+
if (!destinationHeader) {
|
|
369
|
+
return null;
|
|
370
|
+
}
|
|
371
|
+
try {
|
|
372
|
+
let destPath;
|
|
373
|
+
if (destinationHeader.startsWith("http://") || destinationHeader.startsWith("https://")) {
|
|
374
|
+
const url = new URL(destinationHeader);
|
|
375
|
+
destPath = decodeURIComponent(url.pathname);
|
|
376
|
+
} else {
|
|
377
|
+
destPath = decodeURIComponent(destinationHeader);
|
|
378
|
+
}
|
|
379
|
+
if (destPath.startsWith(prefix)) {
|
|
380
|
+
destPath = destPath.slice(prefix.length);
|
|
381
|
+
}
|
|
382
|
+
return resolveWebDAVPath(destPath, rootPath);
|
|
383
|
+
} catch {
|
|
384
|
+
return null;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
function createWebDAVHandler(config) {
|
|
388
|
+
const { rootPath, prefix = WEBDAV_PREFIX, auth } = config;
|
|
389
|
+
return async (req, res) => {
|
|
390
|
+
const rawUrl = req.url || "/";
|
|
391
|
+
if (rawUrl.includes("..")) {
|
|
392
|
+
if (rawUrl.startsWith(prefix)) {
|
|
393
|
+
res.writeHead(403, { "Content-Type": "text/plain", "Access-Control-Allow-Origin": "*" });
|
|
394
|
+
res.end("Forbidden");
|
|
395
|
+
return true;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
const url = new URL(rawUrl, `http://${req.headers.host || "localhost"}`);
|
|
399
|
+
const pathname = decodeURIComponent(url.pathname);
|
|
400
|
+
if (!pathname.startsWith(prefix)) {
|
|
401
|
+
return false;
|
|
402
|
+
}
|
|
403
|
+
let webdavPath = pathname.slice(prefix.length);
|
|
404
|
+
if (!webdavPath.startsWith("/")) {
|
|
405
|
+
webdavPath = "/" + webdavPath;
|
|
406
|
+
}
|
|
407
|
+
if (req.method === "OPTIONS") {
|
|
408
|
+
handleOptions(res, prefix);
|
|
409
|
+
return true;
|
|
410
|
+
}
|
|
411
|
+
if (!checkAuth(req, auth)) {
|
|
412
|
+
res.writeHead(401, {
|
|
413
|
+
"WWW-Authenticate": 'Basic realm="ClawVault WebDAV"',
|
|
414
|
+
"Content-Type": "text/plain",
|
|
415
|
+
"Access-Control-Allow-Origin": "*"
|
|
416
|
+
});
|
|
417
|
+
res.end("Unauthorized");
|
|
418
|
+
return true;
|
|
419
|
+
}
|
|
420
|
+
if (!isPathSafe(webdavPath, rootPath)) {
|
|
421
|
+
res.writeHead(403, { "Content-Type": "text/plain", "Access-Control-Allow-Origin": "*" });
|
|
422
|
+
res.end("Forbidden");
|
|
423
|
+
return true;
|
|
424
|
+
}
|
|
425
|
+
const filePath = resolveWebDAVPath(webdavPath, rootPath);
|
|
426
|
+
if (!filePath) {
|
|
427
|
+
res.writeHead(403, { "Content-Type": "text/plain", "Access-Control-Allow-Origin": "*" });
|
|
428
|
+
res.end("Forbidden");
|
|
429
|
+
return true;
|
|
430
|
+
}
|
|
431
|
+
const depth = req.headers.depth || "infinity";
|
|
432
|
+
const overwrite = req.headers.overwrite?.toUpperCase() !== "F";
|
|
433
|
+
const destinationHeader = req.headers.destination;
|
|
434
|
+
switch (req.method) {
|
|
435
|
+
case "HEAD":
|
|
436
|
+
handleHead(res, filePath);
|
|
437
|
+
return true;
|
|
438
|
+
case "GET":
|
|
439
|
+
handleGet(res, filePath);
|
|
440
|
+
return true;
|
|
441
|
+
case "PUT": {
|
|
442
|
+
const chunks = [];
|
|
443
|
+
for await (const chunk of req) {
|
|
444
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
445
|
+
}
|
|
446
|
+
const body = Buffer.concat(chunks);
|
|
447
|
+
handlePut(res, filePath, body);
|
|
448
|
+
return true;
|
|
449
|
+
}
|
|
450
|
+
case "DELETE":
|
|
451
|
+
handleDelete(res, filePath);
|
|
452
|
+
return true;
|
|
453
|
+
case "MKCOL":
|
|
454
|
+
handleMkcol(res, filePath);
|
|
455
|
+
return true;
|
|
456
|
+
case "PROPFIND":
|
|
457
|
+
handlePropfind(res, filePath, webdavPath, prefix, depth);
|
|
458
|
+
return true;
|
|
459
|
+
case "MOVE": {
|
|
460
|
+
const destPath = parseDestinationHeader(destinationHeader, prefix, rootPath);
|
|
461
|
+
if (destPath && destinationHeader) {
|
|
462
|
+
const destWebdavPath = destinationHeader.includes(prefix) ? destinationHeader.slice(destinationHeader.indexOf(prefix) + prefix.length) : destinationHeader;
|
|
463
|
+
if (!isPathSafe(destWebdavPath, rootPath)) {
|
|
464
|
+
res.writeHead(403, { "Content-Type": "text/plain", "Access-Control-Allow-Origin": "*" });
|
|
465
|
+
res.end("Forbidden");
|
|
466
|
+
return true;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
handleMove(res, filePath, destPath, overwrite);
|
|
470
|
+
return true;
|
|
471
|
+
}
|
|
472
|
+
case "COPY": {
|
|
473
|
+
const destPath = parseDestinationHeader(destinationHeader, prefix, rootPath);
|
|
474
|
+
if (destPath && destinationHeader) {
|
|
475
|
+
const destWebdavPath = destinationHeader.includes(prefix) ? destinationHeader.slice(destinationHeader.indexOf(prefix) + prefix.length) : destinationHeader;
|
|
476
|
+
if (!isPathSafe(destWebdavPath, rootPath)) {
|
|
477
|
+
res.writeHead(403, { "Content-Type": "text/plain", "Access-Control-Allow-Origin": "*" });
|
|
478
|
+
res.end("Forbidden");
|
|
479
|
+
return true;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
handleCopy(res, filePath, destPath, overwrite);
|
|
483
|
+
return true;
|
|
484
|
+
}
|
|
485
|
+
default:
|
|
486
|
+
res.writeHead(405, {
|
|
487
|
+
"Allow": SUPPORTED_METHODS.join(", "),
|
|
488
|
+
"Content-Type": "text/plain",
|
|
489
|
+
"Access-Control-Allow-Origin": "*"
|
|
490
|
+
});
|
|
491
|
+
res.end("Method Not Allowed");
|
|
492
|
+
return true;
|
|
493
|
+
}
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
export {
|
|
498
|
+
WEBDAV_PREFIX,
|
|
499
|
+
isPathSafe,
|
|
500
|
+
resolveWebDAVPath,
|
|
501
|
+
checkAuth,
|
|
502
|
+
generatePropfindResponse,
|
|
503
|
+
handleOptions,
|
|
504
|
+
handleHead,
|
|
505
|
+
handleGet,
|
|
506
|
+
handlePut,
|
|
507
|
+
handleDelete,
|
|
508
|
+
handleMkcol,
|
|
509
|
+
handlePropfind,
|
|
510
|
+
handleMove,
|
|
511
|
+
handleCopy,
|
|
512
|
+
createWebDAVHandler
|
|
513
|
+
};
|
|
@@ -147,6 +147,7 @@ function createTask(vaultPath, title, options = {}) {
|
|
|
147
147
|
created: now,
|
|
148
148
|
updated: now
|
|
149
149
|
};
|
|
150
|
+
if (options.source) frontmatter.source = options.source;
|
|
150
151
|
if (options.owner) frontmatter.owner = options.owner;
|
|
151
152
|
if (options.project) frontmatter.project = options.project;
|
|
152
153
|
if (options.priority) frontmatter.priority = options.priority;
|
|
@@ -264,6 +265,25 @@ ${options.content}
|
|
|
264
265
|
path: backlogPath
|
|
265
266
|
};
|
|
266
267
|
}
|
|
268
|
+
function updateBacklogItem(vaultPath, slug, updates) {
|
|
269
|
+
const backlogItem = readBacklogItem(vaultPath, slug);
|
|
270
|
+
if (!backlogItem) {
|
|
271
|
+
throw new Error(`Backlog item not found: ${slug}`);
|
|
272
|
+
}
|
|
273
|
+
const newFrontmatter = {
|
|
274
|
+
...backlogItem.frontmatter
|
|
275
|
+
};
|
|
276
|
+
if (updates.source !== void 0) newFrontmatter.source = updates.source;
|
|
277
|
+
if (updates.project !== void 0) newFrontmatter.project = updates.project;
|
|
278
|
+
if (updates.tags !== void 0) newFrontmatter.tags = updates.tags;
|
|
279
|
+
if (updates.lastSeen !== void 0) newFrontmatter.lastSeen = updates.lastSeen;
|
|
280
|
+
const fileContent = matter.stringify(backlogItem.content, newFrontmatter);
|
|
281
|
+
fs.writeFileSync(backlogItem.path, fileContent);
|
|
282
|
+
return {
|
|
283
|
+
...backlogItem,
|
|
284
|
+
frontmatter: newFrontmatter
|
|
285
|
+
};
|
|
286
|
+
}
|
|
267
287
|
function promoteBacklogItem(vaultPath, slug, options = {}) {
|
|
268
288
|
const backlogItem = readBacklogItem(vaultPath, slug);
|
|
269
289
|
if (!backlogItem) {
|
|
@@ -343,6 +363,7 @@ export {
|
|
|
343
363
|
updateTask,
|
|
344
364
|
completeTask,
|
|
345
365
|
createBacklogItem,
|
|
366
|
+
updateBacklogItem,
|
|
346
367
|
promoteBacklogItem,
|
|
347
368
|
getBlockedTasks,
|
|
348
369
|
getActiveTasks,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
Observer
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-FEFPBHH4.js";
|
|
4
4
|
import {
|
|
5
5
|
resolveVaultPath
|
|
6
6
|
} from "./chunk-MXSSG3QU.js";
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
getLegacyObservationPath,
|
|
9
9
|
getObservationPath,
|
|
10
10
|
listRawTranscriptFiles
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-NAMFB7ZA.js";
|
|
12
12
|
|
|
13
13
|
// src/commands/rebuild.ts
|
|
14
14
|
import * as fs from "fs";
|