@tycoworks/tycoslide 0.15.0 → 0.16.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 (62) hide show
  1. package/README.md +10 -6
  2. package/dist/agents/catalog.d.ts +32 -0
  3. package/dist/agents/catalog.js +37 -0
  4. package/dist/agents/commands.d.ts +9 -0
  5. package/dist/agents/commands.js +57 -0
  6. package/dist/agents/extract.d.ts +22 -0
  7. package/dist/agents/extract.js +54 -0
  8. package/dist/{files.d.ts → agents/files.d.ts} +13 -3
  9. package/dist/{files.js → agents/files.js} +16 -3
  10. package/dist/agents/inventory.d.ts +83 -0
  11. package/dist/agents/inventory.js +568 -0
  12. package/dist/agents/manifest.d.ts +3 -0
  13. package/dist/{manifest.js → agents/manifest.js} +3 -14
  14. package/dist/agents/media.d.ts +38 -0
  15. package/dist/agents/media.js +83 -0
  16. package/dist/agents/pptx.d.ts +48 -0
  17. package/dist/agents/pptx.js +123 -0
  18. package/dist/agents/skill.d.ts +43 -0
  19. package/dist/agents/skill.js +134 -0
  20. package/dist/cli.js +8 -44
  21. package/dist/engine/dom.d.ts +3 -0
  22. package/dist/engine/dom.js +4 -0
  23. package/dist/engine/fillers/image.d.ts +6 -5
  24. package/dist/engine/fillers/image.js +24 -7
  25. package/dist/engine/generate.d.ts +2 -2
  26. package/dist/engine/generate.js +2 -2
  27. package/dist/engine/index.d.ts +3 -0
  28. package/dist/engine/index.js +2 -0
  29. package/dist/engine/notes.js +20 -62
  30. package/dist/engine/ooxml.d.ts +36 -0
  31. package/dist/engine/ooxml.js +56 -0
  32. package/dist/engine/types.d.ts +4 -0
  33. package/dist/index.d.ts +4 -11
  34. package/dist/index.js +2 -15
  35. package/dist/markdown/blocks/code.js +1 -2
  36. package/dist/markdown/blocks/image.d.ts +23 -0
  37. package/dist/markdown/blocks/image.js +65 -3
  38. package/dist/markdown/blocks/mermaid.d.ts +1 -1
  39. package/dist/markdown/blocks/mermaid.js +6 -4
  40. package/dist/markdown/blocks/table.js +7 -6
  41. package/dist/markdown/blocks/text.js +9 -8
  42. package/dist/markdown/deckCompiler.d.ts +2 -13
  43. package/dist/markdown/deckCompiler.js +7 -78
  44. package/dist/markdown/index.d.ts +4 -2
  45. package/dist/markdown/index.js +3 -1
  46. package/dist/markdown/inline.d.ts +2 -0
  47. package/dist/markdown/inline.js +4 -3
  48. package/dist/markdown/schema/themeConfigSchema.d.ts +2 -11
  49. package/dist/markdown/schema/themeConfigSchema.js +6 -16
  50. package/dist/markdown/slideParser.d.ts +2 -0
  51. package/dist/markdown/slideParser.js +6 -3
  52. package/dist/markdown/types.d.ts +4 -29
  53. package/dist/markdown/types.js +0 -11
  54. package/{theme-package/syntax.md → docs/markdown.md} +60 -28
  55. package/docs/theme.md +144 -0
  56. package/package.json +3 -2
  57. package/theme-package/SKILL.md +24 -87
  58. package/dist/engine/rels.d.ts +0 -18
  59. package/dist/engine/rels.js +0 -55
  60. package/dist/manifest.d.ts +0 -5
  61. package/dist/skillZip.d.ts +0 -49
  62. package/dist/skillZip.js +0 -177
