clawvault 2.2.1 → 2.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/bin/clawvault.js +12 -0
  2. package/bin/register-tailscale-commands.js +106 -0
  3. package/bin/register-task-commands.js +257 -0
  4. package/dist/{chunk-2HM7ZI4X.js → chunk-2AYPFUGX.js} +1 -1
  5. package/dist/chunk-4GBPTBFJ.js +628 -0
  6. package/dist/{chunk-LB6P4CD5.js → chunk-6AQZIPLV.js} +7 -7
  7. package/dist/chunk-CLE2HHNT.js +513 -0
  8. package/dist/{chunk-VR5NE7PZ.js → chunk-HVTTYDCJ.js} +1 -1
  9. package/dist/{chunk-GQVYQCY5.js → chunk-JVAWKNIZ.js} +2 -2
  10. package/dist/chunk-MDIH26GC.js +183 -0
  11. package/dist/{chunk-Z2XBWN7A.js → chunk-NAMFB7ZA.js} +2 -0
  12. package/dist/chunk-NGVAEFT2.js +352 -0
  13. package/dist/chunk-NZ4ZZNSR.js +373 -0
  14. package/dist/{chunk-MQUJNOHK.js → chunk-QALB2V3E.js} +1 -1
  15. package/dist/{chunk-L6NB43WV.js → chunk-RARDNTUP.js} +4 -4
  16. package/dist/{chunk-I5X6J4FX.js → chunk-TB3BM2PQ.js} +6 -6
  17. package/dist/{chunk-GJEGPO7U.js → chunk-TT3FXYCN.js} +1 -1
  18. package/dist/{chunk-73P7XCQM.js → chunk-USZU5CBB.js} +5 -5
  19. package/dist/{chunk-WZI3OAE5.js → chunk-VBVEXNI5.js} +4 -4
  20. package/dist/commands/archive.js +3 -3
  21. package/dist/commands/backlog.d.ts +53 -0
  22. package/dist/commands/backlog.js +119 -0
  23. package/dist/commands/blocked.d.ts +25 -0
  24. package/dist/commands/blocked.js +43 -0
  25. package/dist/commands/canvas.d.ts +20 -0
  26. package/dist/commands/canvas.js +683 -0
  27. package/dist/commands/context.js +3 -3
  28. package/dist/commands/doctor.js +1 -1
  29. package/dist/commands/graph.js +2 -2
  30. package/dist/commands/link.js +3 -3
  31. package/dist/commands/migrate-observations.js +3 -3
  32. package/dist/commands/observe.js +5 -5
  33. package/dist/commands/rebuild.js +4 -4
  34. package/dist/commands/recover.js +2 -2
  35. package/dist/commands/reflect.js +5 -5
  36. package/dist/commands/replay.js +6 -6
  37. package/dist/commands/sleep.js +7 -7
  38. package/dist/commands/status.js +1 -1
  39. package/dist/commands/tailscale.d.ts +52 -0
  40. package/dist/commands/tailscale.js +25 -0
  41. package/dist/commands/task.d.ts +71 -0
  42. package/dist/commands/task.js +189 -0
  43. package/dist/commands/wake.js +8 -8
  44. package/dist/index.d.ts +4 -0
  45. package/dist/index.js +104 -42
  46. package/dist/lib/canvas-layout.d.ts +115 -0
  47. package/dist/lib/canvas-layout.js +34 -0
  48. package/dist/lib/tailscale.d.ts +225 -0
  49. package/dist/lib/tailscale.js +49 -0
  50. package/dist/lib/task-utils.d.ts +159 -0
  51. package/dist/lib/task-utils.js +46 -0
  52. package/dist/lib/webdav.d.ts +109 -0
  53. package/dist/lib/webdav.js +34 -0
  54. package/package.json +2 -2
  55. package/dist/{chunk-MILVYUPK.js → chunk-IWYZAXKJ.js} +3 -3
  56. package/dist/{chunk-H7JW4L7H.js → chunk-OZ7RIXTO.js} +3 -3
