@tycoworks/tycoslide 0.7.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.
Files changed (50) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +73 -0
  3. package/SKILL.md +249 -0
  4. package/bin/tycoslide.js +2 -0
  5. package/dist/cli.d.ts +1 -0
  6. package/dist/cli.js +197 -0
  7. package/dist/engine/dom.d.ts +92 -0
  8. package/dist/engine/dom.js +354 -0
  9. package/dist/engine/fillers/filler.d.ts +22 -0
  10. package/dist/engine/fillers/filler.js +53 -0
  11. package/dist/engine/fillers/image.d.ts +19 -0
  12. package/dist/engine/fillers/image.js +105 -0
  13. package/dist/engine/fillers/table.d.ts +21 -0
  14. package/dist/engine/fillers/table.js +62 -0
  15. package/dist/engine/fillers/template.d.ts +27 -0
  16. package/dist/engine/fillers/template.js +221 -0
  17. package/dist/engine/fillers/text.d.ts +28 -0
  18. package/dist/engine/fillers/text.js +29 -0
  19. package/dist/engine/generate.d.ts +51 -0
  20. package/dist/engine/generate.js +161 -0
  21. package/dist/engine/index.d.ts +8 -0
  22. package/dist/engine/index.js +7 -0
  23. package/dist/engine/types.d.ts +128 -0
  24. package/dist/engine/types.js +22 -0
  25. package/dist/index.d.ts +46 -0
  26. package/dist/index.js +146 -0
  27. package/dist/manifest.d.ts +7 -0
  28. package/dist/manifest.js +88 -0
  29. package/dist/markdown/deckCompiler.d.ts +36 -0
  30. package/dist/markdown/deckCompiler.js +289 -0
  31. package/dist/markdown/index.d.ts +13 -0
  32. package/dist/markdown/index.js +13 -0
  33. package/dist/markdown/parsers.d.ts +32 -0
  34. package/dist/markdown/parsers.js +233 -0
  35. package/dist/markdown/resolvers/code.d.ts +17 -0
  36. package/dist/markdown/resolvers/code.js +44 -0
  37. package/dist/markdown/resolvers/mermaid.d.ts +14 -0
  38. package/dist/markdown/resolvers/mermaid.js +89 -0
  39. package/dist/markdown/resolvers/mermaidTheme.d.ts +27 -0
  40. package/dist/markdown/resolvers/mermaidTheme.js +113 -0
  41. package/dist/markdown/resolvers/resolver.d.ts +42 -0
  42. package/dist/markdown/resolvers/resolver.js +52 -0
  43. package/dist/markdown/slideParser.d.ts +14 -0
  44. package/dist/markdown/slideParser.js +196 -0
  45. package/dist/markdown/textTemplate.d.ts +15 -0
  46. package/dist/markdown/textTemplate.js +75 -0
  47. package/dist/markdown/types.d.ts +239 -0
  48. package/dist/markdown/types.js +40 -0
  49. package/package.json +41 -0
  50. package/syntax.md +291 -0
