Project Files
dist / tools / analysis.js
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.getAnalysisTools = getAnalysisTools;
exports.getMacroTools = getMacroTools;
const sdk_1 = require("@lmstudio/sdk");
const zod_1 = require("zod");
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const document_1 = require("../utils/document");
const config_1 = require("../utils/config");
function getAnalysisTools(workingDir) {
const resolve = (p) => (0, config_1.resolvePath)(p, workingDir);
return [
// 1. word_info
(0, sdk_1.tool)({
name: "word_info",
description: "Get file metadata, word count, paragraph count, image count, and structure info from a .docx file.",
parameters: {
filePath: zod_1.z.string(),
},
implementation: async ({ filePath }) => {
filePath = resolve(filePath);
const err = (0, config_1.checkReadable)(filePath);
if (err)
return `Error: ${err}`;
try {
const doc = await document_1.DocxDocument.load(filePath);
const paras = doc.getParagraphs();
const tables = doc.getTables();
const images = await doc.getImages();
const comments = await doc.getComments();
const meta = await doc.getMetadata();
const nonEmpty = paras.filter(p => p.text.trim().length > 0);
const wordCount = nonEmpty.reduce((sum, p) => sum + p.text.trim().split(/\s+/).length, 0);
const charCount = nonEmpty.reduce((sum, p) => sum + p.text.length, 0);
const headingsByLevel = {};
for (const p of paras.filter(p => p.style.toLowerCase().startsWith("heading"))) {
headingsByLevel[p.style] = (headingsByLevel[p.style] ?? 0) + 1;
}
const fileStat = fs.statSync(filePath);
const ext = path.extname(filePath).toLowerCase();
return JSON.stringify({
file: filePath,
format: ext,
file_size_kb: Math.round(fileStat.size / 1024 * 10) / 10,
metadata: meta,
stats: {
word_count: wordCount,
character_count: charCount,
total_paragraphs: paras.length,
non_empty_paragraphs: nonEmpty.length,
tables: tables.length,
images: images.length,
comments: comments.length,
headings_by_level: headingsByLevel,
},
has_vba: ext === ".docm",
}, null, 2);
}
catch (e) {
return `Error: ${e}`;
}
},
}),
// 2. word_list_headings
(0, sdk_1.tool)({
name: "word_list_headings",
description: "Extract the heading outline of a .docx document — title, level, and paragraph index.",
parameters: {
filePath: zod_1.z.string(),
maxLevel: zod_1.z.number().int().min(1).max(9).optional().describe("Max heading level to include (default: all)"),
},
implementation: async ({ filePath, maxLevel }) => {
filePath = resolve(filePath);
const err = (0, config_1.checkReadable)(filePath);
if (err)
return `Error: ${err}`;
try {
const doc = await document_1.DocxDocument.load(filePath);
const paras = doc.getParagraphs();
const headings = paras
.filter(p => {
if (!p.style.toLowerCase().startsWith("heading"))
return false;
const lvl = parseInt(p.style.replace(/\D/g, "") || "99");
return maxLevel === undefined || lvl <= maxLevel;
})
.map(p => ({
index: p.index,
level: parseInt(p.style.replace(/\D/g, "") || "1"),
style: p.style,
text: p.text,
}));
return JSON.stringify({ file: filePath, heading_count: headings.length, headings }, null, 2);
}
catch (e) {
return `Error: ${e}`;
}
},
}),
// 3. word_extract_comments
(0, sdk_1.tool)({
name: "word_extract_comments",
description: "Extract all review comments from a .docx — author, date, and text of each comment.",
parameters: {
filePath: zod_1.z.string(),
author: zod_1.z.string().optional().describe("Filter by author name (case-insensitive partial match)"),
},
implementation: async ({ filePath, author }) => {
filePath = resolve(filePath);
const err = (0, config_1.checkReadable)(filePath);
if (err)
return `Error: ${err}`;
try {
const doc = await document_1.DocxDocument.load(filePath);
let comments = await doc.getComments();
if (author) {
comments = comments.filter(c => c.author.toLowerCase().includes(author.toLowerCase()));
}
return JSON.stringify({
file: filePath,
total_comments: comments.length,
filtered_by_author: author ?? null,
comments,
}, null, 2);
}
catch (e) {
return `Error: ${e}`;
}
},
}),
// 4. word_extract_images
(0, sdk_1.tool)({
name: "word_extract_images",
description: "List all embedded images in a .docx — name, type, and size. Optionally extract them to disk.",
parameters: {
filePath: zod_1.z.string(),
extractTo: zod_1.z.string().optional().describe("Directory to extract image files to (optional)"),
},
implementation: async ({ filePath, extractTo }) => {
filePath = resolve(filePath);
const err = (0, config_1.checkReadable)(filePath);
if (err)
return `Error: ${err}`;
if (extractTo)
extractTo = resolve(extractTo);
try {
const doc = await document_1.DocxDocument.load(filePath);
const images = await doc.getImages();
const extracted = [];
if (extractTo) {
if (!fs.existsSync(extractTo))
fs.mkdirSync(extractTo, { recursive: true });
const zip = doc.getZip();
for (const img of images) {
const mediaPath = `word/media/${img.name}`;
const file = zip.file(mediaPath);
if (!file)
continue;
const data = await file.async("nodebuffer");
const outPath = path.join(extractTo, img.name);
fs.writeFileSync(outPath, data);
extracted.push(outPath);
}
}
return JSON.stringify({
file: filePath,
image_count: images.length,
images,
extracted_to: extractTo ?? null,
extracted_files: extracted,
}, null, 2);
}
catch (e) {
return `Error: ${e}`;
}
},
}),
// 5. word_diff
(0, sdk_1.tool)({
name: "word_diff",
description: "Compare two .docx files and report paragraphs added, removed, and unchanged.",
parameters: {
fileA: zod_1.z.string().describe("Original (base) file"),
fileB: zod_1.z.string().describe("Modified (new) file"),
},
implementation: async ({ fileA, fileB }) => {
fileA = resolve(fileA);
fileB = resolve(fileB);
const errA = (0, config_1.checkReadable)(fileA);
if (errA)
return `Error (fileA): ${errA}`;
const errB = (0, config_1.checkReadable)(fileB);
if (errB)
return `Error (fileB): ${errB}`;
try {
const docA = await document_1.DocxDocument.load(fileA);
const docB = await document_1.DocxDocument.load(fileB);
const parasA = docA.getParagraphs().filter(p => p.text.trim()).map(p => p.text);
const parasB = docB.getParagraphs().filter(p => p.text.trim()).map(p => p.text);
const setA = new Set(parasA);
const setB = new Set(parasB);
const added = parasB.filter(t => !setA.has(t));
const removed = parasA.filter(t => !setB.has(t));
const common = parasA.filter(t => setB.has(t));
return JSON.stringify({
file_a: fileA,
file_b: fileB,
summary: {
total_a: parasA.length,
total_b: parasB.length,
added: added.length,
removed: removed.length,
unchanged: common.length,
},
added: added.map(t => ({ type: "added", text: t })),
removed: removed.map(t => ({ type: "removed", text: t })),
}, null, 2);
}
catch (e) {
return `Error: ${e}`;
}
},
}),
];
}
function getMacroTools(workingDir) {
const resolve = (p) => (0, config_1.resolvePath)(p, workingDir);
return [
// 6. word_read_vba
(0, sdk_1.tool)({
name: "word_read_vba",
description: "Extract VBA module information from a .docm (macro-enabled Word document).",
parameters: {
filePath: zod_1.z.string().describe("Path to a .docm file"),
},
implementation: async ({ filePath }) => {
filePath = resolve(filePath);
if (!fs.existsSync(filePath))
return `Error: File not found: "${filePath}"`;
if (!filePath.toLowerCase().endsWith(".docm")) {
return `Error: VBA is only available in .docm files. Got: "${filePath}"`;
}
try {
const doc = await document_1.DocxDocument.load(filePath);
const modules = await doc.readVba();
return JSON.stringify({
file: filePath,
vba_modules: Object.keys(modules).length,
modules,
}, null, 2);
}
catch (e) {
return `Error: ${e}`;
}
},
}),
];
}
//# sourceMappingURL=analysis.js.map