@tycoworks/bon 0.1.0 → 0.1.2

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/SKILL.md CHANGED
@@ -9,6 +9,18 @@ description: >
9
9
 
10
10
  # slides
11
11
 
12
+ ## Setup
13
+
14
+ Before first use, install dependencies from the plugin root:
15
+
16
+ ```bash
17
+ npm install
18
+ ```
19
+
20
+ This installs the bon engine and its dependencies. You only need to do this once.
21
+
22
+ ## Overview
23
+
12
24
  This skill builds on-brand decks from a deck file. The theme provides slide layouts that control design. Your job: pick the right layouts, fill them with content, and build. You never restyle the layout; the engine clones the real slides, so brand, layout, fonts, and chrome come for free.
13
25
 
14
26
  For brand voice and naming guidelines, read `brand.md` if it exists alongside this skill.
@@ -89,7 +101,7 @@ Build:
89
101
 
90
102
  ```bash
91
103
  # Run the command from manifest.json's build.command
92
- # e.g.: node src/run-spec.ts deck.json my-deck.pptx
104
+ # e.g.: npx bon build deck.json
93
105
  ```
94
106
 
95
107
  The deck is written to your current working directory (not inside the skill).
@@ -137,7 +149,7 @@ Your first draft almost never comes out clean. Approach QA as a debugging sessio
137
149
  Run the command from `manifest.json`'s `build.command`:
138
150
 
139
151
  ```bash
140
- # e.g.: node src/run-spec.ts deck.json my-deck.pptx
152
+ # e.g.: npx bon build deck.json
141
153
  ```
142
154
 
143
155
  Read output carefully. Common errors and fixes:
@@ -210,7 +222,7 @@ If the subagent finds issues, fix them and rebuild.
210
222
 
