decklight 0.1.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.
- package/LICENSE +21 -0
- package/README.md +257 -0
- package/SPEC.md +372 -0
- package/cli/bundle.mjs +388 -0
- package/cli/decklight.mjs +95 -0
- package/cli/edit.mjs +142 -0
- package/cli/init.mjs +215 -0
- package/cli/rec.mjs +538 -0
- package/dist/decklight.css +1068 -0
- package/dist/decklight.js +239 -0
- package/dist/decklight.js.map +7 -0
- package/docs/architecture.svg +94 -0
- package/docs/demo.svg +124 -0
- package/package.json +54 -0
- package/themes/README.md +61 -0
- package/themes/aliens.css +84 -0
- package/themes/apple2.css +126 -0
- package/themes/aurora.css +84 -0
- package/themes/berry.css +80 -0
- package/themes/blade-runner.css +83 -0
- package/themes/c64.css +85 -0
- package/themes/carbon.css +82 -0
- package/themes/citrus.css +80 -0
- package/themes/coastal.css +80 -0
- package/themes/cosmos.css +86 -0
- package/themes/dune.css +80 -0
- package/themes/eclipse.css +82 -0
- package/themes/ember.css +80 -0
- package/themes/fjord.css +80 -0
- package/themes/friends.css +82 -0
- package/themes/gallery.html +203 -0
- package/themes/gameboy.css +118 -0
- package/themes/genesis.css +84 -0
- package/themes/glacier.css +82 -0
- package/themes/godfather.css +82 -0
- package/themes/graphite.css +80 -0
- package/themes/harvest.css +80 -0
- package/themes/ibm-modern.css +83 -0
- package/themes/ibm-oldschool.css +84 -0
- package/themes/ink.css +82 -0
- package/themes/latte.css +80 -0
- package/themes/linen.css +80 -0
- package/themes/meadow.css +80 -0
- package/themes/metropolis.css +85 -0
- package/themes/miami-vice.css +84 -0
- package/themes/midnight.css +80 -0
- package/themes/mint.css +82 -0
- package/themes/moss.css +82 -0
- package/themes/obsidian.css +82 -0
- package/themes/orchid.css +82 -0
- package/themes/packs.json +89 -0
- package/themes/paper.css +83 -0
- package/themes/peony.css +84 -0
- package/themes/porcelain.css +82 -0
- package/themes/pulp-fiction.css +84 -0
- package/themes/reveal-beige.css +87 -0
- package/themes/reveal-black.css +83 -0
- package/themes/reveal-blood.css +84 -0
- package/themes/reveal-dracula.css +83 -0
- package/themes/reveal-league.css +88 -0
- package/themes/reveal-moon.css +83 -0
- package/themes/reveal-night.css +86 -0
- package/themes/reveal-serif.css +82 -0
- package/themes/reveal-simple.css +84 -0
- package/themes/reveal-sky.css +85 -0
- package/themes/reveal-solarized.css +83 -0
- package/themes/reveal-white.css +82 -0
- package/themes/sepia.css +81 -0
- package/themes/seriph.css +82 -0
- package/themes/severance.css +82 -0
- package/themes/slate.css +80 -0
- package/themes/snes.css +82 -0
- package/themes/star-wars.css +85 -0
- package/themes/storm.css +80 -0
- package/themes/stranger-things.css +84 -0
- package/themes/synthwave.css +90 -0
- package/themes/terminator.css +84 -0
- package/themes/velvet.css +80 -0
- package/tools/gemini-tts.mjs +140 -0
- package/tools/publish-site-voices.mjs +71 -0
- package/tools/voiceover-server.mjs +0 -0
- package/tools/voiceover.mjs +155 -0
package/cli/init.mjs
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* decklight init — scaffold a starter deck, plus an agent skill so Claude
|
|
4
|
+
* Code (and any AGENTS.md-reading agent) knows the authoring contract
|
|
5
|
+
* without a web search or a guess from Reveal.js memory.
|
|
6
|
+
*
|
|
7
|
+
* decklight init ["My Deck"] [-o deck.html] [--dir path] [--force] [--no-skill]
|
|
8
|
+
*
|
|
9
|
+
* The deck is fully self-contained (runtime + theme inlined, like
|
|
10
|
+
* `decklight bundle` produces) — double-click it, it presents, no sibling
|
|
11
|
+
* files. The skill is regenerated every run (it's derived, not authored
|
|
12
|
+
* content) so re-running after an upgrade refreshes it; the deck file is
|
|
13
|
+
* only touched with --force.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import fs from 'node:fs';
|
|
17
|
+
import path from 'node:path';
|
|
18
|
+
import { fileURLToPath } from 'node:url';
|
|
19
|
+
|
|
20
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
21
|
+
const PKG_ROOT = path.resolve(here, '..');
|
|
22
|
+
const PKG = JSON.parse(fs.readFileSync(path.join(PKG_ROOT, 'package.json'), 'utf8'));
|
|
23
|
+
|
|
24
|
+
function fail(msg) {
|
|
25
|
+
process.stderr.write(`decklight init: ${msg}\n`);
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const scriptSafe = (s) => s.replace(/<\/script/gi, '<\\/script').replace(/<!--/g, '<\\u0021--');
|
|
30
|
+
|
|
31
|
+
const STARTER_THEME = 'aurora';
|
|
32
|
+
|
|
33
|
+
function starterDeck(title) {
|
|
34
|
+
const css = fs.readFileSync(path.join(PKG_ROOT, 'dist/decklight.css'), 'utf8');
|
|
35
|
+
const theme = fs.readFileSync(path.join(PKG_ROOT, 'themes', `${STARTER_THEME}.css`), 'utf8');
|
|
36
|
+
return `<!doctype html>
|
|
37
|
+
<html lang="en">
|
|
38
|
+
<head>
|
|
39
|
+
<meta charset="utf-8">
|
|
40
|
+
<title>${title}</title>
|
|
41
|
+
<style>
|
|
42
|
+
${css}
|
|
43
|
+
</style>
|
|
44
|
+
<style data-theme="${STARTER_THEME}">
|
|
45
|
+
${theme}
|
|
46
|
+
</style>
|
|
47
|
+
</head>
|
|
48
|
+
<body>
|
|
49
|
+
<div class="decklight">
|
|
50
|
+
|
|
51
|
+
<section>
|
|
52
|
+
<h1>${title}</h1>
|
|
53
|
+
<p>Made with Decklight — press → to advance, ? for every key</p>
|
|
54
|
+
<aside class="notes">
|
|
55
|
+
<p>Welcome. This is the title slide — say a line or two about what this deck covers.</p>
|
|
56
|
+
</aside>
|
|
57
|
+
</section>
|
|
58
|
+
|
|
59
|
+
<section>
|
|
60
|
+
<h2>A slide with a build</h2>
|
|
61
|
+
<ul data-build="fade-up">
|
|
62
|
+
<li>One attribute on the container: <code>data-build</code></li>
|
|
63
|
+
<li>Each direct child becomes one build step, in document order</li>
|
|
64
|
+
<li>Speaker notes segment with ⟨CLICK⟩ to match — see below</li>
|
|
65
|
+
</ul>
|
|
66
|
+
<aside class="notes">
|
|
67
|
+
<p>The container opts in and the engine does the rest — not a single class on the items themselves.</p>
|
|
68
|
+
<p>⟨CLICK⟩</p>
|
|
69
|
+
<p>Every press of the arrow reveals the next one, in order.</p>
|
|
70
|
+
<p>⟨CLICK⟩</p>
|
|
71
|
+
<p>And that's it — replace this slide's content, duplicate the section for more, and you have a deck.</p>
|
|
72
|
+
</aside>
|
|
73
|
+
</section>
|
|
74
|
+
|
|
75
|
+
</div>
|
|
76
|
+
<script>${scriptSafe(fs.readFileSync(path.join(PKG_ROOT, 'dist/decklight.js'), 'utf8').replace(/\/\/# sourceMappingURL=.*$/m, ''))}</script>
|
|
77
|
+
<script>Decklight.init({});</script>
|
|
78
|
+
</body>
|
|
79
|
+
</html>
|
|
80
|
+
`;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function specSlice() {
|
|
84
|
+
const spec = fs.readFileSync(path.join(PKG_ROOT, 'SPEC.md'), 'utf8');
|
|
85
|
+
const cut = spec.indexOf('\n## 10. Repository layout & tooling');
|
|
86
|
+
return (cut > 0 ? spec.slice(0, cut) : spec).trimEnd() + '\n';
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function skillMd() {
|
|
90
|
+
return `---
|
|
91
|
+
name: decklight
|
|
92
|
+
description: Author and edit Decklight presentations — single-file HTML decks with Keynote-style builds, theme-aware SVG diagrams, 61 built-in themes, truthful terminal recordings, and live TTS narration. Use whenever creating or editing a Decklight deck (a .html file with a <div class="decklight"> of <section> slides) in this project.
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
Decklight decks are one HTML file: no build step, no bundler, no server to
|
|
96
|
+
author. A deck is \`<div class="decklight">\` containing \`<section>\` slides;
|
|
97
|
+
the runtime is one JS file + one CSS file + one theme CSS file.
|
|
98
|
+
|
|
99
|
+
**Full authoring contract**: read [reference.md](reference.md) in this same
|
|
100
|
+
skill directory before authoring or editing a slide — it covers builds,
|
|
101
|
+
speaker notes segmentation (⟨CLICK⟩), SVG diagrams, theming, motion, code
|
|
102
|
+
blocks, terminal recordings, narration, and the public JS API. It's sliced
|
|
103
|
+
straight from Decklight's SPEC.md (v${PKG.version}), so it won't drift from
|
|
104
|
+
the installed runtime's actual behavior — trust it over prior training.
|
|
105
|
+
|
|
106
|
+
**Minimal skeleton** (see \`deck.html\` in this project for a worked example
|
|
107
|
+
with a build and notes already wired):
|
|
108
|
+
|
|
109
|
+
\`\`\`html
|
|
110
|
+
<div class="decklight">
|
|
111
|
+
<section>
|
|
112
|
+
<h1>Title</h1>
|
|
113
|
+
<aside class="notes"><p>What you'd say on this slide.</p></aside>
|
|
114
|
+
</section>
|
|
115
|
+
</div>
|
|
116
|
+
\`\`\`
|
|
117
|
+
|
|
118
|
+
**CLI** (\`npx decklight <command>\`, no install needed):
|
|
119
|
+
- \`decklight edit deck.html\` — serve with live reload; **E** in the browser edits speaker notes back into the file
|
|
120
|
+
- \`decklight rec script.term.yaml\` — record a truthful terminal cast in a real PTY, for \`<div class="terminal">\`
|
|
121
|
+
- \`decklight bundle deck.html --themes all\` — flatten into one self-contained file to hand off or publish
|
|
122
|
+
- \`decklight tts\` — live voice bridge so the deck can narrate itself on the fly
|
|
123
|
+
- \`decklight init\` — regenerate this skill after upgrading Decklight (deck file untouched unless \`--force\`)
|
|
124
|
+
|
|
125
|
+
Speaker notes drive both live narration and the transcript/caption
|
|
126
|
+
features, so write them even for decks that will only ever be read: split
|
|
127
|
+
multi-beat notes with a bare \`⟨CLICK⟩\` line so narration and build steps
|
|
128
|
+
stay in sync (§8 in the reference).
|
|
129
|
+
`;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const AGENTS_MARKER = '<!-- decklight:skill -->';
|
|
133
|
+
|
|
134
|
+
function agentsSection() {
|
|
135
|
+
return `${AGENTS_MARKER}
|
|
136
|
+
## Decklight decks
|
|
137
|
+
|
|
138
|
+
This project contains a Decklight presentation (a single-file HTML deck —
|
|
139
|
+
see \`.claude/skills/decklight/reference.md\` for the full authoring
|
|
140
|
+
contract: builds, notes, SVG diagrams, themes, terminals, narration).
|
|
141
|
+
Read that file before adding or editing slides.
|
|
142
|
+
${AGENTS_MARKER}
|
|
143
|
+
`;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export async function initMain(argv = process.argv.slice(2)) {
|
|
147
|
+
if (argv.includes('--help') || argv.includes('-h')) {
|
|
148
|
+
process.stdout.write(`decklight init — scaffold a starter deck + agent skill
|
|
149
|
+
|
|
150
|
+
Usage:
|
|
151
|
+
decklight init ["Deck Title"] [-o deck.html] [--dir path] [--force] [--no-skill]
|
|
152
|
+
|
|
153
|
+
Options:
|
|
154
|
+
-o <file> deck output path (default: deck.html)
|
|
155
|
+
--dir <path> target directory (default: current directory)
|
|
156
|
+
--force overwrite an existing deck file (default: refuses)
|
|
157
|
+
--no-skill skip .claude/skills/decklight/ and AGENTS.md
|
|
158
|
+
|
|
159
|
+
Always writes/refreshes the skill files (they're generated from the
|
|
160
|
+
installed version's SPEC.md, so re-running after an upgrade updates them)
|
|
161
|
+
unless --no-skill is given. The deck file is only touched with --force.
|
|
162
|
+
`);
|
|
163
|
+
process.exit(0);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
let title = null, outFile = 'deck.html', dir = '.', force = false, withSkill = true;
|
|
167
|
+
const args = [...argv];
|
|
168
|
+
for (let i = 0; i < args.length; i++) {
|
|
169
|
+
const a = args[i];
|
|
170
|
+
if (a === '-o') outFile = args[++i];
|
|
171
|
+
else if (a === '--dir') dir = args[++i];
|
|
172
|
+
else if (a === '--force') force = true;
|
|
173
|
+
else if (a === '--no-skill') withSkill = false;
|
|
174
|
+
else if (!a.startsWith('-')) title = title ?? a;
|
|
175
|
+
else fail(`unknown argument: ${a}`);
|
|
176
|
+
}
|
|
177
|
+
title = title || 'My Deck';
|
|
178
|
+
|
|
179
|
+
const root = path.resolve(dir);
|
|
180
|
+
fs.mkdirSync(root, { recursive: true });
|
|
181
|
+
|
|
182
|
+
const deckPath = path.resolve(root, outFile);
|
|
183
|
+
if (fs.existsSync(deckPath) && !force) {
|
|
184
|
+
fail(`${path.relative('.', deckPath) || outFile} already exists — pass --force to overwrite`);
|
|
185
|
+
}
|
|
186
|
+
fs.writeFileSync(deckPath, starterDeck(title));
|
|
187
|
+
process.stdout.write(`created ${path.relative('.', deckPath) || outFile}\n`);
|
|
188
|
+
|
|
189
|
+
if (!withSkill) return;
|
|
190
|
+
|
|
191
|
+
const skillDir = path.join(root, '.claude', 'skills', 'decklight');
|
|
192
|
+
fs.mkdirSync(skillDir, { recursive: true });
|
|
193
|
+
fs.writeFileSync(path.join(skillDir, 'SKILL.md'), skillMd());
|
|
194
|
+
fs.writeFileSync(path.join(skillDir, 'reference.md'), specSlice());
|
|
195
|
+
process.stdout.write(`wrote .claude/skills/decklight/{SKILL.md,reference.md} (v${PKG.version})\n`);
|
|
196
|
+
|
|
197
|
+
const agentsPath = path.join(root, 'AGENTS.md');
|
|
198
|
+
const section = agentsSection();
|
|
199
|
+
if (!fs.existsSync(agentsPath)) {
|
|
200
|
+
fs.writeFileSync(agentsPath, `# Agent notes\n\n${section}`);
|
|
201
|
+
process.stdout.write('created AGENTS.md\n');
|
|
202
|
+
} else {
|
|
203
|
+
const existing = fs.readFileSync(agentsPath, 'utf8');
|
|
204
|
+
const markerRe = new RegExp(`${AGENTS_MARKER}[\\s\\S]*?${AGENTS_MARKER}\\n?`);
|
|
205
|
+
if (markerRe.test(existing)) {
|
|
206
|
+
fs.writeFileSync(agentsPath, existing.replace(markerRe, section));
|
|
207
|
+
process.stdout.write('refreshed the Decklight section in AGENTS.md\n');
|
|
208
|
+
} else {
|
|
209
|
+
fs.writeFileSync(agentsPath, existing.replace(/\n*$/, '\n\n') + section);
|
|
210
|
+
process.stdout.write('appended a Decklight section to AGENTS.md\n');
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (import.meta.url === `file://${process.argv[1]}`) await initMain();
|