@dooc-dev/dooc 0.0.0-stage → 0.0.2
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 +26 -2
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +311 -0
- package/dist/code.global.js +285 -0
- package/dist/compiler/bundle-urls.d.ts +3 -0
- package/dist/compiler/bundle-urls.js +294 -0
- package/dist/compiler/bundle.d.ts +2 -0
- package/dist/compiler/bundle.js +100 -0
- package/dist/compiler/check-prose.d.ts +11 -0
- package/dist/compiler/check-prose.js +138 -0
- package/dist/compiler/check-types.d.ts +44 -0
- package/dist/compiler/check-types.js +1 -0
- package/dist/compiler/check.d.ts +5 -0
- package/dist/compiler/check.js +450 -0
- package/dist/compiler/code-blocks.d.ts +17 -0
- package/dist/compiler/code-blocks.js +198 -0
- package/dist/compiler/code-languages.d.ts +10 -0
- package/dist/compiler/code-languages.js +109 -0
- package/dist/compiler/compile.d.ts +95 -0
- package/dist/compiler/compile.js +410 -0
- package/dist/compiler/config.d.ts +7 -0
- package/dist/compiler/config.js +52 -0
- package/dist/compiler/dialect.d.ts +24 -0
- package/dist/compiler/dialect.js +198 -0
- package/dist/compiler/drawio.d.ts +26 -0
- package/dist/compiler/drawio.js +176 -0
- package/dist/compiler/figures.d.ts +14 -0
- package/dist/compiler/figures.js +221 -0
- package/dist/compiler/file-cache.d.ts +14 -0
- package/dist/compiler/file-cache.js +61 -0
- package/dist/compiler/files.d.ts +29 -0
- package/dist/compiler/files.js +431 -0
- package/dist/compiler/fonts.d.ts +10 -0
- package/dist/compiler/fonts.js +54 -0
- package/dist/compiler/format.d.ts +1 -0
- package/dist/compiler/format.js +42 -0
- package/dist/compiler/from-mdast.d.ts +16 -0
- package/dist/compiler/from-mdast.js +142 -0
- package/dist/compiler/icons.d.ts +14 -0
- package/dist/compiler/icons.js +85 -0
- package/dist/compiler/language-codes.d.ts +7 -0
- package/dist/compiler/language-codes.js +17 -0
- package/dist/compiler/locales.d.ts +7 -0
- package/dist/compiler/locales.js +67 -0
- package/dist/compiler/manifest.d.ts +6 -0
- package/dist/compiler/manifest.js +30 -0
- package/dist/compiler/ops.d.ts +44 -0
- package/dist/compiler/ops.js +362 -0
- package/dist/compiler/public-file.d.ts +2 -0
- package/dist/compiler/public-file.js +93 -0
- package/dist/compiler/serialize.d.ts +3 -0
- package/dist/compiler/serialize.js +16 -0
- package/dist/compiler/terms.d.ts +9 -0
- package/dist/compiler/terms.js +44 -0
- package/dist/compiler/textfile-remote.d.ts +12 -0
- package/dist/compiler/textfile-remote.js +124 -0
- package/dist/compiler/textfile.d.ts +9 -0
- package/dist/compiler/textfile.js +113 -0
- package/dist/compiler/theme-variants.d.ts +4 -0
- package/dist/compiler/theme-variants.js +22 -0
- package/dist/compiler/zip.d.ts +6 -0
- package/dist/compiler/zip.js +48 -0
- package/dist/core/blocks.d.ts +99 -0
- package/dist/core/blocks.js +29 -0
- package/dist/core/code-file.d.ts +12 -0
- package/dist/core/code-file.js +68 -0
- package/dist/core/content-metadata.d.ts +48 -0
- package/dist/core/content-metadata.js +264 -0
- package/dist/core/content-types.d.ts +45 -0
- package/dist/core/content-types.js +1 -0
- package/dist/core/extension-package.d.ts +34 -0
- package/dist/core/extension-package.js +147 -0
- package/dist/core/figure-frame.d.ts +12 -0
- package/dist/core/figure-frame.js +108 -0
- package/dist/core/file-embed.d.ts +13 -0
- package/dist/core/file-embed.js +43 -0
- package/dist/core/files.d.ts +15 -0
- package/dist/core/files.js +59 -0
- package/dist/core/flow-base-css.generated.d.ts +1 -0
- package/dist/core/flow-base-css.generated.js +4 -0
- package/dist/core/flow-css.d.ts +9 -0
- package/dist/core/flow-css.js +54 -0
- package/dist/core/graph-common.d.ts +32 -0
- package/dist/core/graph-common.js +113 -0
- package/dist/core/graph.d.ts +117 -0
- package/dist/core/graph.js +480 -0
- package/dist/core/html.d.ts +29 -0
- package/dist/core/html.js +444 -0
- package/dist/core/icons.d.ts +2 -0
- package/dist/core/icons.js +2 -0
- package/dist/core/inline.d.ts +27 -0
- package/dist/core/inline.js +42 -0
- package/dist/core/language.d.ts +41 -0
- package/dist/core/language.js +44 -0
- package/dist/core/languages/ja.d.ts +2 -0
- package/dist/core/languages/ja.js +5 -0
- package/dist/core/markdown.d.ts +27 -0
- package/dist/core/markdown.js +154 -0
- package/dist/core/previewers.d.ts +37 -0
- package/dist/core/previewers.js +103 -0
- package/dist/core/routing.d.ts +20 -0
- package/dist/core/routing.js +64 -0
- package/dist/core/schema-comments.d.ts +14 -0
- package/dist/core/schema-comments.js +52 -0
- package/dist/core/schema-graph-flow.d.ts +9 -0
- package/dist/core/schema-graph-flow.js +379 -0
- package/dist/core/schema-graph-mount.d.ts +9 -0
- package/dist/core/schema-graph-mount.js +105 -0
- package/dist/core/schema-graph.d.ts +254 -0
- package/dist/core/schema-graph.js +981 -0
- package/dist/core/table-ui.d.ts +2 -0
- package/dist/core/table-ui.js +180 -0
- package/dist/core/taxonomy.d.ts +12 -0
- package/dist/core/taxonomy.js +49 -0
- package/dist/core/term-reference.d.ts +5 -0
- package/dist/core/term-reference.js +19 -0
- package/dist/core/terms-ui.d.ts +6 -0
- package/dist/core/terms-ui.js +22 -0
- package/dist/core/textfile.d.ts +10 -0
- package/dist/core/textfile.js +39 -0
- package/dist/core/theme-variants.d.ts +3 -0
- package/dist/core/theme-variants.js +37 -0
- package/dist/core/toc-ui.d.ts +2 -0
- package/dist/core/toc-ui.js +161 -0
- package/dist/core/video-ui.d.ts +2 -0
- package/dist/core/video-ui.js +253 -0
- package/dist/core/workspace.d.ts +15 -0
- package/dist/core/workspace.js +28 -0
- package/dist/edit.global.js +306 -0
- package/dist/flow.global.js +4959 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +31 -0
- package/dist/mcp.d.ts +24 -0
- package/dist/mcp.js +467 -0
- package/dist/plugins/actions.d.ts +45 -0
- package/dist/plugins/actions.js +125 -0
- package/dist/plugins/blocks.d.ts +93 -0
- package/dist/plugins/blocks.js +100 -0
- package/dist/plugins/index.d.ts +4 -0
- package/dist/plugins/index.js +1 -0
- package/dist/plugins/local-storage.d.ts +18 -0
- package/dist/plugins/local-storage.js +115 -0
- package/dist/plugins/oauth.d.ts +14 -0
- package/dist/plugins/oauth.js +170 -0
- package/dist/plugins/runtime.d.ts +26 -0
- package/dist/plugins/runtime.js +395 -0
- package/dist/plugins/types.d.ts +59 -0
- package/dist/plugins/types.js +1 -0
- package/dist/plugins/view.d.ts +7 -0
- package/dist/plugins/view.js +21 -0
- package/dist/ui.global.js +78 -0
- package/dist/viewer/assets.d.ts +2 -0
- package/dist/viewer/assets.js +3 -0
- package/dist/viewer/build.d.ts +12 -0
- package/dist/viewer/build.js +55 -0
- package/dist/viewer/bundle-network.d.ts +2 -0
- package/dist/viewer/bundle-network.js +88 -0
- package/dist/viewer/code-file.d.ts +20 -0
- package/dist/viewer/code-file.js +42 -0
- package/dist/viewer/editor-tools.d.ts +38 -0
- package/dist/viewer/editor-tools.js +211 -0
- package/dist/viewer/extension-pack.d.ts +2 -0
- package/dist/viewer/extension-pack.js +36 -0
- package/dist/viewer/extensions.d.ts +10 -0
- package/dist/viewer/extensions.js +302 -0
- package/dist/viewer/file-references.d.ts +14 -0
- package/dist/viewer/file-references.js +38 -0
- package/dist/viewer/font-picker.d.ts +4 -0
- package/dist/viewer/font-picker.js +145 -0
- package/dist/viewer/font-settings.d.ts +11 -0
- package/dist/viewer/font-settings.js +50 -0
- package/dist/viewer/fonts/LICENSE.txt +119 -0
- package/dist/viewer/fonts/udev-gothic-regular.woff2 +0 -0
- package/dist/viewer/fonts.d.ts +2 -0
- package/dist/viewer/fonts.js +13 -0
- package/dist/viewer/git-diff.d.ts +3 -0
- package/dist/viewer/git-diff.js +33 -0
- package/dist/viewer/git-types.d.ts +73 -0
- package/dist/viewer/git-types.js +1 -0
- package/dist/viewer/git.d.ts +5 -0
- package/dist/viewer/git.js +337 -0
- package/dist/viewer/hmr.d.ts +53 -0
- package/dist/viewer/hmr.js +337 -0
- package/dist/viewer/i18n.d.ts +6 -0
- package/dist/viewer/i18n.js +65 -0
- package/dist/viewer/ja-lint.d.ts +3 -0
- package/dist/viewer/ja-lint.js +44 -0
- package/dist/viewer/language-picker.d.ts +2 -0
- package/dist/viewer/language-picker.js +233 -0
- package/dist/viewer/languages.d.ts +11 -0
- package/dist/viewer/languages.js +20 -0
- package/dist/viewer/locales/eng.json +317 -0
- package/dist/viewer/locales/jpn.json +317 -0
- package/dist/viewer/locales/zho.json +317 -0
- package/dist/viewer/nav-focus.d.ts +2 -0
- package/dist/viewer/nav-focus.js +64 -0
- package/dist/viewer/plugin-blocks.d.ts +3 -0
- package/dist/viewer/plugin-blocks.js +115 -0
- package/dist/viewer/plugin-config.d.ts +35 -0
- package/dist/viewer/plugin-config.js +57 -0
- package/dist/viewer/plugin-view.d.ts +2 -0
- package/dist/viewer/plugin-view.js +6 -0
- package/dist/viewer/plugins.d.ts +12 -0
- package/dist/viewer/plugins.js +267 -0
- package/dist/viewer/resize.d.ts +4 -0
- package/dist/viewer/resize.js +238 -0
- package/dist/viewer/resource-library.d.ts +29 -0
- package/dist/viewer/resource-library.js +104 -0
- package/dist/viewer/search.d.ts +16 -0
- package/dist/viewer/search.js +358 -0
- package/dist/viewer/serve.d.ts +210 -0
- package/dist/viewer/serve.js +3312 -0
- package/dist/viewer/site-icon.d.ts +8 -0
- package/dist/viewer/site-icon.js +56 -0
- package/dist/viewer/textfile.d.ts +9 -0
- package/dist/viewer/textfile.js +50 -0
- package/dist/viewer/theme-picker.d.ts +2 -0
- package/dist/viewer/theme-picker.js +135 -0
- package/dist/viewer/workspace.d.ts +4 -0
- package/dist/viewer/workspace.js +47 -0
- package/package.json +128 -5
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export * from "./core/blocks.js";
|
|
2
|
+
export * from "./core/content-metadata.js";
|
|
3
|
+
export type * from "./core/content-types.js";
|
|
4
|
+
export * from "./core/figure-frame.js";
|
|
5
|
+
export * from "./core/graph.js";
|
|
6
|
+
export * from "./core/graph-common.js";
|
|
7
|
+
export * from "./core/html.js";
|
|
8
|
+
export * from "./core/inline.js";
|
|
9
|
+
export * from "./core/language.js";
|
|
10
|
+
export * from "./core/languages/ja.js";
|
|
11
|
+
export * from "./core/previewers.js";
|
|
12
|
+
export * from "./core/routing.js";
|
|
13
|
+
export * from "./core/schema-graph.js";
|
|
14
|
+
export * from "./core/taxonomy.js";
|
|
15
|
+
export * from "./core/terms-ui.js";
|
|
16
|
+
export { mountThemeVariants, themeVariantFile } from "./core/theme-variants.js";
|
|
17
|
+
export * from "./core/workspace.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// Browser-safe content types, parsers and rendering helpers. No project data is bundled.
|
|
2
|
+
export * from "./core/blocks.js";
|
|
3
|
+
export * from "./core/content-metadata.js";
|
|
4
|
+
// Isolated figure rendering (sandboxed iframe) and its parent-side mount (theme following /
|
|
5
|
+
// html figure height).
|
|
6
|
+
export * from "./core/figure-frame.js";
|
|
7
|
+
// node / edge DSL (`graphth`): parse / graph model / deterministic layout via Dagre.
|
|
8
|
+
// graph-common.js is the SoT for the geometry parts shared with the schema graph (text width
|
|
9
|
+
// estimation / orthogonal routing / uid).
|
|
10
|
+
export * from "./core/graph.js";
|
|
11
|
+
export * from "./core/graph-common.js";
|
|
12
|
+
// Block AST → HTML renderer, shared with the viewer.
|
|
13
|
+
export * from "./core/html.js";
|
|
14
|
+
export * from "./core/inline.js";
|
|
15
|
+
// Language socket: the adapter interface + generic rules, and the built-in Japanese adapter
|
|
16
|
+
// (browser-safe part; its prose lint is Node-only and lives in the viewer).
|
|
17
|
+
export * from "./core/language.js";
|
|
18
|
+
export * from "./core/languages/ja.js";
|
|
19
|
+
export * from "./core/previewers.js";
|
|
20
|
+
export * from "./core/routing.js";
|
|
21
|
+
// table DSL (`schemath`): parse / graph model / deterministic layout. html.ts uses it for
|
|
22
|
+
// rendering, and it also serves as the model source when consumers build their own UI
|
|
23
|
+
// (interactive graphs etc.).
|
|
24
|
+
export * from "./core/schema-graph.js";
|
|
25
|
+
// The schema graph's interactive UI (React Flow) requires react, so it is imported not from
|
|
26
|
+
// this react-free entry but from the subpath export `./flow` (dist/core/schema-graph-mount.js)
|
|
27
|
+
// and, for script tags, `./flow.global.js`.
|
|
28
|
+
export * from "./core/taxonomy.js";
|
|
29
|
+
export * from "./core/terms-ui.js";
|
|
30
|
+
export { mountThemeVariants, themeVariantFile } from "./core/theme-variants.js";
|
|
31
|
+
export * from "./core/workspace.js";
|
package/dist/mcp.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type CompileOptions } from "./compiler/compile.js";
|
|
2
|
+
import type { GitOrigin } from "./viewer/git-types.js";
|
|
3
|
+
type Args = Record<string, unknown>;
|
|
4
|
+
interface Property {
|
|
5
|
+
type: string | string[];
|
|
6
|
+
description?: string;
|
|
7
|
+
enum?: readonly string[];
|
|
8
|
+
items?: Property;
|
|
9
|
+
minimum?: number;
|
|
10
|
+
}
|
|
11
|
+
interface Tool {
|
|
12
|
+
description: string;
|
|
13
|
+
props: Record<string, Property>;
|
|
14
|
+
required?: string[];
|
|
15
|
+
run: (a: Args) => unknown;
|
|
16
|
+
}
|
|
17
|
+
export interface McpOptions extends CompileOptions {
|
|
18
|
+
gitOrigin?: GitOrigin;
|
|
19
|
+
}
|
|
20
|
+
export declare const createTools: (root: string, options?: McpOptions) => Record<string, Tool>;
|
|
21
|
+
/** One request → response (undefined for notifications). */
|
|
22
|
+
export declare const handleRpc: (tools: Record<string, Tool>, msg: unknown) => Promise<object | undefined>;
|
|
23
|
+
export declare const startMcp: (root: string, options?: McpOptions) => Promise<void>;
|
|
24
|
+
export {};
|
package/dist/mcp.js
ADDED
|
@@ -0,0 +1,467 @@
|
|
|
1
|
+
// stdio MCP server that reads and writes the docs root (`dooc mcp [root]`). MCP is
|
|
2
|
+
// newline-delimited JSON-RPC 2.0 and we only use initialize / tools/list / tools/call, so we
|
|
3
|
+
// speak it directly without the SDK. stdout is for the protocol only; logs go to stderr.
|
|
4
|
+
import { randomUUID } from "node:crypto";
|
|
5
|
+
import { mkdirSync, readFileSync, realpathSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
6
|
+
import { dirname, join, relative } from "node:path";
|
|
7
|
+
import { createInterface } from "node:readline";
|
|
8
|
+
import { checkSite } from "./compiler/check.js";
|
|
9
|
+
import { CHECK_KINDS } from "./compiler/check-types.js";
|
|
10
|
+
import { createCompiler } from "./compiler/compile.js";
|
|
11
|
+
import { filesSnapshot, updateFiles, uploadFile } from "./compiler/files.js";
|
|
12
|
+
import { addProjectFont, listProjectFonts, removeProjectFont } from "./compiler/fonts.js";
|
|
13
|
+
import { createDocOps, isOpError } from "./compiler/ops.js";
|
|
14
|
+
import { readTextfile, textfilePath, writeTextfile } from "./compiler/textfile.js";
|
|
15
|
+
import { localFile } from "./core/files.js";
|
|
16
|
+
import { docDisplayName } from "./core/taxonomy.js";
|
|
17
|
+
import { buildBundle } from "./viewer/build.js";
|
|
18
|
+
import { createGitHandler, validateGitOrigin } from "./viewer/git.js";
|
|
19
|
+
import { resourceIndex } from "./viewer/resource-library.js";
|
|
20
|
+
const str = { type: "string" };
|
|
21
|
+
const num = { type: "integer", minimum: 0 };
|
|
22
|
+
const bool = { type: "boolean" };
|
|
23
|
+
const strings = { type: "array", items: str };
|
|
24
|
+
export const createTools = (root, options = {}) => {
|
|
25
|
+
const compiler = createCompiler(root, options);
|
|
26
|
+
const ops = createDocOps(root);
|
|
27
|
+
const origin = options.gitOrigin && validateGitOrigin(options.gitOrigin);
|
|
28
|
+
const git = createGitHandler(root, () => origin);
|
|
29
|
+
const fileArg = (value) => {
|
|
30
|
+
if (!localFile(value))
|
|
31
|
+
throw new Error("Expected a local bundle file path");
|
|
32
|
+
if (!localFile(relative(realpathSync(root), textfilePath(root, value)).split("\\").join("/")))
|
|
33
|
+
throw new Error("Expected a local bundle file path");
|
|
34
|
+
return value;
|
|
35
|
+
};
|
|
36
|
+
const optStr = (v) => typeof v === "string" && v !== "" ? v : undefined;
|
|
37
|
+
const docErrors = (id) => {
|
|
38
|
+
const compiled = compiler.compile();
|
|
39
|
+
const file = compiled.docs.find((doc) => doc.id === id)?.file;
|
|
40
|
+
return file ? (compiled.sources.get(file)?.errors ?? []) : [];
|
|
41
|
+
};
|
|
42
|
+
return {
|
|
43
|
+
build: {
|
|
44
|
+
description: "Build dist/bundle.html or dist/bundle.zip. url_preview=embed snapshots embedded URL content; network preserves URL references. Defaults to embed. Returns the output path, byte count and document count.",
|
|
45
|
+
props: {
|
|
46
|
+
format: { ...str, enum: ["html", "zip"] },
|
|
47
|
+
url_preview: { ...str, enum: ["embed", "network"] },
|
|
48
|
+
},
|
|
49
|
+
required: ["format"],
|
|
50
|
+
run: async (a) => {
|
|
51
|
+
const file = `dist/bundle.${a.format}`;
|
|
52
|
+
textfilePath(root, file);
|
|
53
|
+
const bundle = await buildBundle(root, {
|
|
54
|
+
format: a.format,
|
|
55
|
+
urlPreview: (a.url_preview ?? "embed"),
|
|
56
|
+
});
|
|
57
|
+
const path = textfilePath(root, file);
|
|
58
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
59
|
+
const temp = join(dirname(path), `.bundle-${randomUUID()}.tmp`);
|
|
60
|
+
try {
|
|
61
|
+
writeFileSync(temp, bundle.data, { flag: "wx" });
|
|
62
|
+
renameSync(temp, path);
|
|
63
|
+
}
|
|
64
|
+
finally {
|
|
65
|
+
rmSync(temp, { force: true });
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
file,
|
|
69
|
+
path,
|
|
70
|
+
bytes: Buffer.byteLength(bundle.data),
|
|
71
|
+
docs: bundle.docs,
|
|
72
|
+
url_preview: a.url_preview ?? "embed",
|
|
73
|
+
};
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
list_files: {
|
|
77
|
+
description: "List bundle files, sizes, layout and document mappings. Save revision for update_files.",
|
|
78
|
+
props: {},
|
|
79
|
+
run: () => filesSnapshot(root),
|
|
80
|
+
},
|
|
81
|
+
read_file: {
|
|
82
|
+
description: "Read a local UTF-8 bundle file, including dooc.yaml, translations and theme variants. Save content as the base for write_file.",
|
|
83
|
+
props: { file: str },
|
|
84
|
+
required: ["file"],
|
|
85
|
+
run: (a) => ({ file: fileArg(a.file), content: readTextfile(root, fileArg(a.file)) }),
|
|
86
|
+
},
|
|
87
|
+
write_file: {
|
|
88
|
+
description: "Create or update a local UTF-8 bundle file. base must equal the content from read_file, or null to create a new file. Conflicting edits are never overwritten.",
|
|
89
|
+
props: { file: str, content: str, base: { type: ["string", "null"] } },
|
|
90
|
+
required: ["file", "content", "base"],
|
|
91
|
+
run: (a) => writeTextfile(root, fileArg(a.file), a.content, a.base)
|
|
92
|
+
? { file: a.file, ok: true }
|
|
93
|
+
: { error: "File changed. Read it again before writing.", status: 409 },
|
|
94
|
+
},
|
|
95
|
+
upload_file: {
|
|
96
|
+
description: "Import a binary or text file from base64 data using the bundle file layout. Existing files are preserved with a unique name. Maximum 64 MiB.",
|
|
97
|
+
props: { name: str, data: { ...str, description: "Canonical base64 file bytes" } },
|
|
98
|
+
required: ["name", "data"],
|
|
99
|
+
run: (a) => uploadFile(root, a),
|
|
100
|
+
},
|
|
101
|
+
update_files: {
|
|
102
|
+
description: "Rename a file (file, name), move files/folders (paths, directory, optional before), change automatic layout (enabled), or restructure by extension. Updates document references and variants. Requires revision from list_files.",
|
|
103
|
+
props: {
|
|
104
|
+
action: { ...str, enum: ["rename", "move", "layout", "restructure"] },
|
|
105
|
+
revision: str,
|
|
106
|
+
file: str,
|
|
107
|
+
name: str,
|
|
108
|
+
paths: strings,
|
|
109
|
+
directory: str,
|
|
110
|
+
before: str,
|
|
111
|
+
enabled: bool,
|
|
112
|
+
},
|
|
113
|
+
required: ["action", "revision"],
|
|
114
|
+
run: (a) => updateFiles(root, a),
|
|
115
|
+
},
|
|
116
|
+
list_resources: {
|
|
117
|
+
description: "List Statics: dictionary terms, code blocks and external URLs with their source files and line numbers.",
|
|
118
|
+
props: {},
|
|
119
|
+
run: () => resourceIndex(root, filesSnapshot(root).files.map((file) => file.path)),
|
|
120
|
+
},
|
|
121
|
+
list_fonts: {
|
|
122
|
+
description: "List registered project fonts. Editor and viewer font selection remains a browser display preference.",
|
|
123
|
+
props: {},
|
|
124
|
+
run: () => ({ fonts: listProjectFonts(root) }),
|
|
125
|
+
},
|
|
126
|
+
add_font: {
|
|
127
|
+
description: "Copy a local TTF, OTF, WOFF or WOFF2 font into .dooc/fonts. Existing names are never overwritten.",
|
|
128
|
+
props: { path: { ...str, description: "Local source font path" } },
|
|
129
|
+
required: ["path"],
|
|
130
|
+
run: (a) => ({ file: addProjectFont(root, a.path) }),
|
|
131
|
+
},
|
|
132
|
+
delete_font: {
|
|
133
|
+
description: "Remove a registered project font by the name returned by list_fonts.",
|
|
134
|
+
props: { name: str },
|
|
135
|
+
required: ["name"],
|
|
136
|
+
run: (a) => {
|
|
137
|
+
removeProjectFont(root, a.name);
|
|
138
|
+
return { ok: true };
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
git: {
|
|
142
|
+
description: "Git workflow in the repository containing this bundle. Read first; every other action except branches requires that state's version. diff/stage/unstage use path; commit uses message; branch creates and switches to branch; switch accepts stash=true to save dirty work in the single stash slot. Origin operations require an externally injected provider. createPr needs title/body/base/draft; updatePr needs number/title/body/revision; mergePr needs number/method/revision. PR revision must be JSON.stringify of the PR returned by read. Read again after each action.",
|
|
143
|
+
props: {
|
|
144
|
+
action: {
|
|
145
|
+
...str,
|
|
146
|
+
enum: [
|
|
147
|
+
"read",
|
|
148
|
+
"branches",
|
|
149
|
+
"diff",
|
|
150
|
+
"stage",
|
|
151
|
+
"unstage",
|
|
152
|
+
"commit",
|
|
153
|
+
"branch",
|
|
154
|
+
"switch",
|
|
155
|
+
"stash",
|
|
156
|
+
"restoreStash",
|
|
157
|
+
"fetch",
|
|
158
|
+
"pull",
|
|
159
|
+
"push",
|
|
160
|
+
"createPr",
|
|
161
|
+
"updatePr",
|
|
162
|
+
"mergePr",
|
|
163
|
+
],
|
|
164
|
+
},
|
|
165
|
+
version: str,
|
|
166
|
+
path: str,
|
|
167
|
+
message: str,
|
|
168
|
+
branch: str,
|
|
169
|
+
stash: bool,
|
|
170
|
+
title: str,
|
|
171
|
+
body: str,
|
|
172
|
+
base: str,
|
|
173
|
+
draft: bool,
|
|
174
|
+
number: { ...num, minimum: 1 },
|
|
175
|
+
revision: str,
|
|
176
|
+
method: { ...str, enum: ["merge", "squash", "rebase"] },
|
|
177
|
+
},
|
|
178
|
+
required: ["action"],
|
|
179
|
+
run: git,
|
|
180
|
+
},
|
|
181
|
+
list_docs: {
|
|
182
|
+
description: "The doc tree (in manifest display order) and figures. Each doc has id / file / title / parent / depth.",
|
|
183
|
+
props: {},
|
|
184
|
+
run: () => {
|
|
185
|
+
const { docs, figures } = compiler.compile();
|
|
186
|
+
const depth = new Map();
|
|
187
|
+
return {
|
|
188
|
+
docs: docs.map((d) => {
|
|
189
|
+
const dep = d.parent == null ? 0 : (depth.get(d.parent) ?? 0) + 1;
|
|
190
|
+
depth.set(d.id, dep);
|
|
191
|
+
return {
|
|
192
|
+
id: d.id,
|
|
193
|
+
file: d.file,
|
|
194
|
+
title: docDisplayName(d, d.id),
|
|
195
|
+
parent: d.parent,
|
|
196
|
+
depth: dep,
|
|
197
|
+
};
|
|
198
|
+
}),
|
|
199
|
+
figures: figures.map((f) => ({
|
|
200
|
+
name: f.name,
|
|
201
|
+
file: f.file,
|
|
202
|
+
title: f.title,
|
|
203
|
+
})),
|
|
204
|
+
};
|
|
205
|
+
},
|
|
206
|
+
},
|
|
207
|
+
search_docs: {
|
|
208
|
+
description: "Find docs by case-insensitive substring match on id / title / body. Includes a snippet around each match.",
|
|
209
|
+
props: { query: { ...str, description: "Search query" } },
|
|
210
|
+
required: ["query"],
|
|
211
|
+
run: (a) => {
|
|
212
|
+
const q = String(a.query ?? "").toLowerCase();
|
|
213
|
+
return compiler.compile().docs.flatMap((d) => {
|
|
214
|
+
const title = docDisplayName(d, d.id);
|
|
215
|
+
const i = d.body.toLowerCase().indexOf(q);
|
|
216
|
+
if (i === -1 && !`${d.id}\n${title}`.toLowerCase().includes(q)) {
|
|
217
|
+
return [];
|
|
218
|
+
}
|
|
219
|
+
const snippet = i === -1 ? "" : d.body.slice(Math.max(0, i - 60), i + q.length + 60);
|
|
220
|
+
return [{ id: d.id, title, snippet }];
|
|
221
|
+
});
|
|
222
|
+
},
|
|
223
|
+
},
|
|
224
|
+
read_doc: {
|
|
225
|
+
description: "A doc's raw md (including front-matter) and its validation errors. Figure links use file paths; list_docs maps each file to its read_figure name.",
|
|
226
|
+
props: { id: { ...str, description: "doc id" } },
|
|
227
|
+
required: ["id"],
|
|
228
|
+
run: (a) => {
|
|
229
|
+
const id = String(a.id);
|
|
230
|
+
const compiled = compiler.compile();
|
|
231
|
+
const doc = compiled.docs.find((d) => d.id === id);
|
|
232
|
+
return doc
|
|
233
|
+
? {
|
|
234
|
+
file: doc.file,
|
|
235
|
+
raw: readTextfile(root, doc.file),
|
|
236
|
+
errors: compiled.sources.get(doc.file)?.errors ?? [],
|
|
237
|
+
}
|
|
238
|
+
: { error: `doc "${id}" not found`, status: 404 };
|
|
239
|
+
},
|
|
240
|
+
},
|
|
241
|
+
read_figure: {
|
|
242
|
+
description: "A figure's markup (SVG / HTML; raster images as an <img> data URI).",
|
|
243
|
+
props: { name: { ...str, description: "Figure name (without extension)" } },
|
|
244
|
+
required: ["name"],
|
|
245
|
+
run: (a) => compiler.compile().figures.find((f) => f.name === a.name) ?? {
|
|
246
|
+
error: `figure "${String(a.name)}" not found`,
|
|
247
|
+
status: 404,
|
|
248
|
+
},
|
|
249
|
+
},
|
|
250
|
+
check: {
|
|
251
|
+
description: "Check rendering, translations and theme variants before building. Returns the CLI CheckReport plus the legacy errors list.",
|
|
252
|
+
props: {
|
|
253
|
+
only: { type: "array", items: { ...str, enum: CHECK_KINDS } },
|
|
254
|
+
allow_shared_svg: bool,
|
|
255
|
+
warnings_as_errors: bool,
|
|
256
|
+
},
|
|
257
|
+
run: (a) => {
|
|
258
|
+
const report = checkSite(root, {
|
|
259
|
+
only: a.only,
|
|
260
|
+
allowSharedSvg: a.allow_shared_svg === true,
|
|
261
|
+
warningsAsErrors: a.warnings_as_errors === true,
|
|
262
|
+
});
|
|
263
|
+
return {
|
|
264
|
+
...report,
|
|
265
|
+
errors: report.diagnostics
|
|
266
|
+
.filter((d) => d.severity === "error")
|
|
267
|
+
.map((d) => `${d.file}: ${d.message}`),
|
|
268
|
+
};
|
|
269
|
+
},
|
|
270
|
+
},
|
|
271
|
+
add_doc: {
|
|
272
|
+
description: "Create a new doc using the file layout, appended as the last child of parent in the manifest.",
|
|
273
|
+
props: {
|
|
274
|
+
id: { ...str, description: "New id (lowercase alphanumerics and hyphens)" },
|
|
275
|
+
title: { ...str, description: "Title" },
|
|
276
|
+
parent: { ...str, description: "Parent doc id (omit = top level)" },
|
|
277
|
+
},
|
|
278
|
+
required: ["id", "title"],
|
|
279
|
+
run: (a) => ops.add(String(a.id), String(a.title), optStr(a.parent)),
|
|
280
|
+
},
|
|
281
|
+
write_doc: {
|
|
282
|
+
description: "Overwrite the entire raw text of an existing doc (including front-matter). Returns the validation errors after writing.",
|
|
283
|
+
props: {
|
|
284
|
+
id: { ...str, description: "doc id" },
|
|
285
|
+
content: { ...str, description: "Full md text (from ---\\ntitle: ...\\n---)" },
|
|
286
|
+
},
|
|
287
|
+
required: ["id", "content"],
|
|
288
|
+
run: (a) => {
|
|
289
|
+
const r = ops.write(String(a.id), String(a.content));
|
|
290
|
+
return isOpError(r) ? r : { ...r, errors: docErrors(r.id) };
|
|
291
|
+
},
|
|
292
|
+
},
|
|
293
|
+
move_doc: {
|
|
294
|
+
description: "Move a doc (with its children) to position index under parent.",
|
|
295
|
+
props: {
|
|
296
|
+
id: { ...str, description: "doc id" },
|
|
297
|
+
parent: { ...str, description: "New parent (omit = top level)" },
|
|
298
|
+
index: { ...num, description: "Position among siblings (omit = last)" },
|
|
299
|
+
},
|
|
300
|
+
required: ["id"],
|
|
301
|
+
run: (a) => ops.move(String(a.id), optStr(a.parent), typeof a.index === "number" ? a.index : undefined),
|
|
302
|
+
},
|
|
303
|
+
rename_doc: {
|
|
304
|
+
description: "Change a doc id (the file name / manifest / file links in all docs follow).",
|
|
305
|
+
props: {
|
|
306
|
+
id: { ...str, description: "Current id" },
|
|
307
|
+
new_id: { ...str, description: "New id" },
|
|
308
|
+
},
|
|
309
|
+
required: ["id", "new_id"],
|
|
310
|
+
run: (a) => ops.rename(String(a.id), String(a.new_id)),
|
|
311
|
+
},
|
|
312
|
+
delete_doc: {
|
|
313
|
+
description: "Delete a doc. Docs with children cannot be deleted. Requires force if other docs reference it via file links.",
|
|
314
|
+
props: {
|
|
315
|
+
id: { ...str, description: "doc id" },
|
|
316
|
+
force: { ...bool, description: "Delete even if referenced" },
|
|
317
|
+
},
|
|
318
|
+
required: ["id"],
|
|
319
|
+
run: (a) => ops.remove(String(a.id), a.force === true),
|
|
320
|
+
},
|
|
321
|
+
add_figure: {
|
|
322
|
+
description: "Add a figure to figures/ (svg / drawio.svg / html / png / webp / jpg). Give either path (a local file) or text (svg / html source). If the name is taken, -2 etc. is appended.",
|
|
323
|
+
props: {
|
|
324
|
+
file: {
|
|
325
|
+
...str,
|
|
326
|
+
description: "Figure file name (e.g. overview.svg; the extension determines the kind)",
|
|
327
|
+
},
|
|
328
|
+
path: { ...str, description: "Absolute path of the local file to copy from" },
|
|
329
|
+
text: { ...str, description: "svg / html source" },
|
|
330
|
+
},
|
|
331
|
+
required: ["file"],
|
|
332
|
+
run: (a) => {
|
|
333
|
+
const path = optStr(a.path);
|
|
334
|
+
const text = optStr(a.text);
|
|
335
|
+
if ((path == null) === (text == null)) {
|
|
336
|
+
return { error: "specify exactly one of path or text", status: 400 };
|
|
337
|
+
}
|
|
338
|
+
return ops.addFigure(String(a.file), path == null ? Buffer.from(text ?? "", "utf8") : readFileSync(path));
|
|
339
|
+
},
|
|
340
|
+
},
|
|
341
|
+
delete_figure: {
|
|
342
|
+
description: "Delete a figure. Requires force if a doc references it.",
|
|
343
|
+
props: {
|
|
344
|
+
name: { ...str, description: "Figure name (without extension)" },
|
|
345
|
+
force: { ...bool, description: "Delete even if referenced" },
|
|
346
|
+
},
|
|
347
|
+
required: ["name"],
|
|
348
|
+
run: (a) => ops.removeFigure(String(a.name), a.force === true),
|
|
349
|
+
},
|
|
350
|
+
};
|
|
351
|
+
};
|
|
352
|
+
const record = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
353
|
+
const validateProperty = (name, value, schema) => {
|
|
354
|
+
const type = value === null ? "null" : Array.isArray(value) ? "array" : typeof value;
|
|
355
|
+
const types = Array.isArray(schema.type) ? schema.type : [schema.type];
|
|
356
|
+
if (!types.some((t) => (t === "integer" ? Number.isSafeInteger(value) : t === type)) ||
|
|
357
|
+
(schema.enum && !schema.enum.includes(value)) ||
|
|
358
|
+
(schema.minimum !== undefined && Number(value) < schema.minimum))
|
|
359
|
+
throw new Error(`Invalid argument: ${name}`);
|
|
360
|
+
if (schema.items && Array.isArray(value))
|
|
361
|
+
for (const item of value)
|
|
362
|
+
validateProperty(name, item, schema.items);
|
|
363
|
+
};
|
|
364
|
+
/** One request → response (undefined for notifications). */
|
|
365
|
+
export const handleRpc = async (tools, msg) => {
|
|
366
|
+
const error = (code, message) => ({
|
|
367
|
+
jsonrpc: "2.0",
|
|
368
|
+
id: record(msg) && (typeof msg.id === "string" || typeof msg.id === "number")
|
|
369
|
+
? msg.id
|
|
370
|
+
: null,
|
|
371
|
+
error: { code, message },
|
|
372
|
+
});
|
|
373
|
+
if (!record(msg) ||
|
|
374
|
+
msg.jsonrpc !== "2.0" ||
|
|
375
|
+
typeof msg.method !== "string" ||
|
|
376
|
+
(msg.id !== undefined && typeof msg.id !== "string" && typeof msg.id !== "number"))
|
|
377
|
+
return error(-32600, "invalid request");
|
|
378
|
+
if (msg.id === undefined) {
|
|
379
|
+
return undefined;
|
|
380
|
+
}
|
|
381
|
+
if (msg.params !== undefined && !record(msg.params))
|
|
382
|
+
return error(-32602, "invalid params");
|
|
383
|
+
const params = (msg.params ?? {});
|
|
384
|
+
const reply = (result) => ({ jsonrpc: "2.0", id: msg.id, result });
|
|
385
|
+
switch (msg.method) {
|
|
386
|
+
case "initialize":
|
|
387
|
+
return reply({
|
|
388
|
+
protocolVersion: params.protocolVersion ?? "2025-06-18",
|
|
389
|
+
capabilities: { tools: {} },
|
|
390
|
+
serverInfo: { name: "dooc", version: "0" },
|
|
391
|
+
});
|
|
392
|
+
case "ping":
|
|
393
|
+
return reply({});
|
|
394
|
+
case "tools/list":
|
|
395
|
+
return reply({
|
|
396
|
+
tools: Object.entries(tools).map(([name, t]) => ({
|
|
397
|
+
name,
|
|
398
|
+
description: t.description,
|
|
399
|
+
inputSchema: {
|
|
400
|
+
type: "object",
|
|
401
|
+
properties: t.props,
|
|
402
|
+
required: t.required ?? [],
|
|
403
|
+
additionalProperties: false,
|
|
404
|
+
},
|
|
405
|
+
})),
|
|
406
|
+
});
|
|
407
|
+
case "tools/call": {
|
|
408
|
+
if (typeof params.name !== "string" || !Object.hasOwn(tools, params.name))
|
|
409
|
+
return error(-32602, "unknown tool");
|
|
410
|
+
const tool = tools[params.name];
|
|
411
|
+
if (!tool)
|
|
412
|
+
return error(-32602, "unknown tool");
|
|
413
|
+
let result;
|
|
414
|
+
try {
|
|
415
|
+
const args = params.arguments === undefined ? {} : params.arguments;
|
|
416
|
+
if (!record(args))
|
|
417
|
+
throw new Error("Tool arguments must be an object");
|
|
418
|
+
for (const name of tool.required ?? [])
|
|
419
|
+
if (!Object.hasOwn(args, name))
|
|
420
|
+
throw new Error(`Missing argument: ${name}`);
|
|
421
|
+
for (const [name, value] of Object.entries(args)) {
|
|
422
|
+
if (!Object.hasOwn(tool.props, name))
|
|
423
|
+
throw new Error(`Unknown argument: ${name}`);
|
|
424
|
+
const schema = tool.props[name];
|
|
425
|
+
if (schema)
|
|
426
|
+
validateProperty(name, value, schema);
|
|
427
|
+
}
|
|
428
|
+
result = await tool.run(args);
|
|
429
|
+
}
|
|
430
|
+
catch (e) {
|
|
431
|
+
result = { error: e instanceof Error ? e.message : String(e) };
|
|
432
|
+
}
|
|
433
|
+
return reply({
|
|
434
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
435
|
+
isError: isOpError(result),
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
default:
|
|
439
|
+
return {
|
|
440
|
+
jsonrpc: "2.0",
|
|
441
|
+
id: msg.id,
|
|
442
|
+
error: { code: -32601, message: "method not found" },
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
};
|
|
446
|
+
export const startMcp = async (root, options = {}) => {
|
|
447
|
+
const tools = createTools(root, options);
|
|
448
|
+
console.error(`dooc mcp: ${root}`);
|
|
449
|
+
// Process requests in order so asynchronous builds and Git mutations cannot race edits.
|
|
450
|
+
for await (const line of createInterface({ input: process.stdin })) {
|
|
451
|
+
if (line.trim() === "") {
|
|
452
|
+
continue;
|
|
453
|
+
}
|
|
454
|
+
let msg;
|
|
455
|
+
try {
|
|
456
|
+
msg = JSON.parse(line);
|
|
457
|
+
}
|
|
458
|
+
catch {
|
|
459
|
+
process.stdout.write(`${JSON.stringify({ jsonrpc: "2.0", id: null, error: { code: -32700, message: "parse error" } })}\n`);
|
|
460
|
+
continue;
|
|
461
|
+
}
|
|
462
|
+
const res = await handleRpc(tools, msg);
|
|
463
|
+
if (res) {
|
|
464
|
+
process.stdout.write(`${JSON.stringify(res)}\n`);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { PluginUser } from "./types.js";
|
|
2
|
+
export interface PluginActionContext {
|
|
3
|
+
user: PluginUser;
|
|
4
|
+
request(url: string, init?: {
|
|
5
|
+
method?: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
|
|
6
|
+
body?: unknown;
|
|
7
|
+
}): Promise<unknown>;
|
|
8
|
+
}
|
|
9
|
+
export interface PluginActionField {
|
|
10
|
+
name: string;
|
|
11
|
+
label: string;
|
|
12
|
+
type: "text" | "textarea" | "select" | "multiselect" | "date" | "number";
|
|
13
|
+
required?: boolean;
|
|
14
|
+
value?: string;
|
|
15
|
+
maxLength?: number;
|
|
16
|
+
min?: number;
|
|
17
|
+
max?: number;
|
|
18
|
+
options?: {
|
|
19
|
+
value: string;
|
|
20
|
+
label: string;
|
|
21
|
+
}[];
|
|
22
|
+
}
|
|
23
|
+
export interface PluginAction {
|
|
24
|
+
id: string;
|
|
25
|
+
label: string;
|
|
26
|
+
target: string;
|
|
27
|
+
fields: PluginActionField[];
|
|
28
|
+
destructive?: boolean;
|
|
29
|
+
confirm?: boolean;
|
|
30
|
+
version?: string;
|
|
31
|
+
partial?: boolean;
|
|
32
|
+
}
|
|
33
|
+
export interface PluginActionRequest {
|
|
34
|
+
id: string;
|
|
35
|
+
values: Record<string, string>;
|
|
36
|
+
version?: string;
|
|
37
|
+
confirmed?: boolean;
|
|
38
|
+
}
|
|
39
|
+
export interface PluginActionResult {
|
|
40
|
+
message: string;
|
|
41
|
+
deleted?: boolean;
|
|
42
|
+
}
|
|
43
|
+
export declare const validatePluginActions: (input: unknown) => PluginAction[];
|
|
44
|
+
export declare const validatePluginActionRequest: (input: unknown) => PluginActionRequest;
|
|
45
|
+
export declare const validatePluginActionValues: (action: PluginAction, input: PluginActionRequest) => void;
|