211
223
  ```bash
212
224
  # Build a deck (use command from manifest.json's build.command)
213
- # e.g.: node src/run-spec.ts deck.json my-deck.pptx
225
+ # e.g.: npx bon build deck.json
214
226
 
215
227
  # Render to images for visual QA
216
228
  soffice --headless --convert-to pdf --outdir . <deck>.pptx
package/bin/bon.js CHANGED
@@ -1,2 +1,2 @@
1
- #!/usr/bin/env node --experimental-strip-types --no-warnings
2
- import("../src/cli.ts");
1
+ #!/usr/bin/env node
2
+ import("../dist/cli.js");
package/dist/cli.d.ts ADDED
@@ -0,0 +1 @@
1
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,92 @@
1
+ import { copyFileSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { dirname, resolve } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { generate } from "@tycoworks/bon-core";
5
+ import { Command } from "commander";
6
+ import { generateManifest } from "./manifest.js";
7
+ import { smokeSteps } from "./smoke.js";
8
+ const DEFAULT_CONFIG = "theme.config.ts";
9
+ const DEFAULT_SMOKE_OUTPUT = "smoke-all.pptx";
10
+ const SKILL_DIR = "skills/slides";
11
+ const MANIFEST_FILE = "manifest.json";
12
+ const SKILL_FILE = "SKILL.md";
13
+ const BUILD_COMMAND = "npx bon build";
14
+ const sdkDir = dirname(fileURLToPath(import.meta.url));
15
+ const skillMdPath = resolve(sdkDir, "..", SKILL_FILE);
16
+ async function loadConfig(configPath) {
17
+ const abs = resolve(process.cwd(), configPath);
18
+ let mod;
19
+ try {
20
+ mod = await import(abs);
21
+ }
22
+ catch {
23
+ throw new Error(`Config file not found: ${configPath}`);
24
+ }
25
+ const raw = (mod.default ?? mod.config);
26
+ if (!raw) {
27
+ throw new Error(`${configPath} must export a default or named "config" of type ThemeConfig`);
28
+ }
29
+ return { ...raw, rootDir: dirname(abs) };
30
+ }
31
+ const program = new Command().name("bon").description("PPTX template engine CLI").version("0.1.0");
32
+ program
33
+ .command("build")
34
+ .description("Build a PPTX deck from a JSON spec")
35
+ .argument("<deck>", "path to deck JSON file")
36
+ .argument("[output]", "output PPTX filename")
37
+ .option(`-c, --config <path>`, "path to theme config file", DEFAULT_CONFIG)
38
+ .action(async (deckPath, output, opts) => {
39
+ const config = await loadConfig(opts.config);
40
+ let deck;
41
+ try {
42
+ const raw = readFileSync(resolve(process.cwd(), deckPath), "utf-8");
43
+ deck = JSON.parse(raw);
44
+ }
45
+ catch (err) {
46
+ throw new Error(`Failed to read deck file: ${deckPath} (${err.message})`);
47
+ }
48
+ if (output)
49
+ deck.output = output;
50
+ await generate(deck, config);
51
+ });
52
+ program
53
+ .command("manifest")
54
+ .description("Generate manifest.json from theme config")
55
+ .option(`-c, --config <path>`, "path to theme config file", DEFAULT_CONFIG)
56
+ .option(`-o, --out <file>`, "write to file instead of stdout")
57
+ .action(async (opts) => {
58
+ const config = await loadConfig(opts.config);
59
+ const json = generateManifest(config, { build: { command: BUILD_COMMAND } });
60
+ if (opts.out) {
61
+ writeFileSync(resolve(process.cwd(), opts.out), `${json}\n`);
62
+ console.log(`WROTE ${opts.out}`);
63
+ }
64
+ else {
65
+ process.stdout.write(`${json}\n`);
66
+ }
67
+ });
68
+ program
69
+ .command("smoke")
70
+ .description("Generate one smoke-test slide per layout")
71
+ .option(`-c, --config <path>`, "path to theme config file", DEFAULT_CONFIG)
72
+ .option(`-o, --out <file>`, "output PPTX filename", DEFAULT_SMOKE_OUTPUT)
73
+ .action(async (opts) => {
74
+ const config = await loadConfig(opts.config);
75
+ const steps = smokeSteps(config);
76
+ await generate({ output: opts.out, steps }, config);
77
+ });
78
+ program
79
+ .command("plugin")
80
+ .description("Generate plugin package (manifest.json + SKILL.md) for AI agents")
81
+ .option(`-c, --config <path>`, "path to theme config file", DEFAULT_CONFIG)
82
+ .action(async (opts) => {
83
+ const config = await loadConfig(opts.config);
84
+ const outDir = resolve(process.cwd(), SKILL_DIR);
85
+ mkdirSync(outDir, { recursive: true });
86
+ const json = generateManifest(config, { build: { command: BUILD_COMMAND } });
87
+ writeFileSync(resolve(outDir, MANIFEST_FILE), `${json}\n`);
88
+ console.log(`WROTE ${SKILL_DIR}/${MANIFEST_FILE}`);
89
+ copyFileSync(skillMdPath, resolve(outDir, SKILL_FILE));
90
+ console.log(`WROTE ${SKILL_DIR}/${SKILL_FILE}`);
91
+ });
92
+ await program.parseAsync(process.argv);
@@ -0,0 +1,2 @@
1
+ import type { ThemeConfig } from "@tycoworks/bon-core";
2
+ export declare function defineConfig(config: ThemeConfig): ThemeConfig;
package/dist/config.js ADDED
@@ -0,0 +1,3 @@
1
+ export function defineConfig(config) {
2
+ return config;
3
+ }
@@ -0,0 +1,6 @@
1
+ export type { AssetCatalog, AssetEntry, AssetSlot, Config, ContentSlot, Deck, DeckStep, Layout, ParagraphSelector, ThemeConfig, } from "@tycoworks/bon-core";
2
+ export { AssetType, Fit, generate, SlotType } from "@tycoworks/bon-core";
3
+ export { defineConfig } from "./config.js";
4
+ export type { ManifestOptions } from "./manifest.js";
5
+ export { generateManifest } from "./manifest.js";
6
+ export { smokeSteps } from "./smoke.js";
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export { AssetType, Fit, generate, SlotType } from "@tycoworks/bon-core";
2
+ export { defineConfig } from "./config.js";
3
+ export { generateManifest } from "./manifest.js";
4
+ export { smokeSteps } from "./smoke.js";
@@ -0,0 +1,7 @@
1
+ import type { Config } from "@tycoworks/bon-core";
2
+ export type ManifestOptions = {
3
+ build?: {
4
+ command: string;
5
+ };
6
+ };
7
+ export declare function generateManifest(config: Config, options?: ManifestOptions): string;
@@ -0,0 +1,53 @@
1
+ function stripContentSlot(slot) {
2
+ const result = { key: slot.key, type: slot.type };
3
+ if (slot.limit) {
4
+ result.limit = slot.limit;
5
+ }
6
+ return result;
7
+ }
8
+ function stripAssetSlot(slot) {
9
+ const result = { key: slot.key, accepts: slot.accepts };
10
+ if (slot.required) {
11
+ result.required = true;
12
+ }
13
+ return result;
14
+ }
15
+ export function generateManifest(config, options) {
16
+ const layouts = config.layouts.map((layout) => ({
17
+ name: layout.name,
18
+ description: layout.description,
19
+ whenToUse: layout.whenToUse,
20
+ whenNotToUse: layout.whenNotToUse,
21
+ contentSlots: layout.contentSlots.map(stripContentSlot),
22
+ assetSlots: (layout.assetSlots ?? []).map(stripAssetSlot),
23
+ }));
24
+ const assets = {};
25
+ for (const [category, entries] of Object.entries(config.assets)) {
26
+ assets[category] = {};
27
+ for (const [name, entry] of Object.entries(entries)) {
28
+ const manifestEntry = {
29
+ path: entry.path,
30
+ type: entry.type,
31
+ description: entry.description,
32
+ };
33
+ if (entry.whenToUse) {
34
+ manifestEntry.whenToUse = entry.whenToUse;
35
+ }
36
+ assets[category][name] = manifestEntry;
37
+ }
38
+ }
39
+ const manifest = {
40
+ version: 1,
41
+ layouts,
42
+ assets,
43
+ build: {
44
+ command: options?.build?.command ?? "npx bon build",
45
+ deckFormat: "json",
46
+ deckSchema: {
47
+ output: "string",
48
+ steps: "array of { layout, content, assets }",
49
+ },
50
+ },
51
+ };
52
+ return JSON.stringify(manifest, null, 2);
53
+ }
@@ -0,0 +1,2 @@
1
+ import type { Config, DeckStep } from "@tycoworks/bon-core";
2
+ export declare function smokeSteps(config: Config): DeckStep[];
package/dist/smoke.js ADDED
@@ -0,0 +1,36 @@
1
+ import { SlotType } from "@tycoworks/bon-core";
2
+ function pickAsset(config, slot) {
3
+ for (const group of Object.values(config.assets)) {
4
+ for (const entry of Object.values(group)) {
5
+ if (entry.type === slot.accepts)
6
+ return entry.path;
7
+ }
8
+ }
9
+ return undefined;
10
+ }
11
+ export function smokeSteps(config) {
12
+ return config.layouts.map((layout) => {
13
+ const content = {};
14
+ for (const s of layout.contentSlots) {
15
+ if (s.type === SlotType.Prose) {
16
+ content[s.key] = ["Sample intro line for this block.", "- First point", "- Second point"];
17
+ }
18
+ else if (s.type === SlotType.Lines) {
19
+ content[s.key] = ["Sample Name", "Sample Role, Company"];
20
+ }
21
+ else if (s.key.includes("value")) {
22
+ content[s.key] = "42%";
23
+ }
24
+ else {
25
+ content[s.key] = `Sample ${s.key.replace(/_/g, " ")}`;
26
+ }
27
+ }
28
+ const assets = {};
29
+ for (const a of layout.assetSlots ?? []) {
30
+ const path = pickAsset(config, a);
31
+ if (path)
32
+ assets[a.key] = path;
33
+ }
34
+ return { layout: layout.name, content, assets };
35
+ });
36
+ }
package/package.json CHANGED
@@ -1,16 +1,19 @@
1
1
  {
2
2
  "name": "@tycoworks/bon",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "type": "module",
5
- "main": "src/index.ts",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
6
7
  "bin": {
7
8
  "bon": "bin/bon.js"
8
9
  },
10
+ "files": ["dist", "bin", "SKILL.md"],
9
11
  "scripts": {
12
+ "build": "tsc --build",
10
13
  "typecheck": "tsc --noEmit"
11
14
  },
12
15
  "dependencies": {
13
- "@tycoworks/bon-core": "^0.1.0",
16
+ "@tycoworks/bon-core": "^0.1.1",
14
17
  "commander": "^15.0.0"
15
18
  },
16
19
  "devDependencies": {
package/src/cli.ts DELETED
@@ -1,98 +0,0 @@
1
- import { copyFileSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
- import { dirname, resolve } from "node:path";
3
- import { fileURLToPath } from "node:url";
4
- import type { Config, ThemeConfig } from "@tycoworks/bon-core";
5
- import { generate } from "@tycoworks/bon-core";
6
- import { Command } from "commander";
7
- import { generateManifest } from "./manifest.ts";
8
- import { smokeSteps } from "./smoke.ts";
9
-
10
- const DEFAULT_CONFIG = "theme.config.ts";
11
- const DEFAULT_SMOKE_OUTPUT = "smoke-all.pptx";
12
- const SKILL_DIR = "skills/slides";
13
- const MANIFEST_FILE = "manifest.json";
14
- const SKILL_FILE = "SKILL.md";
15
- const BUILD_COMMAND = "npx bon build";
16
-
17
- const sdkDir = dirname(fileURLToPath(import.meta.url));
18
- const skillMdPath = resolve(sdkDir, "..", SKILL_FILE);
19
-
20
- async function loadConfig(configPath: string): Promise<Config> {
21
- const abs = resolve(process.cwd(), configPath);
22
- let mod: Record<string, unknown>;
23
- try {
24
- mod = await import(abs);
25
- } catch {
26
- throw new Error(`Config file not found: ${configPath}`);
27
- }
28
- const raw = (mod.default ?? mod.config) as ThemeConfig | undefined;
29
- if (!raw) {
30
- throw new Error(`${configPath} must export a default or named "config" of type ThemeConfig`);
31
- }
32
- return { ...raw, rootDir: dirname(abs) };
33
- }
34
-
35
- const program = new Command().name("bon").description("PPTX template engine CLI").version("0.1.0");
36
-
37
- program
38
- .command("build")
39
- .description("Build a PPTX deck from a JSON spec")
40
- .argument("<deck>", "path to deck JSON file")
41
- .argument("[output]", "output PPTX filename")
42
- .option(`-c, --config <path>`, "path to theme config file", DEFAULT_CONFIG)
43
- .action(async (deckPath: string, output: string | undefined, opts: { config: string }) => {
44
- const config = await loadConfig(opts.config);
45
- let deck: Record<string, unknown>;
46
- try {
47
- const raw = readFileSync(resolve(process.cwd(), deckPath), "utf-8");
48
- deck = JSON.parse(raw);
49
- } catch (err) {
50
- throw new Error(`Failed to read deck file: ${deckPath} (${(err as Error).message})`);
51
- }
52
- if (output) deck.output = output;
53
- await generate(deck as any, config);
54
- });
55
-
56
- program
57
- .command("manifest")
58
- .description("Generate manifest.json from theme config")
59
- .option(`-c, --config <path>`, "path to theme config file", DEFAULT_CONFIG)
60
- .option(`-o, --out <file>`, "write to file instead of stdout")
61
- .action(async (opts: { config: string; out?: string }) => {
62
- const config = await loadConfig(opts.config);
63
- const json = generateManifest(config, { build: { command: BUILD_COMMAND } });
64
- if (opts.out) {
65
- writeFileSync(resolve(process.cwd(), opts.out), `${json}\n`);
66
- console.log(`WROTE ${opts.out}`);
67
- } else {
68
- process.stdout.write(`${json}\n`);
69
- }
70
- });
71
-
72
- program
73
- .command("smoke")
74
- .description("Generate one smoke-test slide per layout")
75
- .option(`-c, --config <path>`, "path to theme config file", DEFAULT_CONFIG)
76
- .option(`-o, --out <file>`, "output PPTX filename", DEFAULT_SMOKE_OUTPUT)
77
- .action(async (opts: { config: string; out: string }) => {
78
- const config = await loadConfig(opts.config);
79
- const steps = smokeSteps(config);
80
- await generate({ output: opts.out, steps }, config);
81
- });
82
-
83
- program
84
- .command("plugin")
85
- .description("Generate plugin package (manifest.json + SKILL.md) for AI agents")
86
- .option(`-c, --config <path>`, "path to theme config file", DEFAULT_CONFIG)
87
- .action(async (opts: { config: string }) => {
88
- const config = await loadConfig(opts.config);
89
- const outDir = resolve(process.cwd(), SKILL_DIR);
90
- mkdirSync(outDir, { recursive: true });
91
- const json = generateManifest(config, { build: { command: BUILD_COMMAND } });
92
- writeFileSync(resolve(outDir, MANIFEST_FILE), `${json}\n`);
93
- console.log(`WROTE ${SKILL_DIR}/${MANIFEST_FILE}`);
94
- copyFileSync(skillMdPath, resolve(outDir, SKILL_FILE));
95
- console.log(`WROTE ${SKILL_DIR}/${SKILL_FILE}`);
96
- });
97
-
98
- await program.parseAsync(process.argv);
package/src/config.ts DELETED
@@ -1,5 +0,0 @@
1
- import type { ThemeConfig } from "@tycoworks/bon-core";
2
-
3
- export function defineConfig(config: ThemeConfig): ThemeConfig {
4
- return config;
5
- }
package/src/index.ts DELETED
@@ -1,17 +0,0 @@
1
- export type {
2
- AssetCatalog,
3
- AssetEntry,
4
- AssetSlot,
5
- Config,
6
- ContentSlot,
7
- Deck,
8
- DeckStep,
9
- Layout,
10
- ParagraphSelector,
11
- ThemeConfig,
12
- } from "@tycoworks/bon-core";
13
- export { AssetType, Fit, generate, SlotType } from "@tycoworks/bon-core";
14
- export { defineConfig } from "./config.ts";
15
- export type { ManifestOptions } from "./manifest.ts";
16
- export { generateManifest } from "./manifest.ts";
17
- export { smokeSteps } from "./smoke.ts";
package/src/manifest.ts DELETED
@@ -1,106 +0,0 @@
1
- import type { Config } from "@tycoworks/bon-core";
2
-
3
- export type ManifestOptions = {
4
- build?: { command: string };
5
- };
6
-
7
- type ManifestContentSlot = {
8
- key: string;
9
- type: string;
10
- limit?: { maxChars?: number; maxLines?: number; maxItems?: number };
11
- };
12
-
13
- type ManifestAssetSlot = {
14
- key: string;
15
- accepts: string;
16
- required?: true;
17
- };
18
-
19
- type ManifestLayout = {
20
- name: string;
21
- description: string;
22
- whenToUse: string;
23
- whenNotToUse: string;
24
- contentSlots: ManifestContentSlot[];
25
- assetSlots: ManifestAssetSlot[];
26
- };
27
-
28
- type ManifestAssetEntry = {
29
- path: string;
30
- type: string;
31
- description: string;
32
- whenToUse?: string;
33
- };
34
-
35
- type Manifest = {
36
- version: 1;
37
- layouts: ManifestLayout[];
38
- assets: Record<string, Record<string, ManifestAssetEntry>>;
39
- build: {
40
- command: string;
41
- deckFormat: "json";
42
- deckSchema: {
43
- output: "string";
44
- steps: "array of { layout, content, assets }";
45
- };
46
- };
47
- };
48
-
49
- function stripContentSlot(slot: { key: string; type: string; limit?: object }): ManifestContentSlot {
50
- const result: ManifestContentSlot = { key: slot.key, type: slot.type };
51
- if (slot.limit) {
52
- result.limit = slot.limit;
53
- }
54
- return result;
55
- }
56
-
57
- function stripAssetSlot(slot: { key: string; accepts: string; required?: boolean }): ManifestAssetSlot {
58
- const result: ManifestAssetSlot = { key: slot.key, accepts: slot.accepts };
59
- if (slot.required) {
60
- result.required = true;
61
- }
62
- return result;
63
- }
64
-
65
- export function generateManifest(config: Config, options?: ManifestOptions): string {
66
- const layouts: ManifestLayout[] = config.layouts.map((layout) => ({
67
- name: layout.name,
68
- description: layout.description,
69
- whenToUse: layout.whenToUse,
70
- whenNotToUse: layout.whenNotToUse,
71
- contentSlots: layout.contentSlots.map(stripContentSlot),
72
- assetSlots: (layout.assetSlots ?? []).map(stripAssetSlot),
73
- }));
74
-
75
- const assets: Record<string, Record<string, ManifestAssetEntry>> = {};
76
- for (const [category, entries] of Object.entries(config.assets)) {
77
- assets[category] = {};
78
- for (const [name, entry] of Object.entries(entries)) {
79
- const manifestEntry: ManifestAssetEntry = {
80
- path: entry.path,
81
- type: entry.type,
82
- description: entry.description,
83
- };
84
- if (entry.whenToUse) {
85
- manifestEntry.whenToUse = entry.whenToUse;
86
- }
87
- assets[category][name] = manifestEntry;
88
- }
89
- }
90
-
91
- const manifest: Manifest = {
92
- version: 1,
93
- layouts,
94
- assets,
95
- build: {
96
- command: options?.build?.command ?? "npx bon build",
97
- deckFormat: "json",
98
- deckSchema: {
99
- output: "string",
100
- steps: "array of { layout, content, assets }",
101
- },
102
- },
103
- };
104
-
105
- return JSON.stringify(manifest, null, 2);
106
- }
package/src/smoke.ts DELETED
@@ -1,34 +0,0 @@
1
- import type { Config, DeckStep } from "@tycoworks/bon-core";
2
- import { SlotType } from "@tycoworks/bon-core";
3
-
4
- function pickAsset(config: Config, slot: { key: string; accepts: string }): string | undefined {
5
- for (const group of Object.values(config.assets)) {
6
- for (const entry of Object.values(group)) {
7
- if (entry.type === slot.accepts) return entry.path;
8
- }
9
- }
10
- return undefined;
11
- }
12
-
13
- export function smokeSteps(config: Config): DeckStep[] {
14
- return config.layouts.map((layout) => {
15
- const content: Record<string, string | string[]> = {};
16
- for (const s of layout.contentSlots) {
17
- if (s.type === SlotType.Prose) {
18
- content[s.key] = ["Sample intro line for this block.", "- First point", "- Second point"];
19
- } else if (s.type === SlotType.Lines) {
20
- content[s.key] = ["Sample Name", "Sample Role, Company"];
21
- } else if (s.key.includes("value")) {
22
- content[s.key] = "42%";
23
- } else {
24
- content[s.key] = `Sample ${s.key.replace(/_/g, " ")}`;
25
- }
26
- }
27
- const assets: Record<string, string> = {};
28
- for (const a of layout.assetSlots ?? []) {
29
- const path = pickAsset(config, a);
30
- if (path) assets[a.key] = path;
31
- }
32
- return { layout: layout.name, content, assets };
33
- });
34
- }
package/tsconfig.json DELETED
@@ -1,19 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "es2022",
4
- "lib": ["es2023"],
5
- "module": "nodenext",
6
- "moduleResolution": "nodenext",
7
- "allowImportingTsExtensions": true,
8
- "verbatimModuleSyntax": true,
9
- "erasableSyntaxOnly": true,
10
- "declaration": true,
11
- "declarationDir": "dist",
12
- "emitDeclarationOnly": true,
13
- "strict": true,
14
- "skipLibCheck": true,
15
- "composite": true
16
- },
17
- "include": ["src/**/*.ts"],
18
- "references": [{ "path": "../core" }]
19
- }