@devinilabs/reelstack 1.2.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 (145) hide show
  1. package/LICENSE +128 -0
  2. package/README.md +125 -0
  3. package/cli/beats.js +124 -0
  4. package/cli/bootstrap.js +124 -0
  5. package/cli/capture.js +34 -0
  6. package/cli/direction.js +114 -0
  7. package/cli/icons.js +49 -0
  8. package/cli/index.js +101 -0
  9. package/cli/init.js +253 -0
  10. package/cli/license.js +168 -0
  11. package/cli/lint.js +865 -0
  12. package/cli/preview.js +59 -0
  13. package/cli/render.js +404 -0
  14. package/cli/scaffold.js +239 -0
  15. package/cli/smoke.js +76 -0
  16. package/cli/update.js +26 -0
  17. package/cli/utils.js +184 -0
  18. package/docs/buyers-guide.md +220 -0
  19. package/docs/design-discipline.md +130 -0
  20. package/docs/family-galleries/dark.md +95 -0
  21. package/docs/family-galleries/forbidden.md +78 -0
  22. package/docs/family-galleries/glass.md +98 -0
  23. package/docs/family-galleries/paper.md +82 -0
  24. package/docs/family-galleries/warm.md +86 -0
  25. package/docs/superpowers/plans/2026-05-09-reelstack-init-readiness-gate.md +1166 -0
  26. package/docs/superpowers/specs/2026-05-09-reelstack-init-readiness-gate-design.md +233 -0
  27. package/families/dark/components/DriftingSpotlights.tsx +59 -0
  28. package/families/dark/components/FilmGrain.tsx +44 -0
  29. package/families/dark/components/ForestCard.tsx +43 -0
  30. package/families/dark/components/GridBackground.tsx +29 -0
  31. package/families/dark/components/RadialVignette.tsx +21 -0
  32. package/families/dark/components/Scanlines.tsx +35 -0
  33. package/families/dark/components/SegmentOpacity.ts +37 -0
  34. package/families/dark/components/index.ts +13 -0
  35. package/families/dark/index.ts +31 -0
  36. package/families/dark/palette.ts +98 -0
  37. package/families/dark/presets/claudedispatch.ts +46 -0
  38. package/families/dark/presets/codedrop.ts +37 -0
  39. package/families/dark/presets/gpt55.ts +54 -0
  40. package/families/dark/presets/notebooklm.ts +50 -0
  41. package/families/dark/presets/resourcescta.ts +35 -0
  42. package/families/dark/presets/skills.ts +40 -0
  43. package/families/dark/presets/stitch.ts +46 -0
  44. package/families/dark/presets/stitch2.ts +43 -0
  45. package/families/dark/typography.ts +16 -0
  46. package/families/forbidden/components/ForbiddenCausticBlobs.tsx +52 -0
  47. package/families/forbidden/components/NewsprintTexture.tsx +28 -0
  48. package/families/forbidden/components/TintedShadow.tsx +36 -0
  49. package/families/forbidden/components/index.ts +38 -0
  50. package/families/forbidden/index.ts +17 -0
  51. package/families/forbidden/palette.ts +88 -0
  52. package/families/forbidden/presets/heretic.ts +44 -0
  53. package/families/forbidden/typography.ts +18 -0
  54. package/families/glass/components/BreakdownCard.tsx +158 -0
  55. package/families/glass/components/CausticBlobs.tsx +49 -0
  56. package/families/glass/components/Counter.tsx +72 -0
  57. package/families/glass/components/EyebrowPill.tsx +59 -0
  58. package/families/glass/components/FilmStrip.tsx +202 -0
  59. package/families/glass/components/FloatingGlyphs.tsx +78 -0
  60. package/families/glass/components/GlassCard.tsx +58 -0
  61. package/families/glass/components/GlassCardBezel.tsx +45 -0
  62. package/families/glass/components/HairlineGrid.tsx +30 -0
  63. package/families/glass/components/IridescentRing.tsx +114 -0
  64. package/families/glass/components/IridescentText.tsx +98 -0
  65. package/families/glass/components/LightBeam.tsx +46 -0
  66. package/families/glass/components/ParticleBurst.tsx +62 -0
  67. package/families/glass/components/SonarRings.tsx +81 -0
  68. package/families/glass/components/StaggeredWords.tsx +74 -0
  69. package/families/glass/components/index.ts +20 -0
  70. package/families/glass/index.ts +31 -0
  71. package/families/glass/palette.ts +93 -0
  72. package/families/glass/presets/claudewatch.ts +64 -0
  73. package/families/glass/presets/claudewatchcta.ts +43 -0
  74. package/families/glass/presets/graphify.ts +45 -0
  75. package/families/glass/presets/gstack.ts +48 -0
  76. package/families/glass/presets/jcode.ts +50 -0
  77. package/families/glass/presets/lilagents.ts +52 -0
  78. package/families/glass/presets/paperclip.ts +43 -0
  79. package/families/glass/typography.ts +15 -0
  80. package/families/index.ts +49 -0
  81. package/families/paper/components/CardSpring.tsx +42 -0
  82. package/families/paper/components/CreamGrid.tsx +26 -0
  83. package/families/paper/components/EditorialSerifText.tsx +51 -0
  84. package/families/paper/components/GreenAccentCard.tsx +10 -0
  85. package/families/paper/components/PaperShadow.tsx +30 -0
  86. package/families/paper/components/ScaleBlurText.tsx +40 -0
  87. package/families/paper/components/index.ts +11 -0
  88. package/families/paper/index.ts +23 -0
  89. package/families/paper/palette.ts +102 -0
  90. package/families/paper/presets/designreel.ts +32 -0
  91. package/families/paper/presets/devini3d.ts +45 -0
  92. package/families/paper/presets/justdrop.ts +39 -0
  93. package/families/paper/presets/opus.ts +48 -0
  94. package/families/paper/typography.ts +17 -0
  95. package/families/warm/components/AccentGlow.tsx +60 -0
  96. package/families/warm/components/BentoCell.tsx +56 -0
  97. package/families/warm/components/BentoGrid.tsx +30 -0
  98. package/families/warm/components/FilmGrain.tsx +36 -0
  99. package/families/warm/components/ScaleBlurCounter.tsx +71 -0
  100. package/families/warm/components/WarmSurface.tsx +35 -0
  101. package/families/warm/components/index.ts +11 -0
  102. package/families/warm/index.ts +19 -0
  103. package/families/warm/palette.ts +81 -0
  104. package/families/warm/presets/huashu.ts +49 -0
  105. package/families/warm/presets/mempalace.ts +51 -0
  106. package/families/warm/typography.ts +17 -0
  107. package/package.json +85 -0
  108. package/reference/dark/claudedispatch.tsx +2441 -0
  109. package/reference/dark/notebooklm.tsx +2316 -0
  110. package/reference/dark/stitch.tsx +3040 -0
  111. package/reference/forbidden/heretic.tsx +2636 -0
  112. package/reference/glass/claudewatch.tsx +3827 -0
  113. package/reference/glass/graphify.tsx +2418 -0
  114. package/reference/glass/paperclip.tsx +2218 -0
  115. package/reference/paper/designreel.tsx +883 -0
  116. package/reference/paper/justdrop.tsx +1898 -0
  117. package/reference/paper/opus.tsx +1770 -0
  118. package/reference/warm/huashu.tsx +3413 -0
  119. package/reference/warm/mempalace.tsx +2909 -0
  120. package/skill/SKILL.md +229 -0
  121. package/skill/commands/reelstack-beats.md +20 -0
  122. package/skill/commands/reelstack-capture.md +24 -0
  123. package/skill/commands/reelstack-critique.md +15 -0
  124. package/skill/commands/reelstack-dark.md +40 -0
  125. package/skill/commands/reelstack-direction.md +17 -0
  126. package/skill/commands/reelstack-forbidden.md +25 -0
  127. package/skill/commands/reelstack-glass.md +39 -0
  128. package/skill/commands/reelstack-icons.md +22 -0
  129. package/skill/commands/reelstack-init.md +17 -0
  130. package/skill/commands/reelstack-lint.md +22 -0
  131. package/skill/commands/reelstack-paper.md +36 -0
  132. package/skill/commands/reelstack-render.md +20 -0
  133. package/skill/commands/reelstack-warm.md +36 -0
  134. package/templates/dark/template.tsx +115 -0
  135. package/templates/forbidden/template.tsx +111 -0
  136. package/templates/glass/template.tsx +201 -0
  137. package/templates/paper/template.tsx +133 -0
  138. package/templates/warm/template.tsx +210 -0
  139. package/utils/ai-purple-blocklist.ts +13 -0
  140. package/utils/banned-fonts.ts +11 -0
  141. package/utils/cubic-bezier.ts +36 -0
  142. package/utils/easing.ts +84 -0
  143. package/utils/grid.ts +13 -0
  144. package/utils/render-presets.json +56 -0
  145. package/utils/safe-zones.tsx +57 -0
