@tycoworks/tycoslide 0.17.0 → 0.17.1

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.
@@ -4,7 +4,7 @@ import { loadThemeConfig } from "../index.js";
4
4
  import { extractTemplate, summarizeExtraction } from "./extract.js";
5
5
  import { ASSETS_DIR, ASSETS_FILE, MANIFEST_FILE, PACKAGE_JSON, SKILL_FILE, SKILL_TEMPLATE_FILE, SKILL_ZIP_EXT, TEMPLATE_FILE, THEME_CONFIG, THEME_PACKAGE_DIR, } from "./files.js";
6
6
  import { generateManifest } from "./manifest.js";
7
- import { loadAssetCatalog, renameSkill, skillPackageJson, zipDir } from "./skill.js";
7
+ import { loadAssetCatalog, personalizeSkill, skillPackageJson, zipDir } from "./skill.js";
8
8
  /** The CLI's line for something written, with an optional note on what it holds. */
9
9
  function wrote(target, detail) {
10
10
  console.log(detail ? `WROTE ${target} ${detail}` : `WROTE ${target}`);
@@ -27,7 +27,7 @@ export function registerAgentCommands(program, tool) {
27
27
  const skillName = basename(themePkg.name);
28
28
  let skillMd;
29
29
  try {
30
- skillMd = renameSkill(readFileSync(skillMdPath, "utf-8"), skillName);
30
+ skillMd = personalizeSkill(readFileSync(skillMdPath, "utf-8"), skillName, themePkg.description);
31
31
  }
32
32
  catch (err) {
33
33
  throw new Error(`${skillMdPath}: ${err.message}`);
@@ -52,8 +52,8 @@ export type InventoryShape = {
52
52
  rows?: number;
53
53
  cols?: number;
54
54
  /**
55
- * The start of the shape's text, with `¶` between paragraphs and `↵` for line
56
- * breaks: where a parameter template puts `\n`.
55
+ * The shape's text, with `¶` between paragraphs and `↵` for line breaks,
56
+ * either of which a parameter template writes as `\n`.
57
57
  */
58
58
  text: string;
59
59
  };
@@ -300,7 +300,6 @@ const SHAPE_ELEMENTS = new Set([
300
300
  const GROUP_SEPARATOR = "/";
301
301
  const PARAGRAPH_MARK = "¶";
302
302
  const LINE_BREAK_MARK = "↵";
303
- const TEXT_PREVIEW_CHARS = 80;
304
303
  const TEXT_BOX_ON = "1";
305
304
  /** A placeholder with no `type` is a body placeholder. */
306
305
  const DEFAULT_PLACEHOLDER_TYPE = "body";
@@ -327,14 +326,14 @@ async function readShapes(presentation, parts) {
327
326
  }
328
327
  }
329
328
  }
330
- // Set fields only, in a fixed order, and the text cut to a preview.
329
+ // Set fields only, in a fixed order.
331
330
  return found.map(({ shape: { name, kind, frame, frameFrom, rows, cols, text } }) => ({
332
331
  name,
333
332
  kind,
334
333
  ...(frame && { frame }),
335
334
  ...(frameFrom && { frameFrom }),
336
335
  ...(rows !== undefined && { rows, cols }),
337
- text: Array.from(text).slice(0, TEXT_PREVIEW_CHARS).join(""),
336
+ text,
338
337
  }));
339
338
  }
340
339
  function walkShapes(container, prefix) {
@@ -373,13 +372,17 @@ function shapeKind(el, table) {
373
372
  return ShapeKind.Other;
374
373
  }
375
374
  }
376
- /** The shape's text, `¶` between paragraphs and `↵` for line breaks, whitespace collapsed. */
375
+ /**
376
+ * The shape's text, `¶` between paragraphs and `↵` for line breaks, whitespace
377
+ * collapsed. Runs join as PowerPoint shows them, with no space added between: a
378
+ * run is a stretch of one style, not a word.
379
+ */
377
380
  function shapeText(el) {
378
381
  const paragraphs = collectElements(el, Tag.PARAGRAPH).map((paragraph) => elementChildren(paragraph)
379
382
  .flatMap((child) => child.tagName === Tag.LINE_BREAK
380
- ? [LINE_BREAK_MARK]
383
+ ? [` ${LINE_BREAK_MARK} `]
381
384
  : collectElements(child, Tag.TEXT).map((text) => text.textContent ?? ""))
382
- .join(" ")
385
+ .join("")
383
386
  .split(/\s+/)
384
387
  .filter(Boolean)
385
388
  .join(" "));
@@ -18,12 +18,14 @@ export declare function loadAssetCatalog(themeDir: string): AssetCatalog;
18
18
  */
19
19
  export declare function packAssets(paths: string[], read: (rel: string) => Buffer): Promise<Buffer>;
20
20
  /**
21
- * Rewrite the `name:` value in a SKILL.md's leading YAML frontmatter so the
22
- * packaged skill is named after the consuming theme, not the source template.
23
- * Only the leading `---`…`---` block is touched; the body is left byte-for-byte.
24
- * Throws if there is no frontmatter or no `name:` line — the caller names the file.
21
+ * Make the skill template the theme's own: its frontmatter `name:` and its title
22
+ * heading become the theme's name, and the theme's `description`, when it has
23
+ * one, leads the skill's description, so an agent with several theme skills can
24
+ * tell them apart. The rest of the body is left byte-for-byte. Throws if there is
25
+ * no frontmatter, no `name:` line, or no folded description to lead — the caller
26
+ * names the file.
25
27
  */
26
- export declare function renameSkill(md: string, name: string): string;
28
+ export declare function personalizeSkill(md: string, name: string, description?: string): string;
27
29
  /**
28
30
  * The `package.json` a packaged skill installs from — deliberately NOT the theme's
29
31
  * own. A theme repo's manifest is a development document: it may carry scripts,
@@ -47,19 +47,32 @@ export async function packAssets(paths, read) {
47
47
  }
48
48
  const FRONTMATTER = /^---\n([\s\S]*?)\n---/;
49
49
  const NAME_LINE = /^name:[ \t]*.*$/m;
50
+ /** The folded `description: >` opener; the description's lines follow it, indented. */
51
+ const FOLDED_DESCRIPTION_LINE = /^description:[ \t]*>[ \t]*$/m;
52
+ const DESCRIPTION_INDENT = " ";
53
+ const TITLE_HEADING = /^# .*$/m;
50
54
  /**
51
- * Rewrite the `name:` value in a SKILL.md's leading YAML frontmatter so the
52
- * packaged skill is named after the consuming theme, not the source template.
53
- * Only the leading `---`…`---` block is touched; the body is left byte-for-byte.
54
- * Throws if there is no frontmatter or no `name:` line — the caller names the file.
55
+ * Make the skill template the theme's own: its frontmatter `name:` and its title
56
+ * heading become the theme's name, and the theme's `description`, when it has
57
+ * one, leads the skill's description, so an agent with several theme skills can
58
+ * tell them apart. The rest of the body is left byte-for-byte. Throws if there is
59
+ * no frontmatter, no `name:` line, or no folded description to lead — the caller
60
+ * names the file.
55
61
  */
56
- export function renameSkill(md, name) {
62
+ export function personalizeSkill(md, name, description) {
57
63
  const block = md.match(FRONTMATTER);
58
64
  if (!block)
59
65
  throw new Error("SKILL.md has no YAML frontmatter block");
60
66
  if (!NAME_LINE.test(block[1]))
61
67
  throw new Error('SKILL.md frontmatter has no "name:" line');
62
- return md.replace(block[0], block[0].replace(NAME_LINE, `name: ${name}`));
68
+ let frontmatter = block[0].replace(NAME_LINE, `name: ${name}`);
69
+ if (description) {
70
+ if (!FOLDED_DESCRIPTION_LINE.test(frontmatter)) {
71
+ throw new Error('SKILL.md frontmatter has no folded "description: >" to lead');
72
+ }
73
+ frontmatter = frontmatter.replace(FOLDED_DESCRIPTION_LINE, (line) => `${line}\n${DESCRIPTION_INDENT}${description}`);
74
+ }
75
+ return frontmatter + md.slice(block[0].length).replace(TITLE_HEADING, `# ${name}`);
63
76
  }
64
77
  /**
65
78
  * Files a packaged skill needs beyond the theme's own declarations. Only the
@@ -136,7 +136,7 @@ function buildSlide(index, fmString, rawContent) {
136
136
  export class FrontmatterParseError extends Error {
137
137
  constructor(slideIndex, yamlSource, cause) {
138
138
  const preview = yamlSource.length > 80 ? `${yamlSource.slice(0, 80)}...` : yamlSource;
139
- super(`Invalid YAML in slide ${slideIndex} frontmatter:\n${preview}`);
139
+ super(`Slide ${slideIndex + 1}: invalid YAML in frontmatter:\n${preview}`);
140
140
  this.name = "FrontmatterParseError";
141
141
  this.cause = cause;
142
142
  }
package/docs/markdown.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  This document covers the detailed syntax for writing slide content in tycoslide deck files.
4
4
 
5
- > **Every layout, parameter, and slot name in the examples below is a placeholder.** Your theme's real names live in `theme.json` — read it first, and never assume a name shown here (`Body`, `TwoColumn`, `hero`, `::left::`, etc.) exists in your theme.
5
+ > **Every layout, parameter, and slot name in the examples below is a placeholder.** Your theme's real names live in its `theme.json`, and in `manifest.json` when the theme is packaged as a skill — read it first, and never assume a name shown here (`Body`, `TwoColumn`, `hero`, `::left::`, etc.) exists in your theme.
6
6
 
7
7
  ---
8
8
 
@@ -216,7 +216,7 @@ The theme owns all styling. These directives are rejected at build time:
216
216
  Each parameter or slot in the layout definition may declare:
217
217
  - **`accepts`** (slots, required) -- an array of `text`, `table`, `image`.
218
218
  - **`required: true`** -- the slide has no usable default and the build fails if the parameter/slot has no value.
219
- - **optional (the default)** -- a parameter or slot you leave unfilled is dropped from the slide (its shape is removed), so a layout with numbered slots (e.g. up to six sections, up to four stats) renders only the ones you fill.
219
+ - **optional (the default)** -- a parameter or slot you leave unfilled is dropped from the slide (its shape is removed), so a layout with numbered slots (e.g. up to six sections, up to four stats) renders only the ones you fill. A shape the theme doesn't map, such as a card drawn behind a stat, stays, so an unfilled one can leave an empty card.
220
220
 
221
221
  Each layout also declares a `slideNumber` pointing at the physical slide in the theme's template -- unique per layout (one layout maps to one physical slide).
222
222
 
@@ -268,7 +268,7 @@ Common errors and fixes:
268
268
 
269
269
  | Error | Fix |
270
270
  |-------|-----|
271
- | `unknown layout "xyz"` | Check layout names in `theme.json` |
271
+ | `unknown layout "xyz"` | Check layout names in `theme.json` (or `manifest.json`) |
272
272
  | A parameter or slot didn't fill | Use the key names the layout declares -- parameters in frontmatter, slots as body regions |
273
273
  | YAML parse error | Fix the YAML syntax in the slide's frontmatter |
274
274
  | `image "…" not found at …` | Check the path; it is relative to the deck file |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tycoworks/tycoslide",
3
- "version": "0.17.0",
3
+ "version": "0.17.1",
4
4
  "description": "Create editable PowerPoint slides from markdown, using your existing .pptx templates.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -39,7 +39,7 @@ This skill builds decks from a markdown deck file. The theme provides slide layo
39
39
 
40
40
  Before writing anything, read `manifest.json`. It lists the theme's **layouts** -- for each: `name`, `description`, `parameters` (frontmatter inputs) and `slots` (body regions). A layout is identified by its `name`; every parameter and slot by its `key`. Slots carry `accepts`, and either may be `required`. A slot that accepts an image also shows its `fit`. A `cover` slot crops the image to fill the slot, a `contain` slot shows the whole image, and a `scale-down` slot shows the whole image without ever enlarging it.
41
41
 
42
- A layout's inputs split two ways, parameters and slots; see [markdown.md](node_modules/@tycoworks/tycoslide/docs/markdown.md#parameters-and-slots) for how to fill each. Unfilled ones are dropped, so fill as many of a layout's numbered slots (sections, stats, columns) as you have content for.
42
+ A layout's inputs split two ways, parameters and slots; see [markdown.md](node_modules/@tycoworks/tycoslide/docs/markdown.md#parameters-and-slots) for how to fill each. Unfilled ones are dropped, so fill as many of a layout's numbered slots (sections, stats, columns) as you have content for. A card or panel drawn behind an unfilled one stays, so choose a layout with no more of them than you can fill.
43
43
 
44
44
  Study each layout's `slots` before writing any slides.
45
45
 
@@ -63,7 +63,7 @@ If your deck is in the theme folder, the file is already there, so skip the copy
63
63
 
64
64
  ## Creating Slides
65
65
 
66
- Write a deck file in markdown, as [markdown.md](node_modules/@tycoworks/tycoslide/docs/markdown.md) describes (see its [full example](node_modules/@tycoworks/tycoslide/docs/markdown.md#full-example)), and build it as its [Build](node_modules/@tycoworks/tycoslide/docs/markdown.md#build) section shows.
66
+ Write a deck file in markdown, as [markdown.md](node_modules/@tycoworks/tycoslide/docs/markdown.md) describes (see its [full example](node_modules/@tycoworks/tycoslide/docs/markdown.md#full-example)), and build it as its [Build](node_modules/@tycoworks/tycoslide/docs/markdown.md#build) section shows. The deck's `theme:` is the path from the deck to this folder's `theme.json`: `./theme.json` when the deck sits in this folder, and the full path to it when the deck is anywhere else.
67
67
 
68
68
  ---
69
69