package/dist/skillZip.js DELETED
@@ -1,177 +0,0 @@
1
- import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
2
- import { dirname, join } from "node:path";
3
- import JSZip from "jszip";
4
- import { TEMPLATE_DIR } from "./engine/index.js";
5
- import { ASSETS_ARCHIVE, PACKAGE_JSON } from "./files.js";
6
- /** Entries are stored, not deflated: assets are already-compressed images. */
7
- const NO_COMPRESSION = { type: "nodebuffer", compression: "STORE" };
8
- /**
9
- * Pack `paths` (theme-relative, POSIX) into one archive, reading each through
10
- * `read`. Entries keep their declared paths, so expanding reproduces the layout
11
- * `theme.json` already refers to.
12
- */
13
- export async function packAssets(paths, read) {
14
- const archive = new JSZip();
15
- for (const rel of paths)
16
- archive.file(rel, read(rel));
17
- return archive.generateAsync(NO_COMPRESSION);
18
- }
19
- /**
20
- * Expand a packaged theme's archive into its directory, so the files the catalog
21
- * names are on disk before anything fills with them. Idempotent per file; loose
22
- * files win, so a stale archive never overwrites a theme's real assets; the
23
- * archive itself stays put. A theme with no archive -- every theme under
24
- * development -- returns immediately.
25
- */
26
- export async function expandAssets(rootDir) {
27
- // An empty rootDir is a supported value elsewhere ("resolve nothing"), and it
28
- // would expand into the process working directory. Refuse rather than guess.
29
- if (!rootDir)
30
- return;
31
- const archivePath = join(rootDir, ASSETS_ARCHIVE);
32
- if (!existsSync(archivePath))
33
- return;
34
- const archive = await JSZip.loadAsync(readFileSync(archivePath));
35
- for (const [rel, entry] of Object.entries(archive.files)) {
36
- if (entry.dir)
37
- continue;
38
- // JSZip collapses `..` and a leading `/` on load, but a backslash survives
39
- // verbatim and traverses on Windows. We wrote this archive, so an entry that
40
- // is not a plain theme-relative path means it was tampered with.
41
- if (rel.includes("\\")) {
42
- throw new Error(`${ASSETS_ARCHIVE} entry "${rel}" is not a theme-relative path`);
43
- }
44
- const abs = join(rootDir, ...rel.split("/"));
45
- if (existsSync(abs))
46
- continue;
47
- mkdirSync(dirname(abs), { recursive: true });
48
- // Write-then-rename. A plain write is not atomic: a build killed partway
49
- // through 2,000 icons leaves a truncated file that `existsSync` then skips
50
- // forever. Rename is atomic within a filesystem, so a reader sees a whole
51
- // file or none, which also makes two concurrent builds in one directory safe.
52
- const partial = `${abs}.${process.pid}.tmp`;
53
- writeFileSync(partial, await entry.async("nodebuffer"));
54
- renameSync(partial, abs);
55
- }
56
- }
57
- const FRONTMATTER = /^---\n([\s\S]*?)\n---/;
58
- const NAME_LINE = /^name:[ \t]*.*$/m;
59
- /**
60
- * Rewrite the `name:` value in a SKILL.md's leading YAML frontmatter so the
61
- * packaged skill is named after the consuming theme, not the source template.
62
- * Only the leading `---`…`---` block is touched; the body is left byte-for-byte.
63
- * Throws if there is no frontmatter or no `name:` line — the caller names the file.
64
- */
65
- export function renameSkill(md, name) {
66
- const block = md.match(FRONTMATTER);
67
- if (!block)
68
- throw new Error("SKILL.md has no YAML frontmatter block");
69
- if (!NAME_LINE.test(block[1]))
70
- throw new Error('SKILL.md frontmatter has no "name:" line');
71
- return md.replace(block[0], block[0].replace(NAME_LINE, `name: ${name}`));
72
- }
73
- /**
74
- * Files a packaged skill needs beyond the theme's own declarations. Only the
75
- * lockfile: `package.json` is authored by `skillPackageJson` rather than taken
76
- * from the theme directory.
77
- */
78
- const SUPPORT_FILES = ["package-lock.json"];
79
- /**
80
- * The `package.json` a packaged skill installs from — deliberately NOT the theme's
81
- * own. A theme repo's manifest is a development document: it carries the script
82
- * that regenerates the skill, and lists the engine as a devDependency because the
83
- * repo builds with it rather than shipping it.
84
- *
85
- * Copying that verbatim breaks the consumer twice. The build script runs as a
86
- * postinstall inside their container, so anything it touches that is read-only
87
- * fails their whole `npm install`. And under `--omit=dev` the engine is never
88
- * installed, so neither the postinstall nor `npx tycoslide build` can find it.
89
- *
90
- * What ships instead declares only what the skill needs to RUN: the theme's own
91
- * dependencies plus the engine, as runtime dependencies, and no scripts at all.
92
- */
93
- export function skillPackageJson(theme, engine) {
94
- const dependencies = {
95
- ...(theme.dependencies ?? {}),
96
- [engine.name]: `^${engine.version}`,
97
- };
98
- const skill = {
99
- name: theme.name,
100
- version: theme.version,
101
- description: theme.description,
102
- private: true,
103
- dependencies: Object.fromEntries(Object.entries(dependencies).sort(([a], [b]) => a.localeCompare(b))),
104
- };
105
- return `${JSON.stringify(skill, null, 2)}\n`;
106
- }
107
- /**
108
- * Every path a packaged theme needs, relative to `rootDir` and POSIX-separated,
109
- * split by how it ships.
110
- *
111
- * Derived from the theme config rather than filtered out of a directory walk:
112
- * the config already declares its template and its whole asset catalog, so an
113
- * allowlist stays correct no matter what else sits in the working directory --
114
- * built decks, PDFs, slide PNGs, scratch files. Font paths are deliberately
115
- * absent when they name a package -- those resolve from node_modules, which
116
- * `npm install` restores -- but a `./`- or `/`-prefixed font path is a file the
117
- * theme owns, and mermaid reads it during COMPILE, before any archive is
118
- * expanded. Those ship plain.
119
- *
120
- * `archived` is the asset catalog, which collapses to one archive because hosts
121
- * cap how many FILES a skill may contain. `plain` is everything read before or
122
- * without an expansion, including the catalog itself.
123
- */
124
- function skillPaths(config, generated) {
125
- const archived = Object.values(config.assets).flatMap((category) => Object.values(category).map((entry) => entry.path));
126
- const localFonts = (config.fonts ?? []).map((f) => f.path).filter((p) => p.startsWith(".") || p.startsWith("/"));
127
- return {
128
- plain: [...SUPPORT_FILES, ...generated, `${TEMPLATE_DIR}/${config.template}`, ...localFonts],
129
- archived,
130
- };
131
- }
132
- /**
133
- * Zip a theme into an uploadable Agent Skill archive whose entries all live
134
- * under a single root folder (e.g. `acme-slides/theme.json`), matching Anthropic's
135
- * custom-skill format. `generated` names the files the caller just wrote (the
136
- * config, manifest, SKILL.md, syntax.md); `packageJson` is the authored manifest
137
- * from `skillPackageJson`. Optional support files are skipped when absent;
138
- * anything the config declares but that is missing is an error.
139
- */
140
- export async function zipDir(rootDir, folderName, config, generated, packageJson) {
141
- const zip = new JSZip();
142
- const folder = zip.folder(folderName);
143
- if (!folder)
144
- throw new Error(`Failed to create zip folder: ${folderName}`);
145
- folder.file(PACKAGE_JSON, packageJson);
146
- const { plain, archived } = skillPaths(config, generated);
147
- const optional = new Set(SUPPORT_FILES);
148
- let count = 1;
149
- // `optional` is a plain-bucket concept (a lockfile a theme may not have). An
150
- // asset the catalog declares is never optional, so the archived loop calls
151
- // `required` and a missing one throws rather than silently vanishing.
152
- const required = (rel) => {
153
- const abs = join(rootDir, ...rel.split("/"));
154
- if (existsSync(abs))
155
- return readFileSync(abs);
156
- throw new Error(`Theme declares "${rel}", but no such file exists`);
157
- };
158
- const read = (rel) => {
159
- if (optional.has(rel) && !existsSync(join(rootDir, ...rel.split("/"))))
160
- return null;
161
- return required(rel);
162
- };
163
- for (const rel of plain) {
164
- const content = read(rel);
165
- if (content === null)
166
- continue;
167
- folder.file(rel, content);
168
- count++;
169
- }
170
- if (archived.length > 0) {
171
- folder.file(ASSETS_ARCHIVE, await packAssets(archived, required));
172
- count++;
173
- }
174
- if (count === 0)
175
- throw new Error(`No files to zip in directory: ${rootDir}`);
176
- return zip.generateAsync({ type: "nodebuffer", compression: "STORE" });
177
- }