@imenam/mcp-documentor 1.0.40 → 1.0.41
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/dist/shared/schemas/doc.schema.d.ts +19 -0
- package/dist/shared/schemas/doc.schema.js +18 -0
- package/dist/src/core/storage-engine.d.ts +3 -1
- package/dist/src/core/storage-engine.js +15 -0
- package/dist/src/gui-worker.js +75 -1
- package/dist/src/index.js +55 -1
- package/dist/src/tools/delete-export-bundle.d.ts +14 -0
- package/dist/src/tools/delete-export-bundle.js +24 -0
- package/dist/src/tools/prepare-export-bundle.d.ts +14 -0
- package/dist/src/tools/prepare-export-bundle.js +40 -0
- package/dist/src/version.d.ts +1 -1
- package/dist/src/version.js +1 -1
- package/gui/dist/assets/{index-Dwlj9iqX.js → index-CtH--sUk.js} +34 -34
- package/gui/dist/assets/index-DWum7n8m.css +1 -0
- package/gui/dist/index.html +2 -2
- package/package.json +2 -1
- package/gui/dist/assets/index-DoIhSuif.css +0 -1
|
@@ -164,6 +164,25 @@ export declare const RestoreDocVersionArgsSchema: z.ZodObject<{
|
|
|
164
164
|
version: z.ZodNumber;
|
|
165
165
|
project: z.ZodOptional<z.ZodString>;
|
|
166
166
|
}, z.core.$strip>;
|
|
167
|
+
export declare const ExportBundleSchema: z.ZodObject<{
|
|
168
|
+
id: z.ZodString;
|
|
169
|
+
name: z.ZodString;
|
|
170
|
+
zipFilename: z.ZodString;
|
|
171
|
+
docs: z.ZodArray<z.ZodString>;
|
|
172
|
+
description: z.ZodOptional<z.ZodString>;
|
|
173
|
+
createdAt: z.ZodString;
|
|
174
|
+
updatedAt: z.ZodString;
|
|
175
|
+
}, z.core.$strip>;
|
|
176
|
+
export type ExportBundle = z.infer<typeof ExportBundleSchema>;
|
|
177
|
+
export declare const PrepareExportBundleArgsSchema: z.ZodObject<{
|
|
178
|
+
name: z.ZodString;
|
|
179
|
+
zip_filename: z.ZodString;
|
|
180
|
+
docs: z.ZodArray<z.ZodString>;
|
|
181
|
+
description: z.ZodOptional<z.ZodString>;
|
|
182
|
+
}, z.core.$strip>;
|
|
183
|
+
export declare const DeleteExportBundleArgsSchema: z.ZodObject<{
|
|
184
|
+
id: z.ZodString;
|
|
185
|
+
}, z.core.$strip>;
|
|
167
186
|
export declare const IPCMessageSchema: z.ZodObject<{
|
|
168
187
|
type: z.ZodString;
|
|
169
188
|
correlationId: z.ZodOptional<z.ZodString>;
|
|
@@ -109,6 +109,24 @@ export const RestoreDocVersionArgsSchema = z.object({
|
|
|
109
109
|
version: z.number().int().positive(),
|
|
110
110
|
project: z.string().optional(),
|
|
111
111
|
});
|
|
112
|
+
export const ExportBundleSchema = z.object({
|
|
113
|
+
id: z.string(),
|
|
114
|
+
name: z.string(),
|
|
115
|
+
zipFilename: z.string(),
|
|
116
|
+
docs: z.array(z.string()),
|
|
117
|
+
description: z.string().optional(),
|
|
118
|
+
createdAt: z.string(),
|
|
119
|
+
updatedAt: z.string(),
|
|
120
|
+
});
|
|
121
|
+
export const PrepareExportBundleArgsSchema = z.object({
|
|
122
|
+
name: z.string().describe("Bundle name (becomes the identifier via slugify)"),
|
|
123
|
+
zip_filename: z.string().describe("ZIP filename to download, e.g. 'my-bundle.zip'"),
|
|
124
|
+
docs: z.array(z.string()).min(1).describe("Doc references: 'project:doc-id' or 'doc-id' for current project"),
|
|
125
|
+
description: z.string().optional().describe("Short description of the bundle"),
|
|
126
|
+
});
|
|
127
|
+
export const DeleteExportBundleArgsSchema = z.object({
|
|
128
|
+
id: z.string().describe("Bundle identifier to delete (slug)"),
|
|
129
|
+
});
|
|
112
130
|
export const IPCMessageSchema = z.object({
|
|
113
131
|
type: z.string(),
|
|
114
132
|
correlationId: z.string().optional(),
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { DocEntry, DocSummary } from "../../shared/schemas/doc.schema.js";
|
|
1
|
+
import type { DocEntry, DocSummary, ExportBundle } from "../../shared/schemas/doc.schema.js";
|
|
2
2
|
import type { HistoryStore } from "./history-store.js";
|
|
3
3
|
interface GlobalConfig {
|
|
4
4
|
currentProject: string;
|
|
@@ -28,6 +28,8 @@ export declare class StorageEngine {
|
|
|
28
28
|
} | null>;
|
|
29
29
|
deleteDoc(id: string, project?: string): Promise<boolean>;
|
|
30
30
|
moveDoc(oldId: string, newId: string, project?: string): Promise<boolean>;
|
|
31
|
+
getBundles(): ExportBundle[];
|
|
32
|
+
saveBundles(bundles: ExportBundle[]): void;
|
|
31
33
|
rewriteLinks(oldId: string, newId: string, project?: string): Promise<string[]>;
|
|
32
34
|
}
|
|
33
35
|
export {};
|
|
@@ -206,6 +206,21 @@ export class StorageEngine {
|
|
|
206
206
|
logger.info(`Doc moved: ${oldFilePath} → ${newFilePath}`);
|
|
207
207
|
return true;
|
|
208
208
|
}
|
|
209
|
+
getBundles() {
|
|
210
|
+
const p = path.join(this.getDataDir(), "export-bundles.json");
|
|
211
|
+
if (!fs.existsSync(p))
|
|
212
|
+
return [];
|
|
213
|
+
try {
|
|
214
|
+
return JSON.parse(fs.readFileSync(p, "utf-8"));
|
|
215
|
+
}
|
|
216
|
+
catch {
|
|
217
|
+
return [];
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
saveBundles(bundles) {
|
|
221
|
+
const p = path.join(this.getDataDir(), "export-bundles.json");
|
|
222
|
+
fs.writeFileSync(p, JSON.stringify(bundles, null, 2), "utf-8");
|
|
223
|
+
}
|
|
209
224
|
async rewriteLinks(oldId, newId, project) {
|
|
210
225
|
const config = this.getConfig();
|
|
211
226
|
const proj = project ?? config.currentProject;
|
package/dist/src/gui-worker.js
CHANGED
|
@@ -8,6 +8,8 @@ import path from "path";
|
|
|
8
8
|
import fs from "fs";
|
|
9
9
|
import { fileURLToPath } from "url";
|
|
10
10
|
import { createLogger, setupLogging } from "@imenam/mcp-gui-interface";
|
|
11
|
+
import matter from "gray-matter";
|
|
12
|
+
import JSZip from "jszip";
|
|
11
13
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
12
14
|
function findProjectRoot(startDir) {
|
|
13
15
|
let current = startDir;
|
|
@@ -20,6 +22,7 @@ function findProjectRoot(startDir) {
|
|
|
20
22
|
return startDir;
|
|
21
23
|
}
|
|
22
24
|
const rootDir = findProjectRoot(__dirname);
|
|
25
|
+
const dataDir = process.env.MCP_DATA_DIR?.trim() || path.join(rootDir, ".docs-data");
|
|
23
26
|
const staticRoot = path.join(rootDir, "gui/dist");
|
|
24
27
|
const absoluteStaticRoot = path.resolve(staticRoot);
|
|
25
28
|
setupLogging({ processLabel: "mcp-documentor:GUI", logDir: path.join(rootDir, ".docs-data", "logs") });
|
|
@@ -116,7 +119,7 @@ function setupIpcErrorHandlers() {
|
|
|
116
119
|
});
|
|
117
120
|
return;
|
|
118
121
|
}
|
|
119
|
-
if (msg && (msg.type === "INITIAL_STATE" || msg.type === "CONFIG_UPDATED")) {
|
|
122
|
+
if (msg && (msg.type === "INITIAL_STATE" || msg.type === "CONFIG_UPDATED" || msg.type === "BUNDLES_UPDATED")) {
|
|
120
123
|
for (const controller of sseControllers) {
|
|
121
124
|
try {
|
|
122
125
|
controller.writeSSE({ data: JSON.stringify(msg), event: "message" });
|
|
@@ -352,6 +355,77 @@ app.post("/api/search", async (c) => {
|
|
|
352
355
|
return c.json({ error: result.error }, 500);
|
|
353
356
|
return c.json(result.data);
|
|
354
357
|
});
|
|
358
|
+
// Export bundles
|
|
359
|
+
app.get("/api/export-bundles", (c) => {
|
|
360
|
+
const bundlesPath = path.join(dataDir, "export-bundles.json");
|
|
361
|
+
if (!fs.existsSync(bundlesPath))
|
|
362
|
+
return c.json([]);
|
|
363
|
+
try {
|
|
364
|
+
return c.json(JSON.parse(fs.readFileSync(bundlesPath, "utf-8")));
|
|
365
|
+
}
|
|
366
|
+
catch {
|
|
367
|
+
return c.json([]);
|
|
368
|
+
}
|
|
369
|
+
});
|
|
370
|
+
app.get("/api/export-zip/:id", async (c) => {
|
|
371
|
+
const id = c.req.param("id");
|
|
372
|
+
const bundlesPath = path.join(dataDir, "export-bundles.json");
|
|
373
|
+
let bundles = [];
|
|
374
|
+
try {
|
|
375
|
+
if (fs.existsSync(bundlesPath))
|
|
376
|
+
bundles = JSON.parse(fs.readFileSync(bundlesPath, "utf-8"));
|
|
377
|
+
}
|
|
378
|
+
catch {
|
|
379
|
+
return c.json({ error: "Failed to read bundles" }, 500);
|
|
380
|
+
}
|
|
381
|
+
const bundle = bundles.find((b) => b.id === id);
|
|
382
|
+
if (!bundle)
|
|
383
|
+
return c.json({ error: "Bundle not found" }, 404);
|
|
384
|
+
let currentProject = "default";
|
|
385
|
+
try {
|
|
386
|
+
const configPath = path.join(dataDir, "config.json");
|
|
387
|
+
if (fs.existsSync(configPath)) {
|
|
388
|
+
currentProject = JSON.parse(fs.readFileSync(configPath, "utf-8")).currentProject ?? "default";
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
catch { /* use default */ }
|
|
392
|
+
const zip = new JSZip();
|
|
393
|
+
for (const ref of bundle.docs) {
|
|
394
|
+
let project;
|
|
395
|
+
let docId;
|
|
396
|
+
if (ref.includes(":")) {
|
|
397
|
+
const colonIdx = ref.indexOf(":");
|
|
398
|
+
project = ref.slice(0, colonIdx);
|
|
399
|
+
docId = ref.slice(colonIdx + 1);
|
|
400
|
+
}
|
|
401
|
+
else {
|
|
402
|
+
project = currentProject;
|
|
403
|
+
docId = ref;
|
|
404
|
+
}
|
|
405
|
+
const docFilePath = path.join(dataDir, "projects", project, ...docId.split("/")) + ".md";
|
|
406
|
+
let content;
|
|
407
|
+
if (!fs.existsSync(docFilePath)) {
|
|
408
|
+
content = `# [Doc not found: ${ref}]\n`;
|
|
409
|
+
}
|
|
410
|
+
else {
|
|
411
|
+
try {
|
|
412
|
+
content = matter(fs.readFileSync(docFilePath, "utf-8")).content.trim();
|
|
413
|
+
}
|
|
414
|
+
catch {
|
|
415
|
+
content = `# [Read error: ${ref}]\n`;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
zip.file(`${project}/${docId}.md`, content);
|
|
419
|
+
}
|
|
420
|
+
const zipBuffer = await zip.generateAsync({ type: "nodebuffer" });
|
|
421
|
+
return new Response(new Uint8Array(zipBuffer), {
|
|
422
|
+
status: 200,
|
|
423
|
+
headers: {
|
|
424
|
+
"Content-Type": "application/zip",
|
|
425
|
+
"Content-Disposition": `attachment; filename="${bundle.zipFilename}"`,
|
|
426
|
+
},
|
|
427
|
+
});
|
|
428
|
+
});
|
|
355
429
|
// Static files
|
|
356
430
|
app.use("/assets/*", serveStatic({ root: path.relative(process.cwd(), absoluteStaticRoot) }));
|
|
357
431
|
// SPA Fallback
|
package/dist/src/index.js
CHANGED
|
@@ -34,6 +34,8 @@ import { listDocVersions } from "./tools/list-doc-versions.js";
|
|
|
34
34
|
import { getDocVersion } from "./tools/get-doc-version.js";
|
|
35
35
|
import { restoreDocVersion } from "./tools/restore-doc-version.js";
|
|
36
36
|
import { reindexEmbeddings } from "./tools/reindex-embeddings.js";
|
|
37
|
+
import { prepareExportBundle } from "./tools/prepare-export-bundle.js";
|
|
38
|
+
import { deleteExportBundle } from "./tools/delete-export-bundle.js";
|
|
37
39
|
const __filename = fileURLToPath(import.meta.url);
|
|
38
40
|
const __dirname = path.dirname(__filename);
|
|
39
41
|
function findProjectRoot(startDir) {
|
|
@@ -150,10 +152,11 @@ async function handleGetInitialState(msg) {
|
|
|
150
152
|
doc.isStale = report.status === "stale";
|
|
151
153
|
}
|
|
152
154
|
}
|
|
155
|
+
const bundles = storageEngine.getBundles();
|
|
153
156
|
broadcastToGUI({
|
|
154
157
|
type: "INITIAL_STATE",
|
|
155
158
|
correlationId: msg.correlationId,
|
|
156
|
-
data: { config, docs, projects: config.projects },
|
|
159
|
+
data: { config, docs, projects: config.projects, bundles },
|
|
157
160
|
timestamp: new Date().toISOString(),
|
|
158
161
|
});
|
|
159
162
|
}
|
|
@@ -898,6 +901,35 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
898
901
|
required: [],
|
|
899
902
|
},
|
|
900
903
|
},
|
|
904
|
+
{
|
|
905
|
+
name: "prepare_export_bundle",
|
|
906
|
+
description: "Create or update a named export bundle: a set of documentation references that the user can download as a ZIP from the GUI. The ZIP contains clean markdown content (no frontmatter). Existing bundle with the same name is replaced.",
|
|
907
|
+
inputSchema: {
|
|
908
|
+
type: "object",
|
|
909
|
+
properties: {
|
|
910
|
+
name: { type: "string", description: "Bundle display name (becomes the identifier via slugify)" },
|
|
911
|
+
zip_filename: { type: "string", description: "ZIP filename to download, e.g. 'my-bundle.zip'" },
|
|
912
|
+
docs: {
|
|
913
|
+
type: "array",
|
|
914
|
+
items: { type: "string" },
|
|
915
|
+
description: "Doc references to include: 'project:doc-id' for cross-project, or 'doc-id' for current project",
|
|
916
|
+
},
|
|
917
|
+
description: { type: "string", description: "Short description of the bundle (optional)" },
|
|
918
|
+
},
|
|
919
|
+
required: ["name", "zip_filename", "docs"],
|
|
920
|
+
},
|
|
921
|
+
},
|
|
922
|
+
{
|
|
923
|
+
name: "delete_export_bundle",
|
|
924
|
+
description: "Delete an export bundle by its identifier (slug).",
|
|
925
|
+
inputSchema: {
|
|
926
|
+
type: "object",
|
|
927
|
+
properties: {
|
|
928
|
+
id: { type: "string", description: "Bundle identifier to delete (slug, e.g. 'my-bundle')" },
|
|
929
|
+
},
|
|
930
|
+
required: ["id"],
|
|
931
|
+
},
|
|
932
|
+
},
|
|
901
933
|
],
|
|
902
934
|
};
|
|
903
935
|
});
|
|
@@ -945,6 +977,28 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
945
977
|
return await restoreDocVersion(historyStore, storageEngine, embeddingEngine, args);
|
|
946
978
|
case "reindex_embeddings":
|
|
947
979
|
return await reindexEmbeddings(storageEngine, embeddingEngine, embeddingStore, args);
|
|
980
|
+
case "prepare_export_bundle": {
|
|
981
|
+
const result = await prepareExportBundle(storageEngine, args);
|
|
982
|
+
if (!result.isError) {
|
|
983
|
+
broadcastToGUI({
|
|
984
|
+
type: "BUNDLES_UPDATED",
|
|
985
|
+
data: { bundles: storageEngine.getBundles() },
|
|
986
|
+
timestamp: new Date().toISOString(),
|
|
987
|
+
});
|
|
988
|
+
}
|
|
989
|
+
return result;
|
|
990
|
+
}
|
|
991
|
+
case "delete_export_bundle": {
|
|
992
|
+
const result = await deleteExportBundle(storageEngine, args);
|
|
993
|
+
if (!result.isError) {
|
|
994
|
+
broadcastToGUI({
|
|
995
|
+
type: "BUNDLES_UPDATED",
|
|
996
|
+
data: { bundles: storageEngine.getBundles() },
|
|
997
|
+
timestamp: new Date().toISOString(),
|
|
998
|
+
});
|
|
999
|
+
}
|
|
1000
|
+
return result;
|
|
1001
|
+
}
|
|
948
1002
|
default:
|
|
949
1003
|
throw new Error(`Tool not found: ${name}`);
|
|
950
1004
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { StorageEngine } from "../core/storage-engine.js";
|
|
2
|
+
export declare function deleteExportBundle(storageEngine: StorageEngine, args: unknown): Promise<{
|
|
3
|
+
isError: boolean;
|
|
4
|
+
content: {
|
|
5
|
+
type: string;
|
|
6
|
+
text: string;
|
|
7
|
+
}[];
|
|
8
|
+
} | {
|
|
9
|
+
content: {
|
|
10
|
+
type: string;
|
|
11
|
+
text: string;
|
|
12
|
+
}[];
|
|
13
|
+
isError?: undefined;
|
|
14
|
+
}>;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { DeleteExportBundleArgsSchema } from "../../shared/schemas/doc.schema.js";
|
|
2
|
+
export async function deleteExportBundle(storageEngine, args) {
|
|
3
|
+
try {
|
|
4
|
+
const validated = DeleteExportBundleArgsSchema.parse(args);
|
|
5
|
+
const bundles = storageEngine.getBundles();
|
|
6
|
+
const index = bundles.findIndex((b) => b.id === validated.id);
|
|
7
|
+
if (index < 0) {
|
|
8
|
+
return {
|
|
9
|
+
isError: true,
|
|
10
|
+
content: [{ type: "text", text: `Bundle with id "${validated.id}" not found.` }],
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
const removed = bundles[index];
|
|
14
|
+
bundles.splice(index, 1);
|
|
15
|
+
storageEngine.saveBundles(bundles);
|
|
16
|
+
return {
|
|
17
|
+
content: [{ type: "text", text: `Export bundle "${removed.name}" (id: "${removed.id}") deleted.` }],
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
catch (err) {
|
|
21
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
22
|
+
return { isError: true, content: [{ type: "text", text: `Error deleting export bundle: ${message}` }] };
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { StorageEngine } from "../core/storage-engine.js";
|
|
2
|
+
export declare function prepareExportBundle(storageEngine: StorageEngine, args: unknown): Promise<{
|
|
3
|
+
content: {
|
|
4
|
+
type: string;
|
|
5
|
+
text: string;
|
|
6
|
+
}[];
|
|
7
|
+
isError?: undefined;
|
|
8
|
+
} | {
|
|
9
|
+
isError: boolean;
|
|
10
|
+
content: {
|
|
11
|
+
type: string;
|
|
12
|
+
text: string;
|
|
13
|
+
}[];
|
|
14
|
+
}>;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { PrepareExportBundleArgsSchema } from "../../shared/schemas/doc.schema.js";
|
|
2
|
+
import { slugify } from "./create-doc.js";
|
|
3
|
+
export async function prepareExportBundle(storageEngine, args) {
|
|
4
|
+
try {
|
|
5
|
+
const validated = PrepareExportBundleArgsSchema.parse(args);
|
|
6
|
+
const id = slugify(validated.name);
|
|
7
|
+
const now = new Date().toISOString();
|
|
8
|
+
const bundles = storageEngine.getBundles();
|
|
9
|
+
const existingIndex = bundles.findIndex((b) => b.id === id);
|
|
10
|
+
const bundle = {
|
|
11
|
+
id,
|
|
12
|
+
name: validated.name,
|
|
13
|
+
zipFilename: validated.zip_filename.endsWith(".zip")
|
|
14
|
+
? validated.zip_filename
|
|
15
|
+
: `${validated.zip_filename}.zip`,
|
|
16
|
+
docs: validated.docs,
|
|
17
|
+
...(validated.description && { description: validated.description }),
|
|
18
|
+
createdAt: existingIndex >= 0 ? bundles[existingIndex].createdAt : now,
|
|
19
|
+
updatedAt: now,
|
|
20
|
+
};
|
|
21
|
+
if (existingIndex >= 0) {
|
|
22
|
+
bundles[existingIndex] = bundle;
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
bundles.push(bundle);
|
|
26
|
+
}
|
|
27
|
+
storageEngine.saveBundles(bundles);
|
|
28
|
+
const action = existingIndex >= 0 ? "updated" : "created";
|
|
29
|
+
return {
|
|
30
|
+
content: [{
|
|
31
|
+
type: "text",
|
|
32
|
+
text: `Export bundle "${validated.name}" (id: "${id}") ${action} with ${validated.docs.length} doc(s). ZIP filename: "${bundle.zipFilename}".`,
|
|
33
|
+
}],
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
catch (err) {
|
|
37
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
38
|
+
return { isError: true, content: [{ type: "text", text: `Error preparing export bundle: ${message}` }] };
|
|
39
|
+
}
|
|
40
|
+
}
|
package/dist/src/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const APP_VERSION = "1.0.
|
|
1
|
+
export declare const APP_VERSION = "1.0.41";
|
package/dist/src/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// Auto-generated version file - do not edit manually
|
|
2
|
-
export const APP_VERSION = '1.0.
|
|
2
|
+
export const APP_VERSION = '1.0.41';
|