@tycoworks/tycoslide 0.10.0 → 0.10.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.
package/README.md CHANGED
@@ -50,7 +50,7 @@ npx tycoslide build deck.md # → deck.pptx
50
50
  ```bash
51
51
  npx tycoslide build deck.md # markdown → PPTX (theme resolved from deck frontmatter)
52
52
  npx tycoslide build deck.md --no-notes # omit speaker notes from the output
53
- npx tycoslide package # generate the Agent Skill (skill.md, syntax.md, manifest.json at the theme root) + an uploadable <package-name>.zip
53
+ npx tycoslide package # regenerate skill.md/syntax.md/manifest.json + zip the whole theme into a self-contained <package-name>.zip
54
54
  ```
55
55
 
56
56
  ## Theme Structure
package/dist/cli.js CHANGED
@@ -5,7 +5,7 @@ import { Command } from "commander";
5
5
  import { buildDeck } from "./index.js";
6
6
  import { generateManifest } from "./manifest.js";
7
7
  import { compileDeck, loadThemeConfig, parseSlideDocument, RESERVED_KEY } from "./markdown/index.js";
8
- import { renameSkill, zipSkill } from "./skillZip.js";
8
+ import { renameSkill, zipDir } from "./skillZip.js";
9
9
  const DEFAULT_CONFIG = "theme.json";
10
10
  const MANIFEST_FILE = "manifest.json";
11
11
  // The theme skill is written as lowercase skill.md (copied from tycoslide's own
@@ -76,13 +76,10 @@ program
76
76
  const syntaxMd = readFileSync(syntaxMdPath, "utf-8");
77
77
  writeFileSync(resolve(process.cwd(), SYNTAX_FILE), syntaxMd);
78
78
  console.log(`WROTE ${SYNTAX_FILE}`);
79
+ // Bundle the WHOLE theme so the skill is self-contained: unzip ->
80
+ // `npm install` (pulls the engine + its deps) -> `npx tycoslide build`.
79
81
  const zipFile = `${skillName}.zip`;
80
- const zipBuf = await zipSkill(skillName, [
81
- { name: SKILL_FILE, content: skillMd },
82
- { name: SYNTAX_FILE, content: syntaxMd },
83
- { name: MANIFEST_FILE, content: manifestJson },
84
- ]);
85
- writeFileSync(resolve(process.cwd(), zipFile), zipBuf);
82
+ writeFileSync(resolve(process.cwd(), zipFile), await zipDir(process.cwd(), skillName));
86
83
  console.log(`WROTE ${zipFile}`);
87
84
  });
88
85
  await program.parseAsync(process.argv);
@@ -6,12 +6,12 @@
6
6
  */
7
7
  export declare function renameSkill(md: string, name: string): string;
8
8
  /**
9
- * Build an uploadable Agent Skill zip whose entries live under a single root
10
- * folder (e.g. `mz-slides/skill.md`), matching Anthropic's custom-skill format.
11
- * Each entry is written from an explicit in-memory list rather than a directory
12
- * scan, so the zip contains exactly the files the caller passes — nothing else.
9
+ * Zip an entire theme directory into an uploadable Agent Skill archive whose
10
+ * entries all live under a single root folder (e.g. `mz-slides/theme.json`),
11
+ * matching Anthropic's custom-skill format. Recursively includes every file
12
+ * except node_modules, hidden entries (any name starting with `.`), and
13
+ * top-level build artifacts (.pptx/.pdf/.zip at the repo root; the template
14
+ * .pptx under template/ is kept). Subdirectory structure is preserved with
15
+ * POSIX slashes. Fails fast if nothing is left to zip.
13
16
  */