package/LICENSE ADDED
@@ -0,0 +1,128 @@
1
+ ReelStack Commercial License v1.0
2
+ Copyright (c) 2026 Devini Labs. All rights reserved.
3
+
4
+ This software is licensed, not sold. Possession of this package without a
5
+ valid license key issued by Devini Labs does NOT grant any rights to use,
6
+ copy, modify, distribute, or sublicense the contents.
7
+
8
+ ================================================================================
9
+ SUMMARY (the legal text below is authoritative)
10
+ ================================================================================
11
+
12
+ - You may use ReelStack on a single end-user machine per license key.
13
+ - You may render unlimited videos for yourself, your employer, or your
14
+ clients with a valid license.
15
+ - You MAY NOT redistribute, repackage, resell, or open-source the families,
16
+ presets, components, templates, or CLI source contained in this package.
17
+ - You MAY NOT bundle ReelStack into another product offered for sale or
18
+ free distribution.
19
+ - License keys are non-transferable.
20
+ - Updates are included for the duration specified in your purchase.
21
+
22
+ ================================================================================
23
+ GRANT
24
+ ================================================================================
25
+
26
+ Subject to the terms of this License and the existence of a valid, active
27
+ license key issued by Devini Labs (via the payment processor at
28
+ https://devini.io/reelstack), Devini Labs ("Licensor") grants you
29
+ ("Licensee") a non-exclusive, non-transferable, revocable license to:
30
+
31
+ (a) install ReelStack on Licensee's development machines;
32
+ (b) use the families, presets, components, templates, and CLI to scaffold,
33
+ compose, and render Licensee's own video content;
34
+ (c) ship the rendered video output to any platform Licensee chooses, with
35
+ no royalty or attribution required.
36
+
37
+ ================================================================================
38
+ RESTRICTIONS
39
+ ================================================================================
40
+
41
+ Licensee shall NOT, without prior written consent from Devini Labs:
42
+
43
+ 1. Redistribute the source files contained in this package, in whole or in
44
+ part, in any form, public or private.
45
+ 2. Resell or sublicense ReelStack or any portion of it.
46
+ 3. Use ReelStack to create a competing product (template marketplace,
47
+ reel builder, video-as-a-service product) that exposes the same
48
+ primitives, presets, or templates to third parties.
49
+ 4. Remove, modify, or obscure copyright notices, license headers, or
50
+ watermarks present in the source files.
51
+ 5. Share, post, leak, or commit license keys to any public location.
52
+
53
+ ================================================================================
54
+ TERMINATION
55
+ ================================================================================
56
+
57
+ This License terminates automatically upon any breach by Licensee. Upon
58
+ termination, Licensee must delete all copies of ReelStack from their
59
+ systems. Rendered video output created prior to termination remains owned
60
+ by Licensee.
61
+
62
+ ================================================================================
63
+ DISCLAIMER OF WARRANTY
64
+ ================================================================================
65
+
66
+ REELSTACK IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
67
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
68
+ FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT. IN NO EVENT SHALL
69
+ THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY ARISING
70
+ FROM, OUT OF, OR IN CONNECTION WITH REELSTACK OR THE USE OR OTHER DEALINGS
71
+ IN THE SOFTWARE.
72
+
73
+ ================================================================================
74
+ CONTACT
75
+ ================================================================================
76
+
77
+ Licensing questions: licensing@devinilabs.com
78
+ Bug reports: https://github.com/devinilabs/reelstack/issues (private)
79
+
80
+ ================================================================================
81
+ ACKNOWLEDGMENTS
82
+ ================================================================================
83
+
84
+ ReelStack v1.1+ incorporates design-discipline rules from leonxlnx/taste-skill
85
+ (MIT-licensed, by @leonxlnx) and adopts UX patterns inspired by
86
+ alchaincyf/huashu-design (Personal Use Only, by @AlchainHust / 花叔).
87
+
88
+ The taste-skill rules are baked into ReelStack's components, palettes, and lint
89
+ under the terms of its MIT license. The huashu-design influence is pattern-only
90
+ — no code, text, or assets from huashu-design are copied into ReelStack;
91
+ ReelStack reimplements equivalent UX patterns (Design Direction Advisor,
92
+ 5-dimension critique, multi-format export) against its own Remotion + CLI
93
+ architecture, with full credit.
94
+
95
+ ReelStack's "huashu" preset (Warm Signature family) is named for
96
+ @AlchainHust's project as a credit hook.
97
+
98
+ Full attribution and rule citations: docs/design-discipline.md.
99
+
100
+ ================================================================================
101
+ REFERENCE REEL LICENSE (v1.1.1+)
102
+ ================================================================================
103
+
104
+ ReelStack v1.1.1+ ships with reference reels at ~/.reelstack/reference/
105
+ containing stripped-down source from Devini Labs reels that have shipped to
106
+ Instagram, TikTok, and YouTube Shorts (GraphifyReel, ClaudeWatchReel,
107
+ HereticReel, and 9 others). Asset imports (audio, video, brand SVGs) have
108
+ been replaced with REFERENCE-STRIP placeholders.
109
+
110
+ THE REFERENCE LICENSE GRANTS LICENSEE:
111
+ (a) the right to STUDY the JSX, motion choreography, BEAT structures, and
112
+ scene composition of every reference reel;
113
+ (b) the right to ADAPT motion patterns, scene transitions, and primitive
114
+ compositions into Licensee's own reels;
115
+ (c) the right to copy SHORT excerpts (a single scene, a specific motion
116
+ combination, a transition recipe) into Licensee's own work.
117
+
118
+ THE REFERENCE LICENSE DOES NOT GRANT:
119
+ (d) the right to publish a reference reel verbatim as Licensee's own
120
+ template, free or paid;
121
+ (e) the right to redistribute the reference reels as a package, fork, or
122
+ derivative work without the rest of ReelStack;
123
+ (f) the right to remove REFERENCE-STRIP markers and ship the result as a
124
+ paid template;
125
+ (g) the right to use a reference reel's branding, copy, or visual identity
126
+ to misrepresent the resulting work as a Devini Labs production.
127
+
128
+ If in doubt: pattern adaptation OK. Verbatim re-publication NOT OK.
package/README.md ADDED
@@ -0,0 +1,125 @@
1
+ # ReelStack
2
+
3
+ > Premium 9:16 Reel OS for Remotion. **5 cinematic style families. 22 production-tested presets. Audio-locked motion. IG-safe by default.**
4
+
5
+ ReelStack is the toolkit behind the Devini Labs reel aesthetic — packaged as a Claude Code skill plus a companion CLI. One slash command (`/reelstack-glass`, `/reelstack-paper`, `/reelstack-dark`, `/reelstack-warm`, `/reelstack-forbidden`) scaffolds a new 9:16 reel with the right palette, primitives, motion vocabulary, BEAT skeleton, and IG safe zones already wired up. From `init` to first preview render in under 10 minutes.
6
+
7
+ ## What you get
8
+
9
+ | Family | Vibe | Presets |
10
+ |---|---|---|
11
+ | **Glass Iridescent** | Light lavender-cream + iridescent particles. Caustic blobs, sonar rings, glass cards, floating glyphs. | graphify · paperclip · gstack · lilagents · jcode · claudewatch · claudewatchcta |
12
+ | **Cream Paper** | Warm editorial paper + dark-green cards + Claude terracotta. | justdrop · opus · designreel · devini3d |
13
+ | **Dark Cinematic** | Zinc void + drifting spotlights + Claude terracotta. Premium ad-film mood. | stitch · stitch2 · codedrop · claudedispatch · notebooklm · gpt55 · resourcescta · skills |
14
+ | **Warm Signature** | Single-accent rule. Amber signature + emerald payoff + bento grid. | huashu · mempalace |
15
+ | **Forbidden** | Cream-rose paper + ember + crimson + plasma. Desaturated, mysterious. | heretic |
16
+
17
+ Each preset carries the **exact palette, BEAT structure, frame count, and motion vocabulary** of a real Devini Labs reel that has shipped to YouTube and Instagram.
18
+
19
+ ## Why pay for it
20
+
21
+ - **House-style enforcement.** `reelstack lint` flags motion-floor violations, IG safe-zone breaches, hero-text overflow, and missing audio locks. Most boring AI-generated reel templates can't do this.
22
+ - **Audio-locked everywhere.** `reelstack beats <vo.wav>` runs whisper-cli and prints frame-accurate `BEAT` constants. No more eyeball-drift across 90-second clips.
23
+ - **GSAP vocabulary, ported.** Power4Out, expoOut, backOut — translated into Remotion `interpolate()` so motion language stays consistent.
24
+ - **Real brand assets.** `reelstack icons <brand>` pulls genuine SVGs from Iconify (logos:react, logos:next-js, etc.) instead of asking you to hand-draw.
25
+ - **Render presets.** `reelstack render <id> --platform=ig` ships with the right H.264, bitrate, and color flags for IG / TikTok / Shorts.
26
+
27
+ ## Install
28
+
29
+ ```bash
30
+ # Inside an existing Remotion project — or any folder; init can bootstrap one
31
+ npx @devinilabs/reelstack init
32
+ ```
33
+
34
+ `init` runs a **4-tier readiness gate** (v1.2+) so its success banner is provable. After it exits 0, every `/reelstack-*` command works — or it exits non-zero with a clear remediation.
35
+
36
+ | Tier | What it checks | If missing |
37
+ |---|---|---|
38
+ | 1 | Node ≥ 20 | exits with `nvm` / `fnm` / nodejs.org guidance |
39
+ | 2 | `ffmpeg` (required) + `whisper-cpp` (soft, only for `/reelstack-beats`) | macOS+Homebrew: offers to `brew install` for you. Linux / Windows / macOS-without-brew: prints the exact command for your platform and exits so you can install + retry |
40
+ | 3 | Remotion project + `@devinilabs/reelstack` dep | runs `npx create-video@latest --yes --blank reelstack-project` (override with `--name=…`, or skip with `--no-bootstrap`) |
41
+ | 4 | Smoke test — scaffolds Demo.tsx, runs `npx remotion compositions`, runs `ffmpeg -version` | exits 1 with the failing step name. Skip with `--skip-smoke`. |
42
+
43
+ The verified state is recorded at `~/.reelstack/state.json` so subcommands like `/reelstack-beats` can fail-fast with helpful messages instead of generic "not found" errors.
44
+
45
+ Don't have a license yet? Try the free preview:
46
+
47
+ ```bash
48
+ npx @devinilabs/reelstack preview
49
+ ```
50
+
51
+ ## First reel in 5 minutes
52
+
53
+ ```text
54
+ You: /reelstack-glass
55
+ ReelStack: Which preset? (graphify | paperclip | gstack | lilagents | jcode)
56
+ You: graphify
57
+ ReelStack: Reel name?
58
+ You: MyLaunch
59
+ ReelStack: Voiceover file? (path/.wav, or skip)
60
+ You: ./public/launch-vo.wav
61
+
62
+ ✓ Created src/MyLaunchReel.tsx
63
+ ✓ Registered in src/Root.tsx (1080×1920, 30fps, 1956 frames)
64
+ ✓ Whisper transcribed → 9 BEAT constants injected
65
+ ✓ Geist fonts pre-wired
66
+ ✓ IG safe zones inlined
67
+ → Open Remotion Studio? (Y/n)
68
+ ```
69
+
70
+ That's it. You're inside a real Devini Labs reel template, with your voiceover beats locked, your primitives imported, and Studio open at frame 0.
71
+
72
+ ## Slash commands
73
+
74
+ ```
75
+ /reelstack-glass Scaffold a Glass Iridescent reel
76
+ /reelstack-paper Scaffold a Cream Paper reel
77
+ /reelstack-dark Scaffold a Dark Cinematic reel
78
+ /reelstack-warm Scaffold a Warm Signature reel
79
+ /reelstack-forbidden Scaffold a Forbidden reel
80
+ /reelstack-init Set up ReelStack in a fresh Remotion project
81
+ /reelstack-beats Convert voiceover .wav → frame-accurate BEAT constants
82
+ /reelstack-capture Pull product screenshots into the reel via reel-capture
83
+ /reelstack-icons Pull real brand SVGs via better-icons (Iconify)
84
+ /reelstack-render Render with platform presets (ig | tiktok | shorts)
85
+ /reelstack-lint Validate motion floors, safe zones, audio lock, text fit
86
+ ```
87
+
88
+ ## Requirements
89
+
90
+ | Tool | macOS | Linux | Windows |
91
+ |---|---|---|---|
92
+ | **Node ≥ 20** | `nvm install 20` / [nodejs.org](https://nodejs.org/) | same | same |
93
+ | **Remotion 4** | auto-bootstrapped by `init`, or `npm create video@latest` | same | same |
94
+ | **ffmpeg** (required) | `brew install ffmpeg` (init can run this) | `apt-get install ffmpeg` · `dnf install ffmpeg` · `pacman -S ffmpeg` | `choco install ffmpeg` · `scoop install ffmpeg` |
95
+ | **whisper-cpp** (only `/reelstack-beats`) | `brew install whisper-cpp` (init can run this) | build from source: [github.com/ggerganov/whisper.cpp](https://github.com/ggerganov/whisper.cpp) | build from source, or `scoop install whisper.cpp` |
96
+ | **Claude Code** | [claude.com/claude-code](https://claude.com/claude-code) | same | same |
97
+
98
+ `init`'s Tier 2 detects your platform and either offers an auto-install (macOS+Homebrew) or prints the exact command for your package manager — so you don't need to know the table above by heart. If you're on Linux/Windows, install + re-run `init` once.
99
+
100
+ ## Pricing
101
+
102
+ **₹149 INR / $3 USD** one-time on [devini.io/reelstack](https://devini.io/reelstack) — region-detected at checkout. See [devini.io/reelstack](https://devini.io/reelstack) for the live regional quote and the full docs at [devini.io/reelstack/docs](https://devini.io/reelstack/docs). Includes:
103
+
104
+ - All 5 families, all 22 presets in v1.0.
105
+ - All future v1.x preset additions for free.
106
+ - Single end-user license. (Team/agency tier coming in v1.1.)
107
+
108
+ ## License
109
+
110
+ ReelStack is **commercial software** licensed under the terms in [LICENSE](LICENSE). You may render unlimited content for yourself or clients with a valid license; you may not redistribute, repackage, or resell ReelStack itself.
111
+
112
+ ---
113
+
114
+ Made by [Devini Labs](https://github.com/devinilabs). Same hands that shipped GraphifyReel, HereticReel, JustDropReel, OpusReel, ClaudeWatchReel, NotebookLMReel, HuashuReel, and 15 more — now boxed into a skill.
115
+
116
+ ---
117
+
118
+ ## Acknowledgments
119
+
120
+ ReelStack v1.1+ stands on the shoulders of two outside skills:
121
+
122
+ - **[leonxlnx/taste-skill](https://github.com/leonxlnx/taste-skill)** (MIT) — UI design-discipline rules. ReelStack bakes the master + per-family variant overlays directly into its components, palettes, and lint. Every scaffolded reel inherits the discipline by default.
123
+ - **[alchaincyf/huashu-design](https://github.com/alchaincyf/huashu-design)** (Personal Use Only — pattern inspiration only, no code/text copied) — productivity-UX patterns adapted to Remotion: Design Direction Advisor, 5-dim critique, multi-format render with BGM. The Warm Signature family's `huashu` preset is named for `@AlchainHust`'s project.
124
+
125
+ Full citation map at [`docs/design-discipline.md`](docs/design-discipline.md).
package/cli/beats.js ADDED
@@ -0,0 +1,124 @@
1
+ /**
2
+ * `reelstack beats <vo.wav>` — converts a voiceover into BEAT constants by
3
+ * running ffmpeg + whisper-cli and parsing the resulting SRT.
4
+ */
5
+ const fs = require("node:fs");
6
+ const path = require("node:path");
7
+ const os = require("node:os");
8
+ const { spawnSync } = require("node:child_process");
9
+ const { info, success, fail, warn, c, which, loadState } = require("./utils");
10
+
11
+ const FPS = 30;
12
+
13
+ function parseSrt(srtPath) {
14
+ const raw = fs.readFileSync(srtPath, "utf8");
15
+ const blocks = raw.split(/\r?\n\r?\n/).map((b) => b.trim()).filter(Boolean);
16
+ return blocks
17
+ .map((block) => {
18
+ const lines = block.split(/\r?\n/);
19
+ // line 0 = index, line 1 = "00:00:01,000 --> 00:00:02,400"
20
+ const tm = lines[1] && lines[1].match(
21
+ /(\d{2}):(\d{2}):(\d{2})[,.](\d{3})\s+-->\s+(\d{2}):(\d{2}):(\d{2})[,.](\d{3})/,
22
+ );
23
+ if (!tm) return null;
24
+ const start =
25
+ Number(tm[1]) * 3600 + Number(tm[2]) * 60 + Number(tm[3]) + Number(tm[4]) / 1000;
26
+ const text = lines.slice(2).join(" ").trim();
27
+ return { startSec: start, text };
28
+ })
29
+ .filter(Boolean);
30
+ }
31
+
32
+ function slugifyBeatKey(text) {
33
+ return text
34
+ .toLowerCase()
35
+ .replace(/[^a-z0-9 ]+/g, "")
36
+ .trim()
37
+ .split(/\s+/)
38
+ .slice(0, 3)
39
+ .join("_") || "beat";
40
+ }
41
+
42
+ async function run(argv) {
43
+ const voPath = argv.find((a) => !a.startsWith("--"));
44
+ if (!voPath) {
45
+ fail("Usage: reelstack beats <path/to/voiceover.wav>");
46
+ process.exit(2);
47
+ }
48
+ if (!fs.existsSync(voPath)) {
49
+ fail(`File not found: ${voPath}`);
50
+ process.exit(2);
51
+ }
52
+ const state = loadState();
53
+ if (state.whisperCli === "missing") {
54
+ fail("whisper-cli is not installed (init flagged this).");
55
+ console.log(" Install it now:");
56
+ console.log(" brew install whisper-cpp # macOS");
57
+ console.log(" Then re-run: reelstack init");
58
+ process.exit(1);
59
+ }
60
+
61
+ if (!which("ffmpeg")) {
62
+ fail("ffmpeg not found. Install: brew install ffmpeg");
63
+ process.exit(2);
64
+ }
65
+ const whisperBin = which("whisper-cli") || which("whisper-cpp");
66
+ if (!whisperBin) {
67
+ fail("whisper-cli not found. Install: brew install whisper-cpp");
68
+ process.exit(2);
69
+ }
70
+
71
+ const tmpWav = path.join(os.tmpdir(), `reelstack_${process.pid}_v16k.wav`);
72
+ const tmpOut = path.join(os.tmpdir(), `reelstack_${process.pid}_out`);
73
+ const modelPath = process.env.WHISPER_MODEL || path.join(os.tmpdir(), "ggml-base.en.bin");
74
+ if (!fs.existsSync(modelPath)) {
75
+ warn(`Whisper model not found at ${modelPath}.`);
76
+ info(`Download: curl -L -o "${modelPath}" https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-base.en.bin`);
77
+ process.exit(2);
78
+ }
79
+
80
+ info("Resampling voiceover to 16kHz mono PCM…");
81
+ spawnSync("ffmpeg", [
82
+ "-y", "-i", voPath, "-ar", "16000", "-ac", "1", "-c:a", "pcm_s16le", tmpWav,
83
+ ], { stdio: ["ignore", "ignore", "inherit"] });
84
+
85
+ info("Running whisper-cli…");
86
+ spawnSync(whisperBin, [
87
+ "-m", modelPath, "-f", tmpWav, "--output-srt", "-of", tmpOut,
88
+ ], { stdio: ["ignore", "ignore", "inherit"] });
89
+
90
+ const srtPath = `${tmpOut}.srt`;
91
+ if (!fs.existsSync(srtPath)) {
92
+ fail(`whisper-cli did not produce ${srtPath}.`);
93
+ process.exit(2);
94
+ }
95
+
96
+ const segments = parseSrt(srtPath);
97
+ if (segments.length === 0) {
98
+ fail("No segments parsed from SRT.");
99
+ process.exit(2);
100
+ }
101
+
102
+ console.log("");
103
+ console.log(c.bold("BEAT constants (paste into your reel):"));
104
+ console.log("");
105
+ console.log("const BEATS = {");
106
+ const seenKeys = new Set();
107
+ for (const seg of segments) {
108
+ let key = slugifyBeatKey(seg.text);
109
+ let i = 1;
110
+ while (seenKeys.has(key)) { key = `${slugifyBeatKey(seg.text)}_${i++}`; }
111
+ seenKeys.add(key);
112
+ const frame = Math.round(seg.startSec * FPS);
113
+ const comment = seg.text.length > 64 ? seg.text.slice(0, 64) + "…" : seg.text;
114
+ console.log(` ${key.padEnd(28)} ${frame.toString().padStart(5)}, // ${seg.startSec.toFixed(2)}s — "${comment}"`);
115
+ }
116
+ console.log("} as const;");
117
+ console.log("");
118
+ warn("whisper base.en mishears tech jargon (Claude → Cloud, Remotion → Re-motion).");
119
+ warn("Use these timings frame-for-frame, but copy ON-SCREEN text from your script.");
120
+ console.log("");
121
+ success(`Parsed ${segments.length} segments → ${segments.length} BEAT constants.`);
122
+ }
123
+
124
+ module.exports = { run };
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Tier 3: detect Remotion project state, prompt for bootstrap, run create-video.
3
+ *
4
+ * Only the pure helpers (`detectCwdState`, `buildCreateVideoArgs`) are unit-tested.
5
+ * The full `run()` function shells out and is exercised manually + via init-flow test.
6
+ */
7
+ const fs = require("node:fs");
8
+ const path = require("node:path");
9
+ const { spawnSync } = require("node:child_process");
10
+ const { info, success, fail, warn, c, askYesNo } = require("./utils");
11
+
12
+ const PKG_NAME = require("../package.json").name;
13
+
14
+ function detectCwdState(cwd) {
15
+ const pkgPath = path.join(cwd, "package.json");
16
+ if (!fs.existsSync(pkgPath)) return "no-package-json";
17
+
18
+ let pkg;
19
+ try {
20
+ pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
21
+ } catch {
22
+ return "corrupt-package-json";
23
+ }
24
+
25
+ const hasRemotion =
26
+ (pkg.dependencies && pkg.dependencies.remotion) ||
27
+ (pkg.devDependencies && pkg.devDependencies.remotion);
28
+
29
+ return hasRemotion ? "has-remotion" : "non-remotion-package";
30
+ }
31
+
32
+ function buildCreateVideoArgs(projectName) {
33
+ return ["create-video@latest", "--yes", "--blank", projectName];
34
+ }
35
+
36
+ function ensureReelstackDep(projectDir) {
37
+ const pkg = JSON.parse(fs.readFileSync(path.join(projectDir, "package.json"), "utf8"));
38
+ const already =
39
+ (pkg.dependencies && pkg.dependencies[PKG_NAME]) ||
40
+ (pkg.devDependencies && pkg.devDependencies[PKG_NAME]);
41
+
42
+ if (already) {
43
+ success(`${PKG_NAME} already present.`);
44
+ return true;
45
+ }
46
+
47
+ info(`Installing ${PKG_NAME}…`);
48
+ const result = spawnSync("npm", ["install", PKG_NAME], {
49
+ stdio: "inherit",
50
+ cwd: projectDir,
51
+ });
52
+ if (result.status !== 0) {
53
+ fail(`npm install ${PKG_NAME} failed.`);
54
+ return false;
55
+ }
56
+ success(`${PKG_NAME} installed.`);
57
+ return true;
58
+ }
59
+
60
+ /**
61
+ * Run Tier 3. Returns { projectDir, isNew } or exits process on
62
+ * unrecoverable failure. `skipped: true` when buyer declines bootstrap or
63
+ * --no-bootstrap was passed.
64
+ */
65
+ async function run({ noBootstrap = false, projectName = "reelstack-project" } = {}) {
66
+ const cwd = process.cwd();
67
+ const state = detectCwdState(cwd);
68
+
69
+ if (state === "has-remotion") {
70
+ info("Remotion project detected in current directory.");
71
+ if (!ensureReelstackDep(cwd)) process.exit(1);
72
+ return { projectDir: cwd, isNew: false, skipped: false };
73
+ }
74
+
75
+ if (state === "corrupt-package-json") {
76
+ fail("Found a package.json in this folder, but it doesn't parse as valid JSON.");
77
+ console.log(c.gray(" Fix the file or run `reelstack init` from a different directory."));
78
+ process.exit(1);
79
+ }
80
+
81
+ if (noBootstrap) {
82
+ warn("No Remotion project here; --no-bootstrap was passed.");
83
+ console.log(c.gray(" Re-run `reelstack init` from inside a Remotion project to wire up imports."));
84
+ return { projectDir: null, isNew: false, skipped: true };
85
+ }
86
+
87
+ if (state === "non-remotion-package") {
88
+ warn("This folder has a non-Remotion package.json.");
89
+ info("Bootstrapping Remotion in a subdirectory instead so we don't muddy your project.");
90
+ }
91
+
92
+ console.log("");
93
+ const yes = await askYesNo(`Scaffold a Remotion project at ./${projectName}/?`);
94
+ if (!yes) {
95
+ warn("Skipped Remotion bootstrap.");
96
+ console.log(c.gray(" Re-run `reelstack init` from inside a Remotion project to wire up imports."));
97
+ return { projectDir: null, isNew: false, skipped: true };
98
+ }
99
+
100
+ const targetDir = path.join(cwd, projectName);
101
+ if (fs.existsSync(targetDir)) {
102
+ fail(`./${projectName}/ already exists. Choose a different name with --name=<dir>, or delete it first.`);
103
+ process.exit(1);
104
+ }
105
+
106
+ info(`Running: npx ${buildCreateVideoArgs(projectName).join(" ")}`);
107
+ const result = spawnSync("npx", buildCreateVideoArgs(projectName), {
108
+ stdio: "inherit",
109
+ cwd,
110
+ });
111
+ if (result.status !== 0) {
112
+ fail(`create-video did not exit cleanly (status ${result.status}).`);
113
+ console.log(c.gray(" Check your network connection, or try running manually:"));
114
+ console.log(c.gray(` npx ${buildCreateVideoArgs(projectName).join(" ")}`));
115
+ process.exit(1);
116
+ }
117
+ success("Remotion project scaffolded.");
118
+
119
+ if (!ensureReelstackDep(targetDir)) process.exit(1);
120
+
121
+ return { projectDir: targetDir, isNew: true, skipped: false };
122
+ }
123
+
124
+ module.exports = { run, detectCwdState, buildCreateVideoArgs, ensureReelstackDep };
package/cli/capture.js ADDED
@@ -0,0 +1,34 @@
1
+ /**
2
+ * `reelstack capture <url> [--scroll] [--name=<slug>]` — delegate to the
3
+ * user's `reel-capture` Claude Code skill via a stub instruction. The CLI
4
+ * does NOT directly drive Chrome; it prints the wiring instructions so the
5
+ * skill can run reel-capture in the same session.
6
+ */
7
+ const path = require("node:path");
8
+ const { info, warn, success, fail, c } = require("./utils");
9
+
10
+ async function run(argv) {
11
+ const url = argv.find((a) => !a.startsWith("--"));
12
+ if (!url) {
13
+ fail("Usage: reelstack capture <url> [--scroll] [--name=<slug>]");
14
+ process.exit(2);
15
+ }
16
+ const slug = argv.find((a) => a.startsWith("--name="))?.replace("--name=", "")
17
+ || new URL(url).hostname.replace(/\W+/g, "-");
18
+ const scroll = argv.includes("--scroll");
19
+
20
+ info(`Delegating to reel-capture skill: ${url}`);
21
+ console.log("");
22
+ console.log(c.gray("In your Claude Code session, run:"));
23
+ console.log(` ${c.cyan(`/reel-capture ${url}${scroll ? " --scroll" : ""}`)}`);
24
+ console.log("");
25
+ console.log(c.gray("Output will land in:"));
26
+ console.log(` ${c.cyan(path.join(process.cwd(), "public", "captures", slug, scroll ? "frame_NNNN.png" : "hero.png"))}`);
27
+ console.log("");
28
+ console.log(c.gray("Then wire it into your reel:"));
29
+ console.log(` ${c.cyan(`<Img src={staticFile("captures/${slug}/hero.png")} />`)}`);
30
+ console.log("");
31
+ warn("ReelStack does not auto-edit your reel — wiring is your call.");
32
+ }
33
+
34
+ module.exports = { run };
@@ -0,0 +1,114 @@
1
+ /**
2
+ * `reelstack direction "<brief>"` — when a buyer's brief is vague, return 3
3
+ * differentiated family picks with reasoning.
4
+ *
5
+ * Inspired by alchaincyf/huashu-design's Design Direction Advisor pattern
6
+ * (no code copied; pattern only).
7
+ */
8
+ const { c, info, success, fail } = require("./utils");
9
+
10
+ const FAMILY_PROFILES = {
11
+ glass: {
12
+ label: "Glass Iridescent",
13
+ mood: "premium product reveal · underwater light · iridescent glass",
14
+ bestFor: ["product launch", "feature reveal", "SaaS announcement", "premium brand", "AI/ML demo"],
15
+ presets: ["graphify", "paperclip", "gstack", "lilagents", "jcode", "claudewatch"],
16
+ },
17
+ paper: {
18
+ label: "Cream Paper",
19
+ mood: "editorial print · warm document · card-stack hierarchy",
20
+ bestFor: ["thesis-driven narrative", "skill ecosystem reveal", "long-form storytelling", "design tool launch"],
21
+ presets: ["justdrop", "opus", "designreel", "devini3d"],
22
+ },
23
+ dark: {
24
+ label: "Dark Cinematic",
25
+ mood: "late-night ad-film · drifting spotlights · multi-brand partnership",
26
+ bestFor: ["partnership announcement", "integration reveal", "secret/insider drop", "dev-tool launch"],
27
+ presets: ["stitch", "stitch2", "codedrop", "claudedispatch", "notebooklm", "gpt55"],
28
+ },
29
+ warm: {
30
+ label: "Warm Signature",
31
+ mood: "confident product launch · single amber accent · bento grid",
32
+ bestFor: ["confident launch", "feature matrix reveal", "memory/learning narrative", "bento-style features"],
33
+ presets: ["huashu", "mempalace"],
34
+ },
35
+ forbidden: {
36
+ label: "Forbidden",
37
+ mood: "declassified document · cream-rose paper · ember/crimson/plasma",
38
+ bestFor: ["mysterious reveal", "unconventional tool", "open-source rebellion", "censorship-strip narrative"],
39
+ presets: ["heretic"],
40
+ },
41
+ };
42
+
43
+ const KEYWORD_MAP = {
44
+ glass: ["product", "launch", "ai", "ml", "saas", "demo", "premium", "reveal", "feature", "ship"],
45
+ paper: ["thesis", "essay", "skill", "ecosystem", "narrative", "design", "editorial", "story", "long"],
46
+ dark: ["partnership", "integration", "secret", "insider", "cinematic", "drop", "tool", "dev", "code"],
47
+ warm: ["confident", "matrix", "memory", "learning", "bento", "feature", "warm", "amber", "ship"],
48
+ forbidden:["mysterious", "declassified", "rebellion", "open source", "censorship", "alt", "uncommon"],
49
+ };
50
+
51
+ /**
52
+ * Score each family by counting matching keywords in the brief.
53
+ */
54
+ function scoreFamilies(brief) {
55
+ const lc = brief.toLowerCase();
56
+ const scores = {};
57
+ for (const [family, keywords] of Object.entries(KEYWORD_MAP)) {
58
+ scores[family] = keywords.reduce((acc, kw) => acc + (lc.includes(kw) ? 1 : 0), 0);
59
+ }
60
+ return scores;
61
+ }
62
+
63
+ function pickTop3(scores) {
64
+ // Sort by score desc; on tie, sort alphabetically by family key for
65
+ // deterministic output (no Object.entries insertion-order surprises).
66
+ return Object.entries(scores)
67
+ .sort(([famA, a], [famB, b]) => (b - a) || famA.localeCompare(famB))
68
+ .slice(0, 3)
69
+ .map(([family, score]) => ({ family, score, profile: FAMILY_PROFILES[family] }));
70
+ }
71
+
72
+ /**
73
+ * Pure helper: scoreFamilies + pickTop3 + zero-score fallback. Returns the
74
+ * 3 picks the CLI's `direction` runtime would print, without touching stdout.
75
+ */
76
+ function directionPicks(brief) {
77
+ const scores = scoreFamilies(brief);
78
+ const allZero = Object.values(scores).every((s) => s === 0);
79
+ if (allZero) {
80
+ return ["glass", "dark", "warm"].map((family) => ({
81
+ family,
82
+ score: 0,
83
+ profile: FAMILY_PROFILES[family],
84
+ }));
85
+ }
86
+ return pickTop3(scores);
87
+ }
88
+
89
+ async function run(argv) {
90
+ const brief = argv.filter((a) => !a.startsWith("--")).join(" ").trim();
91
+ if (!brief) {
92
+ fail("Usage: reelstack direction \"<brief>\"");
93
+ process.exit(2);
94
+ }
95
+
96
+ info(`Brief: "${brief}"`);
97
+ console.log("");
98
+
99
+ const top3 = directionPicks(brief);
100
+
101
+ console.log(c.bold("Three directions for your brief:"));
102
+ console.log("");
103
+ top3.forEach(({ family, profile }, i) => {
104
+ console.log(` ${c.cyan(`${i + 1}.`)} ${c.bold(profile.label)} — ${c.gray(profile.mood)}`);
105
+ console.log(` ${c.gray("best for:")} ${profile.bestFor.slice(0, 3).join(" · ")}`);
106
+ console.log(` ${c.gray("presets: ")} ${profile.presets.slice(0, 4).join(", ")}${profile.presets.length > 4 ? "…" : ""}`);
107
+ console.log(` ${c.gray("trigger: ")} /reelstack-${family}`);
108
+ console.log("");
109
+ });
110
+
111
+ console.log(c.gray("Pick one and run the matching slash command. Or pass `--family=<x>` to scaffold directly."));
112
+ }
113
+
114
+ module.exports = { run, scoreFamilies, pickTop3, directionPicks, FAMILY_PROFILES };