@classic-homes/slides 0.8.2 → 0.8.4
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 +3 -1
- package/assets/fonts/OFL.txt +94 -0
- package/assets/fonts/README.md +4 -0
- package/dist/{chunk-RWMCIAIW.js → chunk-JTVHIFRL.js} +20 -4
- package/dist/chunk-JTVHIFRL.js.map +1 -0
- package/dist/cli.js +2 -2
- package/dist/{mcp-FYZGFSBM.js → mcp-3J47UGAL.js} +85 -53
- package/dist/mcp-3J47UGAL.js.map +1 -0
- package/dist/node.d.ts +5 -0
- package/dist/node.js +1 -1
- package/package.json +3 -2
- package/dist/chunk-RWMCIAIW.js.map +0 -1
- package/dist/mcp-FYZGFSBM.js.map +0 -1
package/README.md
CHANGED
|
@@ -213,7 +213,9 @@ returns a copy in the author's shape (no schema defaults added).
|
|
|
213
213
|
## Brand
|
|
214
214
|
|
|
215
215
|
Colours come from `@classic-homes/theme-tokens`. Fonts (Figtree, Spectral) are
|
|
216
|
-
embedded from `assets/fonts`.
|
|
216
|
+
embedded from `assets/fonts`. Both are licensed under the SIL Open Font License
|
|
217
|
+
1.1; their copyright notices and the licence text ship as
|
|
218
|
+
`assets/fonts/OFL.txt`, which also covers the copies embedded in built decks. Styling lives in `src/styles.ts`; add a layout in
|
|
217
219
|
`src/layouts/` and register it in `src/layouts/index.ts` — its example is
|
|
218
220
|
tested automatically.
|
|
219
221
|
````
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
Copyright 2022 The Figtree Project Authors (https://github.com/erikdkennedy/figtree)
|
|
2
|
+
Copyright 2017 The Spectral Project Authors (https://github.com/productiontype/Spectral)
|
|
3
|
+
|
|
4
|
+
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
|
5
|
+
This license is copied below, and is also available with a FAQ at:
|
|
6
|
+
https://scripts.sil.org/OFL
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
-----------------------------------------------------------
|
|
10
|
+
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
|
11
|
+
-----------------------------------------------------------
|
|
12
|
+
|
|
13
|
+
PREAMBLE
|
|
14
|
+
The goals of the Open Font License (OFL) are to stimulate worldwide
|
|
15
|
+
development of collaborative font projects, to support the font creation
|
|
16
|
+
efforts of academic and linguistic communities, and to provide a free and
|
|
17
|
+
open framework in which fonts may be shared and improved in partnership
|
|
18
|
+
with others.
|
|
19
|
+
|
|
20
|
+
The OFL allows the licensed fonts to be used, studied, modified and
|
|
21
|
+
redistributed freely as long as they are not sold by themselves. The
|
|
22
|
+
fonts, including any derivative works, can be bundled, embedded,
|
|
23
|
+
redistributed and/or sold with any software provided that any reserved
|
|
24
|
+
names are not used by derivative works. The fonts and derivatives,
|
|
25
|
+
however, cannot be released under any other type of license. The
|
|
26
|
+
requirement for fonts to remain under this license does not apply
|
|
27
|
+
to any document created using the fonts or their derivatives.
|
|
28
|
+
|
|
29
|
+
DEFINITIONS
|
|
30
|
+
"Font Software" refers to the set of files released by the Copyright
|
|
31
|
+
Holder(s) under this license and clearly marked as such. This may
|
|
32
|
+
include source files, build scripts and documentation.
|
|
33
|
+
|
|
34
|
+
"Reserved Font Name" refers to any names specified as such after the
|
|
35
|
+
copyright statement(s).
|
|
36
|
+
|
|
37
|
+
"Original Version" refers to the collection of Font Software components as
|
|
38
|
+
distributed by the Copyright Holder(s).
|
|
39
|
+
|
|
40
|
+
"Modified Version" refers to any derivative made by adding to, deleting,
|
|
41
|
+
or substituting -- in part or in whole -- any of the components of the
|
|
42
|
+
Original Version, by changing formats or by porting the Font Software to a
|
|
43
|
+
new environment.
|
|
44
|
+
|
|
45
|
+
"Author" refers to any designer, engineer, programmer, technical
|
|
46
|
+
writer or other person who contributed to the Font Software.
|
|
47
|
+
|
|
48
|
+
PERMISSION & CONDITIONS
|
|
49
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
50
|
+
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
|
51
|
+
redistribute, and sell modified and unmodified copies of the Font
|
|
52
|
+
Software, subject to the following conditions:
|
|
53
|
+
|
|
54
|
+
1) Neither the Font Software nor any of its individual components,
|
|
55
|
+
in Original or Modified Versions, may be sold by itself.
|
|
56
|
+
|
|
57
|
+
2) Original or Modified Versions of the Font Software may be bundled,
|
|
58
|
+
redistributed and/or sold with any software, provided that each copy
|
|
59
|
+
contains the above copyright notice and this license. These can be
|
|
60
|
+
included either as stand-alone text files, human-readable headers or
|
|
61
|
+
in the appropriate machine-readable metadata fields within text or
|
|
62
|
+
binary files as long as those fields can be easily viewed by the user.
|
|
63
|
+
|
|
64
|
+
3) No Modified Version of the Font Software may use the Reserved Font
|
|
65
|
+
Name(s) unless explicit written permission is granted by the corresponding
|
|
66
|
+
Copyright Holder. This restriction only applies to the primary font name as
|
|
67
|
+
presented to the users.
|
|
68
|
+
|
|
69
|
+
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
|
70
|
+
Software shall not be used to promote, endorse or advertise any
|
|
71
|
+
Modified Version, except to acknowledge the contribution(s) of the
|
|
72
|
+
Copyright Holder(s) and the Author(s) or with their explicit written
|
|
73
|
+
permission.
|
|
74
|
+
|
|
75
|
+
5) The Font Software, modified or unmodified, in part or in whole,
|
|
76
|
+
must be distributed entirely under this license, and must not be
|
|
77
|
+
distributed under any other license. The requirement for fonts to
|
|
78
|
+
remain under this license does not apply to any document created
|
|
79
|
+
using the Font Software.
|
|
80
|
+
|
|
81
|
+
TERMINATION
|
|
82
|
+
This license becomes null and void if any of the above conditions are
|
|
83
|
+
not met.
|
|
84
|
+
|
|
85
|
+
DISCLAIMER
|
|
86
|
+
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
87
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
|
88
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
|
89
|
+
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
|
90
|
+
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
91
|
+
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
|
92
|
+
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
93
|
+
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
|
94
|
+
OTHER DEALINGS IN THE FONT SOFTWARE.
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
Figtree (variable, 300–900) and Spectral (400/500/600, roman + italic), both
|
|
2
|
+
licensed under the SIL Open Font License 1.1 (https://openfontlicense.org).
|
|
3
|
+
Subset woff2 files taken from the homeowner-guide deck template. They are
|
|
4
|
+
base64-embedded into `src/generated/fonts.ts` by `scripts/embed-fonts.ts`.
|
|
@@ -8,8 +8,9 @@ import {
|
|
|
8
8
|
} from "./chunk-R5HURANW.js";
|
|
9
9
|
|
|
10
10
|
// src/node/index.ts
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
11
|
+
import { randomBytes } from "crypto";
|
|
12
|
+
import { readFile, rename, rm, stat, writeFile } from "fs/promises";
|
|
13
|
+
import { basename, dirname as dirname2, extname, isAbsolute, join, resolve as resolve2 } from "path";
|
|
13
14
|
|
|
14
15
|
// src/node/paths.ts
|
|
15
16
|
import { lstatSync, realpathSync } from "fs";
|
|
@@ -147,7 +148,22 @@ async function loadDeckFile(path) {
|
|
|
147
148
|
}
|
|
148
149
|
async function saveDeckFile(path, deck) {
|
|
149
150
|
const text = path.endsWith(".json") ? JSON.stringify(deck, null, 2) + "\n" : stringifyDeck(deck);
|
|
150
|
-
await
|
|
151
|
+
const mode = (await stat(path).catch(() => void 0))?.mode;
|
|
152
|
+
const tmp = join(
|
|
153
|
+
dirname2(path),
|
|
154
|
+
`.${basename(path)}.${process.pid}.${randomBytes(6).toString("hex")}.tmp`
|
|
155
|
+
);
|
|
156
|
+
try {
|
|
157
|
+
await writeFile(tmp, text, {
|
|
158
|
+
flag: "wx",
|
|
159
|
+
...mode !== void 0 ? { mode: mode & 4095 } : {}
|
|
160
|
+
});
|
|
161
|
+
await rename(tmp, path);
|
|
162
|
+
} catch (e) {
|
|
163
|
+
await rm(tmp, { force: true }).catch(() => {
|
|
164
|
+
});
|
|
165
|
+
throw e;
|
|
166
|
+
}
|
|
151
167
|
}
|
|
152
168
|
async function buildDeckFile(path, options = {}) {
|
|
153
169
|
const abs = options.rootDir ? confinedPath(options.rootDir, resolve2(path)) : resolve2(path);
|
|
@@ -183,4 +199,4 @@ export {
|
|
|
183
199
|
buildDeckFile,
|
|
184
200
|
exportDeckFile
|
|
185
201
|
};
|
|
186
|
-
//# sourceMappingURL=chunk-
|
|
202
|
+
//# sourceMappingURL=chunk-JTVHIFRL.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/node/index.ts","../src/node/paths.ts"],"sourcesContent":["/** Node-only helpers: read deck files, inline local images, write HTML. */\nimport { randomBytes } from 'node:crypto';\nimport { readFile, rename, rm, stat, writeFile } from 'node:fs/promises';\nimport { basename, dirname, extname, isAbsolute, join, resolve } from 'node:path';\nimport { confinedPath } from './paths.js';\nimport { inlineDeckImages, type InlineImagesResult } from '../images.js';\nimport { parseDeckSource, stringifyDeck } from '../parse.js';\nimport type { ImageResolver, RenderOptions, RenderResult } from '../render.js';\nimport { renderDeck } from '../render-default.js';\n\nconst MIME: Record<string, string> = {\n '.jpg': 'image/jpeg',\n '.jpeg': 'image/jpeg',\n '.png': 'image/png',\n '.webp': 'image/webp',\n '.gif': 'image/gif',\n '.svg': 'image/svg+xml',\n '.avif': 'image/avif',\n};\n\n/** Largest single image we inline without warning. Big photos bloat the deck file. */\nexport const IMAGE_WARN_BYTES = 800 * 1024;\n\n/** Largest remote image we download. Anything bigger is refused, not truncated. */\nconst REMOTE_IMAGE_MAX_BYTES = 25 * 1024 * 1024;\n\n/** How long one remote image (including redirects) may take to download. */\nconst REMOTE_IMAGE_TIMEOUT_MS = 15_000;\n\nconst MAX_REDIRECTS = 5;\n\n/**\n * Downloads a remote image for inlining. Only https is fetched, redirects included, so a\n * deck (or an agent building one over MCP) can't reach plain-http hosts such as cloud\n * metadata endpoints; non-image responses, slow servers and oversized bodies are refused.\n */\nasync function fetchRemoteImage(src: string): Promise<{ type: string; buf: Buffer }> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), REMOTE_IMAGE_TIMEOUT_MS);\n try {\n return await downloadImage(src, controller.signal);\n } finally {\n clearTimeout(timer);\n }\n}\n\nasync function downloadImage(src: string, signal: AbortSignal) {\n let url = src;\n let res: Response;\n for (let hop = 0; ; hop++) {\n if (new URL(url).protocol !== 'https:') throw new Error(`Image ${src} must use https.`);\n try {\n res = await fetch(url, { redirect: 'manual', signal });\n } catch (e) {\n if (signal.aborted)\n throw new Error(`Image ${src} took over ${REMOTE_IMAGE_TIMEOUT_MS / 1000}s to download.`);\n throw e;\n }\n const location = res.headers.get('location');\n if (res.status < 300 || res.status >= 400 || !location) break;\n if (hop === MAX_REDIRECTS) throw new Error(`Image ${src} redirected too many times.`);\n url = new URL(location, url).href;\n }\n if (!res.ok) throw new Error(`Image ${src} returned HTTP ${res.status}`);\n const type = (res.headers.get('content-type') ?? '').split(';')[0].trim().toLowerCase();\n if (!type.startsWith('image/'))\n throw new Error(`Image ${src} is not an image (content-type \"${type || 'none'}\").`);\n const tooBig = () =>\n new Error(\n `Image ${src} is over ${REMOTE_IMAGE_MAX_BYTES / 1024 / 1024} MB; use a smaller copy.`\n );\n if (Number(res.headers.get('content-length')) > REMOTE_IMAGE_MAX_BYTES) {\n await res.body?.cancel();\n throw tooBig();\n }\n const chunks: Uint8Array[] = [];\n let size = 0;\n if (res.body) {\n const reader = res.body.getReader();\n try {\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n size += value.byteLength;\n if (size > REMOTE_IMAGE_MAX_BYTES) {\n await reader.cancel();\n throw tooBig();\n }\n chunks.push(value);\n }\n } catch (e) {\n if (signal.aborted)\n throw new Error(`Image ${src} took over ${REMOTE_IMAGE_TIMEOUT_MS / 1000}s to download.`);\n throw e;\n }\n }\n return { type, buf: Buffer.concat(chunks) };\n}\n\nexport interface FileResolverOptions {\n /** Restrict local image reads and build paths to this root (used by the MCP server). */\n rootDir?: string;\n /**\n * Fetch https images and inline them so the deck works offline. Default true.\n * Plain-http images are never fetched; they stay remote links (with a warning).\n */\n inlineRemote?: boolean;\n onWarning?: (message: string) => void;\n}\n\n/** Resolves image paths relative to the deck file and inlines them as data URIs. */\nexport function fileImageResolver(\n baseDir: string,\n options: FileResolverOptions = {}\n): ImageResolver {\n const warn = options.onWarning ?? (() => {});\n return async (src) => {\n if (src.startsWith('data:')) return src;\n if (/^https?:/i.test(src)) {\n if (options.inlineRemote === false) return src;\n if (!/^https:/i.test(src)) {\n warn(`${src} is not https, so it was left as a remote link; use an https URL.`);\n return src;\n }\n const { type, buf } = await fetchRemoteImage(src);\n if (buf.length > IMAGE_WARN_BYTES)\n warn(`${src} is ${(buf.length / 1024).toFixed(0)} KB; consider a smaller copy.`);\n return `data:${type};base64,${buf.toString('base64')}`;\n }\n const candidate = isAbsolute(src) ? src : join(baseDir, src);\n const path = options.rootDir ? confinedPath(options.rootDir, candidate) : candidate;\n const type = MIME[extname(path).toLowerCase()];\n if (!type)\n throw new Error(`Unsupported image type for \"${src}\". Use jpg, png, webp, gif, svg or avif.`);\n let buf: Buffer;\n try {\n buf = await readFile(path);\n } catch {\n throw new Error(`Image not found: \"${src}\" (looked in ${path})`);\n }\n if (buf.length > IMAGE_WARN_BYTES)\n warn(`${src} is ${(buf.length / 1024).toFixed(0)} KB; consider resizing to ~2000px wide.`);\n return `data:${type};base64,${buf.toString('base64')}`;\n };\n}\n\nexport async function loadDeckFile(path: string): Promise<unknown> {\n return parseDeckSource(await readFile(path, 'utf8'));\n}\n\n/**\n * Saves a deck atomically: the text goes to a temp file in the same directory, which is\n * then renamed over the target. Readers see the old file or the new one, never a partial\n * write, and a failed write leaves the previous file untouched (the temp is removed).\n */\nexport async function saveDeckFile(path: string, deck: unknown): Promise<void> {\n const text = path.endsWith('.json') ? JSON.stringify(deck, null, 2) + '\\n' : stringifyDeck(deck);\n const mode = (await stat(path).catch(() => undefined))?.mode;\n const tmp = join(\n dirname(path),\n `.${basename(path)}.${process.pid}.${randomBytes(6).toString('hex')}.tmp`\n );\n try {\n await writeFile(tmp, text, {\n flag: 'wx',\n ...(mode !== undefined ? { mode: mode & 0o7777 } : {}),\n });\n await rename(tmp, path);\n } catch (e) {\n await rm(tmp, { force: true }).catch(() => {});\n throw e;\n }\n}\n\nexport interface BuildOptions extends Omit<RenderOptions, 'resolveImage'>, FileResolverOptions {\n out?: string;\n}\n\n/** Renders a deck file to a self-contained HTML file next to it (or at `out`). */\nexport async function buildDeckFile(\n path: string,\n options: BuildOptions = {}\n): Promise<RenderResult & { out: string; bytes: number }> {\n const abs = options.rootDir ? confinedPath(options.rootDir, resolve(path)) : resolve(path);\n const deck = await loadDeckFile(abs);\n const result = await renderDeck(deck, {\n ...options,\n resolveImage: fileImageResolver(dirname(abs), options),\n });\n const candidate = options.out\n ? resolve(options.out)\n : abs.replace(/\\.(ya?ml|json)$/i, '') + '.html';\n const out = options.rootDir ? confinedPath(options.rootDir, candidate) : candidate;\n await writeFile(out, result.html);\n return { ...result, out, bytes: (await stat(out)).size };\n}\n\nexport interface ExportOptions extends FileResolverOptions {\n out?: string;\n}\n\n/**\n * Writes a portable copy of a deck file: JSON with every image inlined as a\n * data URI, ready to import into a hosted deck editor. Defaults to\n * `<name>.deck.json` next to the source.\n */\nexport async function exportDeckFile(\n path: string,\n options: ExportOptions = {}\n): Promise<InlineImagesResult & { out: string; bytes: number }> {\n const abs = options.rootDir ? confinedPath(options.rootDir, resolve(path)) : resolve(path);\n const result = await inlineDeckImages(\n await loadDeckFile(abs),\n fileImageResolver(dirname(abs), options)\n );\n const candidate = options.out\n ? resolve(options.out)\n : abs.replace(/\\.(ya?ml|json)$/i, '').replace(/\\.deck$/i, '') + '.deck.json';\n const out = options.rootDir ? confinedPath(options.rootDir, candidate) : candidate;\n if (out === abs) throw new Error(`Refusing to overwrite the source deck ${abs}. Pass --out.`);\n await writeFile(out, JSON.stringify(result.deck) + '\\n');\n return { ...result, out, bytes: (await stat(out)).size };\n}\n\nexport { renderDeck };\n","/** Canonical containment for existing files and new output files. */\nimport { lstatSync, realpathSync } from 'node:fs';\nimport { dirname, relative, resolve, sep } from 'node:path';\n\nexport function confinedPath(root: string, path: string): string {\n const canonicalRoot = realpathSync(root);\n const absolute = resolve(path);\n let ancestor = absolute;\n for (;;) {\n try {\n const canonical = resolve(realpathSync(ancestor), relative(ancestor, absolute));\n if (canonical !== canonicalRoot && !canonical.startsWith(canonicalRoot + sep)) {\n throw new Error(`Path \"${path}\" is outside the deck root ${root}.`);\n }\n return canonical;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'ENOENT' || ancestor === dirname(ancestor))\n throw error;\n if (lstatSync(ancestor, { throwIfNoEntry: false })?.isSymbolicLink()) {\n throw new Error(`Cannot follow dangling symlink \"${ancestor}\".`);\n }\n ancestor = dirname(ancestor);\n }\n }\n}\n"],"mappings":"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","names":["dirname","resolve","type","buf","dirname","resolve"]}
|
package/dist/cli.js
CHANGED
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
exportDeckFile,
|
|
11
11
|
fileImageResolver,
|
|
12
12
|
loadDeckFile
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-JTVHIFRL.js";
|
|
14
14
|
import {
|
|
15
15
|
renderDeck
|
|
16
16
|
} from "./chunk-WTTBHOBM.js";
|
|
@@ -132,7 +132,7 @@ ${"".padEnd(12)} Use for: ${l.whenToUse}
|
|
|
132
132
|
case "mcp": {
|
|
133
133
|
let mcp;
|
|
134
134
|
try {
|
|
135
|
-
mcp = await import("./mcp-
|
|
135
|
+
mcp = await import("./mcp-3J47UGAL.js");
|
|
136
136
|
} catch (e) {
|
|
137
137
|
if (e.code === "ERR_MODULE_NOT_FOUND") {
|
|
138
138
|
throw new Error(
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
confinedPath,
|
|
9
9
|
loadDeckFile,
|
|
10
10
|
saveDeckFile
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-JTVHIFRL.js";
|
|
12
12
|
import "./chunk-WTTBHOBM.js";
|
|
13
13
|
import {
|
|
14
14
|
addSlide,
|
|
@@ -28,6 +28,23 @@ import { access } from "fs/promises";
|
|
|
28
28
|
import { relative, resolve, sep } from "path";
|
|
29
29
|
import { z } from "zod";
|
|
30
30
|
var VERSION = "0.1.0";
|
|
31
|
+
var deckLocks = /* @__PURE__ */ new Map();
|
|
32
|
+
async function withDeckLock(abs, fn) {
|
|
33
|
+
const prev = deckLocks.get(abs) ?? Promise.resolve();
|
|
34
|
+
const run = prev.then(fn, fn);
|
|
35
|
+
const tail = run.then(
|
|
36
|
+
() => {
|
|
37
|
+
},
|
|
38
|
+
() => {
|
|
39
|
+
}
|
|
40
|
+
);
|
|
41
|
+
deckLocks.set(abs, tail);
|
|
42
|
+
try {
|
|
43
|
+
return await run;
|
|
44
|
+
} finally {
|
|
45
|
+
if (deckLocks.get(abs) === tail) deckLocks.delete(abs);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
31
48
|
var text = (t, isError = false) => ({
|
|
32
49
|
content: [{ type: "text", text: t }],
|
|
33
50
|
...isError ? { isError } : {}
|
|
@@ -50,6 +67,13 @@ function createSlidesMcpServer(rootDir = process.cwd()) {
|
|
|
50
67
|
throw new Error(`${p} is not a deck file (no slides list).`);
|
|
51
68
|
return { abs, deck };
|
|
52
69
|
}
|
|
70
|
+
function mutate(p, fn) {
|
|
71
|
+
const abs = safePath(p);
|
|
72
|
+
return withDeckLock(abs, async () => {
|
|
73
|
+
const { deck } = await load(p);
|
|
74
|
+
return fn(abs, deck);
|
|
75
|
+
});
|
|
76
|
+
}
|
|
53
77
|
async function commit(abs, deck, done) {
|
|
54
78
|
const v = validateDeck(deck);
|
|
55
79
|
if (!v.ok) {
|
|
@@ -121,24 +145,26 @@ ${formatIssues(warnings)}` : ""}`
|
|
|
121
145
|
},
|
|
122
146
|
run(async ({ path, title, description, journeySteps, slides, overwrite }) => {
|
|
123
147
|
const abs = safePath(path);
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
()
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
if (exists)
|
|
130
|
-
return text(
|
|
131
|
-
`${path} already exists. Use read_deck and edit it, or pass overwrite: true.`,
|
|
132
|
-
true
|
|
148
|
+
return withDeckLock(abs, async () => {
|
|
149
|
+
if (!overwrite) {
|
|
150
|
+
const exists = await access(abs).then(
|
|
151
|
+
() => true,
|
|
152
|
+
() => false
|
|
133
153
|
);
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
154
|
+
if (exists)
|
|
155
|
+
return text(
|
|
156
|
+
`${path} already exists. Use read_deck and edit it, or pass overwrite: true.`,
|
|
157
|
+
true
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
const deck = {
|
|
161
|
+
title,
|
|
162
|
+
...description ? { description } : {},
|
|
163
|
+
...journeySteps?.length ? { journey: { steps: journeySteps } } : {},
|
|
164
|
+
slides: slides?.length ? slides : [{ layout: "title", title }]
|
|
165
|
+
};
|
|
166
|
+
return commit(abs, deck, `Created ${path}.`);
|
|
167
|
+
});
|
|
142
168
|
})
|
|
143
169
|
);
|
|
144
170
|
server.tool(
|
|
@@ -161,65 +187,71 @@ ${stringifyDeck(deck)}\`\`\``
|
|
|
161
187
|
"update_deck",
|
|
162
188
|
"Change deck-level fields (title, description, author, date, journey). Set a field to null to remove it.",
|
|
163
189
|
{ path: Path, fields: z.record(z.unknown()) },
|
|
164
|
-
run(
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
190
|
+
run(
|
|
191
|
+
async ({ path, fields }) => mutate(path, async (abs, deck) => {
|
|
192
|
+
for (const [k, v] of Object.entries(fields)) {
|
|
193
|
+
if (k === "slides") return text("Use the slide tools to change slides.", true);
|
|
194
|
+
if (v === null) delete deck[k];
|
|
195
|
+
else deck[k] = v;
|
|
196
|
+
}
|
|
197
|
+
return commit(abs, deck, "Deck fields updated.");
|
|
198
|
+
})
|
|
199
|
+
)
|
|
173
200
|
);
|
|
174
201
|
server.tool(
|
|
175
202
|
"add_slide",
|
|
176
203
|
"Insert a slide. position is 1-based; omit to append.",
|
|
177
204
|
{ path: Path, slide: SlideObj, position: z.number().int().positive().optional() },
|
|
178
|
-
run(
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
205
|
+
run(
|
|
206
|
+
async ({ path, slide, position }) => mutate(path, async (abs, deck) => {
|
|
207
|
+
const at = addSlide(deck, slide, position);
|
|
208
|
+
return commit(abs, deck, `Added slide ${at}.`);
|
|
209
|
+
})
|
|
210
|
+
)
|
|
183
211
|
);
|
|
184
212
|
server.tool(
|
|
185
213
|
"add_slides",
|
|
186
214
|
"Append several slides at once (e.g. a whole section). Saved only if all are valid.",
|
|
187
215
|
{ path: Path, slides: z.array(SlideObj).min(1) },
|
|
188
|
-
run(
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
216
|
+
run(
|
|
217
|
+
async ({ path, slides }) => mutate(path, async (abs, deck) => {
|
|
218
|
+
slides.forEach((s) => addSlide(deck, s));
|
|
219
|
+
return commit(abs, deck, `Added ${slides.length} slides.`);
|
|
220
|
+
})
|
|
221
|
+
)
|
|
193
222
|
);
|
|
194
223
|
server.tool(
|
|
195
224
|
"update_slide",
|
|
196
225
|
"Merge fields into a slide. Set a field to null to remove it. Changing layout replaces the slide.",
|
|
197
226
|
{ path: Path, slide: Ref, fields: z.record(z.unknown()) },
|
|
198
|
-
run(
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
227
|
+
run(
|
|
228
|
+
async ({ path, slide, fields }) => mutate(path, async (abs, deck) => {
|
|
229
|
+
const at = updateSlide(deck, slide, fields);
|
|
230
|
+
return commit(abs, deck, `Updated slide ${at}.`);
|
|
231
|
+
})
|
|
232
|
+
)
|
|
203
233
|
);
|
|
204
234
|
server.tool(
|
|
205
235
|
"move_slide",
|
|
206
236
|
"Move a slide to a new 1-based position.",
|
|
207
237
|
{ path: Path, slide: Ref, to: z.number().int().positive() },
|
|
208
|
-
run(
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
238
|
+
run(
|
|
239
|
+
async ({ path, slide, to }) => mutate(path, async (abs, deck) => {
|
|
240
|
+
const at = moveSlide(deck, slide, to);
|
|
241
|
+
return commit(abs, deck, `Moved slide to ${at}.`);
|
|
242
|
+
})
|
|
243
|
+
)
|
|
213
244
|
);
|
|
214
245
|
server.tool(
|
|
215
246
|
"delete_slide",
|
|
216
247
|
"Delete a slide.",
|
|
217
248
|
{ path: Path, slide: Ref },
|
|
218
|
-
run(
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
249
|
+
run(
|
|
250
|
+
async ({ path, slide }) => mutate(path, async (abs, deck) => {
|
|
251
|
+
const removed = removeSlide(deck, slide);
|
|
252
|
+
return commit(abs, deck, `Deleted [${removed.layout}] slide.`);
|
|
253
|
+
})
|
|
254
|
+
)
|
|
223
255
|
);
|
|
224
256
|
server.tool(
|
|
225
257
|
"validate_deck",
|
|
@@ -321,4 +353,4 @@ export {
|
|
|
321
353
|
createSlidesMcpServer,
|
|
322
354
|
runStdio
|
|
323
355
|
};
|
|
324
|
-
//# sourceMappingURL=mcp-
|
|
356
|
+
//# sourceMappingURL=mcp-3J47UGAL.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/node/mcp.ts"],"sourcesContent":["/**\n * Local MCP server (stdio) for building decks on disk with an AI agent.\n *\n * Tools work slide-by-slide on a YAML deck file and validate before every\n * save, so an agent can never leave a deck in a broken state. File access is\n * confined to the root directory the server was started in.\n */\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport { access } from 'node:fs/promises';\nimport { relative, resolve, sep } from 'node:path';\nimport { z } from 'zod';\nimport { confinedPath } from './paths.js';\nimport { AUTHORING_GUIDE, describeLayout, listLayouts } from '../catalog.js';\nimport {\n addSlide,\n moveSlide,\n outline,\n removeSlide,\n updateSlide,\n type EditableDeck,\n} from '../edit.js';\nimport { stringifyDeck } from '../parse.js';\nimport { formatIssues, validateDeck } from '../validate.js';\nimport { buildDeckFile, loadDeckFile, saveDeckFile } from './index.js';\n\nconst VERSION = '0.1.0';\n\n/**\n * Tail of the mutation queue for each deck, keyed by canonical absolute path. Shared by\n * every server in the process, so concurrent tool calls on one deck run their\n * load → mutate → save one after another instead of overwriting each other.\n */\nconst deckLocks = new Map<string, Promise<unknown>>();\n\nasync function withDeckLock<T>(abs: string, fn: () => Promise<T>): Promise<T> {\n const prev = deckLocks.get(abs) ?? Promise.resolve();\n const run = prev.then(fn, fn);\n const tail = run.then(\n () => {},\n () => {}\n );\n deckLocks.set(abs, tail);\n try {\n return await run;\n } finally {\n if (deckLocks.get(abs) === tail) deckLocks.delete(abs);\n }\n}\n\ntype ToolResult = { content: { type: 'text'; text: string }[]; isError?: boolean };\nconst text = (t: string, isError = false): ToolResult => ({\n content: [{ type: 'text', text: t }],\n ...(isError ? { isError } : {}),\n});\n\nexport function createSlidesMcpServer(rootDir = process.cwd()): McpServer {\n const root = resolve(rootDir);\n const server = new McpServer({ name: 'classic-slides', version: VERSION });\n\n function safePath(p: string): string {\n const abs = resolve(root, p);\n if (abs !== root && !abs.startsWith(root + sep))\n throw new Error(`Path \"${p}\" is outside the deck root ${root}.`);\n if (!/\\.(ya?ml|json)$/i.test(abs))\n throw new Error('Deck files must end in .yaml, .yml or .json.');\n return confinedPath(root, abs);\n }\n\n async function load(p: string): Promise<{ abs: string; deck: EditableDeck }> {\n const abs = safePath(p);\n const deck = (await loadDeckFile(abs)) as EditableDeck;\n if (!deck || !Array.isArray(deck.slides))\n throw new Error(`${p} is not a deck file (no slides list).`);\n return { abs, deck };\n }\n\n /** Runs load → mutate → commit for one deck while holding that deck's lock. */\n function mutate(\n p: string,\n fn: (abs: string, deck: EditableDeck) => Promise<ToolResult>\n ): Promise<ToolResult> {\n const abs = safePath(p);\n return withDeckLock(abs, async () => {\n const { deck } = await load(p);\n return fn(abs, deck);\n });\n }\n\n /** Validates, then saves only when there are no errors. */\n async function commit(abs: string, deck: EditableDeck, done: string): Promise<ToolResult> {\n const v = validateDeck(deck);\n if (!v.ok) {\n return text(\n `Not saved — the change would make the deck invalid:\\n${formatIssues(v.issues)}\\n\\nFix the fields and try again.`,\n true\n );\n }\n await saveDeckFile(confinedPath(root, abs), deck);\n const warnings = v.issues.filter((i) => i.level === 'warning');\n return text(\n `${done}\\n\\nOutline:\\n${outline(deck).join('\\n')}${warnings.length ? `\\n\\nWarnings (saved anyway):\\n${formatIssues(warnings)}` : ''}`\n );\n }\n\n const run =\n <A>(fn: (args: A) => Promise<ToolResult>) =>\n async (args: A): Promise<ToolResult> => {\n try {\n return await fn(args);\n } catch (e) {\n return text(e instanceof Error ? e.message : String(e), true);\n }\n };\n\n const Path = z.string().describe(`Deck file path, relative to ${root}.`);\n const Ref = z\n .union([z.string(), z.number().int().positive()])\n .describe('Slide id, or 1-based slide number.');\n const SlideObj = z\n .record(z.unknown())\n .describe('A slide object: { layout, ...fields }. See get_layout for fields.');\n\n server.tool(\n 'list_layouts',\n 'List every slide layout with what it is for. Start here.',\n {},\n async () =>\n text(\n listLayouts()\n .map((l) => `- **${l.name}** — ${l.description}\\n Use for: ${l.whenToUse}`)\n .join('\\n')\n )\n );\n\n server.tool(\n 'get_layout',\n 'Full field reference (JSON Schema) and a working example for one layout.',\n { name: z.string() },\n run(async ({ name }) => {\n const d = describeLayout(name);\n if (!d) return text(`Unknown layout \"${name}\". Call list_layouts.`, true);\n return text(JSON.stringify(d, null, 2));\n })\n );\n\n server.tool(\n 'get_authoring_guide',\n 'Brand and writing rules for Classic Homes decks. Read before writing slides.',\n {},\n async () => text(AUTHORING_GUIDE)\n );\n\n server.tool(\n 'create_deck',\n 'Create a new deck file. Optionally include the first slides.',\n {\n path: Path,\n title: z.string(),\n description: z.string().optional(),\n journeySteps: z\n .array(z.string())\n .optional()\n .describe('Numbered agenda steps; enables journey features.'),\n slides: z.array(SlideObj).optional(),\n overwrite: z.boolean().default(false),\n },\n run(async ({ path, title, description, journeySteps, slides, overwrite }) => {\n const abs = safePath(path);\n return withDeckLock(abs, async () => {\n if (!overwrite) {\n const exists = await access(abs).then(\n () => true,\n () => false\n );\n if (exists)\n return text(\n `${path} already exists. Use read_deck and edit it, or pass overwrite: true.`,\n true\n );\n }\n const deck: EditableDeck = {\n title,\n ...(description ? { description } : {}),\n ...(journeySteps?.length ? { journey: { steps: journeySteps } } : {}),\n slides: slides?.length ? slides : [{ layout: 'title', title }],\n };\n return commit(abs, deck, `Created ${path}.`);\n });\n })\n );\n\n server.tool(\n 'read_deck',\n 'Read a deck: outline plus the full YAML source.',\n { path: Path },\n run(async ({ path }) => {\n const { deck } = await load(path);\n return text(\n `Outline:\\n${outline(deck).join('\\n')}\\n\\nSource:\\n\\`\\`\\`yaml\\n${stringifyDeck(deck)}\\`\\`\\``\n );\n })\n );\n\n server.tool(\n 'update_deck',\n 'Change deck-level fields (title, description, author, date, journey). Set a field to null to remove it.',\n { path: Path, fields: z.record(z.unknown()) },\n run(async ({ path, fields }) =>\n mutate(path, async (abs, deck) => {\n for (const [k, v] of Object.entries(fields)) {\n if (k === 'slides') return text('Use the slide tools to change slides.', true);\n if (v === null) delete deck[k];\n else deck[k] = v;\n }\n return commit(abs, deck, 'Deck fields updated.');\n })\n )\n );\n\n server.tool(\n 'add_slide',\n 'Insert a slide. position is 1-based; omit to append.',\n { path: Path, slide: SlideObj, position: z.number().int().positive().optional() },\n run(async ({ path, slide, position }) =>\n mutate(path, async (abs, deck) => {\n const at = addSlide(deck, slide, position);\n return commit(abs, deck, `Added slide ${at}.`);\n })\n )\n );\n\n server.tool(\n 'add_slides',\n 'Append several slides at once (e.g. a whole section). Saved only if all are valid.',\n { path: Path, slides: z.array(SlideObj).min(1) },\n run(async ({ path, slides }) =>\n mutate(path, async (abs, deck) => {\n slides.forEach((s) => addSlide(deck, s));\n return commit(abs, deck, `Added ${slides.length} slides.`);\n })\n )\n );\n\n server.tool(\n 'update_slide',\n 'Merge fields into a slide. Set a field to null to remove it. Changing layout replaces the slide.',\n { path: Path, slide: Ref, fields: z.record(z.unknown()) },\n run(async ({ path, slide, fields }) =>\n mutate(path, async (abs, deck) => {\n const at = updateSlide(deck, slide, fields);\n return commit(abs, deck, `Updated slide ${at}.`);\n })\n )\n );\n\n server.tool(\n 'move_slide',\n 'Move a slide to a new 1-based position.',\n { path: Path, slide: Ref, to: z.number().int().positive() },\n run(async ({ path, slide, to }) =>\n mutate(path, async (abs, deck) => {\n const at = moveSlide(deck, slide, to);\n return commit(abs, deck, `Moved slide to ${at}.`);\n })\n )\n );\n\n server.tool(\n 'delete_slide',\n 'Delete a slide.',\n { path: Path, slide: Ref },\n run(async ({ path, slide }) =>\n mutate(path, async (abs, deck) => {\n const removed = removeSlide(deck, slide);\n return commit(abs, deck, `Deleted [${removed.layout}] slide.`);\n })\n )\n );\n\n server.tool(\n 'validate_deck',\n 'Check a deck for errors and crowded slides. Fix every error and most warnings before building.',\n { path: Path },\n run(async ({ path }) => {\n const { deck } = await load(path);\n const v = validateDeck(deck);\n if (!v.issues.length) return text('Deck is valid with no warnings.');\n return text(\n `${v.ok ? 'Valid, with warnings' : 'Invalid'}:\\n${formatIssues(v.issues)}`,\n !v.ok\n );\n })\n );\n\n server.tool(\n 'build_deck',\n 'Render the deck to one self-contained HTML file (images and fonts inlined). Returns the output path.',\n {\n path: Path,\n out: z.string().optional().describe('Output .html path; defaults next to the deck.'),\n },\n run(async ({ path, out }) => {\n const abs = safePath(path);\n let outAbs: string | undefined;\n if (out) {\n outAbs = resolve(root, out);\n if (!outAbs.startsWith(root + sep) || !outAbs.endsWith('.html'))\n throw new Error('out must be an .html path inside the deck root.');\n }\n const warnings: string[] = [];\n const r = await buildDeckFile(abs, {\n out: outAbs,\n rootDir: root,\n onWarning: (w) => warnings.push(w),\n });\n return text(\n `Built ${relative(root, r.out)} — ${r.slideCount} slides, ${(r.bytes / 1024 / 1024).toFixed(2)} MB. Open it in a browser to present (N = notes, F = fullscreen).${\n warnings.length ? `\\n\\nImage warnings:\\n- ${warnings.join('\\n- ')}` : ''\n }${r.warnings.length ? `\\n\\nContent warnings:\\n${formatIssues(r.warnings)}` : ''}`\n );\n })\n );\n\n server.resource(\n 'authoring-guide',\n 'slides://guide',\n { mimeType: 'text/markdown' },\n async (uri) => ({\n contents: [{ uri: uri.href, mimeType: 'text/markdown', text: AUTHORING_GUIDE }],\n })\n );\n\n server.prompt(\n 'build_deck_from_outline',\n 'Turn a topic and rough outline into a finished, validated deck.',\n {\n path: z.string().describe('Where to save the deck, e.g. decks/closing-day.yaml'),\n topic: z.string(),\n audience: z.string(),\n outline: z.string().optional().describe('Bullet outline, notes, or pasted source material.'),\n },\n ({ path, topic, audience, outline: src }) => ({\n messages: [\n {\n role: 'user',\n content: {\n type: 'text',\n text: `Build a Classic Homes presentation.\n\nTopic: ${topic}\nAudience: ${audience}\nSave to: ${path}\n${src ? `\\nSource material:\\n${src}\\n` : ''}\nSteps:\n1. Call get_authoring_guide and list_layouts. Call get_layout for each layout you plan to use.\n2. Plan the slide list (one idea per slide) before writing.\n3. create_deck, then add_slides one section at a time. Put detail in each slide's notes.\n4. validate_deck and fix every error and crowding warning.\n5. build_deck and report the output path with a one-line summary per section.`,\n },\n },\n ],\n })\n );\n\n return server;\n}\n\nexport async function runStdio(rootDir?: string): Promise<void> {\n const server = createSlidesMcpServer(rootDir);\n await server.connect(new StdioServerTransport());\n}\n"],"mappings":"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","names":[]}
|
package/dist/node.d.ts
CHANGED
|
@@ -17,6 +17,11 @@ interface FileResolverOptions {
|
|
|
17
17
|
/** Resolves image paths relative to the deck file and inlines them as data URIs. */
|
|
18
18
|
declare function fileImageResolver(baseDir: string, options?: FileResolverOptions): ImageResolver;
|
|
19
19
|
declare function loadDeckFile(path: string): Promise<unknown>;
|
|
20
|
+
/**
|
|
21
|
+
* Saves a deck atomically: the text goes to a temp file in the same directory, which is
|
|
22
|
+
* then renamed over the target. Readers see the old file or the new one, never a partial
|
|
23
|
+
* write, and a failed write leaves the previous file untouched (the temp is removed).
|
|
24
|
+
*/
|
|
20
25
|
declare function saveDeckFile(path: string, deck: unknown): Promise<void>;
|
|
21
26
|
interface BuildOptions extends Omit<RenderOptions, 'resolveImage'>, FileResolverOptions {
|
|
22
27
|
out?: string;
|
package/dist/node.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@classic-homes/slides",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.4",
|
|
4
4
|
"description": "Branded Classic Homes presentations from structured deck files: schema, layouts, renderer, CLI and MCP server",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
"files": [
|
|
27
27
|
"dist",
|
|
28
28
|
"starters",
|
|
29
|
+
"assets/fonts/OFL.txt",
|
|
29
30
|
"README.md"
|
|
30
31
|
],
|
|
31
32
|
"keywords": [
|
|
@@ -55,7 +56,7 @@
|
|
|
55
56
|
"clean": "node ../../scripts/rm.mjs dist src/generated"
|
|
56
57
|
},
|
|
57
58
|
"dependencies": {
|
|
58
|
-
"@classic-homes/charts-core": "^0.4.
|
|
59
|
+
"@classic-homes/charts-core": "^0.4.2",
|
|
59
60
|
"@classic-homes/theme-tokens": "^0.7.0",
|
|
60
61
|
"echarts": "^5.5.0",
|
|
61
62
|
"qrcode": "^1.5.4",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/node/index.ts","../src/node/paths.ts"],"sourcesContent":["/** Node-only helpers: read deck files, inline local images, write HTML. */\nimport { readFile, stat, writeFile } from 'node:fs/promises';\nimport { dirname, extname, isAbsolute, join, resolve } from 'node:path';\nimport { confinedPath } from './paths.js';\nimport { inlineDeckImages, type InlineImagesResult } from '../images.js';\nimport { parseDeckSource, stringifyDeck } from '../parse.js';\nimport type { ImageResolver, RenderOptions, RenderResult } from '../render.js';\nimport { renderDeck } from '../render-default.js';\n\nconst MIME: Record<string, string> = {\n '.jpg': 'image/jpeg',\n '.jpeg': 'image/jpeg',\n '.png': 'image/png',\n '.webp': 'image/webp',\n '.gif': 'image/gif',\n '.svg': 'image/svg+xml',\n '.avif': 'image/avif',\n};\n\n/** Largest single image we inline without warning. Big photos bloat the deck file. */\nexport const IMAGE_WARN_BYTES = 800 * 1024;\n\n/** Largest remote image we download. Anything bigger is refused, not truncated. */\nconst REMOTE_IMAGE_MAX_BYTES = 25 * 1024 * 1024;\n\n/** How long one remote image (including redirects) may take to download. */\nconst REMOTE_IMAGE_TIMEOUT_MS = 15_000;\n\nconst MAX_REDIRECTS = 5;\n\n/**\n * Downloads a remote image for inlining. Only https is fetched, redirects included, so a\n * deck (or an agent building one over MCP) can't reach plain-http hosts such as cloud\n * metadata endpoints; non-image responses, slow servers and oversized bodies are refused.\n */\nasync function fetchRemoteImage(src: string): Promise<{ type: string; buf: Buffer }> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), REMOTE_IMAGE_TIMEOUT_MS);\n try {\n return await downloadImage(src, controller.signal);\n } finally {\n clearTimeout(timer);\n }\n}\n\nasync function downloadImage(src: string, signal: AbortSignal) {\n let url = src;\n let res: Response;\n for (let hop = 0; ; hop++) {\n if (new URL(url).protocol !== 'https:') throw new Error(`Image ${src} must use https.`);\n try {\n res = await fetch(url, { redirect: 'manual', signal });\n } catch (e) {\n if (signal.aborted)\n throw new Error(`Image ${src} took over ${REMOTE_IMAGE_TIMEOUT_MS / 1000}s to download.`);\n throw e;\n }\n const location = res.headers.get('location');\n if (res.status < 300 || res.status >= 400 || !location) break;\n if (hop === MAX_REDIRECTS) throw new Error(`Image ${src} redirected too many times.`);\n url = new URL(location, url).href;\n }\n if (!res.ok) throw new Error(`Image ${src} returned HTTP ${res.status}`);\n const type = (res.headers.get('content-type') ?? '').split(';')[0].trim().toLowerCase();\n if (!type.startsWith('image/'))\n throw new Error(`Image ${src} is not an image (content-type \"${type || 'none'}\").`);\n const tooBig = () =>\n new Error(\n `Image ${src} is over ${REMOTE_IMAGE_MAX_BYTES / 1024 / 1024} MB; use a smaller copy.`\n );\n if (Number(res.headers.get('content-length')) > REMOTE_IMAGE_MAX_BYTES) {\n await res.body?.cancel();\n throw tooBig();\n }\n const chunks: Uint8Array[] = [];\n let size = 0;\n if (res.body) {\n const reader = res.body.getReader();\n try {\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n size += value.byteLength;\n if (size > REMOTE_IMAGE_MAX_BYTES) {\n await reader.cancel();\n throw tooBig();\n }\n chunks.push(value);\n }\n } catch (e) {\n if (signal.aborted)\n throw new Error(`Image ${src} took over ${REMOTE_IMAGE_TIMEOUT_MS / 1000}s to download.`);\n throw e;\n }\n }\n return { type, buf: Buffer.concat(chunks) };\n}\n\nexport interface FileResolverOptions {\n /** Restrict local image reads and build paths to this root (used by the MCP server). */\n rootDir?: string;\n /**\n * Fetch https images and inline them so the deck works offline. Default true.\n * Plain-http images are never fetched; they stay remote links (with a warning).\n */\n inlineRemote?: boolean;\n onWarning?: (message: string) => void;\n}\n\n/** Resolves image paths relative to the deck file and inlines them as data URIs. */\nexport function fileImageResolver(\n baseDir: string,\n options: FileResolverOptions = {}\n): ImageResolver {\n const warn = options.onWarning ?? (() => {});\n return async (src) => {\n if (src.startsWith('data:')) return src;\n if (/^https?:/i.test(src)) {\n if (options.inlineRemote === false) return src;\n if (!/^https:/i.test(src)) {\n warn(`${src} is not https, so it was left as a remote link; use an https URL.`);\n return src;\n }\n const { type, buf } = await fetchRemoteImage(src);\n if (buf.length > IMAGE_WARN_BYTES)\n warn(`${src} is ${(buf.length / 1024).toFixed(0)} KB; consider a smaller copy.`);\n return `data:${type};base64,${buf.toString('base64')}`;\n }\n const candidate = isAbsolute(src) ? src : join(baseDir, src);\n const path = options.rootDir ? confinedPath(options.rootDir, candidate) : candidate;\n const type = MIME[extname(path).toLowerCase()];\n if (!type)\n throw new Error(`Unsupported image type for \"${src}\". Use jpg, png, webp, gif, svg or avif.`);\n let buf: Buffer;\n try {\n buf = await readFile(path);\n } catch {\n throw new Error(`Image not found: \"${src}\" (looked in ${path})`);\n }\n if (buf.length > IMAGE_WARN_BYTES)\n warn(`${src} is ${(buf.length / 1024).toFixed(0)} KB; consider resizing to ~2000px wide.`);\n return `data:${type};base64,${buf.toString('base64')}`;\n };\n}\n\nexport async function loadDeckFile(path: string): Promise<unknown> {\n return parseDeckSource(await readFile(path, 'utf8'));\n}\n\nexport async function saveDeckFile(path: string, deck: unknown): Promise<void> {\n const text = path.endsWith('.json') ? JSON.stringify(deck, null, 2) + '\\n' : stringifyDeck(deck);\n await writeFile(path, text);\n}\n\nexport interface BuildOptions extends Omit<RenderOptions, 'resolveImage'>, FileResolverOptions {\n out?: string;\n}\n\n/** Renders a deck file to a self-contained HTML file next to it (or at `out`). */\nexport async function buildDeckFile(\n path: string,\n options: BuildOptions = {}\n): Promise<RenderResult & { out: string; bytes: number }> {\n const abs = options.rootDir ? confinedPath(options.rootDir, resolve(path)) : resolve(path);\n const deck = await loadDeckFile(abs);\n const result = await renderDeck(deck, {\n ...options,\n resolveImage: fileImageResolver(dirname(abs), options),\n });\n const candidate = options.out\n ? resolve(options.out)\n : abs.replace(/\\.(ya?ml|json)$/i, '') + '.html';\n const out = options.rootDir ? confinedPath(options.rootDir, candidate) : candidate;\n await writeFile(out, result.html);\n return { ...result, out, bytes: (await stat(out)).size };\n}\n\nexport interface ExportOptions extends FileResolverOptions {\n out?: string;\n}\n\n/**\n * Writes a portable copy of a deck file: JSON with every image inlined as a\n * data URI, ready to import into a hosted deck editor. Defaults to\n * `<name>.deck.json` next to the source.\n */\nexport async function exportDeckFile(\n path: string,\n options: ExportOptions = {}\n): Promise<InlineImagesResult & { out: string; bytes: number }> {\n const abs = options.rootDir ? confinedPath(options.rootDir, resolve(path)) : resolve(path);\n const result = await inlineDeckImages(\n await loadDeckFile(abs),\n fileImageResolver(dirname(abs), options)\n );\n const candidate = options.out\n ? resolve(options.out)\n : abs.replace(/\\.(ya?ml|json)$/i, '').replace(/\\.deck$/i, '') + '.deck.json';\n const out = options.rootDir ? confinedPath(options.rootDir, candidate) : candidate;\n if (out === abs) throw new Error(`Refusing to overwrite the source deck ${abs}. Pass --out.`);\n await writeFile(out, JSON.stringify(result.deck) + '\\n');\n return { ...result, out, bytes: (await stat(out)).size };\n}\n\nexport { renderDeck };\n","/** Canonical containment for existing files and new output files. */\nimport { lstatSync, realpathSync } from 'node:fs';\nimport { dirname, relative, resolve, sep } from 'node:path';\n\nexport function confinedPath(root: string, path: string): string {\n const canonicalRoot = realpathSync(root);\n const absolute = resolve(path);\n let ancestor = absolute;\n for (;;) {\n try {\n const canonical = resolve(realpathSync(ancestor), relative(ancestor, absolute));\n if (canonical !== canonicalRoot && !canonical.startsWith(canonicalRoot + sep)) {\n throw new Error(`Path \"${path}\" is outside the deck root ${root}.`);\n }\n return canonical;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'ENOENT' || ancestor === dirname(ancestor))\n throw error;\n if (lstatSync(ancestor, { throwIfNoEntry: false })?.isSymbolicLink()) {\n throw new Error(`Cannot follow dangling symlink \"${ancestor}\".`);\n }\n ancestor = dirname(ancestor);\n }\n }\n}\n"],"mappings":"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","names":["dirname","resolve","type","buf","resolve","dirname"]}
|
package/dist/mcp-FYZGFSBM.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/node/mcp.ts"],"sourcesContent":["/**\n * Local MCP server (stdio) for building decks on disk with an AI agent.\n *\n * Tools work slide-by-slide on a YAML deck file and validate before every\n * save, so an agent can never leave a deck in a broken state. File access is\n * confined to the root directory the server was started in.\n */\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport { access } from 'node:fs/promises';\nimport { relative, resolve, sep } from 'node:path';\nimport { z } from 'zod';\nimport { confinedPath } from './paths.js';\nimport { AUTHORING_GUIDE, describeLayout, listLayouts } from '../catalog.js';\nimport {\n addSlide,\n moveSlide,\n outline,\n removeSlide,\n updateSlide,\n type EditableDeck,\n} from '../edit.js';\nimport { stringifyDeck } from '../parse.js';\nimport { formatIssues, validateDeck } from '../validate.js';\nimport { buildDeckFile, loadDeckFile, saveDeckFile } from './index.js';\n\nconst VERSION = '0.1.0';\n\ntype ToolResult = { content: { type: 'text'; text: string }[]; isError?: boolean };\nconst text = (t: string, isError = false): ToolResult => ({\n content: [{ type: 'text', text: t }],\n ...(isError ? { isError } : {}),\n});\n\nexport function createSlidesMcpServer(rootDir = process.cwd()): McpServer {\n const root = resolve(rootDir);\n const server = new McpServer({ name: 'classic-slides', version: VERSION });\n\n function safePath(p: string): string {\n const abs = resolve(root, p);\n if (abs !== root && !abs.startsWith(root + sep))\n throw new Error(`Path \"${p}\" is outside the deck root ${root}.`);\n if (!/\\.(ya?ml|json)$/i.test(abs))\n throw new Error('Deck files must end in .yaml, .yml or .json.');\n return confinedPath(root, abs);\n }\n\n async function load(p: string): Promise<{ abs: string; deck: EditableDeck }> {\n const abs = safePath(p);\n const deck = (await loadDeckFile(abs)) as EditableDeck;\n if (!deck || !Array.isArray(deck.slides))\n throw new Error(`${p} is not a deck file (no slides list).`);\n return { abs, deck };\n }\n\n /** Validates, then saves only when there are no errors. */\n async function commit(abs: string, deck: EditableDeck, done: string): Promise<ToolResult> {\n const v = validateDeck(deck);\n if (!v.ok) {\n return text(\n `Not saved — the change would make the deck invalid:\\n${formatIssues(v.issues)}\\n\\nFix the fields and try again.`,\n true\n );\n }\n await saveDeckFile(confinedPath(root, abs), deck);\n const warnings = v.issues.filter((i) => i.level === 'warning');\n return text(\n `${done}\\n\\nOutline:\\n${outline(deck).join('\\n')}${warnings.length ? `\\n\\nWarnings (saved anyway):\\n${formatIssues(warnings)}` : ''}`\n );\n }\n\n const run =\n <A>(fn: (args: A) => Promise<ToolResult>) =>\n async (args: A): Promise<ToolResult> => {\n try {\n return await fn(args);\n } catch (e) {\n return text(e instanceof Error ? e.message : String(e), true);\n }\n };\n\n const Path = z.string().describe(`Deck file path, relative to ${root}.`);\n const Ref = z\n .union([z.string(), z.number().int().positive()])\n .describe('Slide id, or 1-based slide number.');\n const SlideObj = z\n .record(z.unknown())\n .describe('A slide object: { layout, ...fields }. See get_layout for fields.');\n\n server.tool(\n 'list_layouts',\n 'List every slide layout with what it is for. Start here.',\n {},\n async () =>\n text(\n listLayouts()\n .map((l) => `- **${l.name}** — ${l.description}\\n Use for: ${l.whenToUse}`)\n .join('\\n')\n )\n );\n\n server.tool(\n 'get_layout',\n 'Full field reference (JSON Schema) and a working example for one layout.',\n { name: z.string() },\n run(async ({ name }) => {\n const d = describeLayout(name);\n if (!d) return text(`Unknown layout \"${name}\". Call list_layouts.`, true);\n return text(JSON.stringify(d, null, 2));\n })\n );\n\n server.tool(\n 'get_authoring_guide',\n 'Brand and writing rules for Classic Homes decks. Read before writing slides.',\n {},\n async () => text(AUTHORING_GUIDE)\n );\n\n server.tool(\n 'create_deck',\n 'Create a new deck file. Optionally include the first slides.',\n {\n path: Path,\n title: z.string(),\n description: z.string().optional(),\n journeySteps: z\n .array(z.string())\n .optional()\n .describe('Numbered agenda steps; enables journey features.'),\n slides: z.array(SlideObj).optional(),\n overwrite: z.boolean().default(false),\n },\n run(async ({ path, title, description, journeySteps, slides, overwrite }) => {\n const abs = safePath(path);\n if (!overwrite) {\n const exists = await access(abs).then(\n () => true,\n () => false\n );\n if (exists)\n return text(\n `${path} already exists. Use read_deck and edit it, or pass overwrite: true.`,\n true\n );\n }\n const deck: EditableDeck = {\n title,\n ...(description ? { description } : {}),\n ...(journeySteps?.length ? { journey: { steps: journeySteps } } : {}),\n slides: slides?.length ? slides : [{ layout: 'title', title }],\n };\n return commit(abs, deck, `Created ${path}.`);\n })\n );\n\n server.tool(\n 'read_deck',\n 'Read a deck: outline plus the full YAML source.',\n { path: Path },\n run(async ({ path }) => {\n const { deck } = await load(path);\n return text(\n `Outline:\\n${outline(deck).join('\\n')}\\n\\nSource:\\n\\`\\`\\`yaml\\n${stringifyDeck(deck)}\\`\\`\\``\n );\n })\n );\n\n server.tool(\n 'update_deck',\n 'Change deck-level fields (title, description, author, date, journey). Set a field to null to remove it.',\n { path: Path, fields: z.record(z.unknown()) },\n run(async ({ path, fields }) => {\n const { abs, deck } = await load(path);\n for (const [k, v] of Object.entries(fields)) {\n if (k === 'slides') return text('Use the slide tools to change slides.', true);\n if (v === null) delete deck[k];\n else deck[k] = v;\n }\n return commit(abs, deck, 'Deck fields updated.');\n })\n );\n\n server.tool(\n 'add_slide',\n 'Insert a slide. position is 1-based; omit to append.',\n { path: Path, slide: SlideObj, position: z.number().int().positive().optional() },\n run(async ({ path, slide, position }) => {\n const { abs, deck } = await load(path);\n const at = addSlide(deck, slide, position);\n return commit(abs, deck, `Added slide ${at}.`);\n })\n );\n\n server.tool(\n 'add_slides',\n 'Append several slides at once (e.g. a whole section). Saved only if all are valid.',\n { path: Path, slides: z.array(SlideObj).min(1) },\n run(async ({ path, slides }) => {\n const { abs, deck } = await load(path);\n slides.forEach((s) => addSlide(deck, s));\n return commit(abs, deck, `Added ${slides.length} slides.`);\n })\n );\n\n server.tool(\n 'update_slide',\n 'Merge fields into a slide. Set a field to null to remove it. Changing layout replaces the slide.',\n { path: Path, slide: Ref, fields: z.record(z.unknown()) },\n run(async ({ path, slide, fields }) => {\n const { abs, deck } = await load(path);\n const at = updateSlide(deck, slide, fields);\n return commit(abs, deck, `Updated slide ${at}.`);\n })\n );\n\n server.tool(\n 'move_slide',\n 'Move a slide to a new 1-based position.',\n { path: Path, slide: Ref, to: z.number().int().positive() },\n run(async ({ path, slide, to }) => {\n const { abs, deck } = await load(path);\n const at = moveSlide(deck, slide, to);\n return commit(abs, deck, `Moved slide to ${at}.`);\n })\n );\n\n server.tool(\n 'delete_slide',\n 'Delete a slide.',\n { path: Path, slide: Ref },\n run(async ({ path, slide }) => {\n const { abs, deck } = await load(path);\n const removed = removeSlide(deck, slide);\n return commit(abs, deck, `Deleted [${removed.layout}] slide.`);\n })\n );\n\n server.tool(\n 'validate_deck',\n 'Check a deck for errors and crowded slides. Fix every error and most warnings before building.',\n { path: Path },\n run(async ({ path }) => {\n const { deck } = await load(path);\n const v = validateDeck(deck);\n if (!v.issues.length) return text('Deck is valid with no warnings.');\n return text(\n `${v.ok ? 'Valid, with warnings' : 'Invalid'}:\\n${formatIssues(v.issues)}`,\n !v.ok\n );\n })\n );\n\n server.tool(\n 'build_deck',\n 'Render the deck to one self-contained HTML file (images and fonts inlined). Returns the output path.',\n {\n path: Path,\n out: z.string().optional().describe('Output .html path; defaults next to the deck.'),\n },\n run(async ({ path, out }) => {\n const abs = safePath(path);\n let outAbs: string | undefined;\n if (out) {\n outAbs = resolve(root, out);\n if (!outAbs.startsWith(root + sep) || !outAbs.endsWith('.html'))\n throw new Error('out must be an .html path inside the deck root.');\n }\n const warnings: string[] = [];\n const r = await buildDeckFile(abs, {\n out: outAbs,\n rootDir: root,\n onWarning: (w) => warnings.push(w),\n });\n return text(\n `Built ${relative(root, r.out)} — ${r.slideCount} slides, ${(r.bytes / 1024 / 1024).toFixed(2)} MB. Open it in a browser to present (N = notes, F = fullscreen).${\n warnings.length ? `\\n\\nImage warnings:\\n- ${warnings.join('\\n- ')}` : ''\n }${r.warnings.length ? `\\n\\nContent warnings:\\n${formatIssues(r.warnings)}` : ''}`\n );\n })\n );\n\n server.resource(\n 'authoring-guide',\n 'slides://guide',\n { mimeType: 'text/markdown' },\n async (uri) => ({\n contents: [{ uri: uri.href, mimeType: 'text/markdown', text: AUTHORING_GUIDE }],\n })\n );\n\n server.prompt(\n 'build_deck_from_outline',\n 'Turn a topic and rough outline into a finished, validated deck.',\n {\n path: z.string().describe('Where to save the deck, e.g. decks/closing-day.yaml'),\n topic: z.string(),\n audience: z.string(),\n outline: z.string().optional().describe('Bullet outline, notes, or pasted source material.'),\n },\n ({ path, topic, audience, outline: src }) => ({\n messages: [\n {\n role: 'user',\n content: {\n type: 'text',\n text: `Build a Classic Homes presentation.\n\nTopic: ${topic}\nAudience: ${audience}\nSave to: ${path}\n${src ? `\\nSource material:\\n${src}\\n` : ''}\nSteps:\n1. Call get_authoring_guide and list_layouts. Call get_layout for each layout you plan to use.\n2. Plan the slide list (one idea per slide) before writing.\n3. create_deck, then add_slides one section at a time. Put detail in each slide's notes.\n4. validate_deck and fix every error and crowding warning.\n5. build_deck and report the output path with a one-line summary per section.`,\n },\n },\n ],\n })\n );\n\n return server;\n}\n\nexport async function runStdio(rootDir?: string): Promise<void> {\n const server = createSlidesMcpServer(rootDir);\n await server.connect(new StdioServerTransport());\n}\n"],"mappings":"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","names":[]}
|