14
- export declare function zipSkill(folderName: string, files: {
15
- name: string;
16
- content: string | Buffer;
17
- }[]): Promise<Buffer>;
17
+ export declare function zipDir(rootDir: string, folderName: string): Promise<Buffer>;
package/dist/skillZip.js CHANGED
@@ -1,3 +1,5 @@
1
+ import { readdirSync, readFileSync } from "node:fs";
2
+ import { extname, join, relative, sep } from "node:path";
1
3
  import JSZip from "jszip";
2
4
  const FRONTMATTER = /^---\n([\s\S]*?)\n---/;
3
5
  const NAME_LINE = /^name:[ \t]*.*$/m;
@@ -15,21 +17,48 @@ export function renameSkill(md, name) {
15
17
  throw new Error('skill.md frontmatter has no "name:" line');
16
18
  return md.replace(block[0], block[0].replace(NAME_LINE, `name: ${name}`));
17
19
  }
20
+ // Never packaged: dependencies (npm install rebuilds them) and hidden entries
21
+ // (name starting with "." — covers VCS, tooling, caches, secrets like .env/.npmrc).
22
+ const EXCLUDE_DIRS = new Set(["node_modules"]);
23
+ // Build artifacts, dropped ONLY at the repo root: decks build to cwd
24
+ // (showcase.pptx, deck.pptx, the output .zip), while the template .pptx lives
25
+ // under template/ and must be kept — so these extensions are pruned top-level only.
26
+ const ROOT_ARTIFACT_EXTS = new Set([".pptx", ".pdf", ".zip"]);
18
27
  /**
19
- * Build an uploadable Agent Skill zip whose entries live under a single root
20
- * folder (e.g. `mz-slides/skill.md`), matching Anthropic's custom-skill format.
21
- * Each entry is written from an explicit in-memory list rather than a directory
22
- * scan, so the zip contains exactly the files the caller passes — nothing else.
28
+ * Zip an entire theme directory into an uploadable Agent Skill archive whose
29
+ * entries all live under a single root folder (e.g. `mz-slides/theme.json`),
30
+ * matching Anthropic's custom-skill format. Recursively includes every file
31
+ * except node_modules, hidden entries (any name starting with `.`), and
32
+ * top-level build artifacts (.pptx/.pdf/.zip at the repo root; the template
33
+ * .pptx under template/ is kept). Subdirectory structure is preserved with
34
+ * POSIX slashes. Fails fast if nothing is left to zip.
23
35
  */
24
- export async function zipSkill(folderName, files) {
25
- if (files.length === 0)
26
- throw new Error(`No files to zip for skill folder: ${folderName}`);
36
+ export async function zipDir(rootDir, folderName) {
27
37
  const zip = new JSZip();
28
38
  const folder = zip.folder(folderName);
29
39
  if (!folder)
30
40
  throw new Error(`Failed to create zip folder: ${folderName}`);
31
- for (const file of files) {
32
- folder.file(file.name, file.content);
33
- }
41
+ let count = 0;
42
+ const walk = (dir) => {
43
+ const atRoot = dir === rootDir;
44
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
45
+ if (entry.name.startsWith("."))
46
+ continue;
47
+ const abs = join(dir, entry.name);
48
+ if (entry.isDirectory()) {
49
+ if (!EXCLUDE_DIRS.has(entry.name))
50
+ walk(abs);
51
+ }
52
+ else if (entry.isFile()) {
53
+ if (atRoot && ROOT_ARTIFACT_EXTS.has(extname(entry.name)))
54
+ continue;
55
+ folder.file(relative(rootDir, abs).split(sep).join("/"), readFileSync(abs));
56
+ count++;
57
+ }
58
+ }
59
+ };
60
+ walk(rootDir);
61
+ if (count === 0)
62
+ throw new Error(`No files to zip in directory: ${rootDir}`);
34
63
  return zip.generateAsync({ type: "nodebuffer" });
35
64
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tycoworks/tycoslide",
3
- "version": "0.10.0",
3
+ "version": "0.10.1",
4
4
  "description": "Create editable, on-brand PowerPoint slides from markdown.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",