@tiramisu-docs/kit 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +103 -0
- package/components.json +14 -0
- package/dist/bin/mcp.d.ts +2 -0
- package/dist/bin/mcp.js +4 -0
- package/dist/config.d.ts +99 -0
- package/dist/config.js +36 -0
- package/dist/highlight.d.ts +10 -0
- package/dist/highlight.js +93 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +3 -0
- package/dist/lib/components/index.d.ts +16 -0
- package/dist/lib/components/index.js +18 -0
- package/dist/lib/components/tiramisu/lang-icons.d.ts +4 -0
- package/dist/lib/components/tiramisu/lang-icons.js +77 -0
- package/dist/lib/components/ui/alert/index.d.ts +5 -0
- package/dist/lib/components/ui/alert/index.js +6 -0
- package/dist/lib/components/ui/badge/index.d.ts +2 -0
- package/dist/lib/components/ui/badge/index.js +1 -0
- package/dist/lib/components/ui/button/index.d.ts +4 -0
- package/dist/lib/components/ui/button/index.js +2 -0
- package/dist/lib/components/ui/card/index.d.ts +8 -0
- package/dist/lib/components/ui/card/index.js +10 -0
- package/dist/lib/components/ui/collapsible/index.d.ts +1 -0
- package/dist/lib/components/ui/collapsible/index.js +1 -0
- package/dist/lib/components/ui/dropdown-menu/index.d.ts +18 -0
- package/dist/lib/components/ui/dropdown-menu/index.js +18 -0
- package/dist/lib/components/ui/scroll-area/index.d.ts +1 -0
- package/dist/lib/components/ui/scroll-area/index.js +1 -0
- package/dist/lib/components/ui/separator/index.d.ts +1 -0
- package/dist/lib/components/ui/separator/index.js +1 -0
- package/dist/lib/components/ui/sheet/index.d.ts +3 -0
- package/dist/lib/components/ui/sheet/index.js +3 -0
- package/dist/lib/components/ui/tabs/index.d.ts +5 -0
- package/dist/lib/components/ui/tabs/index.js +7 -0
- package/dist/lib/open-links.d.ts +22 -0
- package/dist/lib/open-links.js +33 -0
- package/dist/lib/routes/docs/[...slug]/+page.d.ts +25 -0
- package/dist/lib/routes/docs/[...slug]/+page.js +109 -0
- package/dist/lib/utils.d.ts +5 -0
- package/dist/lib/utils.js +5 -0
- package/dist/mcp.d.ts +24 -0
- package/dist/mcp.js +155 -0
- package/dist/scan.d.ts +15 -0
- package/dist/scan.js +72 -0
- package/dist/seo.d.ts +63 -0
- package/dist/seo.js +160 -0
- package/dist/tiramisu-grammar.d.ts +2 -0
- package/dist/tiramisu-grammar.js +77 -0
- package/dist/types.d.ts +66 -0
- package/dist/types.js +1 -0
- package/dist/vite.d.ts +33 -0
- package/dist/vite.js +406 -0
- package/package.json +74 -0
- package/src/config.ts +133 -0
- package/src/highlight.ts +110 -0
- package/src/index.ts +6 -0
- package/src/lib/components/DocPage.svelte +430 -0
- package/src/lib/components/DocsLayout.svelte +145 -0
- package/src/lib/components/Footer.svelte +26 -0
- package/src/lib/components/Navbar.svelte +117 -0
- package/src/lib/components/PageFooter.svelte +63 -0
- package/src/lib/components/PrevNextNav.svelte +83 -0
- package/src/lib/components/SearchDialog.svelte +130 -0
- package/src/lib/components/Sidebar.svelte +237 -0
- package/src/lib/components/TableOfContents.svelte +50 -0
- package/src/lib/components/TopBar.svelte +407 -0
- package/src/lib/components/index.ts +19 -0
- package/src/lib/components/tiramisu/Accordion.svelte +16 -0
- package/src/lib/components/tiramisu/Badge.svelte +16 -0
- package/src/lib/components/tiramisu/Callout.svelte +26 -0
- package/src/lib/components/tiramisu/CodeBlock.svelte +56 -0
- package/src/lib/components/tiramisu/CodeTabs.svelte +123 -0
- package/src/lib/components/tiramisu/Demo.svelte +15 -0
- package/src/lib/components/tiramisu/FileTree.svelte +67 -0
- package/src/lib/components/tiramisu/MathBlock.svelte +26 -0
- package/src/lib/components/tiramisu/Mermaid.svelte +30 -0
- package/src/lib/components/tiramisu/NavCard.svelte +49 -0
- package/src/lib/components/tiramisu/Steps.svelte +60 -0
- package/src/lib/components/tiramisu/Tabs.svelte +87 -0
- package/src/lib/components/tiramisu/ZoomImage.svelte +114 -0
- package/src/lib/components/tiramisu/lang-icons.ts +81 -0
- package/src/lib/open-links.ts +50 -0
- package/src/lib/routes/docs/[...slug]/+page.svelte +26 -0
- package/src/lib/routes/docs/[...slug]/+page.ts +117 -0
- package/src/lib/styles/theme.css +222 -0
- package/src/lib/utils.ts +10 -0
- package/src/mcp.ts +180 -0
- package/src/scan.ts +92 -0
- package/src/seo.ts +193 -0
- package/src/tiramisu-grammar.ts +80 -0
- package/src/types.ts +71 -0
- package/src/virtual.d.ts +11 -0
- package/src/vite.ts +478 -0
- package/tests/config.test.ts +60 -0
- package/tests/mcp.test.ts +116 -0
- package/tests/scan.test.ts +48 -0
- package/tests/seo.test.ts +174 -0
- package/tests/vite.test.ts +283 -0
- package/tsconfig.json +19 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export interface OpenLinkOptions {
|
|
2
|
+
baseUrl: string;
|
|
3
|
+
slug: string;
|
|
4
|
+
locale?: string;
|
|
5
|
+
github?: {
|
|
6
|
+
repo: string;
|
|
7
|
+
branch?: string;
|
|
8
|
+
dir?: string;
|
|
9
|
+
};
|
|
10
|
+
mcp?: boolean | string;
|
|
11
|
+
title?: string;
|
|
12
|
+
}
|
|
13
|
+
export interface OpenLink {
|
|
14
|
+
label: string;
|
|
15
|
+
href?: string;
|
|
16
|
+
copy?: string;
|
|
17
|
+
icon: "chatgpt" | "claude" | "cursor" | "github" | "mcp" | "vscode";
|
|
18
|
+
type: "open" | "edit" | "mcp";
|
|
19
|
+
}
|
|
20
|
+
export declare function getPageUrl(opts: OpenLinkOptions): string;
|
|
21
|
+
export declare function getGitHubEditUrl(opts: OpenLinkOptions): string | null;
|
|
22
|
+
export declare function getOpenLinks(opts: OpenLinkOptions): OpenLink[];
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export function getPageUrl(opts) {
|
|
2
|
+
const localePart = opts.locale ? `${opts.locale}/` : "";
|
|
3
|
+
const slug = opts.slug === "index" ? "" : opts.slug.replace(/\/index$/, "");
|
|
4
|
+
return `${opts.baseUrl}/docs/${localePart}${slug}`;
|
|
5
|
+
}
|
|
6
|
+
export function getGitHubEditUrl(opts) {
|
|
7
|
+
if (!opts.github)
|
|
8
|
+
return null;
|
|
9
|
+
const branch = opts.github.branch ?? "main";
|
|
10
|
+
const dir = opts.github.dir ?? "src/docs";
|
|
11
|
+
const localePart = opts.locale ? `${opts.locale}/` : "";
|
|
12
|
+
return `https://github.com/${opts.github.repo}/edit/${branch}/${dir}/${localePart}${opts.slug}.tiramisu`;
|
|
13
|
+
}
|
|
14
|
+
export function getOpenLinks(opts) {
|
|
15
|
+
const pageUrl = getPageUrl(opts);
|
|
16
|
+
const prompt = encodeURIComponent(`Read ${pageUrl} and answer questions about the content.`);
|
|
17
|
+
const cursorPrompt = encodeURIComponent(`Read ${pageUrl}, I want to ask questions about it.`);
|
|
18
|
+
const links = [
|
|
19
|
+
{ label: "Open in ChatGPT", href: `https://chat.openai.com/?q=${prompt}`, icon: "chatgpt", type: "open" },
|
|
20
|
+
{ label: "Open in Claude", href: `https://claude.ai/new?q=${prompt}`, icon: "claude", type: "open" },
|
|
21
|
+
{ label: "Open in Cursor", href: `https://cursor.com/link/prompt?text=${cursorPrompt}`, icon: "cursor", type: "open" },
|
|
22
|
+
];
|
|
23
|
+
if (opts.mcp) {
|
|
24
|
+
const mcpUrl = typeof opts.mcp === "string" ? opts.mcp : `${opts.baseUrl}/mcp`;
|
|
25
|
+
links.push({ label: "Connect with MCP", copy: mcpUrl, icon: "mcp", type: "mcp" });
|
|
26
|
+
const mcpMeta = JSON.stringify({ name: opts.title ?? "Documentation", url: mcpUrl });
|
|
27
|
+
links.push({ label: "Connect to VSCode", href: `vscode:mcp/install?${encodeURIComponent(mcpMeta)}`, icon: "vscode", type: "mcp" });
|
|
28
|
+
}
|
|
29
|
+
const ghUrl = getGitHubEditUrl(opts);
|
|
30
|
+
if (ghUrl)
|
|
31
|
+
links.push({ label: "Edit on GitHub", href: ghUrl, icon: "github", type: "edit" });
|
|
32
|
+
return links;
|
|
33
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { ResolvedSection, SidebarGroup } from "../../../../types.js";
|
|
2
|
+
export declare function load({ params }: {
|
|
3
|
+
params: {
|
|
4
|
+
slug?: string;
|
|
5
|
+
};
|
|
6
|
+
}): Promise<{
|
|
7
|
+
component: any;
|
|
8
|
+
meta: import("@tiramisu-docs/core").DocMeta;
|
|
9
|
+
headings: import("@tiramisu-docs/core").Heading[];
|
|
10
|
+
lastEdited: string | undefined;
|
|
11
|
+
slug: string;
|
|
12
|
+
locale: string;
|
|
13
|
+
locales: string[];
|
|
14
|
+
sections: ResolvedSection[] | undefined;
|
|
15
|
+
activeSidebar: SidebarGroup[];
|
|
16
|
+
showFallbackBanner: boolean;
|
|
17
|
+
} | {
|
|
18
|
+
component: typeof import("svelte").SvelteComponent;
|
|
19
|
+
meta: import("@tiramisu-docs/core").DocMeta;
|
|
20
|
+
headings: import("@tiramisu-docs/core").Heading[];
|
|
21
|
+
lastEdited: string | undefined;
|
|
22
|
+
slug: string;
|
|
23
|
+
sections: ResolvedSection[] | undefined;
|
|
24
|
+
activeSidebar: SidebarGroup[];
|
|
25
|
+
}>;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { error, redirect } from "@sveltejs/kit";
|
|
2
|
+
export async function load({ params }) {
|
|
3
|
+
const rawSlug = params.slug || "index";
|
|
4
|
+
const mod = await import("virtual:tiramisu-docs");
|
|
5
|
+
// If i18n is enabled, parse locale from slug
|
|
6
|
+
if (mod.locales && mod.defaultLocale) {
|
|
7
|
+
const segments = rawSlug.split("/");
|
|
8
|
+
const possibleLocale = segments[0];
|
|
9
|
+
const localeData = mod.locales[possibleLocale];
|
|
10
|
+
if (localeData) {
|
|
11
|
+
const locale = possibleLocale;
|
|
12
|
+
const slug = segments.slice(1).join("/") || "index";
|
|
13
|
+
return loadDoc(localeData, slug, locale, mod);
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
// No locale prefix — redirect to default locale
|
|
17
|
+
throw redirect(302, `/docs/${mod.defaultLocale}/${rawSlug}`);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
// No i18n — legacy behavior
|
|
21
|
+
return loadLegacy(mod, rawSlug);
|
|
22
|
+
}
|
|
23
|
+
function loadLegacy(mod, slug) {
|
|
24
|
+
let importFn = mod.docImports[slug] ?? mod.docImports[slug + "/index"];
|
|
25
|
+
// Root /docs with no page — redirect to first section
|
|
26
|
+
if (!importFn && slug === "index" && mod.sections) {
|
|
27
|
+
const firstSection = mod.sections.find((s) => s.path);
|
|
28
|
+
if (firstSection)
|
|
29
|
+
throw redirect(302, `/docs/${firstSection.path}`);
|
|
30
|
+
}
|
|
31
|
+
if (!importFn)
|
|
32
|
+
throw error(404, "Page not found");
|
|
33
|
+
if (!mod.docImports[slug])
|
|
34
|
+
slug = slug + "/index";
|
|
35
|
+
return importFn().then((c) => {
|
|
36
|
+
const doc = mod.docs.find((d) => d.slug === slug);
|
|
37
|
+
let activeSidebar = mod.sidebar;
|
|
38
|
+
if (mod.sections) {
|
|
39
|
+
activeSidebar = findActiveSectionSidebar(mod.sections, slug) ?? mod.sidebar;
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
component: c.default ?? c,
|
|
43
|
+
meta: doc?.meta ?? {},
|
|
44
|
+
headings: doc?.headings ?? [],
|
|
45
|
+
lastEdited: doc?.lastEdited,
|
|
46
|
+
slug,
|
|
47
|
+
sections: mod.sections ?? undefined,
|
|
48
|
+
activeSidebar,
|
|
49
|
+
};
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
async function loadDoc(localeData, slug, locale, mod) {
|
|
53
|
+
let importFn = localeData.docImports[slug] ?? localeData.docImports[slug + "/index"];
|
|
54
|
+
if (!localeData.docImports[slug] && localeData.docImports[slug + "/index"])
|
|
55
|
+
slug = slug + "/index";
|
|
56
|
+
let showFallbackBanner = false;
|
|
57
|
+
if (!importFn) {
|
|
58
|
+
const defaultData = mod.locales?.[mod.defaultLocale];
|
|
59
|
+
importFn = defaultData?.docImports[slug] ?? defaultData?.docImports[slug + "/index"];
|
|
60
|
+
if (!defaultData?.docImports[slug] && defaultData?.docImports[slug + "/index"])
|
|
61
|
+
slug = slug + "/index";
|
|
62
|
+
if (!importFn) {
|
|
63
|
+
// Root /docs/<locale> with no index page — redirect to first section
|
|
64
|
+
if (slug === "index" && localeData.sections) {
|
|
65
|
+
const firstSection = localeData.sections.find((s) => s.path);
|
|
66
|
+
if (firstSection)
|
|
67
|
+
throw redirect(302, `/docs/${locale}/${firstSection.path}`);
|
|
68
|
+
}
|
|
69
|
+
throw error(404, "Page not found");
|
|
70
|
+
}
|
|
71
|
+
showFallbackBanner = true;
|
|
72
|
+
}
|
|
73
|
+
const component = await importFn();
|
|
74
|
+
const doc = localeData.docs.find((d) => d.slug === slug)
|
|
75
|
+
?? mod.locales?.[mod.defaultLocale]?.docs.find((d) => d.slug === slug);
|
|
76
|
+
const sections = localeData.sections;
|
|
77
|
+
let activeSidebar = localeData.sidebar;
|
|
78
|
+
if (sections) {
|
|
79
|
+
activeSidebar = findActiveSectionSidebar(sections, slug) ?? localeData.sidebar;
|
|
80
|
+
}
|
|
81
|
+
return {
|
|
82
|
+
component: component.default ?? component,
|
|
83
|
+
meta: doc?.meta ?? {},
|
|
84
|
+
headings: doc?.headings ?? [],
|
|
85
|
+
lastEdited: doc?.lastEdited,
|
|
86
|
+
slug,
|
|
87
|
+
locale,
|
|
88
|
+
locales: Object.keys(mod.locales),
|
|
89
|
+
sections,
|
|
90
|
+
activeSidebar,
|
|
91
|
+
showFallbackBanner,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
function findActiveSectionSidebar(sections, slug) {
|
|
95
|
+
for (const section of sections) {
|
|
96
|
+
if (section.path && (slug === section.path || slug.startsWith(section.path + "/"))) {
|
|
97
|
+
return section.sidebar ?? null;
|
|
98
|
+
}
|
|
99
|
+
if (section.children) {
|
|
100
|
+
const found = findActiveSectionSidebar(section.children, slug);
|
|
101
|
+
if (found)
|
|
102
|
+
return found;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
if (sections.length > 0 && sections[0].sidebar && !sections[0].path) {
|
|
106
|
+
return sections[0].sidebar;
|
|
107
|
+
}
|
|
108
|
+
return null;
|
|
109
|
+
}
|
package/dist/mcp.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { VirtualDoc, SearchIndexEntry, SidebarGroup } from "./types.js";
|
|
2
|
+
export interface McpData {
|
|
3
|
+
docs: VirtualDoc[];
|
|
4
|
+
searchIndex: SearchIndexEntry[];
|
|
5
|
+
sidebar: SidebarGroup[];
|
|
6
|
+
}
|
|
7
|
+
interface JsonRpcRequest {
|
|
8
|
+
jsonrpc: "2.0";
|
|
9
|
+
id?: string | number;
|
|
10
|
+
method: string;
|
|
11
|
+
params?: Record<string, unknown>;
|
|
12
|
+
}
|
|
13
|
+
interface JsonRpcResponse {
|
|
14
|
+
jsonrpc: "2.0";
|
|
15
|
+
id?: string | number | null;
|
|
16
|
+
result?: unknown;
|
|
17
|
+
error?: {
|
|
18
|
+
code: number;
|
|
19
|
+
message: string;
|
|
20
|
+
data?: unknown;
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
export declare function handleMcpRequest(body: JsonRpcRequest, data: McpData): JsonRpcResponse;
|
|
24
|
+
export {};
|
package/dist/mcp.js
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
const TOOL_DEFINITIONS = [
|
|
2
|
+
{
|
|
3
|
+
name: "search_docs",
|
|
4
|
+
description: "Search documentation pages by keyword",
|
|
5
|
+
inputSchema: {
|
|
6
|
+
type: "object",
|
|
7
|
+
properties: {
|
|
8
|
+
query: { type: "string", description: "Search query" },
|
|
9
|
+
limit: { type: "number", description: "Max results (default 10)" },
|
|
10
|
+
},
|
|
11
|
+
required: ["query"],
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
name: "read_doc",
|
|
16
|
+
description: "Read a specific documentation page by slug",
|
|
17
|
+
inputSchema: {
|
|
18
|
+
type: "object",
|
|
19
|
+
properties: {
|
|
20
|
+
slug: { type: "string", description: "Page slug" },
|
|
21
|
+
},
|
|
22
|
+
required: ["slug"],
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
name: "list_pages",
|
|
27
|
+
description: "List documentation pages, optionally filtered by section",
|
|
28
|
+
inputSchema: {
|
|
29
|
+
type: "object",
|
|
30
|
+
properties: {
|
|
31
|
+
section: { type: "string", description: "Filter by section name" },
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
name: "list_sections",
|
|
37
|
+
description: "List all documentation sections with page counts",
|
|
38
|
+
inputSchema: { type: "object", properties: {} },
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
name: "get_table_of_contents",
|
|
42
|
+
description: "Get headings for a documentation page",
|
|
43
|
+
inputSchema: {
|
|
44
|
+
type: "object",
|
|
45
|
+
properties: {
|
|
46
|
+
slug: { type: "string", description: "Page slug" },
|
|
47
|
+
},
|
|
48
|
+
required: ["slug"],
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
];
|
|
52
|
+
function toolResult(data) {
|
|
53
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
54
|
+
}
|
|
55
|
+
function toolError(message) {
|
|
56
|
+
return { content: [{ type: "text", text: message }], isError: true };
|
|
57
|
+
}
|
|
58
|
+
function callTool(name, args, data) {
|
|
59
|
+
switch (name) {
|
|
60
|
+
case "search_docs": {
|
|
61
|
+
const query = String(args.query ?? "");
|
|
62
|
+
const limit = Number(args.limit ?? 10);
|
|
63
|
+
const terms = query.toLowerCase().split(/\s+/);
|
|
64
|
+
const scored = data.searchIndex
|
|
65
|
+
.map((entry) => {
|
|
66
|
+
const haystack = `${entry.title} ${entry.headings} ${entry.text}`.toLowerCase();
|
|
67
|
+
const score = terms.reduce((s, t) => s + (haystack.includes(t) ? 1 : 0), 0);
|
|
68
|
+
return { entry, score };
|
|
69
|
+
})
|
|
70
|
+
.filter((r) => r.score > 0)
|
|
71
|
+
.sort((a, b) => b.score - a.score)
|
|
72
|
+
.slice(0, limit);
|
|
73
|
+
return toolResult(scored.map((r) => ({
|
|
74
|
+
title: r.entry.title,
|
|
75
|
+
slug: r.entry.slug,
|
|
76
|
+
snippet: r.entry.text.slice(0, 200),
|
|
77
|
+
})));
|
|
78
|
+
}
|
|
79
|
+
case "read_doc": {
|
|
80
|
+
const slug = String(args.slug ?? "");
|
|
81
|
+
const doc = data.docs.find((d) => d.slug === slug);
|
|
82
|
+
const idx = data.searchIndex.find((e) => e.slug === slug);
|
|
83
|
+
if (!doc || !idx)
|
|
84
|
+
return toolError(`Page not found: ${slug}`);
|
|
85
|
+
return toolResult({
|
|
86
|
+
title: doc.meta.title ?? doc.slug,
|
|
87
|
+
description: doc.meta.description ?? "",
|
|
88
|
+
content: idx.text,
|
|
89
|
+
headings: doc.headings,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
case "list_pages": {
|
|
93
|
+
const section = args.section ? String(args.section).toLowerCase() : "";
|
|
94
|
+
let filtered = data.searchIndex;
|
|
95
|
+
if (section) {
|
|
96
|
+
filtered = filtered.filter((e) => e.group.toLowerCase().includes(section));
|
|
97
|
+
}
|
|
98
|
+
return toolResult(filtered.map((e) => ({
|
|
99
|
+
title: e.title,
|
|
100
|
+
slug: e.slug,
|
|
101
|
+
description: data.docs.find((d) => d.slug === e.slug)?.meta.description ?? "",
|
|
102
|
+
})));
|
|
103
|
+
}
|
|
104
|
+
case "list_sections": {
|
|
105
|
+
const groups = new Map();
|
|
106
|
+
for (const entry of data.searchIndex) {
|
|
107
|
+
groups.set(entry.group, (groups.get(entry.group) ?? 0) + 1);
|
|
108
|
+
}
|
|
109
|
+
return toolResult(Array.from(groups.entries()).map(([label, pageCount]) => ({
|
|
110
|
+
label,
|
|
111
|
+
path: label.toLowerCase().replace(/ > /g, "/").replace(/ /g, "-"),
|
|
112
|
+
pageCount,
|
|
113
|
+
})));
|
|
114
|
+
}
|
|
115
|
+
case "get_table_of_contents": {
|
|
116
|
+
const slug = String(args.slug ?? "");
|
|
117
|
+
const doc = data.docs.find((d) => d.slug === slug);
|
|
118
|
+
if (!doc)
|
|
119
|
+
return toolError(`Page not found: ${slug}`);
|
|
120
|
+
return toolResult(doc.headings.map((h) => ({ level: h.level, text: h.text, id: h.id })));
|
|
121
|
+
}
|
|
122
|
+
default:
|
|
123
|
+
return toolError(`Unknown tool: ${name}`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
export function handleMcpRequest(body, data) {
|
|
127
|
+
const { method, id, params } = body;
|
|
128
|
+
switch (method) {
|
|
129
|
+
case "initialize":
|
|
130
|
+
return {
|
|
131
|
+
jsonrpc: "2.0",
|
|
132
|
+
id: id ?? null,
|
|
133
|
+
result: {
|
|
134
|
+
protocolVersion: "2024-11-05",
|
|
135
|
+
capabilities: { tools: {} },
|
|
136
|
+
serverInfo: { name: "tiramisu-docs", version: "0.1.0" },
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
case "notifications/initialized":
|
|
140
|
+
return { jsonrpc: "2.0", id: id ?? null, result: {} };
|
|
141
|
+
case "tools/list":
|
|
142
|
+
return { jsonrpc: "2.0", id: id ?? null, result: { tools: TOOL_DEFINITIONS } };
|
|
143
|
+
case "tools/call": {
|
|
144
|
+
const name = String(params?.name ?? "");
|
|
145
|
+
const args = (params?.arguments ?? {});
|
|
146
|
+
return { jsonrpc: "2.0", id: id ?? null, result: callTool(name, args, data) };
|
|
147
|
+
}
|
|
148
|
+
default:
|
|
149
|
+
return {
|
|
150
|
+
jsonrpc: "2.0",
|
|
151
|
+
id: id ?? null,
|
|
152
|
+
error: { code: -32601, message: `Method not found: ${method}` },
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
}
|
package/dist/scan.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { DocMeta, Heading } from "@tiramisu-docs/core";
|
|
2
|
+
export interface ScannedDoc {
|
|
3
|
+
slug: string;
|
|
4
|
+
meta: DocMeta;
|
|
5
|
+
headings: Heading[];
|
|
6
|
+
}
|
|
7
|
+
import type { SearchIndexEntry } from "./types.js";
|
|
8
|
+
export type { SearchIndexEntry } from "./types.js";
|
|
9
|
+
export declare function findTiramisuFiles(dir: string): string[];
|
|
10
|
+
export declare function extractPlainText(html: string): string;
|
|
11
|
+
export declare function titleCase(slug: string): string;
|
|
12
|
+
export declare function scanDocs(docsDir: string): {
|
|
13
|
+
docs: ScannedDoc[];
|
|
14
|
+
searchIndex: SearchIndexEntry[];
|
|
15
|
+
};
|
package/dist/scan.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { compileTiramisu } from "@tiramisu-docs/core";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
export function findTiramisuFiles(dir) {
|
|
5
|
+
const results = [];
|
|
6
|
+
if (!fs.existsSync(dir))
|
|
7
|
+
return results;
|
|
8
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
9
|
+
for (const entry of entries) {
|
|
10
|
+
const fullPath = path.join(dir, entry.name);
|
|
11
|
+
if (entry.isDirectory()) {
|
|
12
|
+
results.push(...findTiramisuFiles(fullPath));
|
|
13
|
+
}
|
|
14
|
+
else if (entry.name.endsWith(".tiramisu")) {
|
|
15
|
+
results.push(fullPath);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return results;
|
|
19
|
+
}
|
|
20
|
+
export function extractPlainText(html) {
|
|
21
|
+
return html
|
|
22
|
+
.replace(/<script[\s\S]*?<\/script>/g, "")
|
|
23
|
+
.replace(/<[^>]+>/g, " ")
|
|
24
|
+
.replace(/&/g, "&")
|
|
25
|
+
.replace(/</g, "<")
|
|
26
|
+
.replace(/>/g, ">")
|
|
27
|
+
.replace(/"/g, '"')
|
|
28
|
+
.replace(/\s+/g, " ")
|
|
29
|
+
.trim();
|
|
30
|
+
}
|
|
31
|
+
export function titleCase(slug) {
|
|
32
|
+
return slug
|
|
33
|
+
.split("-")
|
|
34
|
+
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
|
|
35
|
+
.join(" ");
|
|
36
|
+
}
|
|
37
|
+
function resolveSearchGroup(slug, meta) {
|
|
38
|
+
const segments = slug.split("/");
|
|
39
|
+
if (segments.length === 1)
|
|
40
|
+
return meta.group ?? "Docs";
|
|
41
|
+
return segments
|
|
42
|
+
.slice(0, -1)
|
|
43
|
+
.map(titleCase)
|
|
44
|
+
.join(" > ");
|
|
45
|
+
}
|
|
46
|
+
export function scanDocs(docsDir) {
|
|
47
|
+
const absDocsDir = path.resolve(docsDir);
|
|
48
|
+
const files = findTiramisuFiles(absDocsDir);
|
|
49
|
+
const docs = [];
|
|
50
|
+
for (const file of files) {
|
|
51
|
+
const source = fs.readFileSync(file, "utf-8");
|
|
52
|
+
const { meta, headings } = compileTiramisu(source);
|
|
53
|
+
const relativePath = path.relative(absDocsDir, file);
|
|
54
|
+
const slug = relativePath.replace(/\.tiramisu$/, "").replace(/\\/g, "/");
|
|
55
|
+
docs.push({ slug, meta, headings });
|
|
56
|
+
}
|
|
57
|
+
const searchIndex = docs.map((doc) => {
|
|
58
|
+
const file = path.resolve(absDocsDir, doc.slug + ".tiramisu");
|
|
59
|
+
const source = fs.readFileSync(file, "utf-8");
|
|
60
|
+
const { svelte } = compileTiramisu(source);
|
|
61
|
+
const text = extractPlainText(svelte);
|
|
62
|
+
return {
|
|
63
|
+
id: doc.slug,
|
|
64
|
+
title: doc.meta.title ?? doc.slug,
|
|
65
|
+
group: resolveSearchGroup(doc.slug, doc.meta),
|
|
66
|
+
slug: doc.slug,
|
|
67
|
+
headings: doc.headings.map((h) => h.text).join(" "),
|
|
68
|
+
text,
|
|
69
|
+
};
|
|
70
|
+
});
|
|
71
|
+
return { docs, searchIndex };
|
|
72
|
+
}
|
package/dist/seo.d.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
export declare function generateSitemap(docs: {
|
|
2
|
+
slug: string;
|
|
3
|
+
}[], options: {
|
|
4
|
+
baseUrl: string;
|
|
5
|
+
basePath?: string;
|
|
6
|
+
locales?: {
|
|
7
|
+
code: string;
|
|
8
|
+
}[];
|
|
9
|
+
defaultLocale?: string;
|
|
10
|
+
}): string;
|
|
11
|
+
export declare function generateLlmsTxt(docs: {
|
|
12
|
+
slug: string;
|
|
13
|
+
meta: {
|
|
14
|
+
title?: string;
|
|
15
|
+
description?: string;
|
|
16
|
+
group?: string;
|
|
17
|
+
};
|
|
18
|
+
}[], options: {
|
|
19
|
+
title: string;
|
|
20
|
+
description: string;
|
|
21
|
+
baseUrl: string;
|
|
22
|
+
basePath?: string;
|
|
23
|
+
}): string;
|
|
24
|
+
export declare function generateLlmsFullTxt(searchIndex: {
|
|
25
|
+
slug: string;
|
|
26
|
+
title: string;
|
|
27
|
+
group: string;
|
|
28
|
+
text: string;
|
|
29
|
+
}[], options: {
|
|
30
|
+
title: string;
|
|
31
|
+
description: string;
|
|
32
|
+
baseUrl: string;
|
|
33
|
+
basePath?: string;
|
|
34
|
+
}): string;
|
|
35
|
+
export declare function buildInstantOgUrl(pageUrl: string, options: {
|
|
36
|
+
siteId: string;
|
|
37
|
+
template?: string;
|
|
38
|
+
theme?: "light" | "dark";
|
|
39
|
+
accentColor?: string;
|
|
40
|
+
gradientBg?: boolean;
|
|
41
|
+
}): string;
|
|
42
|
+
export declare function buildCanonicalUrl(baseUrl: string, slug: string): string;
|
|
43
|
+
export declare function buildPageJsonLd(options: {
|
|
44
|
+
title: string;
|
|
45
|
+
slug: string;
|
|
46
|
+
baseUrl: string;
|
|
47
|
+
description?: string;
|
|
48
|
+
lastEdited?: string;
|
|
49
|
+
siteName?: string;
|
|
50
|
+
image?: string;
|
|
51
|
+
author?: string;
|
|
52
|
+
}): string;
|
|
53
|
+
export declare function generateSkillMd(docs: {
|
|
54
|
+
slug: string;
|
|
55
|
+
meta: {
|
|
56
|
+
title?: string;
|
|
57
|
+
description?: string;
|
|
58
|
+
group?: string;
|
|
59
|
+
};
|
|
60
|
+
}[], options: {
|
|
61
|
+
title: string;
|
|
62
|
+
description: string;
|
|
63
|
+
}): string;
|