@@ -0,0 +1,183 @@
1
+ // src/lib/canvas-layout.ts
2
+ import * as crypto from "crypto";
3
+ var CANVAS_COLORS = {
4
+ RED: "1",
5
+ // Critical, blocked
6
+ ORANGE: "2",
7
+ // High priority
8
+ YELLOW: "3",
9
+ // Medium priority
10
+ GREEN: "4",
11
+ // Done, success
12
+ CYAN: "5",
13
+ // Stats
14
+ PURPLE: "6"
15
+ // Knowledge graph
16
+ };
17
+ var LAYOUT = {
18
+ LEFT_COLUMN_X: 0,
19
+ LEFT_COLUMN_WIDTH: 500,
20
+ RIGHT_COLUMN_X: 550,
21
+ RIGHT_COLUMN_WIDTH: 450,
22
+ GROUP_PADDING: 20,
23
+ NODE_SPACING: 15,
24
+ GROUP_SPACING: 50,
25
+ DEFAULT_NODE_WIDTH: 280,
26
+ DEFAULT_NODE_HEIGHT: 80,
27
+ FILE_NODE_HEIGHT: 60,
28
+ SMALL_NODE_HEIGHT: 50,
29
+ GROUP_HEADER_HEIGHT: 40
30
+ };
31
+ function generateId() {
32
+ return crypto.randomBytes(8).toString("hex");
33
+ }
34
+ function createTextNode(x, y, width, height, text, color) {
35
+ const node = {
36
+ id: generateId(),
37
+ type: "text",
38
+ x,
39
+ y,
40
+ width,
41
+ height,
42
+ text
43
+ };
44
+ if (color) node.color = color;
45
+ return node;
46
+ }
47
+ function createFileNode(x, y, width, height, file, color) {
48
+ const node = {
49
+ id: generateId(),
50
+ type: "file",
51
+ x,
52
+ y,
53
+ width,
54
+ height,
55
+ file
56
+ };
57
+ if (color) node.color = color;
58
+ return node;
59
+ }
60
+ function createGroupNode(x, y, width, height, label, color) {
61
+ const node = {
62
+ id: generateId(),
63
+ type: "group",
64
+ x,
65
+ y,
66
+ width,
67
+ height,
68
+ label
69
+ };
70
+ if (color) node.color = color;
71
+ return node;
72
+ }
73
+ function createEdge(fromNode, fromSide, toNode, toSide, label, color) {
74
+ const edge = {
75
+ id: generateId(),
76
+ fromNode,
77
+ fromSide,
78
+ toNode,
79
+ toSide
80
+ };
81
+ if (label) edge.label = label;
82
+ if (color) edge.color = color;
83
+ return edge;
84
+ }
85
+ function stackNodesVertically(nodes, startX, startY, spacing = LAYOUT.NODE_SPACING) {
86
+ let currentY = startY;
87
+ const positionedNodes = [];
88
+ for (const node of nodes) {
89
+ positionedNodes.push({
90
+ ...node,
91
+ x: startX,
92
+ y: currentY
93
+ });
94
+ currentY += node.height + spacing;
95
+ }
96
+ return {
97
+ nodes: positionedNodes,
98
+ totalHeight: currentY - startY - spacing
99
+ };
100
+ }
101
+ function createGroupWithNodes(groupX, groupY, groupWidth, label, childNodes, color) {
102
+ const padding = LAYOUT.GROUP_PADDING;
103
+ const headerHeight = LAYOUT.GROUP_HEADER_HEIGHT;
104
+ const stacked = stackNodesVertically(
105
+ childNodes,
106
+ groupX + padding,
107
+ groupY + headerHeight + padding
108
+ );
109
+ const groupHeight = headerHeight + padding * 2 + stacked.totalHeight + LAYOUT.NODE_SPACING;
110
+ const group = createGroupNode(groupX, groupY, groupWidth, groupHeight, label, color);
111
+ return {
112
+ group,
113
+ nodes: stacked.nodes
114
+ };
115
+ }
116
+ function getPriorityColor(priority) {
117
+ switch (priority) {
118
+ case "critical":
119
+ return CANVAS_COLORS.RED;
120
+ case "high":
121
+ return CANVAS_COLORS.ORANGE;
122
+ case "medium":
123
+ return CANVAS_COLORS.YELLOW;
124
+ default:
125
+ return void 0;
126
+ }
127
+ }
128
+ function truncateText(text, maxChars) {
129
+ if (text.length <= maxChars) return text;
130
+ return text.slice(0, maxChars - 3) + "...";
131
+ }
132
+ function formatCanvasText(lines) {
133
+ return lines.join("\n");
134
+ }
135
+ function calculateColumnHeight(groups) {
136
+ let height = 0;
137
+ for (let i = 0; i < groups.length; i++) {
138
+ height += groups[i].group.height;
139
+ if (i < groups.length - 1) {
140
+ height += LAYOUT.GROUP_SPACING;
141
+ }
142
+ }
143
+ return height;
144
+ }
145
+ function positionGroupsVertically(groups, startY = 0) {
146
+ let currentY = startY;
147
+ const positioned = [];
148
+ for (const { group, nodes } of groups) {
149
+ const yOffset = currentY - group.y;
150
+ positioned.push({
151
+ group: { ...group, y: currentY },
152
+ nodes: nodes.map((n) => ({ ...n, y: n.y + yOffset }))
153
+ });
154
+ currentY += group.height + LAYOUT.GROUP_SPACING;
155
+ }
156
+ return positioned;
157
+ }
158
+ function flattenGroups(groups) {
159
+ const nodes = [];
160
+ for (const { group, nodes: childNodes } of groups) {
161
+ nodes.push(group);
162
+ nodes.push(...childNodes);
163
+ }
164
+ return nodes;
165
+ }
166
+
167
+ export {
168
+ CANVAS_COLORS,
169
+ LAYOUT,
170
+ generateId,
171
+ createTextNode,
172
+ createFileNode,
173
+ createGroupNode,
174
+ createEdge,
175
+ stackNodesVertically,
176
+ createGroupWithNodes,
177
+ getPriorityColor,
178
+ truncateText,
179
+ formatCanvasText,
180
+ calculateColumnHeight,
181
+ positionGroupsVertically,
182
+ flattenGroups
183
+ };
@@ -232,6 +232,8 @@ function ensureParentDir(filePath) {
232
232
  export {
233
233
  toDateKey,
234
234
  parseDateKey,
235
+ getLedgerRoot,
236
+ getRawRoot,
235
237
  getReflectionsRoot,
236
238
  getObservationPath,
237
239
  getArchiveObservationPath,
@@ -0,0 +1,352 @@
1
+ // src/lib/task-utils.ts
2
+ import * as fs from "fs";
3
+ import * as path from "path";
4
+ import matter from "gray-matter";
5
+ function slugify(text) {
6
+ return text.toLowerCase().replace(/[^\w\s-]/g, "").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "").trim();
7
+ }
8
+ function getTasksDir(vaultPath) {
9
+ return path.join(path.resolve(vaultPath), "tasks");
10
+ }
11
+ function getBacklogDir(vaultPath) {
12
+ return path.join(path.resolve(vaultPath), "backlog");
13
+ }
14
+ function ensureTasksDir(vaultPath) {
15
+ const tasksDir = getTasksDir(vaultPath);
16
+ if (!fs.existsSync(tasksDir)) {
17
+ fs.mkdirSync(tasksDir, { recursive: true });
18
+ }
19
+ }
20
+ function ensureBacklogDir(vaultPath) {
21
+ const backlogDir = getBacklogDir(vaultPath);
22
+ if (!fs.existsSync(backlogDir)) {
23
+ fs.mkdirSync(backlogDir, { recursive: true });
24
+ }
25
+ }
26
+ function getTaskPath(vaultPath, slug) {
27
+ return path.join(getTasksDir(vaultPath), `${slug}.md`);
28
+ }
29
+ function getBacklogPath(vaultPath, slug) {
30
+ return path.join(getBacklogDir(vaultPath), `${slug}.md`);
31
+ }
32
+ function extractTitle(content) {
33
+ const match = content.match(/^#\s+(.+)$/m);
34
+ return match ? match[1].trim() : "";
35
+ }
36
+ function readTask(vaultPath, slug) {
37
+ const taskPath = getTaskPath(vaultPath, slug);
38
+ if (!fs.existsSync(taskPath)) {
39
+ return null;
40
+ }
41
+ try {
42
+ const raw = fs.readFileSync(taskPath, "utf-8");
43
+ const { data, content } = matter(raw);
44
+ const title = extractTitle(content) || slug;
45
+ return {
46
+ slug,
47
+ title,
48
+ content,
49
+ frontmatter: data,
50
+ path: taskPath
51
+ };
52
+ } catch {
53
+ return null;
54
+ }
55
+ }
56
+ function readBacklogItem(vaultPath, slug) {
57
+ const backlogPath = getBacklogPath(vaultPath, slug);
58
+ if (!fs.existsSync(backlogPath)) {
59
+ return null;
60
+ }
61
+ try {
62
+ const raw = fs.readFileSync(backlogPath, "utf-8");
63
+ const { data, content } = matter(raw);
64
+ const title = extractTitle(content) || slug;
65
+ return {
66
+ slug,
67
+ title,
68
+ content,
69
+ frontmatter: data,
70
+ path: backlogPath
71
+ };
72
+ } catch {
73
+ return null;
74
+ }
75
+ }
76
+ function listTasks(vaultPath, filters) {
77
+ const tasksDir = getTasksDir(vaultPath);
78
+ if (!fs.existsSync(tasksDir)) {
79
+ return [];
80
+ }
81
+ const tasks = [];
82
+ const entries = fs.readdirSync(tasksDir, { withFileTypes: true });
83
+ for (const entry of entries) {
84
+ if (!entry.isFile() || !entry.name.endsWith(".md")) {
85
+ continue;
86
+ }
87
+ const slug = entry.name.replace(/\.md$/, "");
88
+ const task = readTask(vaultPath, slug);
89
+ if (!task) continue;
90
+ if (filters) {
91
+ if (filters.status && task.frontmatter.status !== filters.status) continue;
92
+ if (filters.owner && task.frontmatter.owner !== filters.owner) continue;
93
+ if (filters.project && task.frontmatter.project !== filters.project) continue;
94
+ if (filters.priority && task.frontmatter.priority !== filters.priority) continue;
95
+ }
96
+ tasks.push(task);
97
+ }
98
+ const priorityOrder = {
99
+ critical: 0,
100
+ high: 1,
101
+ medium: 2,
102
+ low: 3
103
+ };
104
+ return tasks.sort((a, b) => {
105
+ const aPriority = priorityOrder[a.frontmatter.priority || "low"];
106
+ const bPriority = priorityOrder[b.frontmatter.priority || "low"];
107
+ if (aPriority !== bPriority) {
108
+ return aPriority - bPriority;
109
+ }
110
+ return new Date(b.frontmatter.created).getTime() - new Date(a.frontmatter.created).getTime();
111
+ });
112
+ }
113
+ function listBacklogItems(vaultPath, filters) {
114
+ const backlogDir = getBacklogDir(vaultPath);
115
+ if (!fs.existsSync(backlogDir)) {
116
+ return [];
117
+ }
118
+ const items = [];
119
+ const entries = fs.readdirSync(backlogDir, { withFileTypes: true });
120
+ for (const entry of entries) {
121
+ if (!entry.isFile() || !entry.name.endsWith(".md")) {
122
+ continue;
123
+ }
124
+ const slug = entry.name.replace(/\.md$/, "");
125
+ const item = readBacklogItem(vaultPath, slug);
126
+ if (!item) continue;
127
+ if (filters) {
128
+ if (filters.project && item.frontmatter.project !== filters.project) continue;
129
+ if (filters.source && item.frontmatter.source !== filters.source) continue;
130
+ }
131
+ items.push(item);
132
+ }
133
+ return items.sort((a, b) => {
134
+ return new Date(b.frontmatter.created).getTime() - new Date(a.frontmatter.created).getTime();
135
+ });
136
+ }
137
+ function createTask(vaultPath, title, options = {}) {
138
+ ensureTasksDir(vaultPath);
139
+ const slug = slugify(title);
140
+ const taskPath = getTaskPath(vaultPath, slug);
141
+ if (fs.existsSync(taskPath)) {
142
+ throw new Error(`Task already exists: ${slug}`);
143
+ }
144
+ const now = (/* @__PURE__ */ new Date()).toISOString();
145
+ const frontmatter = {
146
+ status: "open",
147
+ created: now,
148
+ updated: now
149
+ };
150
+ if (options.owner) frontmatter.owner = options.owner;
151
+ if (options.project) frontmatter.project = options.project;
152
+ if (options.priority) frontmatter.priority = options.priority;
153
+ if (options.due) frontmatter.due = options.due;
154
+ if (options.tags && options.tags.length > 0) frontmatter.tags = options.tags;
155
+ let content = `# ${title}
156
+ `;
157
+ const links = [];
158
+ if (options.owner) links.push(`[[${options.owner}]]`);
159
+ if (options.project) links.push(`[[${options.project}]]`);
160
+ if (links.length > 0) {
161
+ content += `
162
+ ${links.join(" | ")}
163
+ `;
164
+ }
165
+ if (options.content) {
166
+ content += `
167
+ ${options.content}
168
+ `;
169
+ }
170
+ const fileContent = matter.stringify(content, frontmatter);
171
+ fs.writeFileSync(taskPath, fileContent);
172
+ return {
173
+ slug,
174
+ title,
175
+ content,
176
+ frontmatter,
177
+ path: taskPath
178
+ };
179
+ }
180
+ function updateTask(vaultPath, slug, updates) {
181
+ const task = readTask(vaultPath, slug);
182
+ if (!task) {
183
+ throw new Error(`Task not found: ${slug}`);
184
+ }
185
+ const now = (/* @__PURE__ */ new Date()).toISOString();
186
+ const newFrontmatter = {
187
+ ...task.frontmatter,
188
+ updated: now
189
+ };
190
+ if (updates.status !== void 0) newFrontmatter.status = updates.status;
191
+ if (updates.owner !== void 0) newFrontmatter.owner = updates.owner;
192
+ if (updates.project !== void 0) newFrontmatter.project = updates.project;
193
+ if (updates.priority !== void 0) newFrontmatter.priority = updates.priority;
194
+ if (updates.due !== void 0) newFrontmatter.due = updates.due;
195
+ if (updates.tags !== void 0) newFrontmatter.tags = updates.tags;
196
+ if (updates.blocked_by !== void 0) {
197
+ newFrontmatter.blocked_by = updates.blocked_by;
198
+ } else if (updates.status && updates.status !== "blocked") {
199
+ delete newFrontmatter.blocked_by;
200
+ }
201
+ const fileContent = matter.stringify(task.content, newFrontmatter);
202
+ fs.writeFileSync(task.path, fileContent);
203
+ return {
204
+ ...task,
205
+ frontmatter: newFrontmatter
206
+ };
207
+ }
208
+ function completeTask(vaultPath, slug) {
209
+ const task = readTask(vaultPath, slug);
210
+ if (!task) {
211
+ throw new Error(`Task not found: ${slug}`);
212
+ }
213
+ const now = (/* @__PURE__ */ new Date()).toISOString();
214
+ const newFrontmatter = {
215
+ ...task.frontmatter,
216
+ status: "done",
217
+ updated: now,
218
+ completed: now
219
+ };
220
+ delete newFrontmatter.blocked_by;
221
+ const fileContent = matter.stringify(task.content, newFrontmatter);
222
+ fs.writeFileSync(task.path, fileContent);
223
+ return {
224
+ ...task,
225
+ frontmatter: newFrontmatter
226
+ };
227
+ }
228
+ function createBacklogItem(vaultPath, title, options = {}) {
229
+ ensureBacklogDir(vaultPath);
230
+ const slug = slugify(title);
231
+ const backlogPath = getBacklogPath(vaultPath, slug);
232
+ if (fs.existsSync(backlogPath)) {
233
+ throw new Error(`Backlog item already exists: ${slug}`);
234
+ }
235
+ const now = (/* @__PURE__ */ new Date()).toISOString();
236
+ const frontmatter = {
237
+ created: now
238
+ };
239
+ if (options.source) frontmatter.source = options.source;
240
+ if (options.project) frontmatter.project = options.project;
241
+ if (options.tags && options.tags.length > 0) frontmatter.tags = options.tags;
242
+ let content = `# ${title}
243
+ `;
244
+ const links = [];
245
+ if (options.source) links.push(`[[${options.source}]]`);
246
+ if (options.project) links.push(`[[${options.project}]]`);
247
+ if (links.length > 0) {
248
+ content += `
249
+ ${links.join(" | ")}
250
+ `;
251
+ }
252
+ if (options.content) {
253
+ content += `
254
+ ${options.content}
255
+ `;
256
+ }
257
+ const fileContent = matter.stringify(content, frontmatter);
258
+ fs.writeFileSync(backlogPath, fileContent);
259
+ return {
260
+ slug,
261
+ title,
262
+ content,
263
+ frontmatter,
264
+ path: backlogPath
265
+ };
266
+ }
267
+ function promoteBacklogItem(vaultPath, slug, options = {}) {
268
+ const backlogItem = readBacklogItem(vaultPath, slug);
269
+ if (!backlogItem) {
270
+ throw new Error(`Backlog item not found: ${slug}`);
271
+ }
272
+ const task = createTask(vaultPath, backlogItem.title, {
273
+ owner: options.owner,
274
+ project: backlogItem.frontmatter.project,
275
+ priority: options.priority,
276
+ due: options.due,
277
+ content: backlogItem.content.replace(/^#\s+.+\n/, "").trim(),
278
+ // Remove title from content
279
+ tags: backlogItem.frontmatter.tags
280
+ });
281
+ fs.unlinkSync(backlogItem.path);
282
+ return task;
283
+ }
284
+ function getBlockedTasks(vaultPath, project) {
285
+ const filters = { status: "blocked" };
286
+ if (project) filters.project = project;
287
+ return listTasks(vaultPath, filters);
288
+ }
289
+ function getActiveTasks(vaultPath, filters) {
290
+ const allTasks = listTasks(vaultPath, filters);
291
+ return allTasks.filter((t) => t.frontmatter.status === "open" || t.frontmatter.status === "in-progress");
292
+ }
293
+ function getRecentlyCompletedTasks(vaultPath, limit = 10) {
294
+ const allTasks = listTasks(vaultPath, { status: "done" });
295
+ return allTasks.filter((t) => t.frontmatter.completed).sort((a, b) => {
296
+ const aCompleted = new Date(a.frontmatter.completed || 0).getTime();
297
+ const bCompleted = new Date(b.frontmatter.completed || 0).getTime();
298
+ return bCompleted - aCompleted;
299
+ }).slice(0, limit);
300
+ }
301
+ function getStatusIcon(status) {
302
+ switch (status) {
303
+ case "in-progress":
304
+ return "\u25CF";
305
+ case "blocked":
306
+ return "\u25A0";
307
+ case "open":
308
+ return "\u25CB";
309
+ case "done":
310
+ return "\u2713";
311
+ default:
312
+ return "\u25CB";
313
+ }
314
+ }
315
+ function getStatusDisplay(status) {
316
+ switch (status) {
317
+ case "in-progress":
318
+ return "active";
319
+ case "blocked":
320
+ return "blocked";
321
+ case "open":
322
+ return "open";
323
+ case "done":
324
+ return "done";
325
+ default:
326
+ return status;
327
+ }
328
+ }
329
+
330
+ export {
331
+ slugify,
332
+ getTasksDir,
333
+ getBacklogDir,
334
+ ensureTasksDir,
335
+ ensureBacklogDir,
336
+ getTaskPath,
337
+ getBacklogPath,
338
+ readTask,
339
+ readBacklogItem,
340
+ listTasks,
341
+ listBacklogItems,
342
+ createTask,
343
+ updateTask,
344
+ completeTask,
345
+ createBacklogItem,
346
+ promoteBacklogItem,
347
+ getBlockedTasks,
348
+ getActiveTasks,
349
+ getRecentlyCompletedTasks,
350
+ getStatusIcon,
351
+ getStatusDisplay
352
+ };