@blacksmithers/vaultforge 1.0.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/LICENSE +21 -0
- package/README.md +464 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +705 -0
- package/dist/index.js.map +1 -0
- package/dist/tool-handlers.d.ts +79 -0
- package/dist/tool-handlers.d.ts.map +1 -0
- package/dist/tool-handlers.js +326 -0
- package/dist/tool-handlers.js.map +1 -0
- package/dist/tools/canvas/canvas-create.d.ts +4 -0
- package/dist/tools/canvas/canvas-create.d.ts.map +1 -0
- package/dist/tools/canvas/canvas-create.js +243 -0
- package/dist/tools/canvas/canvas-create.js.map +1 -0
- package/dist/tools/canvas/canvas-patch.d.ts +4 -0
- package/dist/tools/canvas/canvas-patch.d.ts.map +1 -0
- package/dist/tools/canvas/canvas-patch.js +225 -0
- package/dist/tools/canvas/canvas-patch.js.map +1 -0
- package/dist/tools/canvas/canvas-read.d.ts +4 -0
- package/dist/tools/canvas/canvas-read.d.ts.map +1 -0
- package/dist/tools/canvas/canvas-read.js +97 -0
- package/dist/tools/canvas/canvas-read.js.map +1 -0
- package/dist/tools/canvas/canvas-relayout.d.ts +4 -0
- package/dist/tools/canvas/canvas-relayout.d.ts.map +1 -0
- package/dist/tools/canvas/canvas-relayout.js +152 -0
- package/dist/tools/canvas/canvas-relayout.js.map +1 -0
- package/dist/tools/canvas/canvas-utils.d.ts +40 -0
- package/dist/tools/canvas/canvas-utils.d.ts.map +1 -0
- package/dist/tools/canvas/canvas-utils.js +141 -0
- package/dist/tools/canvas/canvas-utils.js.map +1 -0
- package/dist/tools/canvas/layout-engine.d.ts +34 -0
- package/dist/tools/canvas/layout-engine.d.ts.map +1 -0
- package/dist/tools/canvas/layout-engine.js +75 -0
- package/dist/tools/canvas/layout-engine.js.map +1 -0
- package/dist/tools/canvas/types.d.ts +71 -0
- package/dist/tools/canvas/types.d.ts.map +1 -0
- package/dist/tools/canvas/types.js +2 -0
- package/dist/tools/canvas/types.js.map +1 -0
- package/dist/tools/files/batch-rename.d.ts +4 -0
- package/dist/tools/files/batch-rename.d.ts.map +1 -0
- package/dist/tools/files/batch-rename.js +140 -0
- package/dist/tools/files/batch-rename.js.map +1 -0
- package/dist/tools/files/delete-folder.d.ts +17 -0
- package/dist/tools/files/delete-folder.d.ts.map +1 -0
- package/dist/tools/files/delete-folder.js +100 -0
- package/dist/tools/files/delete-folder.js.map +1 -0
- package/dist/tools/files/prune-empty-dirs.d.ts +17 -0
- package/dist/tools/files/prune-empty-dirs.d.ts.map +1 -0
- package/dist/tools/files/prune-empty-dirs.js +106 -0
- package/dist/tools/files/prune-empty-dirs.js.map +1 -0
- package/dist/tools/intelligence/clustering.d.ts +15 -0
- package/dist/tools/intelligence/clustering.d.ts.map +1 -0
- package/dist/tools/intelligence/clustering.js +93 -0
- package/dist/tools/intelligence/clustering.js.map +1 -0
- package/dist/tools/intelligence/tfidf.d.ts +19 -0
- package/dist/tools/intelligence/tfidf.d.ts.map +1 -0
- package/dist/tools/intelligence/tfidf.js +68 -0
- package/dist/tools/intelligence/tfidf.js.map +1 -0
- package/dist/tools/intelligence/vault-suggest.d.ts +7 -0
- package/dist/tools/intelligence/vault-suggest.d.ts.map +1 -0
- package/dist/tools/intelligence/vault-suggest.js +307 -0
- package/dist/tools/intelligence/vault-suggest.js.map +1 -0
- package/dist/tools/intelligence/vault-themes.d.ts +30 -0
- package/dist/tools/intelligence/vault-themes.d.ts.map +1 -0
- package/dist/tools/intelligence/vault-themes.js +88 -0
- package/dist/tools/intelligence/vault-themes.js.map +1 -0
- package/dist/tools/links/backlinks.d.ts +4 -0
- package/dist/tools/links/backlinks.d.ts.map +1 -0
- package/dist/tools/links/backlinks.js +50 -0
- package/dist/tools/links/backlinks.js.map +1 -0
- package/dist/tools/links/link-utils.d.ts +23 -0
- package/dist/tools/links/link-utils.d.ts.map +1 -0
- package/dist/tools/links/link-utils.js +63 -0
- package/dist/tools/links/link-utils.js.map +1 -0
- package/dist/tools/links/update-links.d.ts +16 -0
- package/dist/tools/links/update-links.d.ts.map +1 -0
- package/dist/tools/links/update-links.js +88 -0
- package/dist/tools/links/update-links.js.map +1 -0
- package/dist/tools/metadata/frontmatter.d.ts +12 -0
- package/dist/tools/metadata/frontmatter.d.ts.map +1 -0
- package/dist/tools/metadata/frontmatter.js +190 -0
- package/dist/tools/metadata/frontmatter.js.map +1 -0
- package/dist/tools/notes/edit-regex.d.ts +4 -0
- package/dist/tools/notes/edit-regex.d.ts.map +1 -0
- package/dist/tools/notes/edit-regex.js +142 -0
- package/dist/tools/notes/edit-regex.js.map +1 -0
- package/dist/tools/search/markdown-parser.d.ts +12 -0
- package/dist/tools/search/markdown-parser.d.ts.map +1 -0
- package/dist/tools/search/markdown-parser.js +64 -0
- package/dist/tools/search/markdown-parser.js.map +1 -0
- package/dist/tools/search/orama-engine.d.ts +76 -0
- package/dist/tools/search/orama-engine.d.ts.map +1 -0
- package/dist/tools/search/orama-engine.js +152 -0
- package/dist/tools/search/orama-engine.js.map +1 -0
- package/dist/tools/search/search-reindex.d.ts +7 -0
- package/dist/tools/search/search-reindex.d.ts.map +1 -0
- package/dist/tools/search/search-reindex.js +34 -0
- package/dist/tools/search/search-reindex.js.map +1 -0
- package/dist/tools/search/smart-search.d.ts +7 -0
- package/dist/tools/search/smart-search.d.ts.map +1 -0
- package/dist/tools/search/smart-search.js +102 -0
- package/dist/tools/search/smart-search.js.map +1 -0
- package/dist/vault-index.d.ts +95 -0
- package/dist/vault-index.d.ts.map +1 -0
- package/dist/vault-index.js +432 -0
- package/dist/vault-index.js.map +1 -0
- package/package.json +60 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { rename, mkdir } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { updateWikilinks } from "../links/update-links.js";
|
|
5
|
+
export function registerBatchRename(server, vaultPath, vault) {
|
|
6
|
+
server.tool("batch_rename", "Rename/move files. Explicit pairs or regex pattern on filenames. Auto-updates wikilinks. Dry run by default. Atomic fs.rename preserves metadata.", {
|
|
7
|
+
renames: z
|
|
8
|
+
.array(z.object({ from: z.string(), to: z.string() }))
|
|
9
|
+
.optional()
|
|
10
|
+
.describe("Mode 1: explicit rename pairs"),
|
|
11
|
+
pattern: z
|
|
12
|
+
.object({
|
|
13
|
+
directory: z.string().describe("Directory to search"),
|
|
14
|
+
match: z.string().describe("Regex applied to filename only"),
|
|
15
|
+
replace: z.string().describe("Replacement string ($1, $2 for capture groups)"),
|
|
16
|
+
filter_ext: z.string().default(".md").describe("Extension filter (default: .md)"),
|
|
17
|
+
recursive: z.boolean().default(false).describe("Search subdirectories"),
|
|
18
|
+
})
|
|
19
|
+
.optional()
|
|
20
|
+
.describe("Mode 2: regex pattern on filenames"),
|
|
21
|
+
dry_run: z.boolean().default(true).describe("Preview without executing (default: true)"),
|
|
22
|
+
update_links: z.boolean().default(true).describe("Auto-update wikilinks after rename (default: true)"),
|
|
23
|
+
}, async ({ renames, pattern, dry_run, update_links: doUpdateLinks }) => {
|
|
24
|
+
await vault.waitReady();
|
|
25
|
+
// Build rename pairs
|
|
26
|
+
let pairs = [];
|
|
27
|
+
if (renames && renames.length > 0) {
|
|
28
|
+
// Mode 1: explicit pairs
|
|
29
|
+
for (const r of renames) {
|
|
30
|
+
const fromResolved = vault.resolve(r.from);
|
|
31
|
+
const from = fromResolved?.rel ?? r.from;
|
|
32
|
+
pairs.push({ from, to: r.to });
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
else if (pattern) {
|
|
36
|
+
// Mode 2: regex on filenames
|
|
37
|
+
let regex;
|
|
38
|
+
try {
|
|
39
|
+
regex = new RegExp(pattern.match);
|
|
40
|
+
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
return {
|
|
43
|
+
content: [{ type: "text", text: `ERROR: Invalid regex: ${err.message}` }],
|
|
44
|
+
isError: true,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
const searchDir = pattern.directory;
|
|
48
|
+
const files = pattern.recursive
|
|
49
|
+
? vault.searchPaths(searchDir === "." ? "" : searchDir)
|
|
50
|
+
: vault.listDir(searchDir);
|
|
51
|
+
for (const f of files) {
|
|
52
|
+
if (f.ext !== pattern.filter_ext)
|
|
53
|
+
continue;
|
|
54
|
+
const filename = path.basename(f.rel);
|
|
55
|
+
if (!regex.test(filename))
|
|
56
|
+
continue;
|
|
57
|
+
const newFilename = filename.replace(regex, pattern.replace);
|
|
58
|
+
if (newFilename === filename)
|
|
59
|
+
continue;
|
|
60
|
+
pairs.push({
|
|
61
|
+
from: f.rel,
|
|
62
|
+
to: path.join(path.dirname(f.rel), newFilename).replace(/\\/g, "/"),
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
return {
|
|
68
|
+
content: [{ type: "text", text: "ERROR: Provide either 'renames' or 'pattern'" }],
|
|
69
|
+
isError: true,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
if (pairs.length === 0) {
|
|
73
|
+
return {
|
|
74
|
+
content: [{ type: "text", text: JSON.stringify({ renamed: 0, results: [], dry_run, message: "No files matched" }, null, 2) }],
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
// Pre-flight: detect conflicts
|
|
78
|
+
const targets = new Set();
|
|
79
|
+
const sources = new Set();
|
|
80
|
+
for (const p of pairs) {
|
|
81
|
+
if (targets.has(p.to)) {
|
|
82
|
+
return {
|
|
83
|
+
content: [{ type: "text", text: `ERROR: Duplicate target: ${p.to}` }],
|
|
84
|
+
isError: true,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
targets.add(p.to);
|
|
88
|
+
sources.add(p.from);
|
|
89
|
+
}
|
|
90
|
+
// Detect circular renames
|
|
91
|
+
for (const p of pairs) {
|
|
92
|
+
if (sources.has(p.to) && targets.has(p.from)) {
|
|
93
|
+
return {
|
|
94
|
+
content: [{ type: "text", text: `ERROR: Circular rename detected: ${p.from} ↔ ${p.to}` }],
|
|
95
|
+
isError: true,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
// Check target conflicts with existing files
|
|
100
|
+
for (const p of pairs) {
|
|
101
|
+
if (!sources.has(p.to) && vault.has(p.to)) {
|
|
102
|
+
return {
|
|
103
|
+
content: [{ type: "text", text: `ERROR: Target already exists: ${p.to}` }],
|
|
104
|
+
isError: true,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
const results = [];
|
|
109
|
+
let totalLinksUpdated = 0;
|
|
110
|
+
for (const p of pairs) {
|
|
111
|
+
let linksAffected = 0;
|
|
112
|
+
if (!dry_run) {
|
|
113
|
+
const fromAbs = path.join(vaultPath, p.from);
|
|
114
|
+
const toAbs = path.join(vaultPath, p.to);
|
|
115
|
+
await mkdir(path.dirname(toAbs), { recursive: true });
|
|
116
|
+
await rename(fromAbs, toAbs);
|
|
117
|
+
}
|
|
118
|
+
if (doUpdateLinks) {
|
|
119
|
+
const linkResult = await updateWikilinks(vaultPath, vault, p.from, p.to, dry_run);
|
|
120
|
+
linksAffected = linkResult.totalLinks;
|
|
121
|
+
totalLinksUpdated += linksAffected;
|
|
122
|
+
}
|
|
123
|
+
results.push({ from: p.from, to: p.to, links_affected: linksAffected });
|
|
124
|
+
}
|
|
125
|
+
return {
|
|
126
|
+
content: [
|
|
127
|
+
{
|
|
128
|
+
type: "text",
|
|
129
|
+
text: JSON.stringify({
|
|
130
|
+
renamed: pairs.length,
|
|
131
|
+
links_updated: totalLinksUpdated,
|
|
132
|
+
dry_run,
|
|
133
|
+
results,
|
|
134
|
+
}, null, 2),
|
|
135
|
+
},
|
|
136
|
+
],
|
|
137
|
+
};
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
//# sourceMappingURL=batch-rename.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"batch-rename.js","sourceRoot":"","sources":["../../../src/tools/files/batch-rename.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import type { VaultIndex } from "../../vault-index.js";
|
|
3
|
+
interface ToolResult {
|
|
4
|
+
content: Array<{
|
|
5
|
+
type: "text";
|
|
6
|
+
text: string;
|
|
7
|
+
}>;
|
|
8
|
+
isError?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export declare function handleDeleteFolder(vault: VaultIndex, vaultPath: string, args: {
|
|
11
|
+
path: string;
|
|
12
|
+
recursive: boolean;
|
|
13
|
+
permanent: boolean;
|
|
14
|
+
}): Promise<ToolResult>;
|
|
15
|
+
export declare function registerDeleteFolder(server: McpServer, vaultPath: string, vault: VaultIndex): void;
|
|
16
|
+
export {};
|
|
17
|
+
//# sourceMappingURL=delete-folder.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"delete-folder.d.ts","sourceRoot":"","sources":["../../../src/tools/files/delete-folder.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAIpE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AACvD,UAAU,UAAU;IAClB,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC/C,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAcD,wBAAsB,kBAAkB,CACtC,KAAK,EAAE,UAAU,EACjB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,OAAO,CAAC;IAAC,SAAS,EAAE,OAAO,CAAA;CAAE,GAC7D,OAAO,CAAC,UAAU,CAAC,CAuErB;AAWD,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,SAAS,EACjB,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,UAAU,GAChB,IAAI,CAeN"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { mkdir, readdir, rm, rename as fsRename, stat } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
const PROTECTED_DIRS = [".obsidian", ".vaultforge", ".trash", ".git"];
|
|
5
|
+
function isProtected(relPath) {
|
|
6
|
+
const first = relPath.split("/")[0];
|
|
7
|
+
return PROTECTED_DIRS.includes(first);
|
|
8
|
+
}
|
|
9
|
+
function isVaultRoot(relPath) {
|
|
10
|
+
const norm = relPath.replace(/\\/g, "/").replace(/^\/+/, "").replace(/\/+$/, "");
|
|
11
|
+
return norm === "" || norm === ".";
|
|
12
|
+
}
|
|
13
|
+
export async function handleDeleteFolder(vault, vaultPath, args) {
|
|
14
|
+
const relPath = args.path.replace(/\\/g, "/").replace(/^\/+/, "").replace(/\/+$/, "");
|
|
15
|
+
if (isVaultRoot(relPath)) {
|
|
16
|
+
return { content: [{ type: "text", text: "ERROR: Refusing to delete vault root." }], isError: true };
|
|
17
|
+
}
|
|
18
|
+
if (isProtected(relPath)) {
|
|
19
|
+
return { content: [{ type: "text", text: `ERROR: Refusing to delete protected directory: ${relPath}` }], isError: true };
|
|
20
|
+
}
|
|
21
|
+
const absPath = path.join(vaultPath, relPath);
|
|
22
|
+
// Verify it exists and is a directory
|
|
23
|
+
try {
|
|
24
|
+
const st = await stat(absPath);
|
|
25
|
+
if (!st.isDirectory()) {
|
|
26
|
+
return { content: [{ type: "text", text: `ERROR: Not a directory: ${relPath}` }], isError: true };
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return { content: [{ type: "text", text: `ERROR: Directory not found: ${relPath}` }], isError: true };
|
|
31
|
+
}
|
|
32
|
+
// Count children on disk
|
|
33
|
+
const childCount = await countDiskChildren(absPath);
|
|
34
|
+
if (!args.recursive && childCount > 0) {
|
|
35
|
+
return {
|
|
36
|
+
content: [{
|
|
37
|
+
type: "text",
|
|
38
|
+
text: `ERROR: Directory '${relPath}' is not empty (${childCount} children). Use recursive: true to delete with contents, or delete contents first.`,
|
|
39
|
+
}],
|
|
40
|
+
isError: true,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
// Count index entries BEFORE deletion (fs.watch may clean up after rm)
|
|
44
|
+
const preRemoved = vault.removeDir(relPath);
|
|
45
|
+
let filesRemoved = preRemoved.filesRemoved;
|
|
46
|
+
let dirsRemoved = preRemoved.dirsRemoved;
|
|
47
|
+
if (dirsRemoved === 0)
|
|
48
|
+
dirsRemoved = 1; // at least the dir itself
|
|
49
|
+
if (!args.permanent) {
|
|
50
|
+
// Move to .trash preserving relative path
|
|
51
|
+
const trashDir = path.join(vaultPath, ".trash");
|
|
52
|
+
const trashDest = path.join(trashDir, relPath);
|
|
53
|
+
await mkdir(path.dirname(trashDest), { recursive: true });
|
|
54
|
+
try {
|
|
55
|
+
await fsRename(absPath, trashDest);
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
// Cross-device or other issue — copy then delete
|
|
59
|
+
const { cpSync } = await import("node:fs");
|
|
60
|
+
cpSync(absPath, trashDest, { recursive: true });
|
|
61
|
+
await rm(absPath, { recursive: true, force: true });
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
await rm(absPath, { recursive: true, force: true });
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
content: [{
|
|
69
|
+
type: "text",
|
|
70
|
+
text: JSON.stringify({
|
|
71
|
+
path: relPath,
|
|
72
|
+
deleted: true,
|
|
73
|
+
files_removed: filesRemoved,
|
|
74
|
+
dirs_removed: dirsRemoved,
|
|
75
|
+
permanent: args.permanent,
|
|
76
|
+
}, null, 2),
|
|
77
|
+
}],
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
async function countDiskChildren(absDir) {
|
|
81
|
+
try {
|
|
82
|
+
const entries = await readdir(absDir);
|
|
83
|
+
return entries.length;
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
return 0;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
export function registerDeleteFolder(server, vaultPath, vault) {
|
|
90
|
+
server.tool("delete_folder", "Delete a directory from the vault. Refuses non-empty dirs by default — use recursive: true for non-empty. Moves to .trash by default for safety.", {
|
|
91
|
+
path: z.string().describe("Relative path to the directory"),
|
|
92
|
+
recursive: z.boolean().default(false).describe("If true, delete the folder AND all contents. If false (default), refuse non-empty directories."),
|
|
93
|
+
permanent: z.boolean().default(false).describe("If true, skip .trash and delete permanently. If false (default), move to .trash."),
|
|
94
|
+
}, async ({ path: dirPath, recursive, permanent }) => {
|
|
95
|
+
await vault.waitReady();
|
|
96
|
+
const result = await handleDeleteFolder(vault, vaultPath, { path: dirPath, recursive, permanent });
|
|
97
|
+
return { ...result };
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=delete-folder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"delete-folder.js","sourceRoot":"","sources":["../../../src/tools/files/delete-folder.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import type { VaultIndex } from "../../vault-index.js";
|
|
3
|
+
interface ToolResult {
|
|
4
|
+
content: Array<{
|
|
5
|
+
type: "text";
|
|
6
|
+
text: string;
|
|
7
|
+
}>;
|
|
8
|
+
isError?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export declare function handlePruneEmptyDirs(vault: VaultIndex, vaultPath: string, args: {
|
|
11
|
+
path: string;
|
|
12
|
+
dry_run: boolean;
|
|
13
|
+
exclude: string[];
|
|
14
|
+
}): Promise<ToolResult>;
|
|
15
|
+
export declare function registerPruneEmptyDirs(server: McpServer, vaultPath: string, vault: VaultIndex): void;
|
|
16
|
+
export {};
|
|
17
|
+
//# sourceMappingURL=prune-empty-dirs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prune-empty-dirs.d.ts","sourceRoot":"","sources":["../../../src/tools/files/prune-empty-dirs.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAIpE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AACvD,UAAU,UAAU;IAClB,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC/C,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAID,wBAAsB,oBAAoB,CACxC,KAAK,EAAE,UAAU,EACjB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAA;CAAE,GAC1D,OAAO,CAAC,UAAU,CAAC,CAiGrB;AAED,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,SAAS,EACjB,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,UAAU,GAChB,IAAI,CAeN"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { readdir, rm, rmdir } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
const DEFAULT_EXCLUDES = [".obsidian", ".vaultforge", ".trash", ".git", "Templates"];
|
|
5
|
+
export async function handlePruneEmptyDirs(vault, vaultPath, args) {
|
|
6
|
+
const relStart = args.path.replace(/\\/g, "/").replace(/^\/+/, "").replace(/\/+$/, "") || ".";
|
|
7
|
+
const absStart = relStart === "." ? vaultPath : path.join(vaultPath, relStart);
|
|
8
|
+
const excludeSet = new Set(args.exclude);
|
|
9
|
+
const emptyDirs = [];
|
|
10
|
+
let scanned = 0;
|
|
11
|
+
// Post-order traversal: returns true if the directory is empty after pruning
|
|
12
|
+
async function walk(absDir, relDir) {
|
|
13
|
+
scanned++;
|
|
14
|
+
let entries;
|
|
15
|
+
try {
|
|
16
|
+
entries = await readdir(absDir, { withFileTypes: true });
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
let hasFiles = false;
|
|
22
|
+
let hasNonEmptySubdirs = false;
|
|
23
|
+
let excludedChildCount = 0;
|
|
24
|
+
for (const entry of entries) {
|
|
25
|
+
// Skip hidden files (but let hidden directories through to the excludeSet check)
|
|
26
|
+
if (entry.name.startsWith(".") && !entry.isDirectory())
|
|
27
|
+
continue;
|
|
28
|
+
if (entry.name.startsWith(".") && entry.isDirectory() && !excludeSet.has(entry.name))
|
|
29
|
+
continue;
|
|
30
|
+
const childRel = relDir === "." ? entry.name : `${relDir}/${entry.name}`;
|
|
31
|
+
const childAbs = path.join(absDir, entry.name);
|
|
32
|
+
if (entry.isDirectory()) {
|
|
33
|
+
// Skip excluded directories — track them separately
|
|
34
|
+
if (excludeSet.has(entry.name)) {
|
|
35
|
+
excludedChildCount++;
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
const childEmpty = await walk(childAbs, childRel);
|
|
39
|
+
if (!childEmpty) {
|
|
40
|
+
hasNonEmptySubdirs = true;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
hasFiles = true;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (!hasFiles && !hasNonEmptySubdirs && relDir !== ".") {
|
|
48
|
+
if (excludedChildCount > 0) {
|
|
49
|
+
emptyDirs.push({ path: relDir, reason: "Only contains excluded directories" });
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
const reason = entries.length === 0
|
|
53
|
+
? "No files or subdirectories"
|
|
54
|
+
: "Only contained empty subdirectories (pruned)";
|
|
55
|
+
emptyDirs.push({ path: relDir, reason });
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
await walk(absStart, relStart);
|
|
61
|
+
// Sort bottom-up (deepest paths first) for deletion order
|
|
62
|
+
emptyDirs.sort((a, b) => b.path.split("/").length - a.path.split("/").length);
|
|
63
|
+
let deleted = 0;
|
|
64
|
+
if (!args.dry_run) {
|
|
65
|
+
for (const dir of emptyDirs) {
|
|
66
|
+
const absDir = path.join(vaultPath, dir.path);
|
|
67
|
+
try {
|
|
68
|
+
if (dir.reason.includes("excluded directories")) {
|
|
69
|
+
await rm(absDir, { recursive: true, force: true });
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
await rmdir(absDir);
|
|
73
|
+
}
|
|
74
|
+
vault.removeDir(dir.path);
|
|
75
|
+
deleted++;
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
// Directory may have been already removed or not actually empty
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
content: [{
|
|
84
|
+
type: "text",
|
|
85
|
+
text: JSON.stringify({
|
|
86
|
+
scanned,
|
|
87
|
+
empty_found: emptyDirs.length,
|
|
88
|
+
deleted: args.dry_run ? 0 : deleted,
|
|
89
|
+
dry_run: args.dry_run,
|
|
90
|
+
directories: emptyDirs,
|
|
91
|
+
}, null, 2),
|
|
92
|
+
}],
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
export function registerPruneEmptyDirs(server, vaultPath, vault) {
|
|
96
|
+
server.tool("prune_empty_dirs", "Find and remove empty directories in the vault. Dry run by default — preview before deleting. Bottom-up pruning handles cascading empty dirs.", {
|
|
97
|
+
path: z.string().default(".").describe("Starting directory to scan (default: vault root)"),
|
|
98
|
+
dry_run: z.boolean().default(true).describe("Preview without deleting (default: true). Set false to execute."),
|
|
99
|
+
exclude: z.array(z.string()).default(DEFAULT_EXCLUDES).describe("Directories to skip (default: .obsidian, .vaultforge, .trash, .git, Templates)"),
|
|
100
|
+
}, async ({ path: dirPath, dry_run, exclude }) => {
|
|
101
|
+
await vault.waitReady();
|
|
102
|
+
const result = await handlePruneEmptyDirs(vault, vaultPath, { path: dirPath, dry_run, exclude });
|
|
103
|
+
return { ...result };
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=prune-empty-dirs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prune-empty-dirs.js","sourceRoot":"","sources":["../../../src/tools/files/prune-empty-dirs.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agglomerative clustering using Jaccard similarity on TF-IDF fingerprints.
|
|
3
|
+
*/
|
|
4
|
+
import type { FileFingerprint } from "./tfidf.js";
|
|
5
|
+
export interface ThemeCluster {
|
|
6
|
+
id: string;
|
|
7
|
+
label: string;
|
|
8
|
+
keyTerms: string[];
|
|
9
|
+
files: string[];
|
|
10
|
+
folders: string[];
|
|
11
|
+
coherenceScore: number;
|
|
12
|
+
crossFolder: boolean;
|
|
13
|
+
}
|
|
14
|
+
export declare function clusterFiles(fingerprints: FileFingerprint[], minClusterSize?: number, similarityThreshold?: number): ThemeCluster[];
|
|
15
|
+
//# sourceMappingURL=clustering.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clustering.d.ts","sourceRoot":"","sources":["../../../src/tools/intelligence/clustering.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElD,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,OAAO,CAAC;CACtB;AAgCD,wBAAgB,YAAY,CAC1B,YAAY,EAAE,eAAe,EAAE,EAC/B,cAAc,GAAE,MAAU,EAC1B,mBAAmB,GAAE,MAAa,GACjC,YAAY,EAAE,CAuEhB"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agglomerative clustering using Jaccard similarity on TF-IDF fingerprints.
|
|
3
|
+
*/
|
|
4
|
+
function capitalize(s) {
|
|
5
|
+
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
6
|
+
}
|
|
7
|
+
function jaccardSimilarity(a, b) {
|
|
8
|
+
let intersection = 0;
|
|
9
|
+
for (const t of a) {
|
|
10
|
+
if (b.has(t))
|
|
11
|
+
intersection++;
|
|
12
|
+
}
|
|
13
|
+
const union = a.size + b.size - intersection;
|
|
14
|
+
return union === 0 ? 0 : intersection / union;
|
|
15
|
+
}
|
|
16
|
+
function computeAverageJaccard(files) {
|
|
17
|
+
if (files.length < 2)
|
|
18
|
+
return 1.0;
|
|
19
|
+
const termSets = files.map((f) => new Set(f.topTerms.map((t) => t.term)));
|
|
20
|
+
let totalSim = 0;
|
|
21
|
+
let pairs = 0;
|
|
22
|
+
for (let i = 0; i < termSets.length; i++) {
|
|
23
|
+
for (let j = i + 1; j < termSets.length; j++) {
|
|
24
|
+
totalSim += jaccardSimilarity(termSets[i], termSets[j]);
|
|
25
|
+
pairs++;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return pairs > 0 ? totalSim / pairs : 0;
|
|
29
|
+
}
|
|
30
|
+
export function clusterFiles(fingerprints, minClusterSize = 3, similarityThreshold = 0.15) {
|
|
31
|
+
let clusters = fingerprints.map((fp) => ({
|
|
32
|
+
files: [fp],
|
|
33
|
+
terms: new Set(fp.topTerms.map((t) => t.term)),
|
|
34
|
+
}));
|
|
35
|
+
// Agglomerative merging
|
|
36
|
+
while (true) {
|
|
37
|
+
let bestSim = 0;
|
|
38
|
+
let bestI = -1;
|
|
39
|
+
let bestJ = -1;
|
|
40
|
+
for (let i = 0; i < clusters.length; i++) {
|
|
41
|
+
for (let j = i + 1; j < clusters.length; j++) {
|
|
42
|
+
const sim = jaccardSimilarity(clusters[i].terms, clusters[j].terms);
|
|
43
|
+
if (sim > bestSim) {
|
|
44
|
+
bestSim = sim;
|
|
45
|
+
bestI = i;
|
|
46
|
+
bestJ = j;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (bestSim < similarityThreshold || bestI === -1)
|
|
51
|
+
break;
|
|
52
|
+
// Merge bestJ into bestI
|
|
53
|
+
clusters[bestI].files.push(...clusters[bestJ].files);
|
|
54
|
+
for (const t of clusters[bestJ].terms) {
|
|
55
|
+
clusters[bestI].terms.add(t);
|
|
56
|
+
}
|
|
57
|
+
clusters.splice(bestJ, 1);
|
|
58
|
+
}
|
|
59
|
+
// Convert to ThemeCluster
|
|
60
|
+
return clusters
|
|
61
|
+
.filter((c) => c.files.length >= minClusterSize)
|
|
62
|
+
.map((c) => {
|
|
63
|
+
const folders = [...new Set(c.files.map((f) => f.folder))];
|
|
64
|
+
// Label = top terms by combined TF-IDF
|
|
65
|
+
const termScores = new Map();
|
|
66
|
+
for (const file of c.files) {
|
|
67
|
+
for (const { term, tfidf } of file.topTerms) {
|
|
68
|
+
termScores.set(term, (termScores.get(term) || 0) + tfidf);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
const sortedTerms = [...termScores.entries()]
|
|
72
|
+
.sort(([, a], [, b]) => b - a)
|
|
73
|
+
.slice(0, 6);
|
|
74
|
+
const coherence = computeAverageJaccard(c.files);
|
|
75
|
+
return {
|
|
76
|
+
id: sortedTerms
|
|
77
|
+
.slice(0, 2)
|
|
78
|
+
.map(([t]) => t)
|
|
79
|
+
.join("-"),
|
|
80
|
+
label: sortedTerms
|
|
81
|
+
.slice(0, 3)
|
|
82
|
+
.map(([t]) => capitalize(t))
|
|
83
|
+
.join(" + "),
|
|
84
|
+
keyTerms: sortedTerms.map(([t]) => t),
|
|
85
|
+
files: c.files.map((f) => f.path),
|
|
86
|
+
folders,
|
|
87
|
+
coherenceScore: Math.round(coherence * 100) / 100,
|
|
88
|
+
crossFolder: folders.length > 1,
|
|
89
|
+
};
|
|
90
|
+
})
|
|
91
|
+
.sort((a, b) => b.files.length - a.files.length);
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=clustering.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clustering.js","sourceRoot":"","sources":["../../../src/tools/intelligence/clustering.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TF-IDF computation and tokenizer for vault theme extraction.
|
|
3
|
+
*/
|
|
4
|
+
export declare function tokenize(text: string): string[];
|
|
5
|
+
export interface FileFingerprint {
|
|
6
|
+
path: string;
|
|
7
|
+
title: string;
|
|
8
|
+
topTerms: Array<{
|
|
9
|
+
term: string;
|
|
10
|
+
tfidf: number;
|
|
11
|
+
}>;
|
|
12
|
+
folder: string;
|
|
13
|
+
}
|
|
14
|
+
export declare function computeTfIdf(files: Array<{
|
|
15
|
+
path: string;
|
|
16
|
+
title: string;
|
|
17
|
+
content: string;
|
|
18
|
+
}>): FileFingerprint[];
|
|
19
|
+
//# sourceMappingURL=tfidf.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tfidf.d.ts","sourceRoot":"","sources":["../../../src/tools/intelligence/tfidf.ts"],"names":[],"mappings":"AAAA;;GAEG;AAqBH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAM/C;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACjD,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,YAAY,CAC1B,KAAK,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,GAC7D,eAAe,EAAE,CA4CnB"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TF-IDF computation and tokenizer for vault theme extraction.
|
|
3
|
+
*/
|
|
4
|
+
const STOP_WORDS = new Set([
|
|
5
|
+
// English
|
|
6
|
+
"the", "a", "an", "is", "are", "was", "were", "be", "been", "being",
|
|
7
|
+
"have", "has", "had", "do", "does", "did", "will", "would", "could",
|
|
8
|
+
"should", "may", "might", "can", "shall", "to", "of", "in", "for",
|
|
9
|
+
"on", "with", "at", "by", "from", "this", "that", "these", "those",
|
|
10
|
+
"it", "its", "not", "but", "and", "or", "if", "then", "so", "as",
|
|
11
|
+
"than", "too", "very", "just", "about", "up", "out", "no", "all",
|
|
12
|
+
"also", "each", "which", "their", "there", "them", "they", "we",
|
|
13
|
+
"you", "your", "our", "my", "me", "he", "she", "his", "her",
|
|
14
|
+
// Portuguese
|
|
15
|
+
"de", "da", "do", "das", "dos", "em", "no", "na", "nos", "nas",
|
|
16
|
+
"um", "uma", "uns", "umas", "para", "por", "com", "sem", "sob",
|
|
17
|
+
"que", "se", "como", "mas", "ou", "e", "não", "mais", "muito",
|
|
18
|
+
"também", "já", "ainda", "só", "ao", "aos", "à", "às", "é", "são",
|
|
19
|
+
"foi", "ser", "ter", "está", "este", "esta", "esse", "essa", "isso",
|
|
20
|
+
"ele", "ela", "eles", "elas", "seu", "sua", "seus", "suas",
|
|
21
|
+
]);
|
|
22
|
+
export function tokenize(text) {
|
|
23
|
+
return text
|
|
24
|
+
.toLowerCase()
|
|
25
|
+
.replace(/[^a-záàâãéèêíïóôõöúçñ0-9\s-]/gi, " ")
|
|
26
|
+
.split(/\s+/)
|
|
27
|
+
.filter((t) => t.length > 2 && !STOP_WORDS.has(t));
|
|
28
|
+
}
|
|
29
|
+
export function computeTfIdf(files) {
|
|
30
|
+
// Step 1: tokenize all files
|
|
31
|
+
const tokenized = files.map((f) => ({
|
|
32
|
+
...f,
|
|
33
|
+
tokens: tokenize(f.content),
|
|
34
|
+
}));
|
|
35
|
+
const totalFiles = tokenized.length;
|
|
36
|
+
if (totalFiles === 0)
|
|
37
|
+
return [];
|
|
38
|
+
// Step 2: document frequency
|
|
39
|
+
const df = new Map();
|
|
40
|
+
for (const file of tokenized) {
|
|
41
|
+
const uniqueTerms = new Set(file.tokens);
|
|
42
|
+
for (const term of uniqueTerms) {
|
|
43
|
+
df.set(term, (df.get(term) || 0) + 1);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
// Step 3: TF-IDF per file
|
|
47
|
+
return tokenized.map((file) => {
|
|
48
|
+
const termCounts = new Map();
|
|
49
|
+
for (const token of file.tokens) {
|
|
50
|
+
termCounts.set(token, (termCounts.get(token) || 0) + 1);
|
|
51
|
+
}
|
|
52
|
+
const totalTerms = file.tokens.length || 1;
|
|
53
|
+
const tfidfScores = [];
|
|
54
|
+
for (const [term, count] of termCounts) {
|
|
55
|
+
const tf = count / totalTerms;
|
|
56
|
+
const idf = Math.log(totalFiles / (df.get(term) || 1));
|
|
57
|
+
tfidfScores.push({ term, tfidf: tf * idf });
|
|
58
|
+
}
|
|
59
|
+
tfidfScores.sort((a, b) => b.tfidf - a.tfidf);
|
|
60
|
+
return {
|
|
61
|
+
path: file.path,
|
|
62
|
+
title: file.title,
|
|
63
|
+
topTerms: tfidfScores.slice(0, 15),
|
|
64
|
+
folder: file.path.split("/").slice(0, -1).join("/"),
|
|
65
|
+
};
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=tfidf.js.map
|