@@ -0,0 +1,14 @@
1
+ export interface RawSlide {
2
+ index: number;
3
+ frontmatter: Record<string, unknown>;
4
+ body: string;
5
+ slots: Record<string, string>;
6
+ }
7
+ export interface ParsedDocument {
8
+ global: Record<string, unknown>;
9
+ slides: RawSlide[];
10
+ }
11
+ export declare function parseSlideDocument(source: string): ParsedDocument;
12
+ export declare class FrontmatterParseError extends Error {
13
+ constructor(slideIndex: number, yamlSource: string, cause: unknown);
14
+ }
@@ -0,0 +1,196 @@
1
+ import { parse as parseYaml } from "yaml";
2
+ // ============================================
3
+ // PUBLIC API
4
+ // ============================================
5
+ export function parseSlideDocument(source) {
6
+ const { global, rest } = extractGlobalFrontmatter(source);
7
+ const rawSlides = splitIntoSlides(rest);
8
+ const slides = [];
9
+ for (const raw of rawSlides) {
10
+ slides.push(buildSlide(slides.length, raw.frontmatter, raw.content));
11
+ }
12
+ return { global, slides };
13
+ }
14
+ // ============================================
15
+ // GLOBAL FRONTMATTER
16
+ // ============================================
17
+ const GLOBAL_FM_RE = /^---[ \t]*\r?\n([\s\S]*?)\r?\n---[ \t]*(?:\r?\n|$)/;
18
+ function extractGlobalFrontmatter(source) {
19
+ const match = source.match(GLOBAL_FM_RE);
20
+ if (!match)
21
+ return { global: {}, rest: source };
22
+ const parsed = parseYaml(match[1]);
23
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
24
+ return {
25
+ global: parsed,
26
+ rest: source.slice(match[0].length),
27
+ };
28
+ }
29
+ if (parsed !== null && parsed !== undefined) {
30
+ throw new Error(`Global frontmatter must be a YAML mapping (key: value pairs), got ${Array.isArray(parsed) ? "array" : typeof parsed}.`);
31
+ }
32
+ return { global: {}, rest: source };
33
+ }
34
+ const SEPARATOR_RE = /^---[ \t]*$/;
35
+ const CODE_FENCE_OPEN_RE = /^(`{3,}|~{3,})/;
36
+ /**
37
+ * States:
38
+ * - BODY: accumulating body content lines for current slide
39
+ * - FM: accumulating frontmatter lines (between --- pair)
40
+ *
41
+ * Transitions:
42
+ * - BODY + `---` -> flush current slide, start new slide, enter FM state
43
+ * - FM + `---` -> close frontmatter, enter BODY state
44
+ * - FM + blank line (before any non-blank FM line) -> no frontmatter, enter BODY
45
+ * - EOF while in FM -> unterminated frontmatter, treat accumulated lines as body
46
+ */
47
+ function splitIntoSlides(text) {
48
+ const lines = text.split(/\r?\n/);
49
+ const slides = [];
50
+ let inFM = false;
51
+ let fmStarted = false;
52
+ let fmClosed = false;
53
+ let fmLines = [];
54
+ let bodyLines = [];
55
+ let inCodeFence = false;
56
+ let codeFenceChar = "";
57
+ let codeFenceLen = 0;
58
+ function flushSlide() {
59
+ const fm = fmLines.join("\n").trim();
60
+ const body = bodyLines.join("\n").trim();
61
+ if (fm || body) {
62
+ slides.push({ frontmatter: fm, content: body });
63
+ }
64
+ fmLines = [];
65
+ bodyLines = [];
66
+ }
67
+ for (const line of lines) {
68
+ if (!inCodeFence) {
69
+ const fenceMatch = line.match(CODE_FENCE_OPEN_RE);
70
+ if (fenceMatch) {
71
+ inCodeFence = true;
72
+ codeFenceChar = fenceMatch[1][0];
73
+ codeFenceLen = fenceMatch[1].length;
74
+ }
75
+ }
76
+ else {
77
+ const trimmed = line.trim();
78
+ if (trimmed.length >= codeFenceLen && trimmed === codeFenceChar.repeat(trimmed.length)) {
79
+ inCodeFence = false;
80
+ }
81
+ }
82
+ const isSeparator = !inCodeFence && SEPARATOR_RE.test(line);
83
+ if (isSeparator) {
84
+ if (inFM) {
85
+ inFM = false;
86
+ fmClosed = true;
87
+ }
88
+ else {
89
+ if (fmLines.length > 0 && !fmClosed) {
90
+ bodyLines = [...fmLines, ...bodyLines];
91
+ fmLines = [];
92
+ }
93
+ flushSlide();
94
+ inFM = true;
95
+ fmStarted = false;
96
+ fmClosed = false;
97
+ }
98
+ }
99
+ else if (inFM) {
100
+ if (!fmStarted && line.trim() === "") {
101
+ inFM = false;
102
+ bodyLines.push(line);
103
+ }
104
+ else {
105
+ fmStarted = true;
106
+ fmLines.push(line);
107
+ }
108
+ }
109
+ else {
110
+ bodyLines.push(line);
111
+ }
112
+ }
113
+ // Unterminated FM at EOF -> treat as body content
114
+ if (fmLines.length > 0 && !fmClosed) {
115
+ bodyLines = [...fmLines, ...bodyLines];
116
+ fmLines = [];
117
+ }
118
+ flushSlide();
119
+ return slides;
120
+ }
121
+ // ============================================
122
+ // SLIDE BUILDER
123
+ // ============================================
124
+ function buildSlide(index, fmString, rawContent) {
125
+ const frontmatter = parseFrontmatter(fmString, index);
126
+ const { defaultSlot, slots } = extractSlots(rawContent);
127
+ return { index, frontmatter, body: defaultSlot, slots };
128
+ }
129
+ export class FrontmatterParseError extends Error {
130
+ constructor(slideIndex, yamlSource, cause) {
131
+ const preview = yamlSource.length > 80 ? `${yamlSource.slice(0, 80)}...` : yamlSource;
132
+ super(`Invalid YAML in slide ${slideIndex} frontmatter:\n${preview}`);
133
+ this.name = "FrontmatterParseError";
134
+ this.cause = cause;
135
+ }
136
+ }
137
+ function parseFrontmatter(yaml, slideIndex) {
138
+ if (!yaml)
139
+ return {};
140
+ try {
141
+ const result = parseYaml(yaml);
142
+ if (result && typeof result === "object" && !Array.isArray(result)) {
143
+ return result;
144
+ }
145
+ }
146
+ catch (err) {
147
+ throw new FrontmatterParseError(slideIndex, yaml, err);
148
+ }
149
+ throw new Error(`Slide ${slideIndex + 1}: frontmatter must be a YAML mapping (key: value pairs), got ${Array.isArray(parseYaml(yaml)) ? "array" : typeof parseYaml(yaml)}.`);
150
+ }
151
+ // ============================================
152
+ // CONTENT SLOTS
153
+ // ============================================
154
+ const SLOT_LINE_RE = /^::(\w+)::[ \t]*$/;
155
+ function extractSlots(content) {
156
+ const lines = content.split(/\r?\n/);
157
+ let currentSlot = null;
158
+ const slotLines = new Map([[null, []]]);
159
+ let inCodeFence = false;
160
+ let codeFenceChar = "";
161
+ let codeFenceLen = 0;
162
+ for (const line of lines) {
163
+ if (!inCodeFence) {
164
+ const fenceMatch = line.match(CODE_FENCE_OPEN_RE);
165
+ if (fenceMatch) {
166
+ inCodeFence = true;
167
+ codeFenceChar = fenceMatch[1][0];
168
+ codeFenceLen = fenceMatch[1].length;
169
+ }
170
+ }
171
+ else {
172
+ const trimmed = line.trim();
173
+ if (trimmed.length >= codeFenceLen && trimmed === codeFenceChar.repeat(trimmed.length)) {
174
+ inCodeFence = false;
175
+ }
176
+ }
177
+ const slotMatch = !inCodeFence && line.match(SLOT_LINE_RE);
178
+ if (slotMatch) {
179
+ currentSlot = slotMatch[1];
180
+ if (!slotLines.has(currentSlot)) {
181
+ slotLines.set(currentSlot, []);
182
+ }
183
+ }
184
+ else {
185
+ slotLines.get(currentSlot).push(line);
186
+ }
187
+ }
188
+ const defaultSlot = slotLines.get(null).join("\n").trim();
189
+ const slots = {};
190
+ for (const [name, sLines] of slotLines) {
191
+ if (name !== null) {
192
+ slots[name] = sLines.join("\n").trim();
193
+ }
194
+ }
195
+ return { defaultSlot, slots };
196
+ }
@@ -0,0 +1,15 @@
1
+ import type { TemplateSegment } from "../engine/index.js";
2
+ /**
3
+ * The unique keys named by a template's placeholders, in first-seen order. A key
4
+ * repeated in the template appears once — it is filled once, everywhere it occurs.
5
+ */
6
+ export declare function templateKeys(template: string): string[];
7
+ /**
8
+ * Parse a template into per-line segment lists: split on newlines, then tokenize
9
+ * each line into an ordered list of literal and variable segments. Variables
10
+ * carry their substituted value (escapes collapse: `{{` → `{`, `}}` → `}`).
11
+ * Fails fast — a placeholder with no supplied value throws, naming the shape and
12
+ * the key, since a partially-filled text parameter is an authoring error.
13
+ * `shapeName` is the target PPTX shape's name, used only for the error message.
14
+ */
15
+ export declare function templateToSegments(template: string, values: Map<string, string>, shapeName: string): TemplateSegment[][];
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Text-template parsing. A text parameter's `template` is a single string with
3
+ * `{key}` placeholders; newlines in it are line breaks. Filling substitutes the
4
+ * author's values; the substituted result is written into the shape's existing
5
+ * runs by the engine. Lives in the compiler — the engine never parses `{}`.
6
+ *
7
+ * Syntax:
8
+ * - `{name}` — a placeholder for the key `name`; `name` matches `[A-Za-z0-9_]+`.
9
+ * - `{{` / `}}` — an escaped literal `{` / `}`.
10
+ * - A `{` not followed by a valid name + `}` is a literal brace, left as-is.
11
+ */
12
+ // Order matters: escapes (`{{`, `}}`) are tried before a placeholder so
13
+ // `{{name}}` reads as a literal-brace-wrapped `name`, not a `{name}` placeholder.
14
+ const TOKEN_RE = /\{\{|\}\}|\{([A-Za-z0-9_]+)\}/g;
15
+ /**
16
+ * The unique keys named by a template's placeholders, in first-seen order. A key
17
+ * repeated in the template appears once — it is filled once, everywhere it occurs.
18
+ */
19
+ export function templateKeys(template) {
20
+ const seen = new Set();
21
+ const keys = [];
22
+ for (const match of template.matchAll(TOKEN_RE)) {
23
+ const name = match[1];
24
+ if (name !== undefined && !seen.has(name)) {
25
+ seen.add(name);
26
+ keys.push(name);
27
+ }
28
+ }
29
+ return keys;
30
+ }
31
+ /**
32
+ * Parse a template into per-line segment lists: split on newlines, then tokenize
33
+ * each line into an ordered list of literal and variable segments. Variables
34
+ * carry their substituted value (escapes collapse: `{{` → `{`, `}}` → `}`).
35
+ * Fails fast — a placeholder with no supplied value throws, naming the shape and
36
+ * the key, since a partially-filled text parameter is an authoring error.
37
+ * `shapeName` is the target PPTX shape's name, used only for the error message.
38
+ */
39
+ export function templateToSegments(template, values, shapeName) {
40
+ return template.split("\n").map((line) => lineToSegments(line, values, shapeName));
41
+ }
42
+ function lineToSegments(line, values, shapeName) {
43
+ const segments = [];
44
+ const pushLiteral = (text) => {
45
+ if (text === "")
46
+ return;
47
+ const last = segments[segments.length - 1];
48
+ if (last?.kind === "literal")
49
+ last.text += text;
50
+ else
51
+ segments.push({ kind: "literal", text });
52
+ };
53
+ let lastIndex = 0;
54
+ for (const match of line.matchAll(TOKEN_RE)) {
55
+ pushLiteral(line.slice(lastIndex, match.index));
56
+ const token = match[0];
57
+ const name = match[1];
58
+ if (token === "{{") {
59
+ pushLiteral("{");
60
+ }
61
+ else if (token === "}}") {
62
+ pushLiteral("}");
63
+ }
64
+ else {
65
+ const value = values.get(name);
66
+ if (value === undefined) {
67
+ throw new Error(`Text parameter "${shapeName}": no value supplied for key "${name}" (placeholder "{${name}}")`);
68
+ }
69
+ segments.push({ kind: "variable", key: name, value });
70
+ }
71
+ lastIndex = match.index + token.length;
72
+ }
73
+ pushLiteral(line.slice(lastIndex));
74
+ return segments;
75
+ }
@@ -0,0 +1,239 @@
1
+ import { type FitMode, type ImageFill, type TableFill, type TemplateFill, type TextFill } from "../engine/index.js";
2
+ import type { MermaidConfig } from "./resolvers/mermaidTheme.js";
3
+ /**
4
+ * A theme's declaration of a reusable image asset. Purely compiler-facing —
5
+ * the engine never sees this type; the compiler resolves an entry's `path`
6
+ * to a fully-qualified filesystem path and wraps it as an ImageFill before
7
+ * handing the deck to the engine.
8
+ */
9
+ export type AssetEntry = {
10
+ path: string;
11
+ description: string;
12
+ whenToUse?: string;
13
+ };
14
+ /** Two-level catalog: `{ category: { name: AssetEntry } }`. */
15
+ export type AssetCatalog = Record<string, Record<string, AssetEntry>>;
16
+ /**
17
+ * Discriminator strings for the compiler-internal fence shapes produced by
18
+ * deckCompiler.ts. `Code` and `Mermaid` are resolved away entirely (syntax
19
+ * highlighting → TextFill, PNG rendering → ImageFill), so neither reaches the
20
+ * engine. Prose has no FenceType: it becomes a `TextFill` (`{ paragraphs }`),
21
+ * one of the engine's own fill shapes.
22
+ */
23
+ export declare const FenceType: {
24
+ readonly Code: "code";
25
+ readonly Mermaid: "mermaid";
26
+ };
27
+ export type FenceType = (typeof FenceType)[keyof typeof FenceType];
28
+ /**
29
+ * Discriminator for a layout's frontmatter *parameters* — inputs the author
30
+ * writes as a single `key: value` line. Two kinds: `Template` (fills a styled
31
+ * shape's runs via fillTemplate) and `Image` (a filesystem path filled via
32
+ * fillImage). Both share their value with the engine's `SlotType`, since a
33
+ * parameter maps straight to an engine slot with no resolution step.
34
+ */
35
+ export declare const ParameterType: {
36
+ readonly Template: "template";
37
+ readonly Image: "image";
38
+ };
39
+ export type ParameterType = (typeof ParameterType)[keyof typeof ParameterType];
40
+ /**
41
+ * Compiler-facing slot type for a layout's *slots* — multi-line body regions
42
+ * (the default body region, or a `::name::` region). Narrowed to four kinds;
43
+ * `Template` and `Image` left this list to become parameters. `Code` and `Mermaid`
44
+ * are compiler-only, resolved (syntax highlighting, mermaid PNG rendering)
45
+ * before the engine sees the deck. Required on every CompilerSlot — no silent
46
+ * default.
47
+ */
48
+ export declare const CompilerSlotType: {
49
+ readonly Text: "text";
50
+ readonly Table: "table";
51
+ readonly Code: "code";
52
+ readonly Mermaid: "mermaid";
53
+ };
54
+ export type CompilerSlotType = (typeof CompilerSlotType)[keyof typeof CompilerSlotType];
55
+ /**
56
+ * A fenced code block awaiting syntax highlighting. Produced by deckCompiler,
57
+ * consumed by CodeResolver. Never crosses the engine boundary.
58
+ */
59
+ export type CodeFence = {
60
+ type: typeof FenceType.Code;
61
+ language: string;
62
+ source: string;
63
+ };
64
+ /**
65
+ * A mermaid code fence awaiting rendering. Produced by deckCompiler, consumed
66
+ * by MermaidResolver — the resulting PNG is wrapped as an ImageFill in
67
+ * step.content, so the engine never learns mermaid exists.
68
+ */
69
+ export type MermaidFence = {
70
+ type: typeof FenceType.Mermaid;
71
+ definition: string;
72
+ };
73
+ /**
74
+ * The full set of value shapes a slot may hold during compilation, before code
75
+ * fences are highlighted (→ TextFill) and mermaid fences are rendered
76
+ * (→ ImageFill). CodeFence, MermaidFence, and ImageFill carry a `.type`
77
+ * discriminator; the three engine fills without one — TextFill, TableFill,
78
+ * TemplateFill — are identified structurally by their signature field.
79
+ */
80
+ export type MarkdownBlock = TextFill | TableFill | CodeFence | MermaidFence | ImageFill | TemplateFill;
81
+ /**
82
+ * A DeckStep as compileDeck produces it — content values may still be
83
+ * unresolved (CodeFence, MermaidFence) at this point. `resolveFences` narrows
84
+ * these into StyledParagraph[] / ImageFill before the engine sees the deck.
85
+ */
86
+ export type CompilerDeckStep = {
87
+ layout: string;
88
+ content?: Record<string, MarkdownBlock>;
89
+ };
90
+ /**
91
+ * The intermediate deck shape produced by compileDeck. buildDeck runs the
92
+ * resolvers and hands the resulting engine-shaped Deck to generate().
93
+ */
94
+ export type CompilerDeck = {
95
+ theme: string;
96
+ output?: string;
97
+ steps: CompilerDeckStep[];
98
+ };
99
+ /**
100
+ * A DeckStep whose content values have been narrowed post-resolution — no
101
+ * CodeFence or MermaidFence remains, only shapes the engine understands.
102
+ * Structurally equivalent to the engine's DeckStep.
103
+ */
104
+ export type ResolvedCompilerDeckStep = {
105
+ layout: string;
106
+ content?: Record<string, TextFill | TableFill | ImageFill | TemplateFill>;
107
+ };
108
+ /**
109
+ * A CompilerDeck whose steps have been passed through the resolvers via
110
+ * `resolveFences`. Structurally equivalent to the engine's Deck — deck
111
+ * resolution returns this shape so `generate` can consume it without any cast.
112
+ */
113
+ export type ResolvedCompilerDeck = {
114
+ theme: string;
115
+ output: string;
116
+ steps: ResolvedCompilerDeckStep[];
117
+ };
118
+ /**
119
+ * Fields common to every text shape, image parameter, and slot. A text shape is
120
+ * addressed by `shapeName` and owns a `template` (its placeholders are the
121
+ * fillable keys); image parameters and slots additionally carry a single `key`.
122
+ */
123
+ type CompilerShapeBase = {
124
+ shapeName: string;
125
+ limit?: {
126
+ maxChars?: number;
127
+ maxLines?: number;
128
+ maxItems?: number;
129
+ };
130
+ /**
131
+ * Whether the parameter/slot may be omitted from a slide. Optional (defaults
132
+ * to false): a required one with no value causes the compiler to throw with
133
+ * layout + key names.
134
+ */
135
+ required?: boolean;
136
+ };
137
+ /**
138
+ * Shape base plus a single `key` — the addressing model for image parameters
139
+ * and every slot. Template parameters do NOT extend this: their fillable keys are
140
+ * the placeholders in their `template`, not a single top-level key.
141
+ */
142
+ type CompilerSlotBase = CompilerShapeBase & {
143
+ key: string;
144
+ };
145
+ /**
146
+ * Template parameter: a styled text shape filled by expanding a `template` into the
147
+ * shape's paragraphs via fillTemplate. The template is one string with `{key}`
148
+ * placeholders and newlines for line breaks (`"{lastname}, {firstname} -
149
+ * {company}"`, or `"{name}\n{jobTitle}"`); a shape's fillable keys are the
150
+ * placeholders in its template. The shape carries no top-level `key`; its
151
+ * placeholders are the keys the author fills in frontmatter.
152
+ */
153
+ export type CompilerTemplateParameter = CompilerShapeBase & {
154
+ type: typeof ParameterType.Template;
155
+ /** The shape's text as one template with `{key}` placeholders; newlines are line breaks. */
156
+ template: string;
157
+ };
158
+ /** Image parameter: one frontmatter path filled by fillImage. */
159
+ export type CompilerImageParameter = CompilerSlotBase & {
160
+ type: typeof ParameterType.Image;
161
+ /** How the picture scales inside its frame (required — no silent default). */
162
+ fit: FitMode;
163
+ };
164
+ /**
165
+ * Compiler-facing parameter. A layout's frontmatter inputs — each written as a
166
+ * single `key: value` line, resolved against the layout's `parameters` list.
167
+ */
168
+ export type CompilerParameter = CompilerTemplateParameter | CompilerImageParameter;
169
+ /** Multi-paragraph body filled by fillText (specimen-style rebuild). */
170
+ export type CompilerTextSlot = CompilerSlotBase & {
171
+ type: typeof CompilerSlotType.Text;
172
+ /** Leave the first N specimen paragraphs untouched. */
173
+ startAt?: number;
174
+ };
175
+ /** Table shape backed by an `<a:tbl>` with header + data specimen rows. */
176
+ export type CompilerTableSlot = CompilerSlotBase & {
177
+ type: typeof CompilerSlotType.Table;
178
+ /** Enforced column count. */
179
+ columns?: number;
180
+ };
181
+ /**
182
+ * Text slot that consumes a fenced code block. The resolver (`CodeResolver`)
183
+ * uses `codeTheme` to Shiki-highlight the source into StyledParagraph[]; the
184
+ * engine sees the projected Text slot.
185
+ */
186
+ export type CompilerCodeSlot = CompilerSlotBase & {
187
+ type: typeof CompilerSlotType.Code;
188
+ /** Shiki theme id (required — no silent default). */
189
+ codeTheme: string;
190
+ };
191
+ /**
192
+ * Slot that consumes a mermaid code fence. The resolver (`MermaidResolver`)
193
+ * uses `mermaidVariant` to select colors and renders a PNG; the engine sees the
194
+ * projected Image slot with `contain` fit. Mermaid slots do NOT declare `fit` —
195
+ * mermaid diagrams are always contained.
196
+ */
197
+ export type CompilerMermaidSlot = CompilerSlotBase & {
198
+ type: typeof CompilerSlotType.Mermaid;
199
+ /** Name of the mermaid variant (required — no silent default). */
200
+ mermaidVariant: string;
201
+ };
202
+ /**
203
+ * Compiler-facing slot. Discriminated union on `type` — each variant declares
204
+ * ONLY the fields legal for that slot type. A layout's body regions (the
205
+ * default region, or a `::name::` region), resolved against the layout's
206
+ * `slots` list. Adds markdown-flavored concepts (limit hints, code-fence theme
207
+ * selection, mermaid variant naming) on top of the engine's minimal Slot.
208
+ * Compiler-only types (Code, Mermaid) are projected down to engine types (Text,
209
+ * Image) at the engine boundary before the engine sees the layout.
210
+ */
211
+ export type CompilerSlot = CompilerTextSlot | CompilerTableSlot | CompilerCodeSlot | CompilerMermaidSlot;
212
+ export type CompilerLayout = {
213
+ name: string;
214
+ slideNumber: number;
215
+ description: string;
216
+ whenToUse: string;
217
+ whenNotToUse: string;
218
+ /** Frontmatter inputs (template, image) — one value per `key: value` line. */
219
+ parameters: CompilerParameter[];
220
+ /** Body regions (text, table, code, mermaid) — the body or `::name::` regions. */
221
+ slots: CompilerSlot[];
222
+ };
223
+ /**
224
+ * Compiler-facing theme configuration. Mirrors the engine's ThemeConfig but
225
+ * carries markdown-flavored fields (mermaid variants) that live outside the
226
+ * engine's awareness. Fields are declared explicitly rather than
227
+ * inherit-and-omit, so the boundary is visible.
228
+ */
229
+ export type CompilerThemeConfig = {
230
+ layouts: CompilerLayout[];
231
+ assets: AssetCatalog;
232
+ template: string;
233
+ outputDir?: string;
234
+ mermaid?: MermaidConfig;
235
+ };
236
+ export type CompilerConfig = CompilerThemeConfig & {
237
+ rootDir: string;
238
+ };
239
+ export {};
@@ -0,0 +1,40 @@
1
+ import { SlotType, } from "../engine/index.js";
2
+ // ── FenceType discriminator ───────────────────────────────────────────────────
3
+ /**
4
+ * Discriminator strings for the compiler-internal fence shapes produced by
5
+ * deckCompiler.ts. `Code` and `Mermaid` are resolved away entirely (syntax
6
+ * highlighting → TextFill, PNG rendering → ImageFill), so neither reaches the
7
+ * engine. Prose has no FenceType: it becomes a `TextFill` (`{ paragraphs }`),
8
+ * one of the engine's own fill shapes.
9
+ */
10
+ export const FenceType = {
11
+ Code: "code",
12
+ Mermaid: "mermaid",
13
+ };
14
+ // ── ParameterType discriminator (frontmatter, one value) ──────────────────────
15
+ /**
16
+ * Discriminator for a layout's frontmatter *parameters* — inputs the author
17
+ * writes as a single `key: value` line. Two kinds: `Template` (fills a styled
18
+ * shape's runs via fillTemplate) and `Image` (a filesystem path filled via
19
+ * fillImage). Both share their value with the engine's `SlotType`, since a
20
+ * parameter maps straight to an engine slot with no resolution step.
21
+ */
22
+ export const ParameterType = {
23
+ Template: SlotType.Template,
24
+ Image: SlotType.Image,
25
+ };
26
+ // ── CompilerSlotType discriminator (body region, multi-line) ──────────────────
27
+ /**
28
+ * Compiler-facing slot type for a layout's *slots* — multi-line body regions
29
+ * (the default body region, or a `::name::` region). Narrowed to four kinds;
30
+ * `Template` and `Image` left this list to become parameters. `Code` and `Mermaid`
31
+ * are compiler-only, resolved (syntax highlighting, mermaid PNG rendering)
32
+ * before the engine sees the deck. Required on every CompilerSlot — no silent
33
+ * default.
34
+ */
35
+ export const CompilerSlotType = {
36
+ Text: SlotType.Text,
37
+ Table: SlotType.Table,
38
+ Code: FenceType.Code,
39
+ Mermaid: FenceType.Mermaid,
40
+ };
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@tycoworks/tycoslide",
3
+ "version": "0.7.0",
4
+ "type": "module",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "bin": {
8
+ "tycoslide": "bin/tycoslide.js"
9
+ },
10
+ "files": [
11
+ "dist",
12
+ "bin",
13
+ "SKILL.md",
14
+ "syntax.md"
15
+ ],
16
+ "scripts": {
17
+ "build": "tsc --build",
18
+ "test": "tsc --build && node --test --experimental-transform-types test/**/*.test.ts",
19
+ "typecheck": "tsc --noEmit",
20
+ "lint": "biome check .",
21
+ "lint:fix": "biome check --write .",
22
+ "format": "biome format --write ."
23
+ },
24
+ "dependencies": {
25
+ "commander": "^15.0.0",
26
+ "image-size": "^2.0.2",
27
+ "pptx-automizer": "^0.8.2",
28
+ "remark-gfm": "^4.0.1",
29
+ "remark-ins": "^1.2.5",
30
+ "remark-parse": "^11.0.0",
31
+ "shiki": "^4.3.0",
32
+ "unified": "^11.0.0",
33
+ "yaml": "^2.0.0"
34
+ },
35
+ "devDependencies": {
36
+ "@biomejs/biome": "^2.5.1",
37
+ "@types/node": "^22.20.0",
38
+ "@xmldom/xmldom": "^0.9.10",
39
+ "typescript": "^5.8.0"
40
+ }
41
+ }