clawvault 3.0.0 → 3.1.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/README.md +156 -105
- package/bin/clawvault.js +0 -2
- package/bin/register-core-commands.js +20 -2
- package/dist/{chunk-3D6BCTP6.js → chunk-33UGEQRT.js} +70 -145
- package/dist/{chunk-ZVVFWOLW.js → chunk-3WRJEKN4.js} +1 -1
- package/dist/{chunk-DEFFDRVP.js → chunk-3ZIH425O.js} +3 -70
- package/dist/{chunk-K234IDRJ.js → chunk-D2H45LON.js} +1 -0
- package/dist/{chunk-YKTA5JOJ.js → chunk-H62BP7RI.js} +3 -3
- package/dist/{chunk-WGRQ6HDV.js → chunk-LI4O6NVK.js} +1 -1
- package/dist/{chunk-7R7O6STJ.js → chunk-OCGVIN3L.js} +1 -1
- package/dist/{chunk-GAJV4IGR.js → chunk-YCUNCH2I.js} +3 -7
- package/dist/cli/index.cjs +10 -1459
- package/dist/cli/index.js +5 -8
- package/dist/commands/compat.cjs +70 -145
- package/dist/commands/compat.js +1 -1
- package/dist/commands/context.cjs +1 -0
- package/dist/commands/context.js +3 -3
- package/dist/commands/doctor.cjs +68 -144
- package/dist/commands/doctor.js +4 -4
- package/dist/commands/embed.js +2 -2
- package/dist/commands/setup.cjs +2 -69
- package/dist/commands/setup.d.cts +0 -1
- package/dist/commands/setup.d.ts +0 -1
- package/dist/commands/setup.js +2 -2
- package/dist/commands/sleep.cjs +1 -0
- package/dist/commands/sleep.js +2 -2
- package/dist/commands/status.cjs +1 -0
- package/dist/commands/status.js +2 -2
- package/dist/commands/wake.cjs +1 -0
- package/dist/commands/wake.js +2 -2
- package/dist/index.cjs +447 -2600
- package/dist/index.d.cts +0 -4
- package/dist/index.d.ts +0 -4
- package/dist/index.js +8 -69
- package/dist/plugin/index.cjs +3 -3
- package/dist/plugin/index.js +10 -10
- package/package.json +11 -17
- package/bin/register-tailscale-commands.js +0 -106
- package/dist/chunk-IVRIKYFE.js +0 -520
- package/dist/chunk-THRJVD4L.js +0 -373
- package/dist/chunk-TIGW564L.js +0 -628
- package/dist/commands/tailscale.cjs +0 -1532
- package/dist/commands/tailscale.d.cts +0 -52
- package/dist/commands/tailscale.d.ts +0 -52
- package/dist/commands/tailscale.js +0 -26
- package/dist/lib/canvas-layout.cjs +0 -136
- package/dist/lib/canvas-layout.d.cts +0 -31
- package/dist/lib/canvas-layout.d.ts +0 -31
- package/dist/lib/canvas-layout.js +0 -92
- package/dist/lib/tailscale.cjs +0 -1183
- package/dist/lib/tailscale.d.cts +0 -225
- package/dist/lib/tailscale.d.ts +0 -225
- package/dist/lib/tailscale.js +0 -50
- package/dist/lib/webdav.cjs +0 -568
- package/dist/lib/webdav.d.cts +0 -109
- package/dist/lib/webdav.d.ts +0 -109
- package/dist/lib/webdav.js +0 -35
- package/hooks/clawvault/HOOK.md +0 -83
- package/hooks/clawvault/handler.js +0 -879
- package/hooks/clawvault/handler.test.js +0 -354
package/dist/index.cjs
CHANGED
|
@@ -30,12 +30,10 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/index.ts
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
|
-
CLAWVAULT_SERVE_PATH: () => CLAWVAULT_SERVE_PATH,
|
|
34
33
|
ClawVault: () => ClawVault,
|
|
35
34
|
Compressor: () => Compressor,
|
|
36
35
|
DEFAULT_CATEGORIES: () => DEFAULT_CATEGORIES,
|
|
37
36
|
DEFAULT_CONFIG: () => DEFAULT_CONFIG,
|
|
38
|
-
DEFAULT_SERVE_PORT: () => DEFAULT_SERVE_PORT,
|
|
39
37
|
MEMORY_GRAPH_SCHEMA_VERSION: () => MEMORY_GRAPH_SCHEMA_VERSION,
|
|
40
38
|
MEMORY_TYPES: () => MEMORY_TYPES,
|
|
41
39
|
Observer: () => Observer,
|
|
@@ -62,19 +60,15 @@ __export(index_exports, {
|
|
|
62
60
|
buildTemplateVariables: () => buildTemplateVariables,
|
|
63
61
|
buildTransitionEvent: () => buildTransitionEvent,
|
|
64
62
|
checkOpenClawCompatibility: () => checkOpenClawCompatibility,
|
|
65
|
-
checkPeerClawVault: () => checkPeerClawVault,
|
|
66
63
|
classifyQuestion: () => classifyQuestion,
|
|
67
|
-
compareManifests: () => compareManifests,
|
|
68
64
|
compatCommand: () => compatCommand,
|
|
69
65
|
compatibilityExitCode: () => compatibilityExitCode,
|
|
70
66
|
completeTask: () => completeTask,
|
|
71
|
-
configureTailscaleServe: () => configureTailscaleServe,
|
|
72
67
|
contextCommand: () => contextCommand,
|
|
73
68
|
countBlockedTransitions: () => countBlockedTransitions,
|
|
74
69
|
createProject: () => createProject,
|
|
75
70
|
createVault: () => createVault,
|
|
76
71
|
deterministicInjectMatches: () => deterministicInjectMatches,
|
|
77
|
-
discoverClawVaultPeers: () => discoverClawVaultPeers,
|
|
78
72
|
doctor: () => doctor,
|
|
79
73
|
embedCommand: () => embedCommand,
|
|
80
74
|
entitySimilarity: () => entitySimilarity,
|
|
@@ -84,34 +78,26 @@ __export(index_exports, {
|
|
|
84
78
|
extractPreferences: () => extractPreferences,
|
|
85
79
|
extractTags: () => extractTags,
|
|
86
80
|
extractWikiLinks: () => extractWikiLinks,
|
|
87
|
-
fetchRemoteFile: () => fetchRemoteFile,
|
|
88
|
-
fetchRemoteManifest: () => fetchRemoteManifest,
|
|
89
81
|
filterSuperseded: () => filterSuperseded,
|
|
90
82
|
findNearestVaultPath: () => findNearestVaultPath,
|
|
91
|
-
findPeer: () => findPeer,
|
|
92
83
|
findVault: () => findVault,
|
|
93
84
|
formatContextMarkdown: () => formatContextMarkdown,
|
|
94
85
|
formatKanbanCard: () => formatKanbanCard,
|
|
95
86
|
formatSessionRecapMarkdown: () => formatSessionRecapMarkdown,
|
|
96
87
|
formatTransitionsTable: () => formatTransitionsTable,
|
|
97
88
|
generateKanbanMarkdown: () => generateKanbanMarkdown,
|
|
98
|
-
generateVaultManifest: () => generateVaultManifest,
|
|
99
89
|
getConfig: () => getConfig,
|
|
100
90
|
getConfigValue: () => getConfigValue,
|
|
101
91
|
getMemoryGraph: () => getMemoryGraph,
|
|
102
92
|
getObserverStaleness: () => getObserverStaleness,
|
|
103
|
-
getOnlinePeers: () => getOnlinePeers,
|
|
104
93
|
getProjectActivity: () => getProjectActivity,
|
|
105
94
|
getProjectTasks: () => getProjectTasks,
|
|
106
95
|
getScaledObservationThresholdBytes: () => getScaledObservationThresholdBytes,
|
|
107
96
|
getSupersessionInfo: () => getSupersessionInfo,
|
|
108
|
-
getTailscaleStatus: () => getTailscaleStatus,
|
|
109
|
-
getTailscaleVersion: () => getTailscaleVersion,
|
|
110
97
|
getVaultPath: () => getVaultPath,
|
|
111
98
|
graphCommand: () => graphCommand,
|
|
112
99
|
graphSummary: () => graphSummary,
|
|
113
100
|
hasQmd: () => hasQmd,
|
|
114
|
-
hasTailscale: () => hasTailscale,
|
|
115
101
|
importKanbanBoard: () => importKanbanBoard,
|
|
116
102
|
indexInjectableItems: () => indexInjectableItems,
|
|
117
103
|
inferContextProfile: () => inferContextProfile,
|
|
@@ -135,7 +121,6 @@ __export(index_exports, {
|
|
|
135
121
|
parseKanbanMarkdown: () => parseKanbanMarkdown,
|
|
136
122
|
parseSessionFile: () => parseSessionFile,
|
|
137
123
|
parseSessionSourceLabel: () => parseSessionSourceLabel,
|
|
138
|
-
pushFileToRemote: () => pushFileToRemote,
|
|
139
124
|
qmdEmbed: () => qmdEmbed,
|
|
140
125
|
qmdUpdate: () => qmdUpdate,
|
|
141
126
|
queryTransitions: () => queryTransitions,
|
|
@@ -155,11 +140,6 @@ __export(index_exports, {
|
|
|
155
140
|
registerReflectCommand: () => registerReflectCommand,
|
|
156
141
|
registerReplayCommand: () => registerReplayCommand,
|
|
157
142
|
registerReweaveCommand: () => registerReweaveCommand,
|
|
158
|
-
registerTailscaleCommands: () => registerTailscaleCommands,
|
|
159
|
-
registerTailscaleDiscoverCommand: () => registerTailscaleDiscoverCommand,
|
|
160
|
-
registerTailscaleServeCommand: () => registerTailscaleServeCommand,
|
|
161
|
-
registerTailscaleStatusCommand: () => registerTailscaleStatusCommand,
|
|
162
|
-
registerTailscaleSyncCommand: () => registerTailscaleSyncCommand,
|
|
163
143
|
removeRouteRule: () => removeRouteRule,
|
|
164
144
|
renderTemplate: () => renderTemplate,
|
|
165
145
|
replayCommand: () => replayCommand,
|
|
@@ -167,31 +147,23 @@ __export(index_exports, {
|
|
|
167
147
|
resetConfig: () => resetConfig,
|
|
168
148
|
resolveContextProfile: () => resolveContextProfile,
|
|
169
149
|
resolveLlmProvider: () => resolveLlmProvider,
|
|
170
|
-
resolvePeerIP: () => resolvePeerIP,
|
|
171
150
|
resolveVaultPath: () => resolveVaultPath,
|
|
172
151
|
reweave: () => reweave,
|
|
173
152
|
reweaveCommand: () => reweaveCommand,
|
|
174
153
|
runPromptInjection: () => runPromptInjection,
|
|
175
154
|
runReflection: () => runReflection,
|
|
176
155
|
sentenceChunk: () => sentenceChunk,
|
|
177
|
-
serveVault: () => serveVault,
|
|
178
156
|
sessionRecapCommand: () => sessionRecapCommand,
|
|
179
157
|
setConfigValue: () => setConfigValue,
|
|
180
158
|
setupCommand: () => setupCommand,
|
|
181
|
-
stopTailscaleServe: () => stopTailscaleServe,
|
|
182
159
|
stripSupersededObservations: () => stripSupersededObservations,
|
|
183
160
|
syncKanbanBoard: () => syncKanbanBoard,
|
|
184
|
-
syncWithPeer: () => syncWithPeer,
|
|
185
|
-
tailscaleDiscoverCommand: () => tailscaleDiscoverCommand,
|
|
186
|
-
tailscaleServeCommand: () => tailscaleServeCommand,
|
|
187
|
-
tailscaleStatusCommand: () => tailscaleStatusCommand,
|
|
188
|
-
tailscaleSyncCommand: () => tailscaleSyncCommand,
|
|
189
161
|
testRouteRule: () => testRouteRule,
|
|
190
162
|
updateProject: () => updateProject,
|
|
191
163
|
updateTask: () => updateTask
|
|
192
164
|
});
|
|
193
165
|
module.exports = __toCommonJS(index_exports);
|
|
194
|
-
var
|
|
166
|
+
var fs34 = __toESM(require("fs"), 1);
|
|
195
167
|
|
|
196
168
|
// src/commands/context.ts
|
|
197
169
|
var path5 = __toESM(require("path"), 1);
|
|
@@ -931,6 +903,7 @@ function stripQmdNoise(raw) {
|
|
|
931
903
|
function parseQmdOutput(raw) {
|
|
932
904
|
const trimmed = stripQmdNoise(raw).trim();
|
|
933
905
|
if (!trimmed) return [];
|
|
906
|
+
if (trimmed.startsWith("No results") || trimmed.startsWith("No matches")) return [];
|
|
934
907
|
const direct = tryParseJson(trimmed);
|
|
935
908
|
const extracted = direct ? null : extractJsonPayload(trimmed);
|
|
936
909
|
const parsed = direct ?? (extracted ? tryParseJson(extracted) : null);
|
|
@@ -1684,8 +1657,8 @@ function toUnresolvedNodeId(raw) {
|
|
|
1684
1657
|
return `unresolved:${normalizeUnresolvedKey(raw)}`;
|
|
1685
1658
|
}
|
|
1686
1659
|
function titleFromNoteKey(noteKey) {
|
|
1687
|
-
const
|
|
1688
|
-
return
|
|
1660
|
+
const basename14 = noteKey.split("/").pop() ?? noteKey;
|
|
1661
|
+
return basename14.replace(/[-_]+/g, " ").replace(/\s+/g, " ").trim().replace(/\b\w/g, (char) => char.toUpperCase());
|
|
1689
1662
|
}
|
|
1690
1663
|
function inferNodeType(relativePath, frontmatter) {
|
|
1691
1664
|
const normalized = normalizeRelativePath(relativePath).toLowerCase();
|
|
@@ -4583,9 +4556,9 @@ function collectNodeAliases(node) {
|
|
|
4583
4556
|
aliases.add(node.title);
|
|
4584
4557
|
}
|
|
4585
4558
|
if (node.path) {
|
|
4586
|
-
const
|
|
4587
|
-
aliases.add(
|
|
4588
|
-
aliases.add(
|
|
4559
|
+
const basename14 = path7.basename(node.path, ".md");
|
|
4560
|
+
aliases.add(basename14.replace(/[-_]+/g, " "));
|
|
4561
|
+
aliases.add(basename14);
|
|
4589
4562
|
}
|
|
4590
4563
|
return [...aliases].map((alias) => normalizeText(alias)).filter((alias) => alias.length >= 3);
|
|
4591
4564
|
}
|
|
@@ -8258,8 +8231,8 @@ var SessionWatcher = class {
|
|
|
8258
8231
|
this.fileOffsets.delete(resolved);
|
|
8259
8232
|
this.pendingPaths.delete(resolved);
|
|
8260
8233
|
});
|
|
8261
|
-
await new Promise((
|
|
8262
|
-
this.watcher?.once("ready", () =>
|
|
8234
|
+
await new Promise((resolve28, reject) => {
|
|
8235
|
+
this.watcher?.once("ready", () => resolve28());
|
|
8263
8236
|
this.watcher?.once("error", (error) => reject(error));
|
|
8264
8237
|
});
|
|
8265
8238
|
if (this.ignoreInitial) {
|
|
@@ -9134,12 +9107,12 @@ async function watchSessions(observer, watchPath) {
|
|
|
9134
9107
|
const watcher = new SessionWatcher(watchPath, observer);
|
|
9135
9108
|
await watcher.start();
|
|
9136
9109
|
console.log(`Watching session updates: ${watchPath}`);
|
|
9137
|
-
await new Promise((
|
|
9110
|
+
await new Promise((resolve28) => {
|
|
9138
9111
|
const shutdown = async () => {
|
|
9139
9112
|
process.off("SIGINT", onSigInt);
|
|
9140
9113
|
process.off("SIGTERM", onSigTerm);
|
|
9141
9114
|
await watcher.stop();
|
|
9142
|
-
|
|
9115
|
+
resolve28();
|
|
9143
9116
|
};
|
|
9144
9117
|
const onSigInt = () => {
|
|
9145
9118
|
void shutdown();
|
|
@@ -9806,2344 +9779,322 @@ function registerReweaveCommand(program) {
|
|
|
9806
9779
|
});
|
|
9807
9780
|
}
|
|
9808
9781
|
|
|
9809
|
-
// src/
|
|
9810
|
-
|
|
9811
|
-
|
|
9812
|
-
|
|
9813
|
-
|
|
9814
|
-
|
|
9815
|
-
|
|
9816
|
-
|
|
9817
|
-
|
|
9782
|
+
// src/cli/index.ts
|
|
9783
|
+
function registerCliCommands(program) {
|
|
9784
|
+
registerContextCommand(program);
|
|
9785
|
+
registerInjectCommand(program);
|
|
9786
|
+
registerObserveCommand(program);
|
|
9787
|
+
registerReflectCommand(program);
|
|
9788
|
+
registerEmbedCommand(program);
|
|
9789
|
+
registerReweaveCommand(program);
|
|
9790
|
+
return program;
|
|
9791
|
+
}
|
|
9818
9792
|
|
|
9819
|
-
// src/
|
|
9793
|
+
// src/commands/setup.ts
|
|
9820
9794
|
var fs24 = __toESM(require("fs"), 1);
|
|
9795
|
+
var os2 = __toESM(require("os"), 1);
|
|
9821
9796
|
var path23 = __toESM(require("path"), 1);
|
|
9822
|
-
var
|
|
9823
|
-
var
|
|
9824
|
-
|
|
9825
|
-
|
|
9826
|
-
|
|
9827
|
-
|
|
9828
|
-
|
|
9829
|
-
var SUPPORTED_METHODS = ["GET", "PUT", "DELETE", "MKCOL", "PROPFIND", "OPTIONS", "HEAD", "MOVE", "COPY"];
|
|
9830
|
-
function toRequestSegments(requestPath) {
|
|
9831
|
-
return requestPath.replace(/\\/g, "/").split("/").filter(Boolean);
|
|
9832
|
-
}
|
|
9833
|
-
function isWithinRoot(fullPath, rootPath) {
|
|
9834
|
-
const resolvedRoot = path23.resolve(rootPath);
|
|
9835
|
-
const relative6 = path23.relative(resolvedRoot, fullPath);
|
|
9836
|
-
return !(relative6.startsWith("..") || path23.isAbsolute(relative6));
|
|
9837
|
-
}
|
|
9838
|
-
function isPathSafe(requestPath, rootPath) {
|
|
9839
|
-
const pathParts = toRequestSegments(requestPath);
|
|
9840
|
-
if (pathParts.includes("..")) {
|
|
9841
|
-
return false;
|
|
9797
|
+
var import_child_process4 = require("child_process");
|
|
9798
|
+
var import_gray_matter7 = __toESM(require("gray-matter"), 1);
|
|
9799
|
+
var CONFIG_FILE4 = ".clawvault.json";
|
|
9800
|
+
function resolveVaultTarget(vaultOverride) {
|
|
9801
|
+
if (vaultOverride) {
|
|
9802
|
+
const vaultPath = path23.resolve(vaultOverride);
|
|
9803
|
+
return { vaultPath, source: "--vault flag", existed: fs24.existsSync(vaultPath) };
|
|
9842
9804
|
}
|
|
9843
|
-
const
|
|
9844
|
-
const
|
|
9845
|
-
if (
|
|
9846
|
-
|
|
9805
|
+
const envPath = process.env.CLAWVAULT_PATH?.trim();
|
|
9806
|
+
const home = os2.homedir();
|
|
9807
|
+
if (envPath) {
|
|
9808
|
+
const vaultPath = path23.resolve(envPath);
|
|
9809
|
+
return { vaultPath, source: "CLAWVAULT_PATH", existed: fs24.existsSync(vaultPath) };
|
|
9847
9810
|
}
|
|
9848
|
-
|
|
9849
|
-
|
|
9850
|
-
|
|
9811
|
+
const candidates = [
|
|
9812
|
+
{ vaultPath: path23.join(home, ".openclaw", "workspace", "memory"), source: "OpenClaw default" },
|
|
9813
|
+
{ vaultPath: path23.resolve(process.cwd(), "memory"), source: "./memory" },
|
|
9814
|
+
{ vaultPath: path23.join(home, "memory"), source: "~/memory" }
|
|
9815
|
+
];
|
|
9816
|
+
for (const candidate of candidates) {
|
|
9817
|
+
if (fs24.existsSync(candidate.vaultPath)) {
|
|
9818
|
+
return { ...candidate, existed: true };
|
|
9851
9819
|
}
|
|
9852
9820
|
}
|
|
9853
|
-
|
|
9821
|
+
const fallback = candidates[0];
|
|
9822
|
+
return { ...fallback, existed: false };
|
|
9854
9823
|
}
|
|
9855
|
-
function
|
|
9856
|
-
|
|
9857
|
-
|
|
9858
|
-
|
|
9859
|
-
}
|
|
9860
|
-
const normalizedRelativePath = path23.normalize(pathParts.join(path23.sep));
|
|
9861
|
-
const fullPath = path23.resolve(rootPath, normalizedRelativePath);
|
|
9862
|
-
if (!isWithinRoot(fullPath, rootPath)) {
|
|
9863
|
-
return null;
|
|
9824
|
+
function ensureVaultStructure(vaultPath) {
|
|
9825
|
+
fs24.mkdirSync(vaultPath, { recursive: true });
|
|
9826
|
+
for (const category of DEFAULT_CATEGORIES) {
|
|
9827
|
+
fs24.mkdirSync(path23.join(vaultPath, category), { recursive: true });
|
|
9864
9828
|
}
|
|
9865
|
-
|
|
9829
|
+
const configPath = path23.join(vaultPath, CONFIG_FILE4);
|
|
9830
|
+
if (fs24.existsSync(configPath)) return false;
|
|
9831
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
9832
|
+
const name = path23.basename(vaultPath);
|
|
9833
|
+
const meta = {
|
|
9834
|
+
name,
|
|
9835
|
+
version: "1.0.0",
|
|
9836
|
+
created: now,
|
|
9837
|
+
lastUpdated: now,
|
|
9838
|
+
categories: DEFAULT_CATEGORIES,
|
|
9839
|
+
documentCount: 0,
|
|
9840
|
+
qmdCollection: name,
|
|
9841
|
+
qmdRoot: vaultPath
|
|
9842
|
+
};
|
|
9843
|
+
fs24.writeFileSync(configPath, JSON.stringify(meta, null, 2));
|
|
9844
|
+
return true;
|
|
9866
9845
|
}
|
|
9867
|
-
function
|
|
9868
|
-
|
|
9869
|
-
|
|
9846
|
+
function writeBases(vaultPath, force) {
|
|
9847
|
+
const basesFiles = {
|
|
9848
|
+
"all-tasks.base": `filters:
|
|
9849
|
+
and:
|
|
9850
|
+
- file.inFolder("tasks")
|
|
9851
|
+
- status != "done"
|
|
9852
|
+
formulas:
|
|
9853
|
+
age: (now() - file.ctime).days
|
|
9854
|
+
status_icon: if(status == "blocked", "\u{1F534}", if(status == "in-progress", "\u{1F528}", if(status == "open", "\u26AA", "\u2705")))
|
|
9855
|
+
views:
|
|
9856
|
+
- type: table
|
|
9857
|
+
name: All Active Tasks
|
|
9858
|
+
groupBy:
|
|
9859
|
+
property: status
|
|
9860
|
+
direction: ASC
|
|
9861
|
+
order:
|
|
9862
|
+
- formula.status_icon
|
|
9863
|
+
- file.name
|
|
9864
|
+
- status
|
|
9865
|
+
- owner
|
|
9866
|
+
- project
|
|
9867
|
+
- priority
|
|
9868
|
+
- blocked_by
|
|
9869
|
+
- formula.age
|
|
9870
|
+
- type: cards
|
|
9871
|
+
name: Task Board
|
|
9872
|
+
groupBy:
|
|
9873
|
+
property: status
|
|
9874
|
+
direction: ASC
|
|
9875
|
+
order:
|
|
9876
|
+
- file.name
|
|
9877
|
+
- owner
|
|
9878
|
+
- project
|
|
9879
|
+
- priority`,
|
|
9880
|
+
"blocked.base": `filters:
|
|
9881
|
+
and:
|
|
9882
|
+
- file.inFolder("tasks")
|
|
9883
|
+
- status == "blocked"
|
|
9884
|
+
formulas:
|
|
9885
|
+
days_blocked: (now() - file.ctime).days
|
|
9886
|
+
views:
|
|
9887
|
+
- type: table
|
|
9888
|
+
name: Blocked Tasks
|
|
9889
|
+
order:
|
|
9890
|
+
- file.name
|
|
9891
|
+
- owner
|
|
9892
|
+
- project
|
|
9893
|
+
- blocked_by
|
|
9894
|
+
- formula.days_blocked
|
|
9895
|
+
- priority`,
|
|
9896
|
+
"by-project.base": `filters:
|
|
9897
|
+
and:
|
|
9898
|
+
- file.inFolder("tasks")
|
|
9899
|
+
- status != "done"
|
|
9900
|
+
formulas:
|
|
9901
|
+
status_icon: if(status == "blocked", "\u{1F534}", if(status == "in-progress", "\u{1F528}", "\u26AA"))
|
|
9902
|
+
views:
|
|
9903
|
+
- type: table
|
|
9904
|
+
name: By Project
|
|
9905
|
+
groupBy:
|
|
9906
|
+
property: project
|
|
9907
|
+
direction: ASC
|
|
9908
|
+
order:
|
|
9909
|
+
- formula.status_icon
|
|
9910
|
+
- file.name
|
|
9911
|
+
- status
|
|
9912
|
+
- owner
|
|
9913
|
+
- priority
|
|
9914
|
+
- type: cards
|
|
9915
|
+
name: Project Cards
|
|
9916
|
+
groupBy:
|
|
9917
|
+
property: project
|
|
9918
|
+
direction: ASC
|
|
9919
|
+
order:
|
|
9920
|
+
- file.name
|
|
9921
|
+
- owner
|
|
9922
|
+
- status`,
|
|
9923
|
+
"by-owner.base": `filters:
|
|
9924
|
+
and:
|
|
9925
|
+
- file.inFolder("tasks")
|
|
9926
|
+
- status != "done"
|
|
9927
|
+
views:
|
|
9928
|
+
- type: table
|
|
9929
|
+
name: By Owner
|
|
9930
|
+
groupBy:
|
|
9931
|
+
property: owner
|
|
9932
|
+
direction: ASC
|
|
9933
|
+
order:
|
|
9934
|
+
- file.name
|
|
9935
|
+
- status
|
|
9936
|
+
- project
|
|
9937
|
+
- priority`,
|
|
9938
|
+
"backlog.base": `filters:
|
|
9939
|
+
and:
|
|
9940
|
+
- file.inFolder("backlog")
|
|
9941
|
+
views:
|
|
9942
|
+
- type: table
|
|
9943
|
+
name: Backlog
|
|
9944
|
+
order:
|
|
9945
|
+
- file.name
|
|
9946
|
+
- source
|
|
9947
|
+
- project
|
|
9948
|
+
- file.ctime`
|
|
9949
|
+
};
|
|
9950
|
+
let written = 0;
|
|
9951
|
+
for (const [filename, content] of Object.entries(basesFiles)) {
|
|
9952
|
+
const filePath = path23.join(vaultPath, filename);
|
|
9953
|
+
if (force || !fs24.existsSync(filePath)) {
|
|
9954
|
+
fs24.writeFileSync(filePath, content);
|
|
9955
|
+
written++;
|
|
9956
|
+
}
|
|
9870
9957
|
}
|
|
9871
|
-
|
|
9872
|
-
|
|
9958
|
+
return written;
|
|
9959
|
+
}
|
|
9960
|
+
var NEURAL_GRAPH_CSS = `/* ClawVault Graph Colors \u2014 Neural Neural Style */
|
|
9961
|
+
/* Auto-generated by \`clawvault setup --theme neural\` */
|
|
9962
|
+
|
|
9963
|
+
body.theme-dark .graph-view .graph-view-container { background-color: #0a0a0a; }
|
|
9964
|
+
|
|
9965
|
+
body.theme-dark .graph-view .node.tag-person circle { fill: #00b4d8 !important; }
|
|
9966
|
+
body.theme-dark .graph-view .node.tag-project circle { fill: #2d6a4f !important; }
|
|
9967
|
+
body.theme-dark .graph-view .node.tag-decision circle { fill: #e8590c !important; }
|
|
9968
|
+
body.theme-dark .graph-view .node.tag-lesson circle { fill: #fcc419 !important; }
|
|
9969
|
+
body.theme-dark .graph-view .node.tag-commitment circle { fill: #e03131 !important; }
|
|
9970
|
+
body.theme-dark .graph-view .node.tag-task circle { fill: #22b8cf !important; }
|
|
9971
|
+
body.theme-dark .graph-view .node.tag-observation circle { fill: #7950f2 !important; }
|
|
9972
|
+
body.theme-dark .graph-view .node.tag-handoff circle { fill: #845ef7 !important; }
|
|
9973
|
+
body.theme-dark .graph-view .node.tag-daily circle { fill: #495057 !important; }
|
|
9974
|
+
|
|
9975
|
+
body.theme-dark .graph-view .node.is-focused circle {
|
|
9976
|
+
fill: #e8a430 !important; stroke: #e8a430 !important;
|
|
9977
|
+
stroke-width: 3px; filter: drop-shadow(0 0 6px #e8a430);
|
|
9978
|
+
}
|
|
9979
|
+
|
|
9980
|
+
body.theme-dark .graph-view .link { stroke: rgba(45, 200, 120, 0.15) !important; }
|
|
9981
|
+
body.theme-dark .graph-view .link.is-focused { stroke: rgba(45, 200, 120, 0.6) !important; }
|
|
9982
|
+
body.theme-dark .graph-view .node text { fill: #c1c2c5 !important; font-size: 0.8em; }
|
|
9983
|
+
`;
|
|
9984
|
+
var MINIMAL_GRAPH_CSS = `/* ClawVault Graph Colors \u2014 Minimal */
|
|
9985
|
+
/* Auto-generated by \`clawvault setup --theme minimal\` */
|
|
9986
|
+
|
|
9987
|
+
body.theme-dark .graph-view .node.tag-person circle { fill: #4a90e8 !important; }
|
|
9988
|
+
body.theme-dark .graph-view .node.tag-project circle { fill: #4ae85d !important; }
|
|
9989
|
+
body.theme-dark .graph-view .node.tag-decision circle { fill: #e85d4a !important; }
|
|
9990
|
+
body.theme-dark .graph-view .node.tag-lesson circle { fill: #9b59b6 !important; }
|
|
9991
|
+
body.theme-dark .graph-view .node.tag-task circle { fill: #e8a430 !important; }
|
|
9992
|
+
`;
|
|
9993
|
+
var NEURAL_COLOR_GROUPS = [
|
|
9994
|
+
{ query: "path:people", color: { a: 1, rgb: 47316 } },
|
|
9995
|
+
{ query: "path:projects", color: { a: 1, rgb: 2976335 } },
|
|
9996
|
+
{ query: "path:decisions", color: { a: 1, rgb: 15227916 } },
|
|
9997
|
+
{ query: "path:lessons", color: { a: 1, rgb: 16565273 } },
|
|
9998
|
+
{ query: "path:tasks", color: { a: 1, rgb: 2275535 } },
|
|
9999
|
+
{ query: "path:commitments", color: { a: 1, rgb: 14680369 } },
|
|
10000
|
+
{ query: "path:backlog", color: { a: 1, rgb: 9806262 } },
|
|
10001
|
+
{ query: "path:inbox", color: { a: 1, rgb: 15964178 } },
|
|
10002
|
+
{ query: "path:handoffs", color: { a: 1, rgb: 8675063 } },
|
|
10003
|
+
{ query: "path:ledger", color: { a: 1, rgb: 7950066 } }
|
|
10004
|
+
];
|
|
10005
|
+
var MINIMAL_COLOR_GROUPS = [
|
|
10006
|
+
{ query: "path:people", color: { a: 1, rgb: 4886760 } },
|
|
10007
|
+
{ query: "path:projects", color: { a: 1, rgb: 4909149 } },
|
|
10008
|
+
{ query: "path:decisions", color: { a: 1, rgb: 15228234 } },
|
|
10009
|
+
{ query: "path:lessons", color: { a: 1, rgb: 10181046 } },
|
|
10010
|
+
{ query: "path:tasks", color: { a: 1, rgb: 15246384 } }
|
|
10011
|
+
];
|
|
10012
|
+
function writeGraphColors(vaultPath, theme, force) {
|
|
10013
|
+
const obsidianDir = path23.join(vaultPath, ".obsidian");
|
|
10014
|
+
if (!fs24.existsSync(obsidianDir)) {
|
|
9873
10015
|
return false;
|
|
9874
10016
|
}
|
|
9875
|
-
const
|
|
9876
|
-
|
|
9877
|
-
const
|
|
9878
|
-
|
|
9879
|
-
|
|
9880
|
-
|
|
9881
|
-
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
9882
|
-
}
|
|
9883
|
-
function formatWebDAVDate(date) {
|
|
9884
|
-
return date.toUTCString();
|
|
9885
|
-
}
|
|
9886
|
-
function generatePropfindEntry(href, stats, isCollection) {
|
|
9887
|
-
const resourceType = isCollection ? "<D:resourcetype><D:collection/></D:resourcetype>" : "<D:resourcetype/>";
|
|
9888
|
-
const contentLength = stats && !isCollection ? `<D:getcontentlength>${stats.size}</D:getcontentlength>` : "";
|
|
9889
|
-
const lastModified = stats ? `<D:getlastmodified>${formatWebDAVDate(stats.mtime)}</D:getlastmodified>` : "";
|
|
9890
|
-
const etag = stats ? `<D:getetag>"${stats.mtime.getTime().toString(16)}-${stats.size.toString(16)}"</D:getetag>` : "";
|
|
9891
|
-
const contentType = !isCollection ? "<D:getcontenttype>application/octet-stream</D:getcontenttype>" : "";
|
|
9892
|
-
return ` <D:response>
|
|
9893
|
-
<D:href>${escapeXml(href)}</D:href>
|
|
9894
|
-
<D:propstat>
|
|
9895
|
-
<D:prop>
|
|
9896
|
-
${resourceType}
|
|
9897
|
-
${contentLength}
|
|
9898
|
-
${lastModified}
|
|
9899
|
-
${etag}
|
|
9900
|
-
${contentType}
|
|
9901
|
-
</D:prop>
|
|
9902
|
-
<D:status>HTTP/1.1 200 OK</D:status>
|
|
9903
|
-
</D:propstat>
|
|
9904
|
-
</D:response>`;
|
|
9905
|
-
}
|
|
9906
|
-
function generatePropfindResponse(entries) {
|
|
9907
|
-
const responseEntries = entries.map(
|
|
9908
|
-
(e) => generatePropfindEntry(e.href, e.stats, e.isCollection)
|
|
9909
|
-
).join("\n");
|
|
9910
|
-
return `<?xml version="1.0" encoding="utf-8"?>
|
|
9911
|
-
<D:multistatus xmlns:D="DAV:">
|
|
9912
|
-
${responseEntries}
|
|
9913
|
-
</D:multistatus>`;
|
|
9914
|
-
}
|
|
9915
|
-
function handleOptions(res, prefix) {
|
|
9916
|
-
res.writeHead(200, {
|
|
9917
|
-
"Allow": SUPPORTED_METHODS.join(", "),
|
|
9918
|
-
"DAV": "1, 2",
|
|
9919
|
-
"Content-Length": "0",
|
|
9920
|
-
"Access-Control-Allow-Origin": "*",
|
|
9921
|
-
"Access-Control-Allow-Methods": SUPPORTED_METHODS.join(", "),
|
|
9922
|
-
"Access-Control-Allow-Headers": "Content-Type, Depth, Destination, Overwrite, Authorization",
|
|
9923
|
-
"MS-Author-Via": "DAV"
|
|
9924
|
-
});
|
|
9925
|
-
res.end();
|
|
9926
|
-
}
|
|
9927
|
-
function handleHead(res, filePath) {
|
|
9928
|
-
try {
|
|
9929
|
-
const stats = fs24.statSync(filePath);
|
|
9930
|
-
if (stats.isDirectory()) {
|
|
9931
|
-
res.writeHead(200, {
|
|
9932
|
-
"Content-Type": "httpd/unix-directory",
|
|
9933
|
-
"Last-Modified": formatWebDAVDate(stats.mtime),
|
|
9934
|
-
"ETag": `"${stats.mtime.getTime().toString(16)}"`,
|
|
9935
|
-
"Access-Control-Allow-Origin": "*"
|
|
9936
|
-
});
|
|
9937
|
-
} else {
|
|
9938
|
-
res.writeHead(200, {
|
|
9939
|
-
"Content-Type": "application/octet-stream",
|
|
9940
|
-
"Content-Length": stats.size.toString(),
|
|
9941
|
-
"Last-Modified": formatWebDAVDate(stats.mtime),
|
|
9942
|
-
"ETag": `"${stats.mtime.getTime().toString(16)}-${stats.size.toString(16)}"`,
|
|
9943
|
-
"Access-Control-Allow-Origin": "*"
|
|
9944
|
-
});
|
|
9945
|
-
}
|
|
9946
|
-
res.end();
|
|
9947
|
-
} catch (err) {
|
|
9948
|
-
res.writeHead(404, { "Content-Type": "text/plain", "Access-Control-Allow-Origin": "*" });
|
|
9949
|
-
res.end("Not Found");
|
|
10017
|
+
const snippetsDir = path23.join(obsidianDir, "snippets");
|
|
10018
|
+
fs24.mkdirSync(snippetsDir, { recursive: true });
|
|
10019
|
+
const snippetName = "clawvault-graph";
|
|
10020
|
+
const snippetPath = path23.join(snippetsDir, `${snippetName}.css`);
|
|
10021
|
+
if (!force && fs24.existsSync(snippetPath)) {
|
|
10022
|
+
return false;
|
|
9950
10023
|
}
|
|
9951
|
-
|
|
9952
|
-
|
|
9953
|
-
|
|
9954
|
-
|
|
9955
|
-
|
|
9956
|
-
|
|
9957
|
-
|
|
9958
|
-
|
|
9959
|
-
"Content-Type": "text/plain",
|
|
9960
|
-
"Content-Length": Buffer.byteLength(listing).toString(),
|
|
9961
|
-
"Access-Control-Allow-Origin": "*"
|
|
9962
|
-
});
|
|
9963
|
-
res.end(listing);
|
|
9964
|
-
} else {
|
|
9965
|
-
const content = fs24.readFileSync(filePath);
|
|
9966
|
-
res.writeHead(200, {
|
|
9967
|
-
"Content-Type": "application/octet-stream",
|
|
9968
|
-
"Content-Length": content.length.toString(),
|
|
9969
|
-
"Last-Modified": formatWebDAVDate(stats.mtime),
|
|
9970
|
-
"ETag": `"${stats.mtime.getTime().toString(16)}-${stats.size.toString(16)}"`,
|
|
9971
|
-
"Access-Control-Allow-Origin": "*"
|
|
9972
|
-
});
|
|
9973
|
-
res.end(content);
|
|
10024
|
+
const css = theme === "neural" ? NEURAL_GRAPH_CSS : MINIMAL_GRAPH_CSS;
|
|
10025
|
+
fs24.writeFileSync(snippetPath, css);
|
|
10026
|
+
const appearancePath = path23.join(obsidianDir, "appearance.json");
|
|
10027
|
+
let appearance = {};
|
|
10028
|
+
if (fs24.existsSync(appearancePath)) {
|
|
10029
|
+
try {
|
|
10030
|
+
appearance = JSON.parse(fs24.readFileSync(appearancePath, "utf-8"));
|
|
10031
|
+
} catch {
|
|
9974
10032
|
}
|
|
9975
|
-
} catch (err) {
|
|
9976
|
-
res.writeHead(404, { "Content-Type": "text/plain", "Access-Control-Allow-Origin": "*" });
|
|
9977
|
-
res.end("Not Found");
|
|
9978
10033
|
}
|
|
9979
|
-
|
|
9980
|
-
|
|
9981
|
-
|
|
9982
|
-
|
|
9983
|
-
|
|
9984
|
-
if (!fs24.existsSync(dir)) {
|
|
9985
|
-
fs24.mkdirSync(dir, { recursive: true });
|
|
9986
|
-
}
|
|
9987
|
-
fs24.writeFileSync(filePath, body);
|
|
9988
|
-
const status = exists ? 204 : 201;
|
|
9989
|
-
res.writeHead(status, {
|
|
9990
|
-
"Content-Length": "0",
|
|
9991
|
-
"Access-Control-Allow-Origin": "*"
|
|
9992
|
-
});
|
|
9993
|
-
res.end();
|
|
9994
|
-
} catch (err) {
|
|
9995
|
-
res.writeHead(500, { "Content-Type": "text/plain", "Access-Control-Allow-Origin": "*" });
|
|
9996
|
-
res.end(`Error: ${err}`);
|
|
10034
|
+
const snippets = appearance.enabledCssSnippets || [];
|
|
10035
|
+
if (!snippets.includes(snippetName)) {
|
|
10036
|
+
snippets.push(snippetName);
|
|
10037
|
+
appearance.enabledCssSnippets = snippets;
|
|
10038
|
+
fs24.writeFileSync(appearancePath, JSON.stringify(appearance, null, 2));
|
|
9997
10039
|
}
|
|
9998
|
-
|
|
9999
|
-
|
|
10000
|
-
|
|
10001
|
-
|
|
10002
|
-
|
|
10003
|
-
|
|
10004
|
-
return;
|
|
10005
|
-
}
|
|
10006
|
-
const stats = fs24.statSync(filePath);
|
|
10007
|
-
if (stats.isDirectory()) {
|
|
10008
|
-
fs24.rmSync(filePath, { recursive: true });
|
|
10009
|
-
} else {
|
|
10010
|
-
fs24.unlinkSync(filePath);
|
|
10011
|
-
}
|
|
10012
|
-
res.writeHead(204, {
|
|
10013
|
-
"Content-Length": "0",
|
|
10014
|
-
"Access-Control-Allow-Origin": "*"
|
|
10015
|
-
});
|
|
10016
|
-
res.end();
|
|
10017
|
-
} catch (err) {
|
|
10018
|
-
res.writeHead(500, { "Content-Type": "text/plain", "Access-Control-Allow-Origin": "*" });
|
|
10019
|
-
res.end(`Error: ${err}`);
|
|
10020
|
-
}
|
|
10021
|
-
}
|
|
10022
|
-
function handleMkcol(res, filePath) {
|
|
10023
|
-
try {
|
|
10024
|
-
if (fs24.existsSync(filePath)) {
|
|
10025
|
-
res.writeHead(405, { "Content-Type": "text/plain", "Access-Control-Allow-Origin": "*" });
|
|
10026
|
-
res.end("Resource already exists");
|
|
10027
|
-
return;
|
|
10028
|
-
}
|
|
10029
|
-
const parent = path23.dirname(filePath);
|
|
10030
|
-
if (!fs24.existsSync(parent)) {
|
|
10031
|
-
res.writeHead(409, { "Content-Type": "text/plain", "Access-Control-Allow-Origin": "*" });
|
|
10032
|
-
res.end("Parent directory does not exist");
|
|
10033
|
-
return;
|
|
10034
|
-
}
|
|
10035
|
-
fs24.mkdirSync(filePath);
|
|
10036
|
-
res.writeHead(201, {
|
|
10037
|
-
"Content-Length": "0",
|
|
10038
|
-
"Access-Control-Allow-Origin": "*"
|
|
10039
|
-
});
|
|
10040
|
-
res.end();
|
|
10041
|
-
} catch (err) {
|
|
10042
|
-
res.writeHead(500, { "Content-Type": "text/plain", "Access-Control-Allow-Origin": "*" });
|
|
10043
|
-
res.end(`Error: ${err}`);
|
|
10044
|
-
}
|
|
10045
|
-
}
|
|
10046
|
-
function handlePropfind(res, filePath, webdavPath, prefix, depth) {
|
|
10047
|
-
try {
|
|
10048
|
-
if (!fs24.existsSync(filePath)) {
|
|
10049
|
-
res.writeHead(404, { "Content-Type": "text/plain", "Access-Control-Allow-Origin": "*" });
|
|
10050
|
-
res.end("Not Found");
|
|
10051
|
-
return;
|
|
10052
|
-
}
|
|
10053
|
-
const stats = fs24.statSync(filePath);
|
|
10054
|
-
const entries = [];
|
|
10055
|
-
const normalizedWebdavPath = webdavPath.startsWith("/") ? webdavPath : "/" + webdavPath;
|
|
10056
|
-
const href = prefix + normalizedWebdavPath;
|
|
10057
|
-
entries.push({
|
|
10058
|
-
href: href.endsWith("/") || stats.isDirectory() ? href : href,
|
|
10059
|
-
stats,
|
|
10060
|
-
isCollection: stats.isDirectory()
|
|
10061
|
-
});
|
|
10062
|
-
if (stats.isDirectory() && depth !== "0") {
|
|
10063
|
-
try {
|
|
10064
|
-
const children = fs24.readdirSync(filePath);
|
|
10065
|
-
for (const child of children) {
|
|
10066
|
-
if (BLOCKED_PATHS.includes(child)) {
|
|
10067
|
-
continue;
|
|
10068
|
-
}
|
|
10069
|
-
const childPath = path23.join(filePath, child);
|
|
10070
|
-
const childWebdavPath = normalizedWebdavPath.endsWith("/") ? normalizedWebdavPath + child : normalizedWebdavPath + "/" + child;
|
|
10071
|
-
try {
|
|
10072
|
-
const childStats = fs24.statSync(childPath);
|
|
10073
|
-
entries.push({
|
|
10074
|
-
href: prefix + childWebdavPath,
|
|
10075
|
-
stats: childStats,
|
|
10076
|
-
isCollection: childStats.isDirectory()
|
|
10077
|
-
});
|
|
10078
|
-
} catch {
|
|
10079
|
-
}
|
|
10080
|
-
}
|
|
10081
|
-
} catch {
|
|
10082
|
-
}
|
|
10083
|
-
}
|
|
10084
|
-
const xml = generatePropfindResponse(entries);
|
|
10085
|
-
res.writeHead(207, {
|
|
10086
|
-
"Content-Type": "application/xml; charset=utf-8",
|
|
10087
|
-
"Content-Length": Buffer.byteLength(xml).toString(),
|
|
10088
|
-
"Access-Control-Allow-Origin": "*"
|
|
10089
|
-
});
|
|
10090
|
-
res.end(xml);
|
|
10091
|
-
} catch (err) {
|
|
10092
|
-
res.writeHead(500, { "Content-Type": "text/plain", "Access-Control-Allow-Origin": "*" });
|
|
10093
|
-
res.end(`Error: ${err}`);
|
|
10094
|
-
}
|
|
10095
|
-
}
|
|
10096
|
-
function handleMove(res, sourcePath, destinationPath, overwrite) {
|
|
10097
|
-
try {
|
|
10098
|
-
if (!fs24.existsSync(sourcePath)) {
|
|
10099
|
-
res.writeHead(404, { "Content-Type": "text/plain", "Access-Control-Allow-Origin": "*" });
|
|
10100
|
-
res.end("Source not found");
|
|
10101
|
-
return;
|
|
10102
|
-
}
|
|
10103
|
-
if (!destinationPath) {
|
|
10104
|
-
res.writeHead(400, { "Content-Type": "text/plain", "Access-Control-Allow-Origin": "*" });
|
|
10105
|
-
res.end("Destination header required");
|
|
10106
|
-
return;
|
|
10107
|
-
}
|
|
10108
|
-
const destExists = fs24.existsSync(destinationPath);
|
|
10109
|
-
if (destExists && !overwrite) {
|
|
10110
|
-
res.writeHead(412, { "Content-Type": "text/plain", "Access-Control-Allow-Origin": "*" });
|
|
10111
|
-
res.end("Destination exists and Overwrite is F");
|
|
10112
|
-
return;
|
|
10113
|
-
}
|
|
10114
|
-
const destDir = path23.dirname(destinationPath);
|
|
10115
|
-
if (!fs24.existsSync(destDir)) {
|
|
10116
|
-
fs24.mkdirSync(destDir, { recursive: true });
|
|
10117
|
-
}
|
|
10118
|
-
if (destExists) {
|
|
10119
|
-
const destStats = fs24.statSync(destinationPath);
|
|
10120
|
-
if (destStats.isDirectory()) {
|
|
10121
|
-
fs24.rmSync(destinationPath, { recursive: true });
|
|
10122
|
-
} else {
|
|
10123
|
-
fs24.unlinkSync(destinationPath);
|
|
10124
|
-
}
|
|
10125
|
-
}
|
|
10126
|
-
fs24.renameSync(sourcePath, destinationPath);
|
|
10127
|
-
const status = destExists ? 204 : 201;
|
|
10128
|
-
res.writeHead(status, {
|
|
10129
|
-
"Content-Length": "0",
|
|
10130
|
-
"Access-Control-Allow-Origin": "*"
|
|
10131
|
-
});
|
|
10132
|
-
res.end();
|
|
10133
|
-
} catch (err) {
|
|
10134
|
-
res.writeHead(500, { "Content-Type": "text/plain", "Access-Control-Allow-Origin": "*" });
|
|
10135
|
-
res.end(`Error: ${err}`);
|
|
10136
|
-
}
|
|
10137
|
-
}
|
|
10138
|
-
function handleCopy(res, sourcePath, destinationPath, overwrite) {
|
|
10139
|
-
try {
|
|
10140
|
-
if (!fs24.existsSync(sourcePath)) {
|
|
10141
|
-
res.writeHead(404, { "Content-Type": "text/plain", "Access-Control-Allow-Origin": "*" });
|
|
10142
|
-
res.end("Source not found");
|
|
10143
|
-
return;
|
|
10144
|
-
}
|
|
10145
|
-
if (!destinationPath) {
|
|
10146
|
-
res.writeHead(400, { "Content-Type": "text/plain", "Access-Control-Allow-Origin": "*" });
|
|
10147
|
-
res.end("Destination header required");
|
|
10148
|
-
return;
|
|
10149
|
-
}
|
|
10150
|
-
const destExists = fs24.existsSync(destinationPath);
|
|
10151
|
-
if (destExists && !overwrite) {
|
|
10152
|
-
res.writeHead(412, { "Content-Type": "text/plain", "Access-Control-Allow-Origin": "*" });
|
|
10153
|
-
res.end("Destination exists and Overwrite is F");
|
|
10154
|
-
return;
|
|
10155
|
-
}
|
|
10156
|
-
const destDir = path23.dirname(destinationPath);
|
|
10157
|
-
if (!fs24.existsSync(destDir)) {
|
|
10158
|
-
fs24.mkdirSync(destDir, { recursive: true });
|
|
10159
|
-
}
|
|
10160
|
-
const sourceStats = fs24.statSync(sourcePath);
|
|
10161
|
-
if (sourceStats.isDirectory()) {
|
|
10162
|
-
copyDirRecursive(sourcePath, destinationPath);
|
|
10163
|
-
} else {
|
|
10164
|
-
fs24.copyFileSync(sourcePath, destinationPath);
|
|
10165
|
-
}
|
|
10166
|
-
const status = destExists ? 204 : 201;
|
|
10167
|
-
res.writeHead(status, {
|
|
10168
|
-
"Content-Length": "0",
|
|
10169
|
-
"Access-Control-Allow-Origin": "*"
|
|
10170
|
-
});
|
|
10171
|
-
res.end();
|
|
10172
|
-
} catch (err) {
|
|
10173
|
-
res.writeHead(500, { "Content-Type": "text/plain", "Access-Control-Allow-Origin": "*" });
|
|
10174
|
-
res.end(`Error: ${err}`);
|
|
10175
|
-
}
|
|
10176
|
-
}
|
|
10177
|
-
function copyDirRecursive(src, dest) {
|
|
10178
|
-
if (!fs24.existsSync(dest)) {
|
|
10179
|
-
fs24.mkdirSync(dest, { recursive: true });
|
|
10180
|
-
}
|
|
10181
|
-
const entries = fs24.readdirSync(src, { withFileTypes: true });
|
|
10182
|
-
for (const entry of entries) {
|
|
10183
|
-
const srcPath = path23.join(src, entry.name);
|
|
10184
|
-
const destPath = path23.join(dest, entry.name);
|
|
10185
|
-
if (entry.isDirectory()) {
|
|
10186
|
-
copyDirRecursive(srcPath, destPath);
|
|
10187
|
-
} else {
|
|
10188
|
-
fs24.copyFileSync(srcPath, destPath);
|
|
10189
|
-
}
|
|
10190
|
-
}
|
|
10191
|
-
}
|
|
10192
|
-
function parseDestinationHeader(destinationHeader, prefix, rootPath) {
|
|
10193
|
-
if (!destinationHeader) {
|
|
10194
|
-
return null;
|
|
10195
|
-
}
|
|
10196
|
-
try {
|
|
10197
|
-
let destPath;
|
|
10198
|
-
if (destinationHeader.startsWith("http://") || destinationHeader.startsWith("https://")) {
|
|
10199
|
-
const url = new URL(destinationHeader);
|
|
10200
|
-
destPath = decodeURIComponent(url.pathname);
|
|
10201
|
-
} else {
|
|
10202
|
-
destPath = decodeURIComponent(destinationHeader);
|
|
10203
|
-
}
|
|
10204
|
-
if (destPath.startsWith(prefix)) {
|
|
10205
|
-
destPath = destPath.slice(prefix.length);
|
|
10206
|
-
}
|
|
10207
|
-
return resolveWebDAVPath(destPath, rootPath);
|
|
10208
|
-
} catch {
|
|
10209
|
-
return null;
|
|
10210
|
-
}
|
|
10211
|
-
}
|
|
10212
|
-
function createWebDAVHandler(config) {
|
|
10213
|
-
const { rootPath, prefix = WEBDAV_PREFIX, auth } = config;
|
|
10214
|
-
return async (req, res) => {
|
|
10215
|
-
const rawUrl = req.url || "/";
|
|
10216
|
-
if (rawUrl.includes("..")) {
|
|
10217
|
-
if (rawUrl.startsWith(prefix)) {
|
|
10218
|
-
res.writeHead(403, { "Content-Type": "text/plain", "Access-Control-Allow-Origin": "*" });
|
|
10219
|
-
res.end("Forbidden");
|
|
10220
|
-
return true;
|
|
10221
|
-
}
|
|
10222
|
-
}
|
|
10223
|
-
const url = new URL(rawUrl, `http://${req.headers.host || "localhost"}`);
|
|
10224
|
-
const pathname = decodeURIComponent(url.pathname);
|
|
10225
|
-
if (!pathname.startsWith(prefix)) {
|
|
10226
|
-
return false;
|
|
10227
|
-
}
|
|
10228
|
-
let webdavPath = pathname.slice(prefix.length);
|
|
10229
|
-
if (!webdavPath.startsWith("/")) {
|
|
10230
|
-
webdavPath = "/" + webdavPath;
|
|
10231
|
-
}
|
|
10232
|
-
if (req.method === "OPTIONS") {
|
|
10233
|
-
handleOptions(res, prefix);
|
|
10234
|
-
return true;
|
|
10235
|
-
}
|
|
10236
|
-
if (!checkAuth(req, auth)) {
|
|
10237
|
-
res.writeHead(401, {
|
|
10238
|
-
"WWW-Authenticate": 'Basic realm="ClawVault WebDAV"',
|
|
10239
|
-
"Content-Type": "text/plain",
|
|
10240
|
-
"Access-Control-Allow-Origin": "*"
|
|
10241
|
-
});
|
|
10242
|
-
res.end("Unauthorized");
|
|
10243
|
-
return true;
|
|
10244
|
-
}
|
|
10245
|
-
if (!isPathSafe(webdavPath, rootPath)) {
|
|
10246
|
-
res.writeHead(403, { "Content-Type": "text/plain", "Access-Control-Allow-Origin": "*" });
|
|
10247
|
-
res.end("Forbidden");
|
|
10248
|
-
return true;
|
|
10249
|
-
}
|
|
10250
|
-
const filePath = resolveWebDAVPath(webdavPath, rootPath);
|
|
10251
|
-
if (!filePath) {
|
|
10252
|
-
res.writeHead(403, { "Content-Type": "text/plain", "Access-Control-Allow-Origin": "*" });
|
|
10253
|
-
res.end("Forbidden");
|
|
10254
|
-
return true;
|
|
10255
|
-
}
|
|
10256
|
-
const depth = req.headers.depth || "infinity";
|
|
10257
|
-
const overwrite = req.headers.overwrite?.toUpperCase() !== "F";
|
|
10258
|
-
const destinationHeader = req.headers.destination;
|
|
10259
|
-
switch (req.method) {
|
|
10260
|
-
case "HEAD":
|
|
10261
|
-
handleHead(res, filePath);
|
|
10262
|
-
return true;
|
|
10263
|
-
case "GET":
|
|
10264
|
-
handleGet(res, filePath);
|
|
10265
|
-
return true;
|
|
10266
|
-
case "PUT": {
|
|
10267
|
-
const chunks = [];
|
|
10268
|
-
for await (const chunk of req) {
|
|
10269
|
-
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
10270
|
-
}
|
|
10271
|
-
const body = Buffer.concat(chunks);
|
|
10272
|
-
handlePut(res, filePath, body);
|
|
10273
|
-
return true;
|
|
10274
|
-
}
|
|
10275
|
-
case "DELETE":
|
|
10276
|
-
handleDelete(res, filePath);
|
|
10277
|
-
return true;
|
|
10278
|
-
case "MKCOL":
|
|
10279
|
-
handleMkcol(res, filePath);
|
|
10280
|
-
return true;
|
|
10281
|
-
case "PROPFIND":
|
|
10282
|
-
handlePropfind(res, filePath, webdavPath, prefix, depth);
|
|
10283
|
-
return true;
|
|
10284
|
-
case "MOVE": {
|
|
10285
|
-
const destPath = parseDestinationHeader(destinationHeader, prefix, rootPath);
|
|
10286
|
-
if (destPath && destinationHeader) {
|
|
10287
|
-
const destWebdavPath = destinationHeader.includes(prefix) ? destinationHeader.slice(destinationHeader.indexOf(prefix) + prefix.length) : destinationHeader;
|
|
10288
|
-
if (!isPathSafe(destWebdavPath, rootPath)) {
|
|
10289
|
-
res.writeHead(403, { "Content-Type": "text/plain", "Access-Control-Allow-Origin": "*" });
|
|
10290
|
-
res.end("Forbidden");
|
|
10291
|
-
return true;
|
|
10292
|
-
}
|
|
10293
|
-
}
|
|
10294
|
-
handleMove(res, filePath, destPath, overwrite);
|
|
10295
|
-
return true;
|
|
10296
|
-
}
|
|
10297
|
-
case "COPY": {
|
|
10298
|
-
const destPath = parseDestinationHeader(destinationHeader, prefix, rootPath);
|
|
10299
|
-
if (destPath && destinationHeader) {
|
|
10300
|
-
const destWebdavPath = destinationHeader.includes(prefix) ? destinationHeader.slice(destinationHeader.indexOf(prefix) + prefix.length) : destinationHeader;
|
|
10301
|
-
if (!isPathSafe(destWebdavPath, rootPath)) {
|
|
10302
|
-
res.writeHead(403, { "Content-Type": "text/plain", "Access-Control-Allow-Origin": "*" });
|
|
10303
|
-
res.end("Forbidden");
|
|
10304
|
-
return true;
|
|
10305
|
-
}
|
|
10306
|
-
}
|
|
10307
|
-
handleCopy(res, filePath, destPath, overwrite);
|
|
10308
|
-
return true;
|
|
10309
|
-
}
|
|
10310
|
-
default:
|
|
10311
|
-
res.writeHead(405, {
|
|
10312
|
-
"Allow": SUPPORTED_METHODS.join(", "),
|
|
10313
|
-
"Content-Type": "text/plain",
|
|
10314
|
-
"Access-Control-Allow-Origin": "*"
|
|
10315
|
-
});
|
|
10316
|
-
res.end("Method Not Allowed");
|
|
10317
|
-
return true;
|
|
10318
|
-
}
|
|
10319
|
-
};
|
|
10320
|
-
}
|
|
10321
|
-
|
|
10322
|
-
// src/lib/tailscale.ts
|
|
10323
|
-
var crypto = __toESM(require("crypto"), 1);
|
|
10324
|
-
var DEFAULT_SERVE_PORT = 8384;
|
|
10325
|
-
var CLAWVAULT_SERVE_PATH = "/.clawvault";
|
|
10326
|
-
function hasTailscale() {
|
|
10327
|
-
const probe = (0, import_child_process4.spawnSync)("tailscale", ["version"], {
|
|
10328
|
-
stdio: "pipe",
|
|
10329
|
-
encoding: "utf-8",
|
|
10330
|
-
timeout: 5e3
|
|
10331
|
-
});
|
|
10332
|
-
return !probe.error && probe.status === 0;
|
|
10333
|
-
}
|
|
10334
|
-
function getTailscaleVersion() {
|
|
10335
|
-
const result = (0, import_child_process4.spawnSync)("tailscale", ["version"], {
|
|
10336
|
-
stdio: "pipe",
|
|
10337
|
-
encoding: "utf-8",
|
|
10338
|
-
timeout: 5e3
|
|
10339
|
-
});
|
|
10340
|
-
if (result.error || result.status !== 0) {
|
|
10341
|
-
return null;
|
|
10342
|
-
}
|
|
10343
|
-
const lines = result.stdout.trim().split("\n");
|
|
10344
|
-
return lines[0] || null;
|
|
10345
|
-
}
|
|
10346
|
-
function getTailscaleStatus() {
|
|
10347
|
-
const status = {
|
|
10348
|
-
installed: false,
|
|
10349
|
-
running: false,
|
|
10350
|
-
connected: false,
|
|
10351
|
-
peers: []
|
|
10352
|
-
};
|
|
10353
|
-
if (!hasTailscale()) {
|
|
10354
|
-
status.error = "Tailscale CLI not found. Install from https://tailscale.com/download";
|
|
10355
|
-
return status;
|
|
10356
|
-
}
|
|
10357
|
-
status.installed = true;
|
|
10358
|
-
const result = (0, import_child_process4.spawnSync)("tailscale", ["status", "--json"], {
|
|
10359
|
-
stdio: "pipe",
|
|
10360
|
-
encoding: "utf-8",
|
|
10361
|
-
timeout: 1e4
|
|
10362
|
-
});
|
|
10363
|
-
if (result.error) {
|
|
10364
|
-
status.error = `Failed to get Tailscale status: ${result.error.message}`;
|
|
10365
|
-
return status;
|
|
10366
|
-
}
|
|
10367
|
-
if (result.status !== 0) {
|
|
10368
|
-
status.error = result.stderr?.trim() || "Tailscale daemon not running";
|
|
10369
|
-
return status;
|
|
10370
|
-
}
|
|
10371
|
-
try {
|
|
10372
|
-
const data = JSON.parse(result.stdout);
|
|
10373
|
-
status.running = true;
|
|
10374
|
-
status.backendState = data.BackendState;
|
|
10375
|
-
status.connected = data.BackendState === "Running";
|
|
10376
|
-
status.tailnetName = data.CurrentTailnet?.Name;
|
|
10377
|
-
if (data.Self) {
|
|
10378
|
-
status.selfIP = data.Self.TailscaleIPs?.[0];
|
|
10379
|
-
status.selfHostname = data.Self.HostName;
|
|
10380
|
-
status.selfDNSName = data.Self.DNSName;
|
|
10381
|
-
}
|
|
10382
|
-
if (data.Peer) {
|
|
10383
|
-
for (const [_, peerData] of Object.entries(data.Peer)) {
|
|
10384
|
-
const peer = {
|
|
10385
|
-
hostname: peerData.HostName || "",
|
|
10386
|
-
dnsName: peerData.DNSName || "",
|
|
10387
|
-
tailscaleIPs: peerData.TailscaleIPs || [],
|
|
10388
|
-
online: peerData.Online || false,
|
|
10389
|
-
os: peerData.OS,
|
|
10390
|
-
exitNode: peerData.ExitNode,
|
|
10391
|
-
tags: peerData.Tags,
|
|
10392
|
-
lastSeen: peerData.LastSeen
|
|
10393
|
-
};
|
|
10394
|
-
status.peers.push(peer);
|
|
10395
|
-
}
|
|
10396
|
-
}
|
|
10397
|
-
} catch (err) {
|
|
10398
|
-
status.error = `Failed to parse Tailscale status: ${err}`;
|
|
10399
|
-
}
|
|
10400
|
-
return status;
|
|
10401
|
-
}
|
|
10402
|
-
function findPeer(hostname) {
|
|
10403
|
-
const status = getTailscaleStatus();
|
|
10404
|
-
if (!status.connected) {
|
|
10405
|
-
return null;
|
|
10406
|
-
}
|
|
10407
|
-
const normalizedSearch = hostname.toLowerCase();
|
|
10408
|
-
let peer = status.peers.find(
|
|
10409
|
-
(p) => p.hostname.toLowerCase() === normalizedSearch
|
|
10410
|
-
);
|
|
10411
|
-
if (peer) return peer;
|
|
10412
|
-
peer = status.peers.find(
|
|
10413
|
-
(p) => p.dnsName.toLowerCase().startsWith(normalizedSearch)
|
|
10414
|
-
);
|
|
10415
|
-
if (peer) return peer;
|
|
10416
|
-
peer = status.peers.find(
|
|
10417
|
-
(p) => p.hostname.toLowerCase().includes(normalizedSearch)
|
|
10418
|
-
);
|
|
10419
|
-
return peer || null;
|
|
10420
|
-
}
|
|
10421
|
-
function getOnlinePeers() {
|
|
10422
|
-
const status = getTailscaleStatus();
|
|
10423
|
-
return status.peers.filter((p) => p.online);
|
|
10424
|
-
}
|
|
10425
|
-
function resolvePeerIP(hostname) {
|
|
10426
|
-
const peer = findPeer(hostname);
|
|
10427
|
-
return peer?.tailscaleIPs[0] || null;
|
|
10428
|
-
}
|
|
10429
|
-
function calculateChecksum(filePath) {
|
|
10430
|
-
const content = fs25.readFileSync(filePath);
|
|
10431
|
-
return crypto.createHash("sha256").update(content).digest("hex");
|
|
10432
|
-
}
|
|
10433
|
-
function generateVaultManifest(vaultPath) {
|
|
10434
|
-
const configPath = path24.join(vaultPath, ".clawvault.json");
|
|
10435
|
-
if (!fs25.existsSync(configPath)) {
|
|
10436
|
-
throw new Error(`Not a ClawVault: ${vaultPath}`);
|
|
10437
|
-
}
|
|
10438
|
-
const config = JSON.parse(fs25.readFileSync(configPath, "utf-8"));
|
|
10439
|
-
const files = [];
|
|
10440
|
-
function walkDir(dir, relativePath = "") {
|
|
10441
|
-
const entries = fs25.readdirSync(dir, { withFileTypes: true });
|
|
10442
|
-
for (const entry of entries) {
|
|
10443
|
-
const fullPath = path24.join(dir, entry.name);
|
|
10444
|
-
const relPath = path24.join(relativePath, entry.name);
|
|
10445
|
-
if (entry.name.startsWith(".") && entry.name !== ".clawvault.json") {
|
|
10446
|
-
continue;
|
|
10447
|
-
}
|
|
10448
|
-
if (entry.name === "node_modules") {
|
|
10449
|
-
continue;
|
|
10450
|
-
}
|
|
10451
|
-
if (entry.isDirectory()) {
|
|
10452
|
-
walkDir(fullPath, relPath);
|
|
10453
|
-
} else if (entry.isFile() && (entry.name.endsWith(".md") || entry.name === ".clawvault.json")) {
|
|
10454
|
-
const stats = fs25.statSync(fullPath);
|
|
10455
|
-
const category = relativePath.split(path24.sep)[0] || "root";
|
|
10456
|
-
files.push({
|
|
10457
|
-
path: relPath,
|
|
10458
|
-
size: stats.size,
|
|
10459
|
-
modified: stats.mtime.toISOString(),
|
|
10460
|
-
checksum: calculateChecksum(fullPath),
|
|
10461
|
-
category
|
|
10462
|
-
});
|
|
10463
|
-
}
|
|
10464
|
-
}
|
|
10465
|
-
}
|
|
10466
|
-
walkDir(vaultPath);
|
|
10467
|
-
return {
|
|
10468
|
-
name: config.name,
|
|
10469
|
-
version: config.version || "1.0.0",
|
|
10470
|
-
lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10471
|
-
files
|
|
10472
|
-
};
|
|
10473
|
-
}
|
|
10474
|
-
function compareManifests(local, remote) {
|
|
10475
|
-
const localFiles = new Map(local.files.map((f) => [f.path, f]));
|
|
10476
|
-
const remoteFiles = new Map(remote.files.map((f) => [f.path, f]));
|
|
10477
|
-
const toPush = [];
|
|
10478
|
-
const toPull = [];
|
|
10479
|
-
const conflicts = [];
|
|
10480
|
-
const unchanged = [];
|
|
10481
|
-
for (const [filePath, localFile] of localFiles) {
|
|
10482
|
-
const remoteFile = remoteFiles.get(filePath);
|
|
10483
|
-
if (!remoteFile) {
|
|
10484
|
-
toPush.push(localFile);
|
|
10485
|
-
} else if (localFile.checksum === remoteFile.checksum) {
|
|
10486
|
-
unchanged.push(filePath);
|
|
10487
|
-
} else {
|
|
10488
|
-
const localTime = new Date(localFile.modified).getTime();
|
|
10489
|
-
const remoteTime = new Date(remoteFile.modified).getTime();
|
|
10490
|
-
if (localTime > remoteTime) {
|
|
10491
|
-
toPush.push(localFile);
|
|
10492
|
-
} else if (remoteTime > localTime) {
|
|
10493
|
-
toPull.push(remoteFile);
|
|
10494
|
-
} else {
|
|
10495
|
-
conflicts.push({ path: filePath, local: localFile, remote: remoteFile });
|
|
10496
|
-
}
|
|
10497
|
-
}
|
|
10498
|
-
}
|
|
10499
|
-
for (const [filePath, remoteFile] of remoteFiles) {
|
|
10500
|
-
if (!localFiles.has(filePath)) {
|
|
10501
|
-
toPull.push(remoteFile);
|
|
10502
|
-
}
|
|
10503
|
-
}
|
|
10504
|
-
return { toPush, toPull, conflicts, unchanged };
|
|
10505
|
-
}
|
|
10506
|
-
function serveVault(vaultPath, options = {}) {
|
|
10507
|
-
const port = options.port || DEFAULT_SERVE_PORT;
|
|
10508
|
-
const pathPrefix = options.pathPrefix || CLAWVAULT_SERVE_PATH;
|
|
10509
|
-
if (!fs25.existsSync(path24.join(vaultPath, ".clawvault.json"))) {
|
|
10510
|
-
throw new Error(`Not a ClawVault: ${vaultPath}`);
|
|
10511
|
-
}
|
|
10512
|
-
const webdavHandler = createWebDAVHandler({
|
|
10513
|
-
rootPath: vaultPath,
|
|
10514
|
-
prefix: WEBDAV_PREFIX,
|
|
10515
|
-
auth: options.webdavAuth
|
|
10516
|
-
});
|
|
10517
|
-
const server = http.createServer(async (req, res) => {
|
|
10518
|
-
const url = new URL(req.url || "/", `http://localhost:${port}`);
|
|
10519
|
-
const pathname = url.pathname;
|
|
10520
|
-
if (pathname.startsWith(WEBDAV_PREFIX)) {
|
|
10521
|
-
try {
|
|
10522
|
-
const handled = await webdavHandler(req, res);
|
|
10523
|
-
if (handled) return;
|
|
10524
|
-
} catch (err) {
|
|
10525
|
-
res.writeHead(500, { "Content-Type": "text/plain", "Access-Control-Allow-Origin": "*" });
|
|
10526
|
-
res.end(`WebDAV Error: ${err}`);
|
|
10527
|
-
return;
|
|
10528
|
-
}
|
|
10529
|
-
}
|
|
10530
|
-
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
10531
|
-
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
10532
|
-
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
|
|
10533
|
-
if (req.method === "OPTIONS") {
|
|
10534
|
-
res.writeHead(200);
|
|
10535
|
-
res.end();
|
|
10536
|
-
return;
|
|
10537
|
-
}
|
|
10538
|
-
if (pathname === `${pathPrefix}/health`) {
|
|
10539
|
-
res.writeHead(200, { "Content-Type": "application/json" });
|
|
10540
|
-
res.end(JSON.stringify({ status: "ok", vault: path24.basename(vaultPath) }));
|
|
10541
|
-
return;
|
|
10542
|
-
}
|
|
10543
|
-
if (pathname === `${pathPrefix}/manifest`) {
|
|
10544
|
-
try {
|
|
10545
|
-
const manifest = generateVaultManifest(vaultPath);
|
|
10546
|
-
res.writeHead(200, { "Content-Type": "application/json" });
|
|
10547
|
-
res.end(JSON.stringify(manifest));
|
|
10548
|
-
} catch (err) {
|
|
10549
|
-
res.writeHead(500, { "Content-Type": "application/json" });
|
|
10550
|
-
res.end(JSON.stringify({ error: String(err) }));
|
|
10551
|
-
}
|
|
10552
|
-
return;
|
|
10553
|
-
}
|
|
10554
|
-
if (pathname.startsWith(`${pathPrefix}/files/`)) {
|
|
10555
|
-
const relativePath = decodeURIComponent(pathname.slice(`${pathPrefix}/files/`.length));
|
|
10556
|
-
const filePath = path24.join(vaultPath, relativePath);
|
|
10557
|
-
const resolvedPath = path24.resolve(filePath);
|
|
10558
|
-
const resolvedVault = path24.resolve(vaultPath);
|
|
10559
|
-
if (!resolvedPath.startsWith(resolvedVault)) {
|
|
10560
|
-
res.writeHead(403, { "Content-Type": "application/json" });
|
|
10561
|
-
res.end(JSON.stringify({ error: "Access denied" }));
|
|
10562
|
-
return;
|
|
10563
|
-
}
|
|
10564
|
-
if (!fs25.existsSync(filePath)) {
|
|
10565
|
-
res.writeHead(404, { "Content-Type": "application/json" });
|
|
10566
|
-
res.end(JSON.stringify({ error: "File not found" }));
|
|
10567
|
-
return;
|
|
10568
|
-
}
|
|
10569
|
-
try {
|
|
10570
|
-
const content = fs25.readFileSync(filePath, "utf-8");
|
|
10571
|
-
const stats = fs25.statSync(filePath);
|
|
10572
|
-
res.writeHead(200, {
|
|
10573
|
-
"Content-Type": "text/markdown",
|
|
10574
|
-
"Content-Length": Buffer.byteLength(content),
|
|
10575
|
-
"Last-Modified": stats.mtime.toUTCString()
|
|
10576
|
-
});
|
|
10577
|
-
res.end(content);
|
|
10578
|
-
} catch (err) {
|
|
10579
|
-
res.writeHead(500, { "Content-Type": "application/json" });
|
|
10580
|
-
res.end(JSON.stringify({ error: String(err) }));
|
|
10581
|
-
}
|
|
10582
|
-
return;
|
|
10583
|
-
}
|
|
10584
|
-
if (pathname.startsWith(`${pathPrefix}/upload/`) && req.method === "POST") {
|
|
10585
|
-
const relativePath = decodeURIComponent(pathname.slice(`${pathPrefix}/upload/`.length));
|
|
10586
|
-
const filePath = path24.join(vaultPath, relativePath);
|
|
10587
|
-
const resolvedPath = path24.resolve(filePath);
|
|
10588
|
-
const resolvedVault = path24.resolve(vaultPath);
|
|
10589
|
-
if (!resolvedPath.startsWith(resolvedVault)) {
|
|
10590
|
-
res.writeHead(403, { "Content-Type": "application/json" });
|
|
10591
|
-
res.end(JSON.stringify({ error: "Access denied" }));
|
|
10592
|
-
return;
|
|
10593
|
-
}
|
|
10594
|
-
let body = "";
|
|
10595
|
-
req.on("data", (chunk) => {
|
|
10596
|
-
body += chunk;
|
|
10597
|
-
});
|
|
10598
|
-
req.on("end", () => {
|
|
10599
|
-
try {
|
|
10600
|
-
const dir = path24.dirname(filePath);
|
|
10601
|
-
if (!fs25.existsSync(dir)) {
|
|
10602
|
-
fs25.mkdirSync(dir, { recursive: true });
|
|
10603
|
-
}
|
|
10604
|
-
fs25.writeFileSync(filePath, body, "utf-8");
|
|
10605
|
-
res.writeHead(200, { "Content-Type": "application/json" });
|
|
10606
|
-
res.end(JSON.stringify({ success: true, path: relativePath }));
|
|
10607
|
-
} catch (err) {
|
|
10608
|
-
res.writeHead(500, { "Content-Type": "application/json" });
|
|
10609
|
-
res.end(JSON.stringify({ error: String(err) }));
|
|
10610
|
-
}
|
|
10611
|
-
});
|
|
10612
|
-
return;
|
|
10613
|
-
}
|
|
10614
|
-
if (pathname === pathPrefix || pathname === `${pathPrefix}/`) {
|
|
10615
|
-
res.writeHead(200, { "Content-Type": "application/json" });
|
|
10616
|
-
res.end(JSON.stringify({
|
|
10617
|
-
service: "clawvault-sync",
|
|
10618
|
-
version: "1.0.0",
|
|
10619
|
-
vault: path24.basename(vaultPath),
|
|
10620
|
-
endpoints: {
|
|
10621
|
-
health: `${pathPrefix}/health`,
|
|
10622
|
-
manifest: `${pathPrefix}/manifest`,
|
|
10623
|
-
files: `${pathPrefix}/files/<path>`,
|
|
10624
|
-
upload: `${pathPrefix}/upload/<path>`,
|
|
10625
|
-
webdav: `${WEBDAV_PREFIX}/`
|
|
10626
|
-
}
|
|
10627
|
-
}));
|
|
10628
|
-
return;
|
|
10629
|
-
}
|
|
10630
|
-
res.writeHead(404, { "Content-Type": "application/json" });
|
|
10631
|
-
res.end(JSON.stringify({ error: "Not found" }));
|
|
10632
|
-
});
|
|
10633
|
-
server.listen(port, "0.0.0.0");
|
|
10634
|
-
return {
|
|
10635
|
-
server,
|
|
10636
|
-
port,
|
|
10637
|
-
stop: () => new Promise((resolve30, reject) => {
|
|
10638
|
-
server.close((err) => {
|
|
10639
|
-
if (err) reject(err);
|
|
10640
|
-
else resolve30();
|
|
10641
|
-
});
|
|
10642
|
-
})
|
|
10643
|
-
};
|
|
10644
|
-
}
|
|
10645
|
-
async function fetchRemoteManifest(host, port = DEFAULT_SERVE_PORT, useHttps = false) {
|
|
10646
|
-
return new Promise((resolve30, reject) => {
|
|
10647
|
-
const protocol = useHttps ? https : http;
|
|
10648
|
-
const url = `${useHttps ? "https" : "http"}://${host}:${port}${CLAWVAULT_SERVE_PATH}/manifest`;
|
|
10649
|
-
const req = protocol.get(url, { timeout: 1e4 }, (res) => {
|
|
10650
|
-
let data = "";
|
|
10651
|
-
res.on("data", (chunk) => {
|
|
10652
|
-
data += chunk;
|
|
10653
|
-
});
|
|
10654
|
-
res.on("end", () => {
|
|
10655
|
-
if (res.statusCode !== 200) {
|
|
10656
|
-
reject(new Error(`Failed to fetch manifest: HTTP ${res.statusCode}`));
|
|
10657
|
-
return;
|
|
10658
|
-
}
|
|
10659
|
-
try {
|
|
10660
|
-
resolve30(JSON.parse(data));
|
|
10661
|
-
} catch (err) {
|
|
10662
|
-
reject(new Error(`Invalid manifest response: ${err}`));
|
|
10663
|
-
}
|
|
10664
|
-
});
|
|
10665
|
-
});
|
|
10666
|
-
req.on("error", reject);
|
|
10667
|
-
req.on("timeout", () => {
|
|
10668
|
-
req.destroy();
|
|
10669
|
-
reject(new Error("Request timed out"));
|
|
10670
|
-
});
|
|
10671
|
-
});
|
|
10672
|
-
}
|
|
10673
|
-
async function fetchRemoteFile(host, filePath, port = DEFAULT_SERVE_PORT, useHttps = false) {
|
|
10674
|
-
return new Promise((resolve30, reject) => {
|
|
10675
|
-
const protocol = useHttps ? https : http;
|
|
10676
|
-
const encodedPath = encodeURIComponent(filePath).replace(/%2F/g, "/");
|
|
10677
|
-
const url = `${useHttps ? "https" : "http"}://${host}:${port}${CLAWVAULT_SERVE_PATH}/files/${encodedPath}`;
|
|
10678
|
-
const req = protocol.get(url, { timeout: 3e4 }, (res) => {
|
|
10679
|
-
let data = "";
|
|
10680
|
-
res.on("data", (chunk) => {
|
|
10681
|
-
data += chunk;
|
|
10682
|
-
});
|
|
10683
|
-
res.on("end", () => {
|
|
10684
|
-
if (res.statusCode !== 200) {
|
|
10685
|
-
reject(new Error(`Failed to fetch file: HTTP ${res.statusCode}`));
|
|
10686
|
-
return;
|
|
10687
|
-
}
|
|
10688
|
-
resolve30(data);
|
|
10689
|
-
});
|
|
10690
|
-
});
|
|
10691
|
-
req.on("error", reject);
|
|
10692
|
-
req.on("timeout", () => {
|
|
10693
|
-
req.destroy();
|
|
10694
|
-
reject(new Error("Request timed out"));
|
|
10695
|
-
});
|
|
10696
|
-
});
|
|
10697
|
-
}
|
|
10698
|
-
async function pushFileToRemote(host, filePath, content, port = DEFAULT_SERVE_PORT, useHttps = false) {
|
|
10699
|
-
return new Promise((resolve30, reject) => {
|
|
10700
|
-
const protocol = useHttps ? https : http;
|
|
10701
|
-
const encodedPath = encodeURIComponent(filePath).replace(/%2F/g, "/");
|
|
10702
|
-
const url = new URL(`${useHttps ? "https" : "http"}://${host}:${port}${CLAWVAULT_SERVE_PATH}/upload/${encodedPath}`);
|
|
10703
|
-
const options = {
|
|
10704
|
-
hostname: url.hostname,
|
|
10705
|
-
port: url.port,
|
|
10706
|
-
path: url.pathname,
|
|
10707
|
-
method: "POST",
|
|
10708
|
-
headers: {
|
|
10709
|
-
"Content-Type": "text/markdown",
|
|
10710
|
-
"Content-Length": Buffer.byteLength(content)
|
|
10711
|
-
},
|
|
10712
|
-
timeout: 3e4
|
|
10713
|
-
};
|
|
10714
|
-
const req = protocol.request(options, (res) => {
|
|
10715
|
-
let data = "";
|
|
10716
|
-
res.on("data", (chunk) => {
|
|
10717
|
-
data += chunk;
|
|
10718
|
-
});
|
|
10719
|
-
res.on("end", () => {
|
|
10720
|
-
if (res.statusCode !== 200) {
|
|
10721
|
-
reject(new Error(`Failed to push file: HTTP ${res.statusCode}`));
|
|
10722
|
-
return;
|
|
10723
|
-
}
|
|
10724
|
-
resolve30();
|
|
10725
|
-
});
|
|
10726
|
-
});
|
|
10727
|
-
req.on("error", reject);
|
|
10728
|
-
req.on("timeout", () => {
|
|
10729
|
-
req.destroy();
|
|
10730
|
-
reject(new Error("Request timed out"));
|
|
10731
|
-
});
|
|
10732
|
-
req.write(content);
|
|
10733
|
-
req.end();
|
|
10734
|
-
});
|
|
10735
|
-
}
|
|
10736
|
-
async function syncWithPeer(vaultPath, options) {
|
|
10737
|
-
const startTime = Date.now();
|
|
10738
|
-
const result = {
|
|
10739
|
-
pushed: [],
|
|
10740
|
-
pulled: [],
|
|
10741
|
-
deleted: [],
|
|
10742
|
-
unchanged: [],
|
|
10743
|
-
errors: [],
|
|
10744
|
-
stats: {
|
|
10745
|
-
bytesTransferred: 0,
|
|
10746
|
-
filesProcessed: 0,
|
|
10747
|
-
duration: 0
|
|
10748
|
-
}
|
|
10749
|
-
};
|
|
10750
|
-
const {
|
|
10751
|
-
peer,
|
|
10752
|
-
port = DEFAULT_SERVE_PORT,
|
|
10753
|
-
direction = "bidirectional",
|
|
10754
|
-
dryRun = false,
|
|
10755
|
-
deleteOrphans = false,
|
|
10756
|
-
categories,
|
|
10757
|
-
https: useHttps = false
|
|
10758
|
-
} = options;
|
|
10759
|
-
let host = peer;
|
|
10760
|
-
if (!peer.match(/^\d+\.\d+\.\d+\.\d+$/)) {
|
|
10761
|
-
const resolvedIP = resolvePeerIP(peer);
|
|
10762
|
-
if (!resolvedIP) {
|
|
10763
|
-
result.errors.push(`Could not resolve peer: ${peer}`);
|
|
10764
|
-
result.stats.duration = Date.now() - startTime;
|
|
10765
|
-
return result;
|
|
10766
|
-
}
|
|
10767
|
-
host = resolvedIP;
|
|
10768
|
-
}
|
|
10769
|
-
try {
|
|
10770
|
-
const localManifest = generateVaultManifest(vaultPath);
|
|
10771
|
-
const remoteManifest = await fetchRemoteManifest(host, port, useHttps);
|
|
10772
|
-
let { toPush, toPull, conflicts, unchanged } = compareManifests(localManifest, remoteManifest);
|
|
10773
|
-
if (categories && categories.length > 0) {
|
|
10774
|
-
const categorySet = new Set(categories);
|
|
10775
|
-
toPush = toPush.filter((f) => categorySet.has(f.category));
|
|
10776
|
-
toPull = toPull.filter((f) => categorySet.has(f.category));
|
|
10777
|
-
}
|
|
10778
|
-
result.unchanged = unchanged;
|
|
10779
|
-
for (const conflict of conflicts) {
|
|
10780
|
-
result.errors.push(`Conflict: ${conflict.path} (local and remote have same timestamp but different content)`);
|
|
10781
|
-
}
|
|
10782
|
-
if (direction === "push" || direction === "bidirectional") {
|
|
10783
|
-
for (const file of toPush) {
|
|
10784
|
-
try {
|
|
10785
|
-
if (!dryRun) {
|
|
10786
|
-
const content = fs25.readFileSync(path24.join(vaultPath, file.path), "utf-8");
|
|
10787
|
-
await pushFileToRemote(host, file.path, content, port, useHttps);
|
|
10788
|
-
result.stats.bytesTransferred += file.size;
|
|
10789
|
-
}
|
|
10790
|
-
result.pushed.push(file.path);
|
|
10791
|
-
result.stats.filesProcessed++;
|
|
10792
|
-
} catch (err) {
|
|
10793
|
-
result.errors.push(`Failed to push ${file.path}: ${err}`);
|
|
10794
|
-
}
|
|
10795
|
-
}
|
|
10796
|
-
}
|
|
10797
|
-
if (direction === "pull" || direction === "bidirectional") {
|
|
10798
|
-
for (const file of toPull) {
|
|
10799
|
-
try {
|
|
10800
|
-
if (!dryRun) {
|
|
10801
|
-
const content = await fetchRemoteFile(host, file.path, port, useHttps);
|
|
10802
|
-
const filePath = path24.join(vaultPath, file.path);
|
|
10803
|
-
const dir = path24.dirname(filePath);
|
|
10804
|
-
if (!fs25.existsSync(dir)) {
|
|
10805
|
-
fs25.mkdirSync(dir, { recursive: true });
|
|
10806
|
-
}
|
|
10807
|
-
fs25.writeFileSync(filePath, content, "utf-8");
|
|
10808
|
-
result.stats.bytesTransferred += file.size;
|
|
10809
|
-
}
|
|
10810
|
-
result.pulled.push(file.path);
|
|
10811
|
-
result.stats.filesProcessed++;
|
|
10812
|
-
} catch (err) {
|
|
10813
|
-
result.errors.push(`Failed to pull ${file.path}: ${err}`);
|
|
10814
|
-
}
|
|
10815
|
-
}
|
|
10816
|
-
}
|
|
10817
|
-
if (deleteOrphans && direction === "pull") {
|
|
10818
|
-
const remoteFiles = new Set(remoteManifest.files.map((f) => f.path));
|
|
10819
|
-
for (const file of localManifest.files) {
|
|
10820
|
-
if (!remoteFiles.has(file.path)) {
|
|
10821
|
-
if (!categories || categories.includes(file.category)) {
|
|
10822
|
-
try {
|
|
10823
|
-
if (!dryRun) {
|
|
10824
|
-
fs25.unlinkSync(path24.join(vaultPath, file.path));
|
|
10825
|
-
}
|
|
10826
|
-
result.deleted.push(file.path);
|
|
10827
|
-
} catch (err) {
|
|
10828
|
-
result.errors.push(`Failed to delete ${file.path}: ${err}`);
|
|
10829
|
-
}
|
|
10830
|
-
}
|
|
10831
|
-
}
|
|
10832
|
-
}
|
|
10833
|
-
}
|
|
10834
|
-
} catch (err) {
|
|
10835
|
-
result.errors.push(`Sync failed: ${err}`);
|
|
10836
|
-
}
|
|
10837
|
-
result.stats.duration = Date.now() - startTime;
|
|
10838
|
-
return result;
|
|
10839
|
-
}
|
|
10840
|
-
function configureTailscaleServe(localPort, options = {}) {
|
|
10841
|
-
if (!hasTailscale()) {
|
|
10842
|
-
return null;
|
|
10843
|
-
}
|
|
10844
|
-
const args = ["serve"];
|
|
10845
|
-
if (options.funnel) {
|
|
10846
|
-
args.push("--bg");
|
|
10847
|
-
args.push("funnel");
|
|
10848
|
-
} else if (options.background) {
|
|
10849
|
-
args.push("--bg");
|
|
10850
|
-
}
|
|
10851
|
-
args.push(`localhost:${localPort}`);
|
|
10852
|
-
const proc = (0, import_child_process4.spawn)("tailscale", args, {
|
|
10853
|
-
stdio: "inherit",
|
|
10854
|
-
detached: options.background
|
|
10855
|
-
});
|
|
10856
|
-
if (options.background) {
|
|
10857
|
-
proc.unref();
|
|
10858
|
-
}
|
|
10859
|
-
return proc;
|
|
10860
|
-
}
|
|
10861
|
-
function stopTailscaleServe() {
|
|
10862
|
-
if (!hasTailscale()) {
|
|
10863
|
-
return false;
|
|
10864
|
-
}
|
|
10865
|
-
const result = (0, import_child_process4.spawnSync)("tailscale", ["serve", "off"], {
|
|
10866
|
-
stdio: "pipe",
|
|
10867
|
-
encoding: "utf-8",
|
|
10868
|
-
timeout: 5e3
|
|
10869
|
-
});
|
|
10870
|
-
return result.status === 0;
|
|
10871
|
-
}
|
|
10872
|
-
async function checkPeerClawVault(host, port = DEFAULT_SERVE_PORT) {
|
|
10873
|
-
try {
|
|
10874
|
-
const response = await new Promise((resolve30) => {
|
|
10875
|
-
const req = http.get(
|
|
10876
|
-
`http://${host}:${port}${CLAWVAULT_SERVE_PATH}/health`,
|
|
10877
|
-
{ timeout: 5e3 },
|
|
10878
|
-
(res) => {
|
|
10879
|
-
resolve30(res.statusCode === 200);
|
|
10880
|
-
}
|
|
10881
|
-
);
|
|
10882
|
-
req.on("error", () => resolve30(false));
|
|
10883
|
-
req.on("timeout", () => {
|
|
10884
|
-
req.destroy();
|
|
10885
|
-
resolve30(false);
|
|
10886
|
-
});
|
|
10887
|
-
});
|
|
10888
|
-
return response;
|
|
10889
|
-
} catch {
|
|
10890
|
-
return false;
|
|
10891
|
-
}
|
|
10892
|
-
}
|
|
10893
|
-
async function discoverClawVaultPeers(port = DEFAULT_SERVE_PORT) {
|
|
10894
|
-
const status = getTailscaleStatus();
|
|
10895
|
-
if (!status.connected) {
|
|
10896
|
-
return [];
|
|
10897
|
-
}
|
|
10898
|
-
const clawvaultPeers = [];
|
|
10899
|
-
const checkPromises = status.peers.filter((p) => p.online).map(async (peer) => {
|
|
10900
|
-
const ip = peer.tailscaleIPs[0];
|
|
10901
|
-
if (!ip) return;
|
|
10902
|
-
const isServing = await checkPeerClawVault(ip, port);
|
|
10903
|
-
if (isServing) {
|
|
10904
|
-
peer.clawvaultServing = true;
|
|
10905
|
-
peer.clawvaultPort = port;
|
|
10906
|
-
clawvaultPeers.push(peer);
|
|
10907
|
-
}
|
|
10908
|
-
});
|
|
10909
|
-
await Promise.all(checkPromises);
|
|
10910
|
-
return clawvaultPeers;
|
|
10911
|
-
}
|
|
10912
|
-
|
|
10913
|
-
// src/commands/tailscale.ts
|
|
10914
|
-
async function tailscaleStatusCommand(options = {}) {
|
|
10915
|
-
const status = getTailscaleStatus();
|
|
10916
|
-
if (options.json) {
|
|
10917
|
-
console.log(JSON.stringify(status, null, 2));
|
|
10918
|
-
return status;
|
|
10919
|
-
}
|
|
10920
|
-
if (!status.installed) {
|
|
10921
|
-
console.log("Tailscale: Not installed");
|
|
10922
|
-
console.log(" Install from: https://tailscale.com/download");
|
|
10923
|
-
return status;
|
|
10924
|
-
}
|
|
10925
|
-
const version = getTailscaleVersion();
|
|
10926
|
-
console.log(`Tailscale: ${version || "installed"}`);
|
|
10927
|
-
if (!status.running) {
|
|
10928
|
-
console.log(" Status: Daemon not running");
|
|
10929
|
-
if (status.error) {
|
|
10930
|
-
console.log(` Error: ${status.error}`);
|
|
10931
|
-
}
|
|
10932
|
-
return status;
|
|
10933
|
-
}
|
|
10934
|
-
console.log(` Status: ${status.backendState}`);
|
|
10935
|
-
if (status.connected) {
|
|
10936
|
-
console.log(` Tailnet: ${status.tailnetName || "unknown"}`);
|
|
10937
|
-
console.log(` Self IP: ${status.selfIP || "unknown"}`);
|
|
10938
|
-
console.log(` Hostname: ${status.selfHostname || "unknown"}`);
|
|
10939
|
-
if (status.selfDNSName) {
|
|
10940
|
-
console.log(` DNS Name: ${status.selfDNSName}`);
|
|
10941
|
-
}
|
|
10942
|
-
if (options.peers || status.peers.length > 0) {
|
|
10943
|
-
const onlinePeers = status.peers.filter((p) => p.online);
|
|
10944
|
-
const offlinePeers = status.peers.filter((p) => !p.online);
|
|
10945
|
-
console.log(`
|
|
10946
|
-
Peers (${onlinePeers.length} online, ${offlinePeers.length} offline):`);
|
|
10947
|
-
for (const peer of onlinePeers) {
|
|
10948
|
-
const ip = peer.tailscaleIPs[0] || "no-ip";
|
|
10949
|
-
const os4 = peer.os ? ` (${peer.os})` : "";
|
|
10950
|
-
const clawvault = peer.clawvaultServing ? " [ClawVault]" : "";
|
|
10951
|
-
console.log(` \u25CF ${peer.hostname}${os4} - ${ip}${clawvault}`);
|
|
10952
|
-
}
|
|
10953
|
-
if (options.peers) {
|
|
10954
|
-
for (const peer of offlinePeers) {
|
|
10955
|
-
const ip = peer.tailscaleIPs[0] || "no-ip";
|
|
10956
|
-
const os4 = peer.os ? ` (${peer.os})` : "";
|
|
10957
|
-
console.log(` \u25CB ${peer.hostname}${os4} - ${ip} [offline]`);
|
|
10958
|
-
}
|
|
10959
|
-
}
|
|
10960
|
-
}
|
|
10961
|
-
} else {
|
|
10962
|
-
console.log(" Status: Not connected to tailnet");
|
|
10963
|
-
if (status.error) {
|
|
10964
|
-
console.log(` Error: ${status.error}`);
|
|
10965
|
-
}
|
|
10966
|
-
}
|
|
10967
|
-
return status;
|
|
10968
|
-
}
|
|
10969
|
-
function registerTailscaleStatusCommand(program) {
|
|
10970
|
-
program.command("tailscale-status").alias("ts-status").description("Show Tailscale connection status and peers").option("--json", "Output as JSON").option("--peers", "Show all peers including offline").action(async (rawOptions) => {
|
|
10971
|
-
await tailscaleStatusCommand({
|
|
10972
|
-
json: rawOptions.json,
|
|
10973
|
-
peers: rawOptions.peers
|
|
10974
|
-
});
|
|
10975
|
-
});
|
|
10976
|
-
}
|
|
10977
|
-
async function tailscaleSyncCommand(options) {
|
|
10978
|
-
const vaultPath = resolveVaultPath({ explicitPath: options.vaultPath });
|
|
10979
|
-
const status = getTailscaleStatus();
|
|
10980
|
-
if (!status.installed) {
|
|
10981
|
-
const error = {
|
|
10982
|
-
pushed: [],
|
|
10983
|
-
pulled: [],
|
|
10984
|
-
deleted: [],
|
|
10985
|
-
unchanged: [],
|
|
10986
|
-
errors: ["Tailscale not installed. Install from https://tailscale.com/download"],
|
|
10987
|
-
stats: { bytesTransferred: 0, filesProcessed: 0, duration: 0 }
|
|
10988
|
-
};
|
|
10989
|
-
if (options.json) {
|
|
10990
|
-
console.log(JSON.stringify(error, null, 2));
|
|
10991
|
-
} else {
|
|
10992
|
-
console.error("Error: Tailscale not installed");
|
|
10993
|
-
}
|
|
10994
|
-
return error;
|
|
10995
|
-
}
|
|
10996
|
-
if (!status.connected) {
|
|
10997
|
-
const error = {
|
|
10998
|
-
pushed: [],
|
|
10999
|
-
pulled: [],
|
|
11000
|
-
deleted: [],
|
|
11001
|
-
unchanged: [],
|
|
11002
|
-
errors: ["Not connected to Tailscale. Run `tailscale up` to connect."],
|
|
11003
|
-
stats: { bytesTransferred: 0, filesProcessed: 0, duration: 0 }
|
|
11004
|
-
};
|
|
11005
|
-
if (options.json) {
|
|
11006
|
-
console.log(JSON.stringify(error, null, 2));
|
|
11007
|
-
} else {
|
|
11008
|
-
console.error("Error: Not connected to Tailscale");
|
|
11009
|
-
}
|
|
11010
|
-
return error;
|
|
11011
|
-
}
|
|
11012
|
-
const peer = findPeer(options.peer);
|
|
11013
|
-
if (!peer) {
|
|
11014
|
-
const error = {
|
|
11015
|
-
pushed: [],
|
|
11016
|
-
pulled: [],
|
|
11017
|
-
deleted: [],
|
|
11018
|
-
unchanged: [],
|
|
11019
|
-
errors: [`Peer not found: ${options.peer}`],
|
|
11020
|
-
stats: { bytesTransferred: 0, filesProcessed: 0, duration: 0 }
|
|
11021
|
-
};
|
|
11022
|
-
if (options.json) {
|
|
11023
|
-
console.log(JSON.stringify(error, null, 2));
|
|
11024
|
-
} else {
|
|
11025
|
-
console.error(`Error: Peer not found: ${options.peer}`);
|
|
11026
|
-
console.log("\nAvailable online peers:");
|
|
11027
|
-
for (const p of getOnlinePeers()) {
|
|
11028
|
-
console.log(` - ${p.hostname} (${p.tailscaleIPs[0]})`);
|
|
11029
|
-
}
|
|
11030
|
-
}
|
|
11031
|
-
return error;
|
|
11032
|
-
}
|
|
11033
|
-
if (!peer.online) {
|
|
11034
|
-
const error = {
|
|
11035
|
-
pushed: [],
|
|
11036
|
-
pulled: [],
|
|
11037
|
-
deleted: [],
|
|
11038
|
-
unchanged: [],
|
|
11039
|
-
errors: [`Peer is offline: ${peer.hostname}`],
|
|
11040
|
-
stats: { bytesTransferred: 0, filesProcessed: 0, duration: 0 }
|
|
11041
|
-
};
|
|
11042
|
-
if (options.json) {
|
|
11043
|
-
console.log(JSON.stringify(error, null, 2));
|
|
11044
|
-
} else {
|
|
11045
|
-
console.error(`Error: Peer is offline: ${peer.hostname}`);
|
|
11046
|
-
}
|
|
11047
|
-
return error;
|
|
11048
|
-
}
|
|
11049
|
-
const syncOptions = {
|
|
11050
|
-
peer: peer.tailscaleIPs[0],
|
|
11051
|
-
port: options.port || DEFAULT_SERVE_PORT,
|
|
11052
|
-
direction: options.direction || "bidirectional",
|
|
11053
|
-
dryRun: options.dryRun,
|
|
11054
|
-
deleteOrphans: options.deleteOrphans,
|
|
11055
|
-
categories: options.categories,
|
|
11056
|
-
https: options.https
|
|
11057
|
-
};
|
|
11058
|
-
if (!options.json && !options.dryRun) {
|
|
11059
|
-
console.log(`Syncing with ${peer.hostname} (${peer.tailscaleIPs[0]})...`);
|
|
11060
|
-
}
|
|
11061
|
-
const result = await syncWithPeer(vaultPath, syncOptions);
|
|
11062
|
-
if (options.json) {
|
|
11063
|
-
console.log(JSON.stringify(result, null, 2));
|
|
11064
|
-
} else {
|
|
11065
|
-
const prefix = options.dryRun ? "[dry-run] " : "";
|
|
11066
|
-
if (result.pushed.length > 0) {
|
|
11067
|
-
console.log(`
|
|
11068
|
-
${prefix}Pushed ${result.pushed.length} file(s):`);
|
|
11069
|
-
for (const file of result.pushed.slice(0, 10)) {
|
|
11070
|
-
console.log(` \u2192 ${file}`);
|
|
11071
|
-
}
|
|
11072
|
-
if (result.pushed.length > 10) {
|
|
11073
|
-
console.log(` ... and ${result.pushed.length - 10} more`);
|
|
11074
|
-
}
|
|
11075
|
-
}
|
|
11076
|
-
if (result.pulled.length > 0) {
|
|
11077
|
-
console.log(`
|
|
11078
|
-
${prefix}Pulled ${result.pulled.length} file(s):`);
|
|
11079
|
-
for (const file of result.pulled.slice(0, 10)) {
|
|
11080
|
-
console.log(` \u2190 ${file}`);
|
|
11081
|
-
}
|
|
11082
|
-
if (result.pulled.length > 10) {
|
|
11083
|
-
console.log(` ... and ${result.pulled.length - 10} more`);
|
|
11084
|
-
}
|
|
11085
|
-
}
|
|
11086
|
-
if (result.deleted.length > 0) {
|
|
11087
|
-
console.log(`
|
|
11088
|
-
${prefix}Deleted ${result.deleted.length} file(s):`);
|
|
11089
|
-
for (const file of result.deleted.slice(0, 10)) {
|
|
11090
|
-
console.log(` \u2717 ${file}`);
|
|
11091
|
-
}
|
|
11092
|
-
if (result.deleted.length > 10) {
|
|
11093
|
-
console.log(` ... and ${result.deleted.length - 10} more`);
|
|
11094
|
-
}
|
|
11095
|
-
}
|
|
11096
|
-
if (result.errors.length > 0) {
|
|
11097
|
-
console.log(`
|
|
11098
|
-
Errors (${result.errors.length}):`);
|
|
11099
|
-
for (const error of result.errors) {
|
|
11100
|
-
console.log(` ! ${error}`);
|
|
11101
|
-
}
|
|
11102
|
-
}
|
|
11103
|
-
console.log(`
|
|
11104
|
-
Summary:`);
|
|
11105
|
-
console.log(` Pushed: ${result.pushed.length}`);
|
|
11106
|
-
console.log(` Pulled: ${result.pulled.length}`);
|
|
11107
|
-
console.log(` Deleted: ${result.deleted.length}`);
|
|
11108
|
-
console.log(` Unchanged: ${result.unchanged.length}`);
|
|
11109
|
-
console.log(` Errors: ${result.errors.length}`);
|
|
11110
|
-
console.log(` Duration: ${result.stats.duration}ms`);
|
|
11111
|
-
console.log(` Transferred: ${formatBytes2(result.stats.bytesTransferred)}`);
|
|
11112
|
-
}
|
|
11113
|
-
return result;
|
|
11114
|
-
}
|
|
11115
|
-
function formatBytes2(bytes) {
|
|
11116
|
-
if (bytes === 0) return "0 B";
|
|
11117
|
-
const k = 1024;
|
|
11118
|
-
const sizes = ["B", "KB", "MB", "GB"];
|
|
11119
|
-
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
11120
|
-
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;
|
|
11121
|
-
}
|
|
11122
|
-
function registerTailscaleSyncCommand(program) {
|
|
11123
|
-
program.command("tailscale-sync").alias("ts-sync").description("Sync vault with a peer on the Tailscale network").requiredOption("--peer <hostname>", "Peer hostname or IP to sync with").option("-v, --vault <path>", "Vault path").option("--port <number>", "Port on the peer", parseInt).option("--direction <dir>", "Sync direction: push, pull, or bidirectional", "bidirectional").option("--dry-run", "Show what would be synced without making changes").option("--delete-orphans", "Delete files that exist locally but not on peer (pull only)").option("--categories <list>", "Comma-separated list of categories to sync").option("--https", "Use HTTPS for connection").option("--json", "Output as JSON").action(async (rawOptions) => {
|
|
11124
|
-
await tailscaleSyncCommand({
|
|
11125
|
-
peer: rawOptions.peer,
|
|
11126
|
-
vaultPath: rawOptions.vault,
|
|
11127
|
-
port: rawOptions.port,
|
|
11128
|
-
direction: rawOptions.direction,
|
|
11129
|
-
dryRun: rawOptions.dryRun,
|
|
11130
|
-
deleteOrphans: rawOptions.deleteOrphans,
|
|
11131
|
-
categories: rawOptions.categories?.split(",").map((c) => c.trim()),
|
|
11132
|
-
https: rawOptions.https,
|
|
11133
|
-
json: rawOptions.json
|
|
11134
|
-
});
|
|
11135
|
-
});
|
|
11136
|
-
}
|
|
11137
|
-
var activeServeInstance = null;
|
|
11138
|
-
async function tailscaleServeCommand(options) {
|
|
11139
|
-
if (options.stop) {
|
|
11140
|
-
if (activeServeInstance) {
|
|
11141
|
-
await activeServeInstance.stop();
|
|
11142
|
-
activeServeInstance = null;
|
|
11143
|
-
console.log("ClawVault serve stopped.");
|
|
11144
|
-
}
|
|
11145
|
-
stopTailscaleServe();
|
|
11146
|
-
return;
|
|
11147
|
-
}
|
|
11148
|
-
const vaultPath = resolveVaultPath({ explicitPath: options.vaultPath });
|
|
11149
|
-
const port = options.port || DEFAULT_SERVE_PORT;
|
|
11150
|
-
const status = getTailscaleStatus();
|
|
11151
|
-
console.log(`Starting ClawVault serve...`);
|
|
11152
|
-
console.log(` Vault: ${path25.basename(vaultPath)}`);
|
|
11153
|
-
console.log(` Port: ${port}`);
|
|
11154
|
-
activeServeInstance = serveVault(vaultPath, { port });
|
|
11155
|
-
console.log(` Local URL: http://localhost:${port}/.clawvault`);
|
|
11156
|
-
if (status.connected) {
|
|
11157
|
-
console.log(` Tailscale URL: http://${status.selfIP}:${port}/.clawvault`);
|
|
11158
|
-
if (status.selfDNSName) {
|
|
11159
|
-
const dnsHost = status.selfDNSName.replace(/\.$/, "");
|
|
11160
|
-
console.log(` MagicDNS URL: http://${dnsHost}:${port}/.clawvault`);
|
|
11161
|
-
}
|
|
11162
|
-
if (options.funnel || options.background) {
|
|
11163
|
-
console.log("\nConfiguring Tailscale serve...");
|
|
11164
|
-
configureTailscaleServe(port, {
|
|
11165
|
-
funnel: options.funnel,
|
|
11166
|
-
background: options.background
|
|
11167
|
-
});
|
|
11168
|
-
if (options.funnel) {
|
|
11169
|
-
console.log(" Funnel enabled - vault is accessible from the public internet");
|
|
11170
|
-
}
|
|
11171
|
-
}
|
|
11172
|
-
} else {
|
|
11173
|
-
console.log("\n Note: Not connected to Tailscale. Only local access available.");
|
|
11174
|
-
}
|
|
11175
|
-
console.log("\nEndpoints:");
|
|
11176
|
-
console.log(` Health: /.clawvault/health`);
|
|
11177
|
-
console.log(` Manifest: /.clawvault/manifest`);
|
|
11178
|
-
console.log(` Files: /.clawvault/files/<path>`);
|
|
11179
|
-
if (!options.background) {
|
|
11180
|
-
console.log("\nPress Ctrl+C to stop serving.");
|
|
11181
|
-
process.on("SIGINT", async () => {
|
|
11182
|
-
console.log("\nStopping ClawVault serve...");
|
|
11183
|
-
if (activeServeInstance) {
|
|
11184
|
-
await activeServeInstance.stop();
|
|
11185
|
-
activeServeInstance = null;
|
|
11186
|
-
}
|
|
11187
|
-
stopTailscaleServe();
|
|
11188
|
-
process.exit(0);
|
|
11189
|
-
});
|
|
11190
|
-
await new Promise(() => {
|
|
11191
|
-
});
|
|
11192
|
-
}
|
|
11193
|
-
}
|
|
11194
|
-
function registerTailscaleServeCommand(program) {
|
|
11195
|
-
program.command("tailscale-serve").alias("ts-serve").description("Serve vault for sync over Tailscale").option("-v, --vault <path>", "Vault path").option("--port <number>", `Port to serve on (default: ${DEFAULT_SERVE_PORT})`, parseInt).option("--funnel", "Expose via Tailscale Funnel (public internet)").option("--background", "Run in background").option("--stop", "Stop serving").action(async (rawOptions) => {
|
|
11196
|
-
await tailscaleServeCommand({
|
|
11197
|
-
vaultPath: rawOptions.vault,
|
|
11198
|
-
port: rawOptions.port,
|
|
11199
|
-
funnel: rawOptions.funnel,
|
|
11200
|
-
background: rawOptions.background,
|
|
11201
|
-
stop: rawOptions.stop
|
|
11202
|
-
});
|
|
11203
|
-
});
|
|
11204
|
-
}
|
|
11205
|
-
async function tailscaleDiscoverCommand(options = {}) {
|
|
11206
|
-
const port = options.port || DEFAULT_SERVE_PORT;
|
|
11207
|
-
const status = getTailscaleStatus();
|
|
11208
|
-
if (!status.connected) {
|
|
11209
|
-
if (options.json) {
|
|
11210
|
-
console.log(JSON.stringify({ error: "Not connected to Tailscale", peers: [] }));
|
|
11211
|
-
} else {
|
|
11212
|
-
console.error("Error: Not connected to Tailscale");
|
|
11213
|
-
}
|
|
11214
|
-
return [];
|
|
11215
|
-
}
|
|
11216
|
-
if (!options.json) {
|
|
11217
|
-
console.log("Discovering ClawVault peers on tailnet...");
|
|
11218
|
-
}
|
|
11219
|
-
const peers = await discoverClawVaultPeers(port);
|
|
11220
|
-
if (options.json) {
|
|
11221
|
-
console.log(JSON.stringify({ peers }, null, 2));
|
|
11222
|
-
} else {
|
|
11223
|
-
if (peers.length === 0) {
|
|
11224
|
-
console.log("\nNo ClawVault peers found.");
|
|
11225
|
-
console.log(" Run `clawvault tailscale-serve` on other devices to enable sync.");
|
|
11226
|
-
} else {
|
|
11227
|
-
console.log(`
|
|
11228
|
-
Found ${peers.length} ClawVault peer(s):`);
|
|
11229
|
-
for (const peer of peers) {
|
|
11230
|
-
const ip = peer.tailscaleIPs[0] || "no-ip";
|
|
11231
|
-
const os4 = peer.os ? ` (${peer.os})` : "";
|
|
11232
|
-
console.log(` \u25CF ${peer.hostname}${os4}`);
|
|
11233
|
-
console.log(` IP: ${ip}`);
|
|
11234
|
-
console.log(` Port: ${peer.clawvaultPort}`);
|
|
11235
|
-
if (peer.dnsName) {
|
|
11236
|
-
console.log(` DNS: ${peer.dnsName.replace(/\.$/, "")}`);
|
|
11237
|
-
}
|
|
11238
|
-
}
|
|
11239
|
-
}
|
|
11240
|
-
}
|
|
11241
|
-
return peers;
|
|
11242
|
-
}
|
|
11243
|
-
function registerTailscaleDiscoverCommand(program) {
|
|
11244
|
-
program.command("tailscale-discover").alias("ts-discover").description("Discover ClawVault peers on the Tailscale network").option("--port <number>", `Port to check (default: ${DEFAULT_SERVE_PORT})`, parseInt).option("--json", "Output as JSON").action(async (rawOptions) => {
|
|
11245
|
-
await tailscaleDiscoverCommand({
|
|
11246
|
-
port: rawOptions.port,
|
|
11247
|
-
json: rawOptions.json
|
|
11248
|
-
});
|
|
11249
|
-
});
|
|
11250
|
-
}
|
|
11251
|
-
function registerTailscaleCommands(program) {
|
|
11252
|
-
registerTailscaleStatusCommand(program);
|
|
11253
|
-
registerTailscaleSyncCommand(program);
|
|
11254
|
-
registerTailscaleServeCommand(program);
|
|
11255
|
-
registerTailscaleDiscoverCommand(program);
|
|
11256
|
-
}
|
|
11257
|
-
|
|
11258
|
-
// src/cli/index.ts
|
|
11259
|
-
function registerCliCommands(program) {
|
|
11260
|
-
registerContextCommand(program);
|
|
11261
|
-
registerInjectCommand(program);
|
|
11262
|
-
registerObserveCommand(program);
|
|
11263
|
-
registerReflectCommand(program);
|
|
11264
|
-
registerEmbedCommand(program);
|
|
11265
|
-
registerReweaveCommand(program);
|
|
11266
|
-
registerTailscaleCommands(program);
|
|
11267
|
-
return program;
|
|
11268
|
-
}
|
|
11269
|
-
|
|
11270
|
-
// src/commands/setup.ts
|
|
11271
|
-
var fs26 = __toESM(require("fs"), 1);
|
|
11272
|
-
var os2 = __toESM(require("os"), 1);
|
|
11273
|
-
var path26 = __toESM(require("path"), 1);
|
|
11274
|
-
var import_child_process5 = require("child_process");
|
|
11275
|
-
var import_gray_matter7 = __toESM(require("gray-matter"), 1);
|
|
11276
|
-
var CONFIG_FILE4 = ".clawvault.json";
|
|
11277
|
-
function resolveVaultTarget(vaultOverride) {
|
|
11278
|
-
if (vaultOverride) {
|
|
11279
|
-
const vaultPath = path26.resolve(vaultOverride);
|
|
11280
|
-
return { vaultPath, source: "--vault flag", existed: fs26.existsSync(vaultPath) };
|
|
11281
|
-
}
|
|
11282
|
-
const envPath = process.env.CLAWVAULT_PATH?.trim();
|
|
11283
|
-
const home = os2.homedir();
|
|
11284
|
-
if (envPath) {
|
|
11285
|
-
const vaultPath = path26.resolve(envPath);
|
|
11286
|
-
return { vaultPath, source: "CLAWVAULT_PATH", existed: fs26.existsSync(vaultPath) };
|
|
11287
|
-
}
|
|
11288
|
-
const candidates = [
|
|
11289
|
-
{ vaultPath: path26.join(home, ".openclaw", "workspace", "memory"), source: "OpenClaw default" },
|
|
11290
|
-
{ vaultPath: path26.resolve(process.cwd(), "memory"), source: "./memory" },
|
|
11291
|
-
{ vaultPath: path26.join(home, "memory"), source: "~/memory" }
|
|
11292
|
-
];
|
|
11293
|
-
for (const candidate of candidates) {
|
|
11294
|
-
if (fs26.existsSync(candidate.vaultPath)) {
|
|
11295
|
-
return { ...candidate, existed: true };
|
|
11296
|
-
}
|
|
11297
|
-
}
|
|
11298
|
-
const fallback = candidates[0];
|
|
11299
|
-
return { ...fallback, existed: false };
|
|
11300
|
-
}
|
|
11301
|
-
function ensureVaultStructure(vaultPath) {
|
|
11302
|
-
fs26.mkdirSync(vaultPath, { recursive: true });
|
|
11303
|
-
for (const category of DEFAULT_CATEGORIES) {
|
|
11304
|
-
fs26.mkdirSync(path26.join(vaultPath, category), { recursive: true });
|
|
11305
|
-
}
|
|
11306
|
-
const configPath = path26.join(vaultPath, CONFIG_FILE4);
|
|
11307
|
-
if (fs26.existsSync(configPath)) return false;
|
|
11308
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
11309
|
-
const name = path26.basename(vaultPath);
|
|
11310
|
-
const meta = {
|
|
11311
|
-
name,
|
|
11312
|
-
version: "1.0.0",
|
|
11313
|
-
created: now,
|
|
11314
|
-
lastUpdated: now,
|
|
11315
|
-
categories: DEFAULT_CATEGORIES,
|
|
11316
|
-
documentCount: 0,
|
|
11317
|
-
qmdCollection: name,
|
|
11318
|
-
qmdRoot: vaultPath
|
|
11319
|
-
};
|
|
11320
|
-
fs26.writeFileSync(configPath, JSON.stringify(meta, null, 2));
|
|
11321
|
-
return true;
|
|
11322
|
-
}
|
|
11323
|
-
function writeBases(vaultPath, force) {
|
|
11324
|
-
const basesFiles = {
|
|
11325
|
-
"all-tasks.base": `filters:
|
|
11326
|
-
and:
|
|
11327
|
-
- file.inFolder("tasks")
|
|
11328
|
-
- status != "done"
|
|
11329
|
-
formulas:
|
|
11330
|
-
age: (now() - file.ctime).days
|
|
11331
|
-
status_icon: if(status == "blocked", "\u{1F534}", if(status == "in-progress", "\u{1F528}", if(status == "open", "\u26AA", "\u2705")))
|
|
11332
|
-
views:
|
|
11333
|
-
- type: table
|
|
11334
|
-
name: All Active Tasks
|
|
11335
|
-
groupBy:
|
|
11336
|
-
property: status
|
|
11337
|
-
direction: ASC
|
|
11338
|
-
order:
|
|
11339
|
-
- formula.status_icon
|
|
11340
|
-
- file.name
|
|
11341
|
-
- status
|
|
11342
|
-
- owner
|
|
11343
|
-
- project
|
|
11344
|
-
- priority
|
|
11345
|
-
- blocked_by
|
|
11346
|
-
- formula.age
|
|
11347
|
-
- type: cards
|
|
11348
|
-
name: Task Board
|
|
11349
|
-
groupBy:
|
|
11350
|
-
property: status
|
|
11351
|
-
direction: ASC
|
|
11352
|
-
order:
|
|
11353
|
-
- file.name
|
|
11354
|
-
- owner
|
|
11355
|
-
- project
|
|
11356
|
-
- priority`,
|
|
11357
|
-
"blocked.base": `filters:
|
|
11358
|
-
and:
|
|
11359
|
-
- file.inFolder("tasks")
|
|
11360
|
-
- status == "blocked"
|
|
11361
|
-
formulas:
|
|
11362
|
-
days_blocked: (now() - file.ctime).days
|
|
11363
|
-
views:
|
|
11364
|
-
- type: table
|
|
11365
|
-
name: Blocked Tasks
|
|
11366
|
-
order:
|
|
11367
|
-
- file.name
|
|
11368
|
-
- owner
|
|
11369
|
-
- project
|
|
11370
|
-
- blocked_by
|
|
11371
|
-
- formula.days_blocked
|
|
11372
|
-
- priority`,
|
|
11373
|
-
"by-project.base": `filters:
|
|
11374
|
-
and:
|
|
11375
|
-
- file.inFolder("tasks")
|
|
11376
|
-
- status != "done"
|
|
11377
|
-
formulas:
|
|
11378
|
-
status_icon: if(status == "blocked", "\u{1F534}", if(status == "in-progress", "\u{1F528}", "\u26AA"))
|
|
11379
|
-
views:
|
|
11380
|
-
- type: table
|
|
11381
|
-
name: By Project
|
|
11382
|
-
groupBy:
|
|
11383
|
-
property: project
|
|
11384
|
-
direction: ASC
|
|
11385
|
-
order:
|
|
11386
|
-
- formula.status_icon
|
|
11387
|
-
- file.name
|
|
11388
|
-
- status
|
|
11389
|
-
- owner
|
|
11390
|
-
- priority
|
|
11391
|
-
- type: cards
|
|
11392
|
-
name: Project Cards
|
|
11393
|
-
groupBy:
|
|
11394
|
-
property: project
|
|
11395
|
-
direction: ASC
|
|
11396
|
-
order:
|
|
11397
|
-
- file.name
|
|
11398
|
-
- owner
|
|
11399
|
-
- status`,
|
|
11400
|
-
"by-owner.base": `filters:
|
|
11401
|
-
and:
|
|
11402
|
-
- file.inFolder("tasks")
|
|
11403
|
-
- status != "done"
|
|
11404
|
-
views:
|
|
11405
|
-
- type: table
|
|
11406
|
-
name: By Owner
|
|
11407
|
-
groupBy:
|
|
11408
|
-
property: owner
|
|
11409
|
-
direction: ASC
|
|
11410
|
-
order:
|
|
11411
|
-
- file.name
|
|
11412
|
-
- status
|
|
11413
|
-
- project
|
|
11414
|
-
- priority`,
|
|
11415
|
-
"backlog.base": `filters:
|
|
11416
|
-
and:
|
|
11417
|
-
- file.inFolder("backlog")
|
|
11418
|
-
views:
|
|
11419
|
-
- type: table
|
|
11420
|
-
name: Backlog
|
|
11421
|
-
order:
|
|
11422
|
-
- file.name
|
|
11423
|
-
- source
|
|
11424
|
-
- project
|
|
11425
|
-
- file.ctime`
|
|
11426
|
-
};
|
|
11427
|
-
let written = 0;
|
|
11428
|
-
for (const [filename, content] of Object.entries(basesFiles)) {
|
|
11429
|
-
const filePath = path26.join(vaultPath, filename);
|
|
11430
|
-
if (force || !fs26.existsSync(filePath)) {
|
|
11431
|
-
fs26.writeFileSync(filePath, content);
|
|
11432
|
-
written++;
|
|
11433
|
-
}
|
|
11434
|
-
}
|
|
11435
|
-
return written;
|
|
11436
|
-
}
|
|
11437
|
-
var NEURAL_GRAPH_CSS = `/* ClawVault Graph Colors \u2014 Neural Neural Style */
|
|
11438
|
-
/* Auto-generated by \`clawvault setup --theme neural\` */
|
|
11439
|
-
|
|
11440
|
-
body.theme-dark .graph-view .graph-view-container { background-color: #0a0a0a; }
|
|
11441
|
-
|
|
11442
|
-
body.theme-dark .graph-view .node.tag-person circle { fill: #00b4d8 !important; }
|
|
11443
|
-
body.theme-dark .graph-view .node.tag-project circle { fill: #2d6a4f !important; }
|
|
11444
|
-
body.theme-dark .graph-view .node.tag-decision circle { fill: #e8590c !important; }
|
|
11445
|
-
body.theme-dark .graph-view .node.tag-lesson circle { fill: #fcc419 !important; }
|
|
11446
|
-
body.theme-dark .graph-view .node.tag-commitment circle { fill: #e03131 !important; }
|
|
11447
|
-
body.theme-dark .graph-view .node.tag-task circle { fill: #22b8cf !important; }
|
|
11448
|
-
body.theme-dark .graph-view .node.tag-observation circle { fill: #7950f2 !important; }
|
|
11449
|
-
body.theme-dark .graph-view .node.tag-handoff circle { fill: #845ef7 !important; }
|
|
11450
|
-
body.theme-dark .graph-view .node.tag-daily circle { fill: #495057 !important; }
|
|
11451
|
-
|
|
11452
|
-
body.theme-dark .graph-view .node.is-focused circle {
|
|
11453
|
-
fill: #e8a430 !important; stroke: #e8a430 !important;
|
|
11454
|
-
stroke-width: 3px; filter: drop-shadow(0 0 6px #e8a430);
|
|
11455
|
-
}
|
|
11456
|
-
|
|
11457
|
-
body.theme-dark .graph-view .link { stroke: rgba(45, 200, 120, 0.15) !important; }
|
|
11458
|
-
body.theme-dark .graph-view .link.is-focused { stroke: rgba(45, 200, 120, 0.6) !important; }
|
|
11459
|
-
body.theme-dark .graph-view .node text { fill: #c1c2c5 !important; font-size: 0.8em; }
|
|
11460
|
-
`;
|
|
11461
|
-
var MINIMAL_GRAPH_CSS = `/* ClawVault Graph Colors \u2014 Minimal */
|
|
11462
|
-
/* Auto-generated by \`clawvault setup --theme minimal\` */
|
|
11463
|
-
|
|
11464
|
-
body.theme-dark .graph-view .node.tag-person circle { fill: #4a90e8 !important; }
|
|
11465
|
-
body.theme-dark .graph-view .node.tag-project circle { fill: #4ae85d !important; }
|
|
11466
|
-
body.theme-dark .graph-view .node.tag-decision circle { fill: #e85d4a !important; }
|
|
11467
|
-
body.theme-dark .graph-view .node.tag-lesson circle { fill: #9b59b6 !important; }
|
|
11468
|
-
body.theme-dark .graph-view .node.tag-task circle { fill: #e8a430 !important; }
|
|
11469
|
-
`;
|
|
11470
|
-
var NEURAL_COLOR_GROUPS = [
|
|
11471
|
-
{ query: "path:people", color: { a: 1, rgb: 47316 } },
|
|
11472
|
-
{ query: "path:projects", color: { a: 1, rgb: 2976335 } },
|
|
11473
|
-
{ query: "path:decisions", color: { a: 1, rgb: 15227916 } },
|
|
11474
|
-
{ query: "path:lessons", color: { a: 1, rgb: 16565273 } },
|
|
11475
|
-
{ query: "path:tasks", color: { a: 1, rgb: 2275535 } },
|
|
11476
|
-
{ query: "path:commitments", color: { a: 1, rgb: 14680369 } },
|
|
11477
|
-
{ query: "path:backlog", color: { a: 1, rgb: 9806262 } },
|
|
11478
|
-
{ query: "path:inbox", color: { a: 1, rgb: 15964178 } },
|
|
11479
|
-
{ query: "path:handoffs", color: { a: 1, rgb: 8675063 } },
|
|
11480
|
-
{ query: "path:ledger", color: { a: 1, rgb: 7950066 } }
|
|
11481
|
-
];
|
|
11482
|
-
var MINIMAL_COLOR_GROUPS = [
|
|
11483
|
-
{ query: "path:people", color: { a: 1, rgb: 4886760 } },
|
|
11484
|
-
{ query: "path:projects", color: { a: 1, rgb: 4909149 } },
|
|
11485
|
-
{ query: "path:decisions", color: { a: 1, rgb: 15228234 } },
|
|
11486
|
-
{ query: "path:lessons", color: { a: 1, rgb: 10181046 } },
|
|
11487
|
-
{ query: "path:tasks", color: { a: 1, rgb: 15246384 } }
|
|
11488
|
-
];
|
|
11489
|
-
function writeGraphColors(vaultPath, theme, force) {
|
|
11490
|
-
const obsidianDir = path26.join(vaultPath, ".obsidian");
|
|
11491
|
-
if (!fs26.existsSync(obsidianDir)) {
|
|
11492
|
-
return false;
|
|
11493
|
-
}
|
|
11494
|
-
const snippetsDir = path26.join(obsidianDir, "snippets");
|
|
11495
|
-
fs26.mkdirSync(snippetsDir, { recursive: true });
|
|
11496
|
-
const snippetName = "clawvault-graph";
|
|
11497
|
-
const snippetPath = path26.join(snippetsDir, `${snippetName}.css`);
|
|
11498
|
-
if (!force && fs26.existsSync(snippetPath)) {
|
|
11499
|
-
return false;
|
|
11500
|
-
}
|
|
11501
|
-
const css = theme === "neural" ? NEURAL_GRAPH_CSS : MINIMAL_GRAPH_CSS;
|
|
11502
|
-
fs26.writeFileSync(snippetPath, css);
|
|
11503
|
-
const appearancePath = path26.join(obsidianDir, "appearance.json");
|
|
11504
|
-
let appearance = {};
|
|
11505
|
-
if (fs26.existsSync(appearancePath)) {
|
|
11506
|
-
try {
|
|
11507
|
-
appearance = JSON.parse(fs26.readFileSync(appearancePath, "utf-8"));
|
|
11508
|
-
} catch {
|
|
11509
|
-
}
|
|
11510
|
-
}
|
|
11511
|
-
const snippets = appearance.enabledCssSnippets || [];
|
|
11512
|
-
if (!snippets.includes(snippetName)) {
|
|
11513
|
-
snippets.push(snippetName);
|
|
11514
|
-
appearance.enabledCssSnippets = snippets;
|
|
11515
|
-
fs26.writeFileSync(appearancePath, JSON.stringify(appearance, null, 2));
|
|
11516
|
-
}
|
|
11517
|
-
const graphPath = path26.join(obsidianDir, "graph.json");
|
|
11518
|
-
let graphConfig = {};
|
|
11519
|
-
if (fs26.existsSync(graphPath)) {
|
|
11520
|
-
try {
|
|
11521
|
-
graphConfig = JSON.parse(fs26.readFileSync(graphPath, "utf-8"));
|
|
11522
|
-
} catch {
|
|
11523
|
-
}
|
|
11524
|
-
}
|
|
11525
|
-
graphConfig.colorGroups = theme === "neural" ? NEURAL_COLOR_GROUPS : MINIMAL_COLOR_GROUPS;
|
|
11526
|
-
if (theme === "neural") {
|
|
11527
|
-
graphConfig.showTags = false;
|
|
11528
|
-
graphConfig.showAttachments = false;
|
|
11529
|
-
graphConfig.nodeSizeMultiplier = 1.2;
|
|
11530
|
-
graphConfig.lineSizeMultiplier = 0.8;
|
|
11531
|
-
graphConfig.textFadeMultiplier = 0;
|
|
11532
|
-
graphConfig.repelStrength = 10;
|
|
11533
|
-
graphConfig.linkDistance = 250;
|
|
11534
|
-
graphConfig.centerStrength = 0.5;
|
|
11535
|
-
}
|
|
11536
|
-
fs26.writeFileSync(graphPath, JSON.stringify(graphConfig, null, 2));
|
|
11537
|
-
return true;
|
|
11538
|
-
}
|
|
11539
|
-
function getQmdConfig(vaultPath) {
|
|
11540
|
-
const configPath = path26.join(vaultPath, CONFIG_FILE4);
|
|
11541
|
-
if (fs26.existsSync(configPath)) {
|
|
11542
|
-
try {
|
|
11543
|
-
const meta = JSON.parse(fs26.readFileSync(configPath, "utf-8"));
|
|
11544
|
-
return {
|
|
11545
|
-
collection: meta.qmdCollection || meta.name || path26.basename(vaultPath),
|
|
11546
|
-
root: meta.qmdRoot || vaultPath
|
|
11547
|
-
};
|
|
11548
|
-
} catch {
|
|
11549
|
-
return { collection: path26.basename(vaultPath), root: vaultPath };
|
|
11550
|
-
}
|
|
11551
|
-
}
|
|
11552
|
-
return { collection: path26.basename(vaultPath), root: vaultPath };
|
|
11553
|
-
}
|
|
11554
|
-
function slugify2(text) {
|
|
11555
|
-
return text.toLowerCase().replace(/[^\w\s-]/g, "").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "").trim();
|
|
11556
|
-
}
|
|
11557
|
-
function scanMarkdownFiles(dirPath) {
|
|
11558
|
-
const files = [];
|
|
11559
|
-
const resolvedPath = path26.resolve(dirPath);
|
|
11560
|
-
if (!fs26.existsSync(resolvedPath)) {
|
|
11561
|
-
return files;
|
|
11562
|
-
}
|
|
11563
|
-
const stat = fs26.statSync(resolvedPath);
|
|
11564
|
-
if (stat.isFile() && resolvedPath.endsWith(".md")) {
|
|
11565
|
-
return [resolvedPath];
|
|
11566
|
-
}
|
|
11567
|
-
if (!stat.isDirectory()) {
|
|
11568
|
-
return files;
|
|
11569
|
-
}
|
|
11570
|
-
const entries = fs26.readdirSync(resolvedPath, { withFileTypes: true });
|
|
11571
|
-
for (const entry of entries) {
|
|
11572
|
-
const fullPath = path26.join(resolvedPath, entry.name);
|
|
11573
|
-
if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
11574
|
-
files.push(fullPath);
|
|
11575
|
-
} else if (entry.isDirectory()) {
|
|
11576
|
-
files.push(...scanMarkdownFiles(fullPath));
|
|
11577
|
-
}
|
|
11578
|
-
}
|
|
11579
|
-
return files;
|
|
11580
|
-
}
|
|
11581
|
-
function extractPeople(content) {
|
|
11582
|
-
const people = [];
|
|
11583
|
-
const seenNames = /* @__PURE__ */ new Set();
|
|
11584
|
-
const emailPattern = /([A-Z][a-z]+(?:\s+[A-Z][a-z]+)+)\s*<([^>]+@[^>]+)>/g;
|
|
11585
|
-
let match;
|
|
11586
|
-
while ((match = emailPattern.exec(content)) !== null) {
|
|
11587
|
-
const name = match[1].trim();
|
|
11588
|
-
const email = match[2].trim();
|
|
11589
|
-
if (!seenNames.has(name.toLowerCase())) {
|
|
11590
|
-
seenNames.add(name.toLowerCase());
|
|
11591
|
-
people.push({ name, email });
|
|
11592
|
-
}
|
|
11593
|
-
}
|
|
11594
|
-
const rolePatterns = [
|
|
11595
|
-
/([A-Z][a-z]+(?:\s+[A-Z][a-z]+)+)\s*\(([^)]+)\)/g,
|
|
11596
|
-
/([A-Z][a-z]+(?:\s+[A-Z][a-z]+)+),\s*(CEO|CTO|CFO|COO|VP|Director|Manager|Engineer|Designer|Lead|Head of [A-Za-z]+)/gi
|
|
11597
|
-
];
|
|
11598
|
-
for (const pattern of rolePatterns) {
|
|
11599
|
-
while ((match = pattern.exec(content)) !== null) {
|
|
11600
|
-
const name = match[1].trim();
|
|
11601
|
-
const role = match[2].trim();
|
|
11602
|
-
if (!seenNames.has(name.toLowerCase())) {
|
|
11603
|
-
seenNames.add(name.toLowerCase());
|
|
11604
|
-
people.push({ name, role });
|
|
11605
|
-
}
|
|
11606
|
-
}
|
|
11607
|
-
}
|
|
11608
|
-
const contextPatterns = [
|
|
11609
|
-
/(?:contact|met with|spoke with|emailed|called|messaged|talked to|meeting with)\s+([A-Z][a-zA-Z]*(?:\s+[A-Z][a-zA-Z]*)+)/g,
|
|
11610
|
-
/([A-Z][a-zA-Z]*(?:\s+[A-Z][a-zA-Z]*)+)\s+(?:said|mentioned|suggested|recommended|asked|told me)/g
|
|
11611
|
-
];
|
|
11612
|
-
for (const pattern of contextPatterns) {
|
|
11613
|
-
while ((match = pattern.exec(content)) !== null) {
|
|
11614
|
-
const name = match[1].trim();
|
|
11615
|
-
if (!seenNames.has(name.toLowerCase()) && name.split(" ").length >= 2) {
|
|
11616
|
-
seenNames.add(name.toLowerCase());
|
|
11617
|
-
const lineStart = content.lastIndexOf("\n", match.index) + 1;
|
|
11618
|
-
const lineEnd = content.indexOf("\n", match.index + match[0].length);
|
|
11619
|
-
const context = content.slice(lineStart, lineEnd === -1 ? void 0 : lineEnd).trim();
|
|
11620
|
-
people.push({ name, context: context.slice(0, 200) });
|
|
11621
|
-
}
|
|
11622
|
-
}
|
|
11623
|
-
}
|
|
11624
|
-
return people;
|
|
11625
|
-
}
|
|
11626
|
-
function extractPreferences2(content) {
|
|
11627
|
-
const preferences = [];
|
|
11628
|
-
const seenPrefs = /* @__PURE__ */ new Set();
|
|
11629
|
-
const patterns = [
|
|
11630
|
-
// "prefers X" / "prefer X" / "I prefer X"
|
|
11631
|
-
/(?:I\s+)?prefer(?:s)?\s+(?:to\s+)?([^.,\n]+)/gi,
|
|
11632
|
-
// "likes X" / "like X"
|
|
11633
|
-
/(?:I\s+)?like(?:s)?\s+(?:to\s+)?([^.,\n]+)/gi,
|
|
11634
|
-
// "always use X"
|
|
11635
|
-
/always\s+(?:use|uses?)\s+([^.,\n]+)/gi,
|
|
11636
|
-
// "never use X"
|
|
11637
|
-
/never\s+(?:use|uses?)\s+([^.,\n]+)/gi,
|
|
11638
|
-
// "favorite X is Y"
|
|
11639
|
-
/(?:my\s+)?favorite\s+(\w+)\s+is\s+([^.,\n]+)/gi,
|
|
11640
|
-
// "prefer X over Y"
|
|
11641
|
-
/prefer(?:s)?\s+([^.,\n]+)\s+over\s+([^.,\n]+)/gi,
|
|
11642
|
-
// "use X instead of Y"
|
|
11643
|
-
/use(?:s)?\s+([^.,\n]+)\s+instead\s+of\s+([^.,\n]+)/gi
|
|
11644
|
-
];
|
|
11645
|
-
for (const pattern of patterns) {
|
|
11646
|
-
let match;
|
|
11647
|
-
while ((match = pattern.exec(content)) !== null) {
|
|
11648
|
-
const fullMatch = match[0].trim();
|
|
11649
|
-
const key = fullMatch.toLowerCase();
|
|
11650
|
-
if (!seenPrefs.has(key) && fullMatch.length > 5 && fullMatch.length < 200) {
|
|
11651
|
-
seenPrefs.add(key);
|
|
11652
|
-
const lineStart = content.lastIndexOf("\n", match.index) + 1;
|
|
11653
|
-
const lineEnd = content.indexOf("\n", match.index + match[0].length);
|
|
11654
|
-
const context = content.slice(lineStart, lineEnd === -1 ? void 0 : lineEnd).trim();
|
|
11655
|
-
let subject = "general";
|
|
11656
|
-
let preference = fullMatch;
|
|
11657
|
-
if (match[2]) {
|
|
11658
|
-
subject = match[1].trim();
|
|
11659
|
-
preference = match[2].trim();
|
|
11660
|
-
} else if (match[1]) {
|
|
11661
|
-
preference = match[1].trim();
|
|
11662
|
-
}
|
|
11663
|
-
preferences.push({ subject, preference, context: context.slice(0, 200) });
|
|
11664
|
-
}
|
|
11665
|
-
}
|
|
11666
|
-
}
|
|
11667
|
-
return preferences;
|
|
11668
|
-
}
|
|
11669
|
-
function extractDecisions(content) {
|
|
11670
|
-
const decisions = [];
|
|
11671
|
-
const seenDecisions = /* @__PURE__ */ new Set();
|
|
11672
|
-
const patterns = [
|
|
11673
|
-
// "decided to X"
|
|
11674
|
-
/(?:I\s+|we\s+)?decided\s+(?:to\s+)?([^.,\n]+)/gi,
|
|
11675
|
-
// "decision: X" or "Decision - X"
|
|
11676
|
-
/decision[:\s-]+([^.,\n]+)/gi,
|
|
11677
|
-
// "we chose X" / "chose to X"
|
|
11678
|
-
/(?:we\s+)?chose\s+(?:to\s+)?([^.,\n]+)/gi,
|
|
11679
|
-
// "going with X"
|
|
11680
|
-
/going\s+with\s+([^.,\n]+)/gi,
|
|
11681
|
-
// "settled on X"
|
|
11682
|
-
/settled\s+on\s+([^.,\n]+)/gi,
|
|
11683
|
-
// "will use X" / "using X going forward"
|
|
11684
|
-
/(?:will\s+use|using)\s+([^.,\n]+)\s+(?:going\s+forward|from\s+now\s+on)/gi
|
|
11685
|
-
];
|
|
11686
|
-
for (const pattern of patterns) {
|
|
11687
|
-
let match;
|
|
11688
|
-
while ((match = pattern.exec(content)) !== null) {
|
|
11689
|
-
const decision = match[1].trim();
|
|
11690
|
-
const key = decision.toLowerCase();
|
|
11691
|
-
if (!seenDecisions.has(key) && decision.length > 5 && decision.length < 200) {
|
|
11692
|
-
seenDecisions.add(key);
|
|
11693
|
-
const lineStart = content.lastIndexOf("\n", match.index) + 1;
|
|
11694
|
-
const lineEnd = content.indexOf("\n", match.index + match[0].length);
|
|
11695
|
-
const context = content.slice(lineStart, lineEnd === -1 ? void 0 : lineEnd).trim();
|
|
11696
|
-
const title = decision.length > 50 ? decision.slice(0, 50).trim() + "..." : decision;
|
|
11697
|
-
decisions.push({ title, decision, context: context.slice(0, 200) });
|
|
11698
|
-
}
|
|
10040
|
+
const graphPath = path23.join(obsidianDir, "graph.json");
|
|
10041
|
+
let graphConfig = {};
|
|
10042
|
+
if (fs24.existsSync(graphPath)) {
|
|
10043
|
+
try {
|
|
10044
|
+
graphConfig = JSON.parse(fs24.readFileSync(graphPath, "utf-8"));
|
|
10045
|
+
} catch {
|
|
11699
10046
|
}
|
|
11700
10047
|
}
|
|
11701
|
-
|
|
11702
|
-
|
|
11703
|
-
|
|
11704
|
-
|
|
11705
|
-
|
|
11706
|
-
|
|
11707
|
-
|
|
11708
|
-
|
|
11709
|
-
|
|
11710
|
-
|
|
11711
|
-
// "FIXME: task"
|
|
11712
|
-
/FIXME[:\s-]+(.+)/gi,
|
|
11713
|
-
// "need to X"
|
|
11714
|
-
/(?:I\s+|we\s+)?need\s+to\s+([^.,\n]+)/gi,
|
|
11715
|
-
// "should X" (but not "should have" which is past)
|
|
11716
|
-
/(?:I\s+|we\s+)?should\s+(?!have)([^.,\n]+)/gi,
|
|
11717
|
-
// "must X"
|
|
11718
|
-
/(?:I\s+|we\s+)?must\s+([^.,\n]+)/gi,
|
|
11719
|
-
// "remember to X"
|
|
11720
|
-
/remember\s+to\s+([^.,\n]+)/gi,
|
|
11721
|
-
// "don't forget to X"
|
|
11722
|
-
/don'?t\s+forget\s+to\s+([^.,\n]+)/gi
|
|
11723
|
-
];
|
|
11724
|
-
for (const pattern of patterns) {
|
|
11725
|
-
let match;
|
|
11726
|
-
while ((match = pattern.exec(content)) !== null) {
|
|
11727
|
-
const taskText = match[1].trim();
|
|
11728
|
-
const key = taskText.toLowerCase();
|
|
11729
|
-
if (!seenTasks.has(key) && taskText.length > 3 && taskText.length < 200) {
|
|
11730
|
-
seenTasks.add(key);
|
|
11731
|
-
let priority = "medium";
|
|
11732
|
-
const lowerTask = taskText.toLowerCase();
|
|
11733
|
-
if (lowerTask.includes("urgent") || lowerTask.includes("asap") || lowerTask.includes("critical")) {
|
|
11734
|
-
priority = "critical";
|
|
11735
|
-
} else if (lowerTask.includes("important") || lowerTask.includes("high priority")) {
|
|
11736
|
-
priority = "high";
|
|
11737
|
-
} else if (lowerTask.includes("low priority") || lowerTask.includes("eventually") || lowerTask.includes("someday")) {
|
|
11738
|
-
priority = "low";
|
|
11739
|
-
}
|
|
11740
|
-
tasks.push({ title: taskText, priority });
|
|
11741
|
-
}
|
|
11742
|
-
}
|
|
10048
|
+
graphConfig.colorGroups = theme === "neural" ? NEURAL_COLOR_GROUPS : MINIMAL_COLOR_GROUPS;
|
|
10049
|
+
if (theme === "neural") {
|
|
10050
|
+
graphConfig.showTags = false;
|
|
10051
|
+
graphConfig.showAttachments = false;
|
|
10052
|
+
graphConfig.nodeSizeMultiplier = 1.2;
|
|
10053
|
+
graphConfig.lineSizeMultiplier = 0.8;
|
|
10054
|
+
graphConfig.textFadeMultiplier = 0;
|
|
10055
|
+
graphConfig.repelStrength = 10;
|
|
10056
|
+
graphConfig.linkDistance = 250;
|
|
10057
|
+
graphConfig.centerStrength = 0.5;
|
|
11743
10058
|
}
|
|
11744
|
-
|
|
11745
|
-
|
|
11746
|
-
function extractFromContent(content) {
|
|
11747
|
-
return {
|
|
11748
|
-
people: extractPeople(content),
|
|
11749
|
-
preferences: extractPreferences2(content),
|
|
11750
|
-
decisions: extractDecisions(content),
|
|
11751
|
-
tasks: extractTasks(content)
|
|
11752
|
-
};
|
|
10059
|
+
fs24.writeFileSync(graphPath, JSON.stringify(graphConfig, null, 2));
|
|
10060
|
+
return true;
|
|
11753
10061
|
}
|
|
11754
|
-
function
|
|
11755
|
-
const
|
|
11756
|
-
|
|
11757
|
-
people: [],
|
|
11758
|
-
preferences: [],
|
|
11759
|
-
decisions: [],
|
|
11760
|
-
tasks: []
|
|
11761
|
-
};
|
|
11762
|
-
for (const file of files) {
|
|
10062
|
+
function getQmdConfig(vaultPath) {
|
|
10063
|
+
const configPath = path23.join(vaultPath, CONFIG_FILE4);
|
|
10064
|
+
if (fs24.existsSync(configPath)) {
|
|
11763
10065
|
try {
|
|
11764
|
-
const
|
|
11765
|
-
|
|
11766
|
-
|
|
11767
|
-
|
|
11768
|
-
|
|
11769
|
-
combined.tasks.push(...extracted.tasks);
|
|
10066
|
+
const meta = JSON.parse(fs24.readFileSync(configPath, "utf-8"));
|
|
10067
|
+
return {
|
|
10068
|
+
collection: meta.qmdCollection || meta.name || path23.basename(vaultPath),
|
|
10069
|
+
root: meta.qmdRoot || vaultPath
|
|
10070
|
+
};
|
|
11770
10071
|
} catch {
|
|
10072
|
+
return { collection: path23.basename(vaultPath), root: vaultPath };
|
|
11771
10073
|
}
|
|
11772
10074
|
}
|
|
11773
|
-
|
|
11774
|
-
combined.people = combined.people.filter((p) => {
|
|
11775
|
-
const key = p.name.toLowerCase();
|
|
11776
|
-
if (seenPeople.has(key)) return false;
|
|
11777
|
-
seenPeople.add(key);
|
|
11778
|
-
return true;
|
|
11779
|
-
});
|
|
11780
|
-
const seenPrefs = /* @__PURE__ */ new Set();
|
|
11781
|
-
combined.preferences = combined.preferences.filter((p) => {
|
|
11782
|
-
const key = `${p.subject}:${p.preference}`.toLowerCase();
|
|
11783
|
-
if (seenPrefs.has(key)) return false;
|
|
11784
|
-
seenPrefs.add(key);
|
|
11785
|
-
return true;
|
|
11786
|
-
});
|
|
11787
|
-
const seenDecisions = /* @__PURE__ */ new Set();
|
|
11788
|
-
combined.decisions = combined.decisions.filter((d) => {
|
|
11789
|
-
const key = d.decision.toLowerCase();
|
|
11790
|
-
if (seenDecisions.has(key)) return false;
|
|
11791
|
-
seenDecisions.add(key);
|
|
11792
|
-
return true;
|
|
11793
|
-
});
|
|
11794
|
-
const seenTasks = /* @__PURE__ */ new Set();
|
|
11795
|
-
combined.tasks = combined.tasks.filter((t) => {
|
|
11796
|
-
const key = t.title.toLowerCase();
|
|
11797
|
-
if (seenTasks.has(key)) return false;
|
|
11798
|
-
seenTasks.add(key);
|
|
11799
|
-
return true;
|
|
11800
|
-
});
|
|
11801
|
-
return combined;
|
|
11802
|
-
}
|
|
11803
|
-
function fileExistsWithSimilarName(dir, slug) {
|
|
11804
|
-
if (!fs26.existsSync(dir)) return false;
|
|
11805
|
-
const files = fs26.readdirSync(dir);
|
|
11806
|
-
const targetSlug = slug.toLowerCase();
|
|
11807
|
-
return files.some((f) => {
|
|
11808
|
-
const fileSlug = path26.basename(f, ".md").toLowerCase();
|
|
11809
|
-
return fileSlug === targetSlug || fileSlug.includes(targetSlug) || targetSlug.includes(fileSlug);
|
|
11810
|
-
});
|
|
11811
|
-
}
|
|
11812
|
-
function writePerson(vaultPath, person, force) {
|
|
11813
|
-
const peopleDir = path26.join(vaultPath, "people");
|
|
11814
|
-
fs26.mkdirSync(peopleDir, { recursive: true });
|
|
11815
|
-
const slug = slugify2(person.name);
|
|
11816
|
-
if (!slug) return null;
|
|
11817
|
-
const filePath = path26.join(peopleDir, `${slug}.md`);
|
|
11818
|
-
if (!force && (fs26.existsSync(filePath) || fileExistsWithSimilarName(peopleDir, slug))) {
|
|
11819
|
-
return null;
|
|
11820
|
-
}
|
|
11821
|
-
const template = loadTemplateDefinition("person");
|
|
11822
|
-
const now = /* @__PURE__ */ new Date();
|
|
11823
|
-
const date = now.toISOString().split("T")[0];
|
|
11824
|
-
let content;
|
|
11825
|
-
if (template) {
|
|
11826
|
-
const rendered = renderDocumentFromTemplate(template, {
|
|
11827
|
-
title: person.name,
|
|
11828
|
-
now,
|
|
11829
|
-
overrides: {
|
|
11830
|
-
relationship: person.role ? "colleague" : "contact"
|
|
11831
|
-
}
|
|
11832
|
-
});
|
|
11833
|
-
let body = rendered.content;
|
|
11834
|
-
if (person.context) {
|
|
11835
|
-
body = body.replace(/## Context\n-\s*/, `## Context
|
|
11836
|
-
- ${person.context}
|
|
11837
|
-
`);
|
|
11838
|
-
}
|
|
11839
|
-
if (person.email || person.role) {
|
|
11840
|
-
const details = [];
|
|
11841
|
-
if (person.email) details.push(`- Contact: ${person.email}`);
|
|
11842
|
-
if (person.role) details.push(`- Role: ${person.role}`);
|
|
11843
|
-
body = body.replace(/## Details\n- Contact:\n- Role:\n- Timezone:/, `## Details
|
|
11844
|
-
${details.join("\n")}
|
|
11845
|
-
- Timezone:`);
|
|
11846
|
-
}
|
|
11847
|
-
content = import_gray_matter7.default.stringify(body, rendered.frontmatter);
|
|
11848
|
-
} else {
|
|
11849
|
-
const frontmatter = {
|
|
11850
|
-
title: person.name,
|
|
11851
|
-
date,
|
|
11852
|
-
type: "person",
|
|
11853
|
-
relationship: person.role ? "colleague" : "contact"
|
|
11854
|
-
};
|
|
11855
|
-
let body = `# ${person.name}
|
|
11856
|
-
|
|
11857
|
-
## Context
|
|
11858
|
-
`;
|
|
11859
|
-
if (person.context) body += `- ${person.context}
|
|
11860
|
-
`;
|
|
11861
|
-
else body += "- \n";
|
|
11862
|
-
body += "\n## Details\n";
|
|
11863
|
-
if (person.email) body += `- Contact: ${person.email}
|
|
11864
|
-
`;
|
|
11865
|
-
else body += "- Contact:\n";
|
|
11866
|
-
if (person.role) body += `- Role: ${person.role}
|
|
11867
|
-
`;
|
|
11868
|
-
else body += "- Role:\n";
|
|
11869
|
-
body += "- Timezone:\n";
|
|
11870
|
-
body += `
|
|
11871
|
-
## History
|
|
11872
|
-
- ${date}: Added from memory import
|
|
11873
|
-
`;
|
|
11874
|
-
content = import_gray_matter7.default.stringify(body, frontmatter);
|
|
11875
|
-
}
|
|
11876
|
-
fs26.writeFileSync(filePath, content);
|
|
11877
|
-
return slug;
|
|
11878
|
-
}
|
|
11879
|
-
function writePreference(vaultPath, pref, force) {
|
|
11880
|
-
const prefsDir = path26.join(vaultPath, "preferences");
|
|
11881
|
-
fs26.mkdirSync(prefsDir, { recursive: true });
|
|
11882
|
-
const slug = slugify2(`${pref.subject}-${pref.preference}`.slice(0, 60));
|
|
11883
|
-
if (!slug) return null;
|
|
11884
|
-
const filePath = path26.join(prefsDir, `${slug}.md`);
|
|
11885
|
-
if (!force && (fs26.existsSync(filePath) || fileExistsWithSimilarName(prefsDir, slug))) {
|
|
11886
|
-
return null;
|
|
11887
|
-
}
|
|
11888
|
-
const now = /* @__PURE__ */ new Date();
|
|
11889
|
-
const date = now.toISOString().split("T")[0];
|
|
11890
|
-
const frontmatter = {
|
|
11891
|
-
title: `${pref.subject}: ${pref.preference}`.slice(0, 100),
|
|
11892
|
-
date,
|
|
11893
|
-
type: "preference",
|
|
11894
|
-
category: pref.subject
|
|
11895
|
-
};
|
|
11896
|
-
let body = `# ${frontmatter.title}
|
|
11897
|
-
|
|
11898
|
-
`;
|
|
11899
|
-
body += `## Preference
|
|
11900
|
-
- ${pref.preference}
|
|
11901
|
-
`;
|
|
11902
|
-
if (pref.context) {
|
|
11903
|
-
body += `
|
|
11904
|
-
## Context
|
|
11905
|
-
- ${pref.context}
|
|
11906
|
-
`;
|
|
11907
|
-
}
|
|
11908
|
-
body += `
|
|
11909
|
-
## Source
|
|
11910
|
-
- Imported from memory on ${date}
|
|
11911
|
-
`;
|
|
11912
|
-
const content = import_gray_matter7.default.stringify(body, frontmatter);
|
|
11913
|
-
fs26.writeFileSync(filePath, content);
|
|
11914
|
-
return slug;
|
|
11915
|
-
}
|
|
11916
|
-
function writeDecision(vaultPath, decision, force) {
|
|
11917
|
-
const decisionsDir = path26.join(vaultPath, "decisions");
|
|
11918
|
-
fs26.mkdirSync(decisionsDir, { recursive: true });
|
|
11919
|
-
const slug = slugify2(decision.title);
|
|
11920
|
-
if (!slug) return null;
|
|
11921
|
-
const filePath = path26.join(decisionsDir, `${slug}.md`);
|
|
11922
|
-
if (!force && (fs26.existsSync(filePath) || fileExistsWithSimilarName(decisionsDir, slug))) {
|
|
11923
|
-
return null;
|
|
11924
|
-
}
|
|
11925
|
-
const template = loadTemplateDefinition("decision");
|
|
11926
|
-
const now = /* @__PURE__ */ new Date();
|
|
11927
|
-
const date = now.toISOString().split("T")[0];
|
|
11928
|
-
let content;
|
|
11929
|
-
if (template) {
|
|
11930
|
-
const rendered = renderDocumentFromTemplate(template, {
|
|
11931
|
-
title: decision.title,
|
|
11932
|
-
now,
|
|
11933
|
-
overrides: {
|
|
11934
|
-
status: "decided"
|
|
11935
|
-
}
|
|
11936
|
-
});
|
|
11937
|
-
let body = rendered.content;
|
|
11938
|
-
if (decision.context) {
|
|
11939
|
-
body = body.replace(/## Context\n-\s*/, `## Context
|
|
11940
|
-
- ${decision.context}
|
|
11941
|
-
`);
|
|
11942
|
-
}
|
|
11943
|
-
body = body.replace(/## Decision\n-\s*/, `## Decision
|
|
11944
|
-
- ${decision.decision}
|
|
11945
|
-
`);
|
|
11946
|
-
body = body.replace(/## Consequences\n-\s*/, `## Consequences
|
|
11947
|
-
- Imported from memory on ${date}
|
|
11948
|
-
`);
|
|
11949
|
-
content = import_gray_matter7.default.stringify(body, rendered.frontmatter);
|
|
11950
|
-
} else {
|
|
11951
|
-
const frontmatter = {
|
|
11952
|
-
title: decision.title,
|
|
11953
|
-
date,
|
|
11954
|
-
type: "decision",
|
|
11955
|
-
status: "decided"
|
|
11956
|
-
};
|
|
11957
|
-
let body = `# Decision: ${decision.title}
|
|
11958
|
-
|
|
11959
|
-
`;
|
|
11960
|
-
body += `## Context
|
|
11961
|
-
`;
|
|
11962
|
-
if (decision.context) body += `- ${decision.context}
|
|
11963
|
-
`;
|
|
11964
|
-
else body += "- \n";
|
|
11965
|
-
body += `
|
|
11966
|
-
## Decision
|
|
11967
|
-
- ${decision.decision}
|
|
11968
|
-
`;
|
|
11969
|
-
body += `
|
|
11970
|
-
## Consequences
|
|
11971
|
-
- Imported from memory on ${date}
|
|
11972
|
-
`;
|
|
11973
|
-
content = import_gray_matter7.default.stringify(body, frontmatter);
|
|
11974
|
-
}
|
|
11975
|
-
fs26.writeFileSync(filePath, content);
|
|
11976
|
-
return slug;
|
|
11977
|
-
}
|
|
11978
|
-
function writeTask(vaultPath, task, force) {
|
|
11979
|
-
const tasksDir = path26.join(vaultPath, "tasks");
|
|
11980
|
-
fs26.mkdirSync(tasksDir, { recursive: true });
|
|
11981
|
-
const slug = slugify2(task.title);
|
|
11982
|
-
if (!slug) return null;
|
|
11983
|
-
const filePath = path26.join(tasksDir, `${slug}.md`);
|
|
11984
|
-
if (!force && (fs26.existsSync(filePath) || fileExistsWithSimilarName(tasksDir, slug))) {
|
|
11985
|
-
return null;
|
|
11986
|
-
}
|
|
11987
|
-
const template = loadTemplateDefinition("task");
|
|
11988
|
-
const now = /* @__PURE__ */ new Date();
|
|
11989
|
-
const datetime = now.toISOString();
|
|
11990
|
-
let content;
|
|
11991
|
-
if (template) {
|
|
11992
|
-
const rendered = renderDocumentFromTemplate(template, {
|
|
11993
|
-
title: task.title,
|
|
11994
|
-
now,
|
|
11995
|
-
overrides: {
|
|
11996
|
-
status: "open",
|
|
11997
|
-
priority: task.priority || "medium",
|
|
11998
|
-
source: "memory-import",
|
|
11999
|
-
description: task.description
|
|
12000
|
-
},
|
|
12001
|
-
frontmatter: { pruneEmpty: true }
|
|
12002
|
-
});
|
|
12003
|
-
content = rendered.markdown;
|
|
12004
|
-
} else {
|
|
12005
|
-
const frontmatter = {
|
|
12006
|
-
status: "open",
|
|
12007
|
-
source: "memory-import",
|
|
12008
|
-
created: datetime,
|
|
12009
|
-
updated: datetime,
|
|
12010
|
-
priority: task.priority || "medium"
|
|
12011
|
-
};
|
|
12012
|
-
if (task.description) frontmatter.description = task.description;
|
|
12013
|
-
const body = `# ${task.title}
|
|
12014
|
-
|
|
12015
|
-
`;
|
|
12016
|
-
content = import_gray_matter7.default.stringify(body, frontmatter);
|
|
12017
|
-
}
|
|
12018
|
-
fs26.writeFileSync(filePath, content);
|
|
12019
|
-
return slug;
|
|
12020
|
-
}
|
|
12021
|
-
function importToVault(vaultPath, extracted, force) {
|
|
12022
|
-
const summary = {
|
|
12023
|
-
created: { people: [], preferences: [], decisions: [], tasks: [] },
|
|
12024
|
-
skipped: { people: [], preferences: [], decisions: [], tasks: [] }
|
|
12025
|
-
};
|
|
12026
|
-
for (const person of extracted.people) {
|
|
12027
|
-
const slug = writePerson(vaultPath, person, force);
|
|
12028
|
-
if (slug) {
|
|
12029
|
-
summary.created.people.push(person.name);
|
|
12030
|
-
} else {
|
|
12031
|
-
summary.skipped.people.push(person.name);
|
|
12032
|
-
}
|
|
12033
|
-
}
|
|
12034
|
-
for (const pref of extracted.preferences) {
|
|
12035
|
-
const slug = writePreference(vaultPath, pref, force);
|
|
12036
|
-
if (slug) {
|
|
12037
|
-
summary.created.preferences.push(`${pref.subject}: ${pref.preference}`.slice(0, 50));
|
|
12038
|
-
} else {
|
|
12039
|
-
summary.skipped.preferences.push(`${pref.subject}: ${pref.preference}`.slice(0, 50));
|
|
12040
|
-
}
|
|
12041
|
-
}
|
|
12042
|
-
for (const decision of extracted.decisions) {
|
|
12043
|
-
const slug = writeDecision(vaultPath, decision, force);
|
|
12044
|
-
if (slug) {
|
|
12045
|
-
summary.created.decisions.push(decision.title);
|
|
12046
|
-
} else {
|
|
12047
|
-
summary.skipped.decisions.push(decision.title);
|
|
12048
|
-
}
|
|
12049
|
-
}
|
|
12050
|
-
for (const task of extracted.tasks) {
|
|
12051
|
-
const slug = writeTask(vaultPath, task, force);
|
|
12052
|
-
if (slug) {
|
|
12053
|
-
summary.created.tasks.push(task.title);
|
|
12054
|
-
} else {
|
|
12055
|
-
summary.skipped.tasks.push(task.title);
|
|
12056
|
-
}
|
|
12057
|
-
}
|
|
12058
|
-
return summary;
|
|
12059
|
-
}
|
|
12060
|
-
function printImportSummary(summary) {
|
|
12061
|
-
const totalCreated = summary.created.people.length + summary.created.preferences.length + summary.created.decisions.length + summary.created.tasks.length;
|
|
12062
|
-
const totalSkipped = summary.skipped.people.length + summary.skipped.preferences.length + summary.skipped.decisions.length + summary.skipped.tasks.length;
|
|
12063
|
-
console.log("\n\u{1F4E5} Memory Import Summary");
|
|
12064
|
-
console.log("\u2500".repeat(40));
|
|
12065
|
-
if (summary.created.people.length > 0) {
|
|
12066
|
-
console.log(`\u2713 People (${summary.created.people.length}):`);
|
|
12067
|
-
for (const name of summary.created.people.slice(0, 5)) {
|
|
12068
|
-
console.log(` - ${name}`);
|
|
12069
|
-
}
|
|
12070
|
-
if (summary.created.people.length > 5) {
|
|
12071
|
-
console.log(` ... and ${summary.created.people.length - 5} more`);
|
|
12072
|
-
}
|
|
12073
|
-
}
|
|
12074
|
-
if (summary.created.preferences.length > 0) {
|
|
12075
|
-
console.log(`\u2713 Preferences (${summary.created.preferences.length}):`);
|
|
12076
|
-
for (const pref of summary.created.preferences.slice(0, 5)) {
|
|
12077
|
-
console.log(` - ${pref}`);
|
|
12078
|
-
}
|
|
12079
|
-
if (summary.created.preferences.length > 5) {
|
|
12080
|
-
console.log(` ... and ${summary.created.preferences.length - 5} more`);
|
|
12081
|
-
}
|
|
12082
|
-
}
|
|
12083
|
-
if (summary.created.decisions.length > 0) {
|
|
12084
|
-
console.log(`\u2713 Decisions (${summary.created.decisions.length}):`);
|
|
12085
|
-
for (const dec of summary.created.decisions.slice(0, 5)) {
|
|
12086
|
-
console.log(` - ${dec}`);
|
|
12087
|
-
}
|
|
12088
|
-
if (summary.created.decisions.length > 5) {
|
|
12089
|
-
console.log(` ... and ${summary.created.decisions.length - 5} more`);
|
|
12090
|
-
}
|
|
12091
|
-
}
|
|
12092
|
-
if (summary.created.tasks.length > 0) {
|
|
12093
|
-
console.log(`\u2713 Tasks (${summary.created.tasks.length}):`);
|
|
12094
|
-
for (const task of summary.created.tasks.slice(0, 5)) {
|
|
12095
|
-
console.log(` - ${task}`);
|
|
12096
|
-
}
|
|
12097
|
-
if (summary.created.tasks.length > 5) {
|
|
12098
|
-
console.log(` ... and ${summary.created.tasks.length - 5} more`);
|
|
12099
|
-
}
|
|
12100
|
-
}
|
|
12101
|
-
if (totalSkipped > 0) {
|
|
12102
|
-
console.log(`
|
|
12103
|
-
\u2298 Skipped ${totalSkipped} items (already exist or similar)`);
|
|
12104
|
-
}
|
|
12105
|
-
console.log("\u2500".repeat(40));
|
|
12106
|
-
console.log(`Total: ${totalCreated} created, ${totalSkipped} skipped`);
|
|
10075
|
+
return { collection: path23.basename(vaultPath), root: vaultPath };
|
|
12107
10076
|
}
|
|
12108
10077
|
async function setupCommand(options = {}) {
|
|
12109
10078
|
const target = resolveVaultTarget(options.vault);
|
|
12110
|
-
if (target.existed && !
|
|
10079
|
+
if (target.existed && !fs24.statSync(target.vaultPath).isDirectory()) {
|
|
12111
10080
|
throw new Error(`Vault path is not a directory: ${target.vaultPath}`);
|
|
12112
10081
|
}
|
|
12113
|
-
if (!target.existed)
|
|
10082
|
+
if (!target.existed) fs24.mkdirSync(target.vaultPath, { recursive: true });
|
|
12114
10083
|
console.log(`${target.existed ? "Found" : "Created"} vault path (${target.source}): ${target.vaultPath}`);
|
|
12115
10084
|
const initialized = ensureVaultStructure(target.vaultPath);
|
|
12116
10085
|
console.log(initialized ? "\u2713 Initialized vault structure." : "\u2713 Vault structure already present.");
|
|
12117
10086
|
const force = options.force ?? false;
|
|
12118
10087
|
const theme = options.theme ?? "neural";
|
|
12119
|
-
if (options.from) {
|
|
12120
|
-
const sourcePath = path26.resolve(options.from);
|
|
12121
|
-
if (!fs26.existsSync(sourcePath)) {
|
|
12122
|
-
throw new Error(`Source path does not exist: ${sourcePath}`);
|
|
12123
|
-
}
|
|
12124
|
-
console.log(`
|
|
12125
|
-
\u{1F4C2} Scanning source: ${sourcePath}`);
|
|
12126
|
-
const extracted = scanAndExtract(sourcePath);
|
|
12127
|
-
const totalFound = extracted.people.length + extracted.preferences.length + extracted.decisions.length + extracted.tasks.length;
|
|
12128
|
-
if (totalFound === 0) {
|
|
12129
|
-
console.log("\u2298 No structured data found in source files.");
|
|
12130
|
-
} else {
|
|
12131
|
-
console.log(`Found: ${extracted.people.length} people, ${extracted.preferences.length} preferences, ${extracted.decisions.length} decisions, ${extracted.tasks.length} tasks`);
|
|
12132
|
-
const summary = importToVault(target.vaultPath, extracted, force);
|
|
12133
|
-
printImportSummary(summary);
|
|
12134
|
-
}
|
|
12135
|
-
}
|
|
12136
10088
|
const explicitFlags = options.graphColors !== void 0 || options.bases !== void 0;
|
|
12137
|
-
const
|
|
12138
|
-
const
|
|
12139
|
-
const doBases = fromOnly ? false : explicitFlags ? options.bases !== false : true;
|
|
10089
|
+
const doGraphColors = explicitFlags ? options.graphColors !== false : true;
|
|
10090
|
+
const doBases = explicitFlags ? options.bases !== false : true;
|
|
12140
10091
|
if (doGraphColors && theme !== "none") {
|
|
12141
10092
|
const wrote = writeGraphColors(target.vaultPath, theme, force);
|
|
12142
10093
|
if (wrote) {
|
|
12143
10094
|
console.log(`\u2713 Graph colors configured (${theme} theme)`);
|
|
12144
10095
|
} else {
|
|
12145
|
-
const obsidianDir =
|
|
12146
|
-
if (!
|
|
10096
|
+
const obsidianDir = path23.join(target.vaultPath, ".obsidian");
|
|
10097
|
+
if (!fs24.existsSync(obsidianDir)) {
|
|
12147
10098
|
console.log("\u2298 No .obsidian directory \u2014 skipping graph colors (not an Obsidian vault)");
|
|
12148
10099
|
} else {
|
|
12149
10100
|
console.log("\u2298 Graph colors already exist (use --force to overwrite)");
|
|
@@ -12163,7 +10114,7 @@ async function setupCommand(options = {}) {
|
|
|
12163
10114
|
if (hasQmd()) {
|
|
12164
10115
|
const { collection, root } = getQmdConfig(target.vaultPath);
|
|
12165
10116
|
try {
|
|
12166
|
-
(0,
|
|
10117
|
+
(0, import_child_process4.execFileSync)("qmd", withQmdIndexArgs(["collection", "add", root, "--name", collection, "--mask", "**/*.md"], options.qmdIndexName), {
|
|
12167
10118
|
stdio: "ignore"
|
|
12168
10119
|
});
|
|
12169
10120
|
console.log(`\u2713 qmd collection ready: ${collection}`);
|
|
@@ -12179,82 +10130,52 @@ async function setupCommand(options = {}) {
|
|
|
12179
10130
|
console.log(" clawvault setup --theme neural # Neural neural graph colors");
|
|
12180
10131
|
console.log(" clawvault setup --theme minimal # Subtle category colors");
|
|
12181
10132
|
console.log(" clawvault setup --no-bases --no-graph-colors # Structure only");
|
|
12182
|
-
console.log(" clawvault setup --from <path> # Import from existing memory");
|
|
12183
10133
|
console.log(" clawvault setup --force # Overwrite existing configs");
|
|
12184
10134
|
}
|
|
12185
10135
|
|
|
12186
10136
|
// src/commands/compat.ts
|
|
12187
|
-
var
|
|
12188
|
-
var
|
|
10137
|
+
var fs25 = __toESM(require("fs"), 1);
|
|
10138
|
+
var path24 = __toESM(require("path"), 1);
|
|
12189
10139
|
var import_gray_matter8 = __toESM(require("gray-matter"), 1);
|
|
12190
|
-
var
|
|
10140
|
+
var import_child_process5 = require("child_process");
|
|
12191
10141
|
var import_url3 = require("url");
|
|
12192
10142
|
var import_meta3 = {};
|
|
12193
|
-
var REQUIRED_HOOK_EVENTS = ["gateway:startup", "command:new", "session:start"];
|
|
12194
|
-
var REQUIRED_HOOK_BIN = "clawvault";
|
|
12195
10143
|
function readOptionalFile(filePath) {
|
|
12196
10144
|
try {
|
|
12197
|
-
if (!
|
|
12198
|
-
return
|
|
10145
|
+
if (!fs25.existsSync(filePath)) return null;
|
|
10146
|
+
return fs25.readFileSync(filePath, "utf-8");
|
|
12199
10147
|
} catch {
|
|
12200
10148
|
return null;
|
|
12201
10149
|
}
|
|
12202
10150
|
}
|
|
12203
10151
|
function findPackageRoot() {
|
|
12204
|
-
let dir =
|
|
12205
|
-
while (dir !==
|
|
12206
|
-
if (
|
|
10152
|
+
let dir = path24.dirname((0, import_url3.fileURLToPath)(import_meta3.url));
|
|
10153
|
+
while (dir !== path24.dirname(dir)) {
|
|
10154
|
+
if (fs25.existsSync(path24.join(dir, "package.json"))) {
|
|
12207
10155
|
return dir;
|
|
12208
10156
|
}
|
|
12209
|
-
dir =
|
|
12210
|
-
}
|
|
12211
|
-
return path27.dirname((0, import_url3.fileURLToPath)(import_meta3.url));
|
|
12212
|
-
}
|
|
12213
|
-
function resolveOpenClawHooksDir() {
|
|
12214
|
-
const candidates = [
|
|
12215
|
-
path27.join(process.env.HOME || "", ".openclaw", "hooks", "clawvault"),
|
|
12216
|
-
path27.join(process.env.OPENCLAW_HOME || "", "hooks", "clawvault"),
|
|
12217
|
-
path27.join(process.env.OPENCLAW_STATE_DIR || "", "hooks", "clawvault")
|
|
12218
|
-
].filter((p) => p && !p.startsWith(path27.sep + "hooks"));
|
|
12219
|
-
for (const candidate of candidates) {
|
|
12220
|
-
if (fs27.existsSync(candidate)) {
|
|
12221
|
-
return candidate;
|
|
12222
|
-
}
|
|
10157
|
+
dir = path24.dirname(dir);
|
|
12223
10158
|
}
|
|
12224
|
-
return
|
|
10159
|
+
return path24.dirname((0, import_url3.fileURLToPath)(import_meta3.url));
|
|
12225
10160
|
}
|
|
12226
10161
|
function resolveProjectFile(relativePath, baseDir) {
|
|
12227
10162
|
if (baseDir) {
|
|
12228
|
-
return
|
|
10163
|
+
return path24.resolve(baseDir, relativePath);
|
|
12229
10164
|
}
|
|
12230
|
-
const fromCwd =
|
|
12231
|
-
if (
|
|
10165
|
+
const fromCwd = path24.resolve(process.cwd(), relativePath);
|
|
10166
|
+
if (fs25.existsSync(fromCwd)) {
|
|
12232
10167
|
return fromCwd;
|
|
12233
10168
|
}
|
|
12234
|
-
|
|
12235
|
-
const hooksDir = resolveOpenClawHooksDir();
|
|
12236
|
-
if (hooksDir) {
|
|
12237
|
-
const hookRelative = relativePath.replace("hooks/clawvault/", "");
|
|
12238
|
-
const fromHooks = path27.resolve(hooksDir, hookRelative);
|
|
12239
|
-
if (fs27.existsSync(fromHooks)) {
|
|
12240
|
-
return fromHooks;
|
|
12241
|
-
}
|
|
12242
|
-
const fromNestedHooks = path27.resolve(hooksDir, "hooks", "clawvault", hookRelative);
|
|
12243
|
-
if (fs27.existsSync(fromNestedHooks)) {
|
|
12244
|
-
return fromNestedHooks;
|
|
12245
|
-
}
|
|
12246
|
-
}
|
|
12247
|
-
}
|
|
12248
|
-
return path27.resolve(findPackageRoot(), relativePath);
|
|
10169
|
+
return path24.resolve(findPackageRoot(), relativePath);
|
|
12249
10170
|
}
|
|
12250
10171
|
function checkOpenClawCli() {
|
|
12251
|
-
const result = (0,
|
|
10172
|
+
const result = (0, import_child_process5.spawnSync)("openclaw", ["--version"], { stdio: "ignore" });
|
|
12252
10173
|
if (result.error) {
|
|
12253
10174
|
return {
|
|
12254
10175
|
label: "openclaw CLI available",
|
|
12255
10176
|
status: "warn",
|
|
12256
10177
|
detail: "openclaw binary not found",
|
|
12257
|
-
hint: "Install OpenClaw CLI to enable
|
|
10178
|
+
hint: "Install OpenClaw CLI to enable plugin runtime validation."
|
|
12258
10179
|
};
|
|
12259
10180
|
}
|
|
12260
10181
|
if (typeof result.status === "number" && result.status !== 0) {
|
|
@@ -12275,152 +10196,106 @@ function checkOpenClawCli() {
|
|
|
12275
10196
|
}
|
|
12276
10197
|
return { label: "openclaw CLI available", status: "ok" };
|
|
12277
10198
|
}
|
|
12278
|
-
function
|
|
12279
|
-
|
|
12280
|
-
|
|
12281
|
-
|
|
12282
|
-
|
|
12283
|
-
if (!parsed.openclaw?.hooks) {
|
|
12284
|
-
const fallbackPath = path27.resolve(findPackageRoot(), "package.json");
|
|
12285
|
-
const fallbackRaw = readOptionalFile(fallbackPath);
|
|
12286
|
-
if (fallbackRaw) packageRaw = fallbackRaw;
|
|
12287
|
-
}
|
|
12288
|
-
} catch {
|
|
12289
|
-
}
|
|
12290
|
-
}
|
|
12291
|
-
if (!packageRaw) {
|
|
10199
|
+
function checkPluginManifest(options) {
|
|
10200
|
+
const manifestRaw = readOptionalFile(
|
|
10201
|
+
resolveProjectFile("openclaw.plugin.json", options.baseDir)
|
|
10202
|
+
);
|
|
10203
|
+
if (!manifestRaw) {
|
|
12292
10204
|
return {
|
|
12293
|
-
label: "
|
|
10205
|
+
label: "plugin manifest",
|
|
12294
10206
|
status: "error",
|
|
12295
|
-
detail: "
|
|
10207
|
+
detail: "openclaw.plugin.json not found",
|
|
10208
|
+
hint: "Create openclaw.plugin.json with id, kind, and configSchema fields."
|
|
12296
10209
|
};
|
|
12297
10210
|
}
|
|
12298
10211
|
try {
|
|
12299
|
-
const
|
|
12300
|
-
const
|
|
12301
|
-
if (
|
|
10212
|
+
const manifest = JSON.parse(manifestRaw);
|
|
10213
|
+
const issues = [];
|
|
10214
|
+
if (!manifest.id) issues.push("missing id");
|
|
10215
|
+
if (!manifest.kind) issues.push("missing kind");
|
|
10216
|
+
if (!manifest.configSchema) issues.push("missing configSchema");
|
|
10217
|
+
if (issues.length > 0) {
|
|
12302
10218
|
return {
|
|
12303
|
-
label: "
|
|
12304
|
-
status: "
|
|
12305
|
-
detail: "
|
|
10219
|
+
label: "plugin manifest",
|
|
10220
|
+
status: "error",
|
|
10221
|
+
detail: issues.join(", ")
|
|
12306
10222
|
};
|
|
12307
10223
|
}
|
|
12308
10224
|
return {
|
|
12309
|
-
label: "
|
|
12310
|
-
status: "
|
|
12311
|
-
detail:
|
|
10225
|
+
label: "plugin manifest",
|
|
10226
|
+
status: "ok",
|
|
10227
|
+
detail: `id=${manifest.id} kind=${manifest.kind}`
|
|
12312
10228
|
};
|
|
12313
10229
|
} catch (err) {
|
|
12314
10230
|
return {
|
|
12315
|
-
label: "
|
|
10231
|
+
label: "plugin manifest",
|
|
12316
10232
|
status: "error",
|
|
12317
|
-
detail: err?.message || "Unable to parse
|
|
10233
|
+
detail: err?.message || "Unable to parse openclaw.plugin.json"
|
|
12318
10234
|
};
|
|
12319
10235
|
}
|
|
12320
10236
|
}
|
|
12321
|
-
function
|
|
12322
|
-
|
|
12323
|
-
|
|
10237
|
+
function checkPluginExtensions(options) {
|
|
10238
|
+
let packageRaw = readOptionalFile(
|
|
10239
|
+
resolveProjectFile("package.json", options.baseDir)
|
|
10240
|
+
);
|
|
10241
|
+
if (packageRaw && !options.baseDir) {
|
|
10242
|
+
try {
|
|
10243
|
+
const parsed = JSON.parse(packageRaw);
|
|
10244
|
+
if (!parsed.openclaw?.extensions) {
|
|
10245
|
+
const fallbackPath = path24.resolve(findPackageRoot(), "package.json");
|
|
10246
|
+
const fallbackRaw = readOptionalFile(fallbackPath);
|
|
10247
|
+
if (fallbackRaw) packageRaw = fallbackRaw;
|
|
10248
|
+
}
|
|
10249
|
+
} catch {
|
|
10250
|
+
}
|
|
10251
|
+
}
|
|
10252
|
+
if (!packageRaw) {
|
|
12324
10253
|
return {
|
|
12325
|
-
label: "
|
|
10254
|
+
label: "plugin extensions registration",
|
|
12326
10255
|
status: "error",
|
|
12327
|
-
detail: "
|
|
10256
|
+
detail: "package.json not found"
|
|
12328
10257
|
};
|
|
12329
10258
|
}
|
|
12330
10259
|
try {
|
|
12331
|
-
const parsed =
|
|
12332
|
-
const
|
|
12333
|
-
|
|
12334
|
-
const missingEvents = REQUIRED_HOOK_EVENTS.filter((event) => !events.includes(event));
|
|
12335
|
-
if (missingEvents.length === 0) {
|
|
10260
|
+
const parsed = JSON.parse(packageRaw);
|
|
10261
|
+
const extensions = parsed.openclaw?.extensions ?? [];
|
|
10262
|
+
if (extensions.length === 0) {
|
|
12336
10263
|
return {
|
|
12337
|
-
label: "
|
|
12338
|
-
status: "
|
|
12339
|
-
detail:
|
|
10264
|
+
label: "plugin extensions registration",
|
|
10265
|
+
status: "error",
|
|
10266
|
+
detail: "Missing openclaw.extensions in package.json",
|
|
10267
|
+
hint: 'Add openclaw.extensions: ["./dist/plugin/index.js"] to package.json.'
|
|
12340
10268
|
};
|
|
12341
10269
|
}
|
|
12342
|
-
|
|
12343
|
-
|
|
12344
|
-
|
|
12345
|
-
|
|
12346
|
-
|
|
12347
|
-
} catch (err) {
|
|
12348
|
-
return {
|
|
12349
|
-
label: "hook manifest events",
|
|
12350
|
-
status: "error",
|
|
12351
|
-
detail: err?.message || "Unable to parse HOOK.md frontmatter"
|
|
12352
|
-
};
|
|
12353
|
-
}
|
|
12354
|
-
}
|
|
12355
|
-
function checkHookManifestRequirements(options) {
|
|
12356
|
-
const hookRaw = readOptionalFile(resolveProjectFile("hooks/clawvault/HOOK.md", options.baseDir));
|
|
12357
|
-
if (!hookRaw) {
|
|
12358
|
-
return {
|
|
12359
|
-
label: "hook manifest requirements",
|
|
12360
|
-
status: "error",
|
|
12361
|
-
detail: "HOOK.md not found"
|
|
12362
|
-
};
|
|
12363
|
-
}
|
|
12364
|
-
try {
|
|
12365
|
-
const parsed = (0, import_gray_matter8.default)(hookRaw);
|
|
12366
|
-
const requiresBins = parsed.data?.metadata?.openclaw?.requires?.bins;
|
|
12367
|
-
const bins = Array.isArray(requiresBins) ? requiresBins : [];
|
|
12368
|
-
if (bins.includes(REQUIRED_HOOK_BIN)) {
|
|
10270
|
+
const baseDir = options.baseDir || findPackageRoot();
|
|
10271
|
+
const missing = extensions.filter(
|
|
10272
|
+
(ext) => !fs25.existsSync(path24.resolve(baseDir, ext))
|
|
10273
|
+
);
|
|
10274
|
+
if (missing.length > 0) {
|
|
12369
10275
|
return {
|
|
12370
|
-
label: "
|
|
12371
|
-
status: "
|
|
12372
|
-
detail: `
|
|
10276
|
+
label: "plugin extensions registration",
|
|
10277
|
+
status: "error",
|
|
10278
|
+
detail: `Entry file(s) not found: ${missing.join(", ")}`,
|
|
10279
|
+
hint: "Run npm run build to generate dist files."
|
|
12373
10280
|
};
|
|
12374
10281
|
}
|
|
12375
10282
|
return {
|
|
12376
|
-
label: "
|
|
12377
|
-
status: "
|
|
12378
|
-
detail:
|
|
12379
|
-
hint: 'Add metadata.openclaw.requires.bins: ["clawvault"] to hooks/clawvault/HOOK.md.'
|
|
10283
|
+
label: "plugin extensions registration",
|
|
10284
|
+
status: "ok",
|
|
10285
|
+
detail: extensions.join(", ")
|
|
12380
10286
|
};
|
|
12381
10287
|
} catch (err) {
|
|
12382
10288
|
return {
|
|
12383
|
-
label: "
|
|
12384
|
-
status: "error",
|
|
12385
|
-
detail: err?.message || "Unable to parse HOOK.md frontmatter"
|
|
12386
|
-
};
|
|
12387
|
-
}
|
|
12388
|
-
}
|
|
12389
|
-
function checkHookHandlerSafety(options) {
|
|
12390
|
-
const handlerRaw = readOptionalFile(resolveProjectFile("hooks/clawvault/handler.js", options.baseDir));
|
|
12391
|
-
if (!handlerRaw) {
|
|
12392
|
-
return {
|
|
12393
|
-
label: "hook handler script",
|
|
10289
|
+
label: "plugin extensions registration",
|
|
12394
10290
|
status: "error",
|
|
12395
|
-
detail: "
|
|
12396
|
-
};
|
|
12397
|
-
}
|
|
12398
|
-
const usesExecFileSync = handlerRaw.includes("execFileSync");
|
|
12399
|
-
const usesExecSync = /\bexecSync\b/.test(handlerRaw);
|
|
12400
|
-
const enablesShell = /\bshell\s*:\s*true\b/.test(handlerRaw);
|
|
12401
|
-
const delegatesAutoProfile = /['"]--profile['"]\s*,\s*['"]auto['"]/.test(handlerRaw);
|
|
12402
|
-
const violations = [];
|
|
12403
|
-
if (!usesExecFileSync || usesExecSync) {
|
|
12404
|
-
violations.push("execFileSync-only execution path");
|
|
12405
|
-
}
|
|
12406
|
-
if (enablesShell) {
|
|
12407
|
-
violations.push("shell:false execution option");
|
|
12408
|
-
}
|
|
12409
|
-
if (!delegatesAutoProfile) {
|
|
12410
|
-
violations.push("shared context profile delegation (--profile auto)");
|
|
12411
|
-
}
|
|
12412
|
-
if (violations.length > 0) {
|
|
12413
|
-
return {
|
|
12414
|
-
label: "hook handler safety",
|
|
12415
|
-
status: "warn",
|
|
12416
|
-
detail: `Missing conventions: ${violations.join(", ")}`,
|
|
12417
|
-
hint: "Use execFileSync (no shell), avoid execSync, and delegate profile inference via --profile auto."
|
|
10291
|
+
detail: err?.message || "Unable to parse package.json"
|
|
12418
10292
|
};
|
|
12419
10293
|
}
|
|
12420
|
-
return { label: "hook handler safety", status: "ok" };
|
|
12421
10294
|
}
|
|
12422
10295
|
function checkSkillMetadata(options) {
|
|
12423
|
-
const skillRaw = readOptionalFile(
|
|
10296
|
+
const skillRaw = readOptionalFile(
|
|
10297
|
+
resolveProjectFile("SKILL.md", options.baseDir)
|
|
10298
|
+
);
|
|
12424
10299
|
if (!skillRaw) {
|
|
12425
10300
|
return {
|
|
12426
10301
|
label: "skill metadata",
|
|
@@ -12459,10 +10334,8 @@ function checkSkillMetadata(options) {
|
|
|
12459
10334
|
function checkOpenClawCompatibility(options = {}) {
|
|
12460
10335
|
const checks = [
|
|
12461
10336
|
checkOpenClawCli(),
|
|
12462
|
-
|
|
12463
|
-
|
|
12464
|
-
checkHookManifestRequirements(options),
|
|
12465
|
-
checkHookHandlerSafety(options),
|
|
10337
|
+
checkPluginManifest(options),
|
|
10338
|
+
checkPluginExtensions(options),
|
|
12466
10339
|
checkSkillMetadata(options)
|
|
12467
10340
|
];
|
|
12468
10341
|
const warnings = checks.filter((check) => check.status === "warn").length;
|
|
@@ -12482,7 +10355,9 @@ function formatCompatibilityReport(report) {
|
|
|
12482
10355
|
lines.push("");
|
|
12483
10356
|
for (const check of report.checks) {
|
|
12484
10357
|
const prefix = check.status === "ok" ? "\u2713" : check.status === "warn" ? "\u26A0" : "\u2717";
|
|
12485
|
-
lines.push(
|
|
10358
|
+
lines.push(
|
|
10359
|
+
`${prefix} ${check.label}${check.detail ? ` \u2014 ${check.detail}` : ""}`
|
|
10360
|
+
);
|
|
12486
10361
|
if (check.hint) {
|
|
12487
10362
|
lines.push(` ${check.hint}`);
|
|
12488
10363
|
}
|
|
@@ -12556,8 +10431,8 @@ async function graphCommand(options = {}) {
|
|
|
12556
10431
|
}
|
|
12557
10432
|
|
|
12558
10433
|
// src/commands/kanban.ts
|
|
12559
|
-
var
|
|
12560
|
-
var
|
|
10434
|
+
var fs26 = __toESM(require("fs"), 1);
|
|
10435
|
+
var path25 = __toESM(require("path"), 1);
|
|
12561
10436
|
var import_gray_matter9 = __toESM(require("gray-matter"), 1);
|
|
12562
10437
|
var STATUS_LANES = [
|
|
12563
10438
|
{ status: "open", name: "Open" },
|
|
@@ -12586,14 +10461,14 @@ function normalizeGroupBy(value) {
|
|
|
12586
10461
|
throw new Error(`Unsupported kanban group field: ${normalized}`);
|
|
12587
10462
|
}
|
|
12588
10463
|
function resolveBoardPath(vaultPath, output) {
|
|
12589
|
-
const resolvedVaultPath =
|
|
10464
|
+
const resolvedVaultPath = path25.resolve(vaultPath);
|
|
12590
10465
|
if (!output) {
|
|
12591
|
-
return
|
|
10466
|
+
return path25.join(resolvedVaultPath, "Board.md");
|
|
12592
10467
|
}
|
|
12593
|
-
if (
|
|
10468
|
+
if (path25.isAbsolute(output)) {
|
|
12594
10469
|
return output;
|
|
12595
10470
|
}
|
|
12596
|
-
return
|
|
10471
|
+
return path25.join(resolvedVaultPath, output);
|
|
12597
10472
|
}
|
|
12598
10473
|
function toHashTag(value) {
|
|
12599
10474
|
return value.trim().replace(/\s+/g, "-").replace(/[^A-Za-z0-9/_-]/g, "");
|
|
@@ -12745,7 +10620,7 @@ function syncKanbanBoard(vaultPath, options = {}) {
|
|
|
12745
10620
|
groupBy,
|
|
12746
10621
|
now: options.now
|
|
12747
10622
|
});
|
|
12748
|
-
|
|
10623
|
+
fs26.writeFileSync(outputPath, markdown);
|
|
12749
10624
|
return {
|
|
12750
10625
|
outputPath,
|
|
12751
10626
|
groupBy,
|
|
@@ -12828,10 +10703,10 @@ function hasUpdates(updates) {
|
|
|
12828
10703
|
}
|
|
12829
10704
|
function importKanbanBoard(vaultPath, options = {}) {
|
|
12830
10705
|
const outputPath = resolveBoardPath(vaultPath, options.output);
|
|
12831
|
-
if (!
|
|
10706
|
+
if (!fs26.existsSync(outputPath)) {
|
|
12832
10707
|
throw new Error(`Kanban board not found: ${outputPath}`);
|
|
12833
10708
|
}
|
|
12834
|
-
const markdown =
|
|
10709
|
+
const markdown = fs26.readFileSync(outputPath, "utf-8");
|
|
12835
10710
|
const parsed = parseKanbanMarkdown(markdown);
|
|
12836
10711
|
const changes = [];
|
|
12837
10712
|
const missingSlugs = [];
|
|
@@ -12973,7 +10848,7 @@ function registerArchiveCommand(program) {
|
|
|
12973
10848
|
}
|
|
12974
10849
|
|
|
12975
10850
|
// src/commands/rebuild.ts
|
|
12976
|
-
var
|
|
10851
|
+
var fs27 = __toESM(require("fs"), 1);
|
|
12977
10852
|
var DATE_RE2 = /^\d{4}-\d{2}-\d{2}$/;
|
|
12978
10853
|
function parseDateFlag(raw, label) {
|
|
12979
10854
|
if (!raw) return void 0;
|
|
@@ -12984,7 +10859,7 @@ function parseDateFlag(raw, label) {
|
|
|
12984
10859
|
return trimmed;
|
|
12985
10860
|
}
|
|
12986
10861
|
function loadRawMessages(rawFilePath) {
|
|
12987
|
-
const lines =
|
|
10862
|
+
const lines = fs27.readFileSync(rawFilePath, "utf-8").split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
12988
10863
|
const messages = [];
|
|
12989
10864
|
for (const line of lines) {
|
|
12990
10865
|
try {
|
|
@@ -13025,8 +10900,8 @@ async function rebuildCommand(options) {
|
|
|
13025
10900
|
for (const date of dates) {
|
|
13026
10901
|
const ledgerObservationPath = getObservationPath(vaultPath, date);
|
|
13027
10902
|
const legacyObservationPath = getLegacyObservationPath(vaultPath, date);
|
|
13028
|
-
|
|
13029
|
-
|
|
10903
|
+
fs27.rmSync(ledgerObservationPath, { force: true });
|
|
10904
|
+
fs27.rmSync(legacyObservationPath, { force: true });
|
|
13030
10905
|
const fixedNow = () => /* @__PURE__ */ new Date(`${date}T12:00:00.000Z`);
|
|
13031
10906
|
const observer = new Observer(vaultPath, {
|
|
13032
10907
|
tokenThreshold: 1,
|
|
@@ -13061,29 +10936,29 @@ function registerRebuildCommand(program) {
|
|
|
13061
10936
|
}
|
|
13062
10937
|
|
|
13063
10938
|
// src/commands/doctor.ts
|
|
13064
|
-
var
|
|
10939
|
+
var fs30 = __toESM(require("fs"), 1);
|
|
13065
10940
|
var os3 = __toESM(require("os"), 1);
|
|
13066
|
-
var
|
|
10941
|
+
var path28 = __toESM(require("path"), 1);
|
|
13067
10942
|
|
|
13068
10943
|
// src/lib/backlinks.ts
|
|
13069
|
-
var
|
|
13070
|
-
var
|
|
10944
|
+
var fs29 = __toESM(require("fs"), 1);
|
|
10945
|
+
var path27 = __toESM(require("path"), 1);
|
|
13071
10946
|
|
|
13072
10947
|
// src/lib/entity-index.ts
|
|
13073
|
-
var
|
|
13074
|
-
var
|
|
10948
|
+
var fs28 = __toESM(require("fs"), 1);
|
|
10949
|
+
var path26 = __toESM(require("path"), 1);
|
|
13075
10950
|
var import_gray_matter10 = __toESM(require("gray-matter"), 1);
|
|
13076
10951
|
function buildEntityIndex(vaultPath) {
|
|
13077
10952
|
const entries = /* @__PURE__ */ new Map();
|
|
13078
10953
|
const byPath = /* @__PURE__ */ new Map();
|
|
13079
10954
|
const entityFolders = ["people", "projects", "agents", "lessons", "decisions", "commitments"];
|
|
13080
10955
|
for (const folder of entityFolders) {
|
|
13081
|
-
const folderPath =
|
|
13082
|
-
if (!
|
|
13083
|
-
const files =
|
|
10956
|
+
const folderPath = path26.join(vaultPath, folder);
|
|
10957
|
+
if (!fs28.existsSync(folderPath)) continue;
|
|
10958
|
+
const files = fs28.readdirSync(folderPath).filter((f) => f.endsWith(".md"));
|
|
13084
10959
|
for (const file of files) {
|
|
13085
|
-
const filePath =
|
|
13086
|
-
const content =
|
|
10960
|
+
const filePath = path26.join(folderPath, file);
|
|
10961
|
+
const content = fs28.readFileSync(filePath, "utf-8");
|
|
13087
10962
|
const { data: frontmatter } = (0, import_gray_matter10.default)(content);
|
|
13088
10963
|
const relativePath = `${folder}/${file.replace(".md", "")}`;
|
|
13089
10964
|
const baseName = file.replace(".md", "");
|
|
@@ -13109,8 +10984,8 @@ function buildEntityIndex(vaultPath) {
|
|
|
13109
10984
|
// src/lib/backlinks.ts
|
|
13110
10985
|
var WIKI_LINK_REGEX = /\[\[([^\]]+)\]\]/g;
|
|
13111
10986
|
function toVaultId(vaultPath, filePath) {
|
|
13112
|
-
const
|
|
13113
|
-
return
|
|
10987
|
+
const relative5 = path27.relative(vaultPath, filePath).replace(/\.md$/, "");
|
|
10988
|
+
return relative5.split(path27.sep).join("/");
|
|
13114
10989
|
}
|
|
13115
10990
|
function normalizeLinkTarget(raw) {
|
|
13116
10991
|
let target = raw.trim();
|
|
@@ -13141,9 +11016,9 @@ function listMarkdownFiles(vaultPath) {
|
|
|
13141
11016
|
const files = [];
|
|
13142
11017
|
const skipDirs = /* @__PURE__ */ new Set(["archive", "templates", "node_modules"]);
|
|
13143
11018
|
function walk(dir) {
|
|
13144
|
-
const entries =
|
|
11019
|
+
const entries = fs29.readdirSync(dir, { withFileTypes: true });
|
|
13145
11020
|
for (const entry of entries) {
|
|
13146
|
-
const fullPath =
|
|
11021
|
+
const fullPath = path27.join(dir, entry.name);
|
|
13147
11022
|
if (entry.isDirectory()) {
|
|
13148
11023
|
if (entry.name.startsWith(".") || skipDirs.has(entry.name)) continue;
|
|
13149
11024
|
walk(fullPath);
|
|
@@ -13185,7 +11060,7 @@ function scanVaultLinks(vaultPath, options = {}) {
|
|
|
13185
11060
|
let linkCount = 0;
|
|
13186
11061
|
for (const file of files) {
|
|
13187
11062
|
const sourceId = toVaultId(vaultPath, file);
|
|
13188
|
-
const content =
|
|
11063
|
+
const content = fs29.readFileSync(file, "utf-8");
|
|
13189
11064
|
const matches = content.match(WIKI_LINK_REGEX) || [];
|
|
13190
11065
|
linkCount += matches.length;
|
|
13191
11066
|
for (const match of matches) {
|
|
@@ -13234,12 +11109,12 @@ function describeAge(date, now = Date.now()) {
|
|
|
13234
11109
|
return formatAge(now - date.getTime());
|
|
13235
11110
|
}
|
|
13236
11111
|
function loadCheckpointTimestamp(vaultPath) {
|
|
13237
|
-
const checkpointPath =
|
|
13238
|
-
if (!
|
|
11112
|
+
const checkpointPath = path28.join(vaultPath, CLAWVAULT_DIR, CHECKPOINT_FILE);
|
|
11113
|
+
if (!fs30.existsSync(checkpointPath)) {
|
|
13239
11114
|
return {};
|
|
13240
11115
|
}
|
|
13241
11116
|
try {
|
|
13242
|
-
const data = JSON.parse(
|
|
11117
|
+
const data = JSON.parse(fs30.readFileSync(checkpointPath, "utf-8"));
|
|
13243
11118
|
return { timestamp: data.timestamp };
|
|
13244
11119
|
} catch (err) {
|
|
13245
11120
|
return { error: err?.message || "Failed to parse checkpoint" };
|
|
@@ -13247,20 +11122,20 @@ function loadCheckpointTimestamp(vaultPath) {
|
|
|
13247
11122
|
}
|
|
13248
11123
|
function getShellConfigPaths(shellPath) {
|
|
13249
11124
|
const home = os3.homedir();
|
|
13250
|
-
const shellName = shellPath ?
|
|
11125
|
+
const shellName = shellPath ? path28.basename(shellPath) : "bash";
|
|
13251
11126
|
if (shellName === "zsh") {
|
|
13252
|
-
return [
|
|
11127
|
+
return [path28.join(home, ".zshenv"), path28.join(home, ".zshrc"), path28.join(home, ".zprofile")];
|
|
13253
11128
|
}
|
|
13254
11129
|
if (shellName === "fish") {
|
|
13255
|
-
return [
|
|
11130
|
+
return [path28.join(home, ".config", "fish", "config.fish")];
|
|
13256
11131
|
}
|
|
13257
|
-
return [
|
|
11132
|
+
return [path28.join(home, ".bashrc"), path28.join(home, ".bash_profile"), path28.join(home, ".profile")];
|
|
13258
11133
|
}
|
|
13259
11134
|
function hasClawvaultPathConfig(paths) {
|
|
13260
11135
|
for (const filePath of paths) {
|
|
13261
|
-
if (!
|
|
11136
|
+
if (!fs30.existsSync(filePath)) continue;
|
|
13262
11137
|
try {
|
|
13263
|
-
const content =
|
|
11138
|
+
const content = fs30.readFileSync(filePath, "utf-8");
|
|
13264
11139
|
if (/CLAWVAULT_PATH\s*=/.test(content)) {
|
|
13265
11140
|
return true;
|
|
13266
11141
|
}
|
|
@@ -13271,13 +11146,13 @@ function hasClawvaultPathConfig(paths) {
|
|
|
13271
11146
|
}
|
|
13272
11147
|
async function resolveVault(vaultPath) {
|
|
13273
11148
|
if (vaultPath) {
|
|
13274
|
-
const vault = new ClawVault(
|
|
11149
|
+
const vault = new ClawVault(path28.resolve(vaultPath));
|
|
13275
11150
|
await vault.load();
|
|
13276
11151
|
return vault;
|
|
13277
11152
|
}
|
|
13278
11153
|
const envPath = process.env.CLAWVAULT_PATH;
|
|
13279
11154
|
if (envPath) {
|
|
13280
|
-
const vault = new ClawVault(
|
|
11155
|
+
const vault = new ClawVault(path28.resolve(envPath));
|
|
13281
11156
|
await vault.load();
|
|
13282
11157
|
return vault;
|
|
13283
11158
|
}
|
|
@@ -13326,12 +11201,12 @@ async function doctor(options) {
|
|
|
13326
11201
|
});
|
|
13327
11202
|
errors++;
|
|
13328
11203
|
}
|
|
13329
|
-
const shellConfigs = getShellConfigPaths(process.env.SHELL).filter(
|
|
11204
|
+
const shellConfigs = getShellConfigPaths(process.env.SHELL).filter(fs30.existsSync);
|
|
13330
11205
|
if (hasClawvaultPathConfig(shellConfigs)) {
|
|
13331
11206
|
checks.push({
|
|
13332
11207
|
label: "CLAWVAULT_PATH in shell config",
|
|
13333
11208
|
status: "ok",
|
|
13334
|
-
detail: shellConfigs.map((p) =>
|
|
11209
|
+
detail: shellConfigs.map((p) => path28.basename(p)).join(", ")
|
|
13335
11210
|
});
|
|
13336
11211
|
} else {
|
|
13337
11212
|
checks.push({
|
|
@@ -13529,8 +11404,8 @@ async function doctor(options) {
|
|
|
13529
11404
|
}
|
|
13530
11405
|
|
|
13531
11406
|
// src/commands/replay.ts
|
|
13532
|
-
var
|
|
13533
|
-
var
|
|
11407
|
+
var fs31 = __toESM(require("fs"), 1);
|
|
11408
|
+
var path29 = __toESM(require("path"), 1);
|
|
13534
11409
|
|
|
13535
11410
|
// src/replay/normalizers/chatgpt.ts
|
|
13536
11411
|
function normalizeText3(value) {
|
|
@@ -13833,10 +11708,10 @@ function parseDateFlag2(raw, label) {
|
|
|
13833
11708
|
return trimmed;
|
|
13834
11709
|
}
|
|
13835
11710
|
function collectFiles(rootPath, predicate) {
|
|
13836
|
-
if (!
|
|
11711
|
+
if (!fs31.existsSync(rootPath)) {
|
|
13837
11712
|
return [];
|
|
13838
11713
|
}
|
|
13839
|
-
const stat =
|
|
11714
|
+
const stat = fs31.statSync(rootPath);
|
|
13840
11715
|
if (stat.isFile()) {
|
|
13841
11716
|
return predicate(rootPath) ? [rootPath] : [];
|
|
13842
11717
|
}
|
|
@@ -13844,8 +11719,8 @@ function collectFiles(rootPath, predicate) {
|
|
|
13844
11719
|
return [];
|
|
13845
11720
|
}
|
|
13846
11721
|
const files = [];
|
|
13847
|
-
for (const entry of
|
|
13848
|
-
const absolute =
|
|
11722
|
+
for (const entry of fs31.readdirSync(rootPath, { withFileTypes: true })) {
|
|
11723
|
+
const absolute = path29.join(rootPath, entry.name);
|
|
13849
11724
|
if (entry.isDirectory()) {
|
|
13850
11725
|
files.push(...collectFiles(absolute, predicate));
|
|
13851
11726
|
continue;
|
|
@@ -13857,11 +11732,11 @@ function collectFiles(rootPath, predicate) {
|
|
|
13857
11732
|
return files.sort((left, right) => left.localeCompare(right));
|
|
13858
11733
|
}
|
|
13859
11734
|
function loadJson(filePath) {
|
|
13860
|
-
return JSON.parse(
|
|
11735
|
+
return JSON.parse(fs31.readFileSync(filePath, "utf-8"));
|
|
13861
11736
|
}
|
|
13862
11737
|
function normalizeReplayMessages(source, inputPath) {
|
|
13863
11738
|
if (source === "chatgpt") {
|
|
13864
|
-
const files2 = collectFiles(inputPath, (filePath) =>
|
|
11739
|
+
const files2 = collectFiles(inputPath, (filePath) => path29.basename(filePath).toLowerCase() === "conversations.json");
|
|
13865
11740
|
if (files2.length === 0) {
|
|
13866
11741
|
throw new Error("ChatGPT replay expects conversations.json in --input path.");
|
|
13867
11742
|
}
|
|
@@ -13884,7 +11759,7 @@ function normalizeReplayMessages(source, inputPath) {
|
|
|
13884
11759
|
}
|
|
13885
11760
|
return files2.flatMap((filePath) => {
|
|
13886
11761
|
if (filePath.toLowerCase().endsWith(".jsonl")) {
|
|
13887
|
-
return normalizeOpenCodeExport(
|
|
11762
|
+
return normalizeOpenCodeExport(fs31.readFileSync(filePath, "utf-8"));
|
|
13888
11763
|
}
|
|
13889
11764
|
return normalizeOpenCodeExport(loadJson(filePath));
|
|
13890
11765
|
});
|
|
@@ -13893,7 +11768,7 @@ function normalizeReplayMessages(source, inputPath) {
|
|
|
13893
11768
|
if (files.length === 0) {
|
|
13894
11769
|
throw new Error("OpenClaw replay expects .jsonl session transcript files.");
|
|
13895
11770
|
}
|
|
13896
|
-
return files.flatMap((filePath) => normalizeOpenClawTranscript(
|
|
11771
|
+
return files.flatMap((filePath) => normalizeOpenClawTranscript(fs31.readFileSync(filePath, "utf-8")));
|
|
13897
11772
|
}
|
|
13898
11773
|
function normalizeDateFromTimestamp(timestamp, fallbackDate) {
|
|
13899
11774
|
if (!timestamp) return fallbackDate;
|
|
@@ -13914,8 +11789,8 @@ async function replayCommand(options) {
|
|
|
13914
11789
|
throw new Error(`Invalid range: --from ${fromDate} is after --to ${toDate}.`);
|
|
13915
11790
|
}
|
|
13916
11791
|
const vaultPath = resolveVaultPath({ explicitPath: options.vaultPath });
|
|
13917
|
-
const resolvedInput =
|
|
13918
|
-
if (!
|
|
11792
|
+
const resolvedInput = path29.resolve(options.inputPath);
|
|
11793
|
+
if (!fs31.existsSync(resolvedInput)) {
|
|
13919
11794
|
throw new Error(`Replay input path not found: ${resolvedInput}`);
|
|
13920
11795
|
}
|
|
13921
11796
|
const allMessages = normalizeReplayMessages(source, resolvedInput);
|
|
@@ -13959,7 +11834,7 @@ async function replayCommand(options) {
|
|
|
13959
11834
|
}
|
|
13960
11835
|
await observer.processMessages(messages, {
|
|
13961
11836
|
source,
|
|
13962
|
-
transcriptId:
|
|
11837
|
+
transcriptId: path29.basename(resolvedInput),
|
|
13963
11838
|
timestamp: nowForDate()
|
|
13964
11839
|
});
|
|
13965
11840
|
await observer.flush();
|
|
@@ -13992,7 +11867,7 @@ function registerReplayCommand(program) {
|
|
|
13992
11867
|
}
|
|
13993
11868
|
|
|
13994
11869
|
// src/commands/migrate-observations.ts
|
|
13995
|
-
var
|
|
11870
|
+
var fs32 = __toESM(require("fs"), 1);
|
|
13996
11871
|
function toBackupPath(filePath) {
|
|
13997
11872
|
if (filePath.toLowerCase().endsWith(".md")) {
|
|
13998
11873
|
return `${filePath.slice(0, -3)}.emoji-backup.md`;
|
|
@@ -14039,7 +11914,7 @@ function migrateObservations(vaultPath, options = {}) {
|
|
|
14039
11914
|
let migrated = 0;
|
|
14040
11915
|
let backups = 0;
|
|
14041
11916
|
for (const entry of files) {
|
|
14042
|
-
const raw =
|
|
11917
|
+
const raw = fs32.readFileSync(entry.path, "utf-8");
|
|
14043
11918
|
const { converted, changed } = convertObservationMarkdown(raw);
|
|
14044
11919
|
if (!changed) {
|
|
14045
11920
|
continue;
|
|
@@ -14049,11 +11924,11 @@ function migrateObservations(vaultPath, options = {}) {
|
|
|
14049
11924
|
continue;
|
|
14050
11925
|
}
|
|
14051
11926
|
const backupPath = toBackupPath(entry.path);
|
|
14052
|
-
if (!
|
|
14053
|
-
|
|
11927
|
+
if (!fs32.existsSync(backupPath)) {
|
|
11928
|
+
fs32.copyFileSync(entry.path, backupPath);
|
|
14054
11929
|
backups += 1;
|
|
14055
11930
|
}
|
|
14056
|
-
|
|
11931
|
+
fs32.writeFileSync(entry.path, `${converted.trim()}
|
|
14057
11932
|
`, "utf-8");
|
|
14058
11933
|
}
|
|
14059
11934
|
return {
|
|
@@ -14086,8 +11961,8 @@ function registerMigrateObservationsCommand(program) {
|
|
|
14086
11961
|
}
|
|
14087
11962
|
|
|
14088
11963
|
// src/commands/session-recap.ts
|
|
14089
|
-
var
|
|
14090
|
-
var
|
|
11964
|
+
var fs33 = __toESM(require("fs"), 1);
|
|
11965
|
+
var path30 = __toESM(require("path"), 1);
|
|
14091
11966
|
var DEFAULT_LIMIT2 = 15;
|
|
14092
11967
|
var MAX_LIMIT = 50;
|
|
14093
11968
|
var READ_CHUNK_SIZE = 64 * 1024;
|
|
@@ -14130,33 +12005,33 @@ function normalizeLimit(limit) {
|
|
|
14130
12005
|
return Math.min(MAX_LIMIT, Math.max(1, parsed));
|
|
14131
12006
|
}
|
|
14132
12007
|
function isPathInside(parentPath, candidatePath) {
|
|
14133
|
-
const normalizedParent = parentPath.endsWith(
|
|
12008
|
+
const normalizedParent = parentPath.endsWith(path30.sep) ? parentPath : `${parentPath}${path30.sep}`;
|
|
14134
12009
|
return candidatePath.startsWith(normalizedParent);
|
|
14135
12010
|
}
|
|
14136
12011
|
function resolveSafeTranscriptPath(agentId, sessionId, sessionFile) {
|
|
14137
12012
|
const sessionsDir = getSessionsDir(agentId);
|
|
14138
|
-
if (!
|
|
12013
|
+
if (!fs33.existsSync(sessionsDir)) {
|
|
14139
12014
|
throw new Error(`Sessions directory not found for agent "${agentId}".`);
|
|
14140
12015
|
}
|
|
14141
|
-
const sessionsDirRealPath =
|
|
12016
|
+
const sessionsDirRealPath = fs33.realpathSync(sessionsDir);
|
|
14142
12017
|
const candidatePaths = [];
|
|
14143
12018
|
if (typeof sessionFile === "string" && sessionFile.trim()) {
|
|
14144
|
-
candidatePaths.push(
|
|
12019
|
+
candidatePaths.push(path30.resolve(sessionFile));
|
|
14145
12020
|
}
|
|
14146
12021
|
candidatePaths.push(getSessionFilePath(agentId, sessionId));
|
|
14147
12022
|
for (const candidatePath of candidatePaths) {
|
|
14148
|
-
if (
|
|
14149
|
-
if (!
|
|
12023
|
+
if (path30.extname(candidatePath).toLowerCase() !== ".jsonl") continue;
|
|
12024
|
+
if (!fs33.existsSync(candidatePath)) continue;
|
|
14150
12025
|
let candidateRealPath = "";
|
|
14151
12026
|
try {
|
|
14152
|
-
candidateRealPath =
|
|
12027
|
+
candidateRealPath = fs33.realpathSync(candidatePath);
|
|
14153
12028
|
} catch {
|
|
14154
12029
|
continue;
|
|
14155
12030
|
}
|
|
14156
12031
|
if (!isPathInside(sessionsDirRealPath, candidateRealPath)) {
|
|
14157
12032
|
continue;
|
|
14158
12033
|
}
|
|
14159
|
-
const stat =
|
|
12034
|
+
const stat = fs33.statSync(candidateRealPath);
|
|
14160
12035
|
if (!stat.isFile()) continue;
|
|
14161
12036
|
return candidateRealPath;
|
|
14162
12037
|
}
|
|
@@ -14249,16 +12124,16 @@ function applyOutputBudget(turns) {
|
|
|
14249
12124
|
}
|
|
14250
12125
|
function readRecentTurnsFromTranscript(filePath, limit) {
|
|
14251
12126
|
if (limit <= 0) return [];
|
|
14252
|
-
const fileHandle =
|
|
12127
|
+
const fileHandle = fs33.openSync(filePath, "r");
|
|
14253
12128
|
const collected = [];
|
|
14254
12129
|
let remainder = "";
|
|
14255
12130
|
try {
|
|
14256
|
-
let position =
|
|
12131
|
+
let position = fs33.fstatSync(fileHandle).size;
|
|
14257
12132
|
while (position > 0 && collected.length < limit) {
|
|
14258
12133
|
const readSize = Math.min(READ_CHUNK_SIZE, position);
|
|
14259
12134
|
position -= readSize;
|
|
14260
12135
|
const buffer = Buffer.allocUnsafe(readSize);
|
|
14261
|
-
|
|
12136
|
+
fs33.readSync(fileHandle, buffer, 0, readSize, position);
|
|
14262
12137
|
const chunk = buffer.toString("utf-8");
|
|
14263
12138
|
const text = chunk + remainder;
|
|
14264
12139
|
const lines = text.split("\n");
|
|
@@ -14274,7 +12149,7 @@ function readRecentTurnsFromTranscript(filePath, limit) {
|
|
|
14274
12149
|
if (turn) collected.push(turn);
|
|
14275
12150
|
}
|
|
14276
12151
|
} finally {
|
|
14277
|
-
|
|
12152
|
+
fs33.closeSync(fileHandle);
|
|
14278
12153
|
}
|
|
14279
12154
|
return applyOutputBudget(collected.reverse());
|
|
14280
12155
|
}
|
|
@@ -14352,7 +12227,7 @@ var import_meta4 = {};
|
|
|
14352
12227
|
function readPackageVersion() {
|
|
14353
12228
|
try {
|
|
14354
12229
|
const pkgUrl = new URL("../package.json", import_meta4.url);
|
|
14355
|
-
const pkg = JSON.parse(
|
|
12230
|
+
const pkg = JSON.parse(fs34.readFileSync(pkgUrl, "utf-8"));
|
|
14356
12231
|
return pkg.version ?? "0.0.0";
|
|
14357
12232
|
} catch {
|
|
14358
12233
|
return "0.0.0";
|
|
@@ -14364,12 +12239,10 @@ function registerCommanderCommands(program) {
|
|
|
14364
12239
|
}
|
|
14365
12240
|
// Annotate the CommonJS export names for ESM import in node:
|
|
14366
12241
|
0 && (module.exports = {
|
|
14367
|
-
CLAWVAULT_SERVE_PATH,
|
|
14368
12242
|
ClawVault,
|
|
14369
12243
|
Compressor,
|
|
14370
12244
|
DEFAULT_CATEGORIES,
|
|
14371
12245
|
DEFAULT_CONFIG,
|
|
14372
|
-
DEFAULT_SERVE_PORT,
|
|
14373
12246
|
MEMORY_GRAPH_SCHEMA_VERSION,
|
|
14374
12247
|
MEMORY_TYPES,
|
|
14375
12248
|
Observer,
|
|
@@ -14396,19 +12269,15 @@ function registerCommanderCommands(program) {
|
|
|
14396
12269
|
buildTemplateVariables,
|
|
14397
12270
|
buildTransitionEvent,
|
|
14398
12271
|
checkOpenClawCompatibility,
|
|
14399
|
-
checkPeerClawVault,
|
|
14400
12272
|
classifyQuestion,
|
|
14401
|
-
compareManifests,
|
|
14402
12273
|
compatCommand,
|
|
14403
12274
|
compatibilityExitCode,
|
|
14404
12275
|
completeTask,
|
|
14405
|
-
configureTailscaleServe,
|
|
14406
12276
|
contextCommand,
|
|
14407
12277
|
countBlockedTransitions,
|
|
14408
12278
|
createProject,
|
|
14409
12279
|
createVault,
|
|
14410
12280
|
deterministicInjectMatches,
|
|
14411
|
-
discoverClawVaultPeers,
|
|
14412
12281
|
doctor,
|
|
14413
12282
|
embedCommand,
|
|
14414
12283
|
entitySimilarity,
|
|
@@ -14418,34 +12287,26 @@ function registerCommanderCommands(program) {
|
|
|
14418
12287
|
extractPreferences,
|
|
14419
12288
|
extractTags,
|
|
14420
12289
|
extractWikiLinks,
|
|
14421
|
-
fetchRemoteFile,
|
|
14422
|
-
fetchRemoteManifest,
|
|
14423
12290
|
filterSuperseded,
|
|
14424
12291
|
findNearestVaultPath,
|
|
14425
|
-
findPeer,
|
|
14426
12292
|
findVault,
|
|
14427
12293
|
formatContextMarkdown,
|
|
14428
12294
|
formatKanbanCard,
|
|
14429
12295
|
formatSessionRecapMarkdown,
|
|
14430
12296
|
formatTransitionsTable,
|
|
14431
12297
|
generateKanbanMarkdown,
|
|
14432
|
-
generateVaultManifest,
|
|
14433
12298
|
getConfig,
|
|
14434
12299
|
getConfigValue,
|
|
14435
12300
|
getMemoryGraph,
|
|
14436
12301
|
getObserverStaleness,
|
|
14437
|
-
getOnlinePeers,
|
|
14438
12302
|
getProjectActivity,
|
|
14439
12303
|
getProjectTasks,
|
|
14440
12304
|
getScaledObservationThresholdBytes,
|
|
14441
12305
|
getSupersessionInfo,
|
|
14442
|
-
getTailscaleStatus,
|
|
14443
|
-
getTailscaleVersion,
|
|
14444
12306
|
getVaultPath,
|
|
14445
12307
|
graphCommand,
|
|
14446
12308
|
graphSummary,
|
|
14447
12309
|
hasQmd,
|
|
14448
|
-
hasTailscale,
|
|
14449
12310
|
importKanbanBoard,
|
|
14450
12311
|
indexInjectableItems,
|
|
14451
12312
|
inferContextProfile,
|
|
@@ -14469,7 +12330,6 @@ function registerCommanderCommands(program) {
|
|
|
14469
12330
|
parseKanbanMarkdown,
|
|
14470
12331
|
parseSessionFile,
|
|
14471
12332
|
parseSessionSourceLabel,
|
|
14472
|
-
pushFileToRemote,
|
|
14473
12333
|
qmdEmbed,
|
|
14474
12334
|
qmdUpdate,
|
|
14475
12335
|
queryTransitions,
|
|
@@ -14489,11 +12349,6 @@ function registerCommanderCommands(program) {
|
|
|
14489
12349
|
registerReflectCommand,
|
|
14490
12350
|
registerReplayCommand,
|
|
14491
12351
|
registerReweaveCommand,
|
|
14492
|
-
registerTailscaleCommands,
|
|
14493
|
-
registerTailscaleDiscoverCommand,
|
|
14494
|
-
registerTailscaleServeCommand,
|
|
14495
|
-
registerTailscaleStatusCommand,
|
|
14496
|
-
registerTailscaleSyncCommand,
|
|
14497
12352
|
removeRouteRule,
|
|
14498
12353
|
renderTemplate,
|
|
14499
12354
|
replayCommand,
|
|
@@ -14501,25 +12356,17 @@ function registerCommanderCommands(program) {
|
|
|
14501
12356
|
resetConfig,
|
|
14502
12357
|
resolveContextProfile,
|
|
14503
12358
|
resolveLlmProvider,
|
|
14504
|
-
resolvePeerIP,
|
|
14505
12359
|
resolveVaultPath,
|
|
14506
12360
|
reweave,
|
|
14507
12361
|
reweaveCommand,
|
|
14508
12362
|
runPromptInjection,
|
|
14509
12363
|
runReflection,
|
|
14510
12364
|
sentenceChunk,
|
|
14511
|
-
serveVault,
|
|
14512
12365
|
sessionRecapCommand,
|
|
14513
12366
|
setConfigValue,
|
|
14514
12367
|
setupCommand,
|
|
14515
|
-
stopTailscaleServe,
|
|
14516
12368
|
stripSupersededObservations,
|
|
14517
12369
|
syncKanbanBoard,
|
|
14518
|
-
syncWithPeer,
|
|
14519
|
-
tailscaleDiscoverCommand,
|
|
14520
|
-
tailscaleServeCommand,
|
|
14521
|
-
tailscaleStatusCommand,
|
|
14522
|
-
tailscaleSyncCommand,
|
|
14523
12370
|
testRouteRule,
|
|
14524
12371
|
updateProject,
|
|
14525
12372
|
updateTask
|