voxflow 1.14.0 → 1.15.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 +69 -2
- package/bin/voxflow.js +27 -0
- package/dist/index.js +1 -1
- package/dist/remotion-bundle/02a2fb2eb80bc7bf.woff2 +0 -0
- package/dist/remotion-bundle/052ca5351e5e06ba.woff2 +0 -0
- package/dist/remotion-bundle/05853dd28f4019cb.woff2 +0 -0
- package/dist/remotion-bundle/072ead3737f7c0d0.woff2 +0 -0
- package/dist/remotion-bundle/07d4248613c86a2e.woff2 +0 -0
- package/dist/remotion-bundle/0884a5c2d1d2d99b.woff2 +0 -0
- package/dist/remotion-bundle/0b0e185b2752095e.woff2 +0 -0
- package/dist/remotion-bundle/0e66c11bde067d91.woff2 +0 -0
- package/dist/remotion-bundle/0f7794cfba2c5d21.woff2 +0 -0
- package/dist/remotion-bundle/0fdbae5a4365783a.woff2 +0 -0
- package/dist/remotion-bundle/112.bundle.js +11 -0
- package/dist/remotion-bundle/112.bundle.js.map +1 -0
- package/dist/remotion-bundle/113.bundle.js +11 -0
- package/dist/remotion-bundle/113.bundle.js.map +1 -0
- package/dist/remotion-bundle/119cae0c4c16f7ed.woff2 +0 -0
- package/dist/remotion-bundle/14725f649fd1e78c.woff2 +0 -0
- package/dist/remotion-bundle/14abe9e3f95f7888.woff2 +0 -0
- package/dist/remotion-bundle/163.bundle.js +14678 -0
- package/dist/remotion-bundle/163.bundle.js.map +1 -0
- package/dist/remotion-bundle/1808c54072bf6d14.woff2 +0 -0
- package/dist/remotion-bundle/18948bec3e3012fe.woff2 +0 -0
- package/dist/remotion-bundle/1a661c60d0fc84fc.woff2 +0 -0
- package/dist/remotion-bundle/1af94941e1bc7e1e.woff2 +0 -0
- package/dist/remotion-bundle/1bee0219595f606c.woff2 +0 -0
- package/dist/remotion-bundle/1bfd5da7ce9d4ec4.woff2 +0 -0
- package/dist/remotion-bundle/1c158d56f1884f3f.woff2 +0 -0
- package/dist/remotion-bundle/1cf5e88e667610eb.woff2 +0 -0
- package/dist/remotion-bundle/1d431bd10f53c481.woff2 +0 -0
- package/dist/remotion-bundle/1d701a81a7670db2.woff2 +0 -0
- package/dist/remotion-bundle/1da0fecad4240f16.woff2 +0 -0
- package/dist/remotion-bundle/1ed14d3d0c5c63fe.woff2 +0 -0
- package/dist/remotion-bundle/1edfecf40e586f53.woff2 +0 -0
- package/dist/remotion-bundle/1f479711bc34b054.woff +0 -0
- package/dist/remotion-bundle/1f86e54a0ff5fcd1.woff2 +0 -0
- package/dist/remotion-bundle/2043ea87d9aabd11.woff2 +0 -0
- package/dist/remotion-bundle/20563c39ee8a0e40.woff2 +0 -0
- package/dist/remotion-bundle/20c231590fd12c44.woff2 +0 -0
- package/dist/remotion-bundle/20ce61713f754c07.woff2 +0 -0
- package/dist/remotion-bundle/21eb9306fce24bb1.woff2 +0 -0
- package/dist/remotion-bundle/244bf71c0cc851af.woff2 +0 -0
- package/dist/remotion-bundle/274d4cfc02bffbcb.woff2 +0 -0
- package/dist/remotion-bundle/275.bundle.js +21 -0
- package/dist/remotion-bundle/275.bundle.js.map +1 -0
- package/dist/remotion-bundle/2958f540b39513dc.woff2 +0 -0
- package/dist/remotion-bundle/2a168b98fd97722e.woff2 +0 -0
- package/dist/remotion-bundle/2d1f6373937ab55f.woff2 +0 -0
- package/dist/remotion-bundle/2d213ae47ff6daa9.woff2 +0 -0
- package/dist/remotion-bundle/2e4b1f04fcd05047.woff2 +0 -0
- package/dist/remotion-bundle/304170d98f4c4563.woff2 +0 -0
- package/dist/remotion-bundle/30d02e136e7a5642.woff2 +0 -0
- package/dist/remotion-bundle/3135562b52a714cd.woff2 +0 -0
- package/dist/remotion-bundle/313713af2c8144e9.woff2 +0 -0
- package/dist/remotion-bundle/325fa4108d2285b9.woff2 +0 -0
- package/dist/remotion-bundle/338e927ed3345e0c.woff2 +0 -0
- package/dist/remotion-bundle/35fc6b190365bc17.woff2 +0 -0
- package/dist/remotion-bundle/37a51f1122d4efc5.woff2 +0 -0
- package/dist/remotion-bundle/39a4d63e02736f5e.woff2 +0 -0
- package/dist/remotion-bundle/3a00e0d62dfc4171.woff2 +0 -0
- package/dist/remotion-bundle/3a6955e6561affe1.woff2 +0 -0
- package/dist/remotion-bundle/3c573945aef49b89.woff2 +0 -0
- package/dist/remotion-bundle/3cdbfbfa23b516a5.woff2 +0 -0
- package/dist/remotion-bundle/3e42f85a9e64ca8a.woff2 +0 -0
- package/dist/remotion-bundle/3e83eaf1ec859415.woff2 +0 -0
- package/dist/remotion-bundle/3f3c8c90de1250ee.woff2 +0 -0
- package/dist/remotion-bundle/434.bundle.js +205 -0
- package/dist/remotion-bundle/434.bundle.js.map +1 -0
- package/dist/remotion-bundle/44ffc6ca4d781692.woff2 +0 -0
- package/dist/remotion-bundle/4670d9c4580b09eb.woff2 +0 -0
- package/dist/remotion-bundle/479756881b302824.woff2 +0 -0
- package/dist/remotion-bundle/481b82134bfa9c82.woff2 +0 -0
- package/dist/remotion-bundle/48d27029626f4328.woff2 +0 -0
- package/dist/remotion-bundle/49b7b2a30329c511.woff2 +0 -0
- package/dist/remotion-bundle/4c8b25a1a9337045.woff2 +0 -0
- package/dist/remotion-bundle/4cba14788ca9259b.woff2 +0 -0
- package/dist/remotion-bundle/4cd6c589c004a6a7.woff2 +0 -0
- package/dist/remotion-bundle/4cd8d79c1021608d.woff2 +0 -0
- package/dist/remotion-bundle/4d8fa99b3f00f9f0.woff2 +0 -0
- package/dist/remotion-bundle/4e7805a643f86d53.woff2 +0 -0
- package/dist/remotion-bundle/4ff91be454542e3f.woff2 +0 -0
- package/dist/remotion-bundle/504cbcba1f63591b.woff2 +0 -0
- package/dist/remotion-bundle/5202d792e5791d6c.woff2 +0 -0
- package/dist/remotion-bundle/534db5ad4770cc1d.woff2 +0 -0
- package/dist/remotion-bundle/53b9568eb85f866b.woff2 +0 -0
- package/dist/remotion-bundle/543ad386ca171de9.woff2 +0 -0
- package/dist/remotion-bundle/54798e55bbf7976e.woff2 +0 -0
- package/dist/remotion-bundle/580.bundle.js +11 -0
- package/dist/remotion-bundle/580.bundle.js.map +1 -0
- package/dist/remotion-bundle/58d174d1193af6d1.woff2 +0 -0
- package/dist/remotion-bundle/591d29ff3ff53c80.woff2 +0 -0
- package/dist/remotion-bundle/5c28c4f4824383c6.woff2 +0 -0
- package/dist/remotion-bundle/5da9740d2ce894c8.woff2 +0 -0
- package/dist/remotion-bundle/6197735364642360.woff2 +0 -0
- package/dist/remotion-bundle/6265a4335724080f.woff2 +0 -0
- package/dist/remotion-bundle/633f5e4f6394daa7.woff2 +0 -0
- package/dist/remotion-bundle/637d95ace6a69c49.woff2 +0 -0
- package/dist/remotion-bundle/648e04a04dacff8f.woff2 +0 -0
- package/dist/remotion-bundle/64a6e83045a008b2.woff2 +0 -0
- package/dist/remotion-bundle/651.bundle.js +11 -0
- package/dist/remotion-bundle/651.bundle.js.map +1 -0
- package/dist/remotion-bundle/65e2a988c070facc.woff2 +0 -0
- package/dist/remotion-bundle/66a2f6ce5cc69105.woff2 +0 -0
- package/dist/remotion-bundle/690.bundle.js +3479 -0
- package/dist/remotion-bundle/690.bundle.js.map +1 -0
- package/dist/remotion-bundle/690ff55252ca715d.woff2 +0 -0
- package/dist/remotion-bundle/6a01a1cff49314fc.woff2 +0 -0
- package/dist/remotion-bundle/6cbc32670982986c.woff2 +0 -0
- package/dist/remotion-bundle/6d3cc42ae547f454.woff2 +0 -0
- package/dist/remotion-bundle/6d8f4cfa1ddc0830.woff2 +0 -0
- package/dist/remotion-bundle/6e4d7c6ae65e2dc3.woff2 +0 -0
- package/dist/remotion-bundle/6e86418bbcefb2e8.woff2 +0 -0
- package/dist/remotion-bundle/6ee02884b29cf7fb.woff2 +0 -0
- package/dist/remotion-bundle/6f436a74c9e3252c.woff2 +0 -0
- package/dist/remotion-bundle/78c8022f1657618b.woff2 +0 -0
- package/dist/remotion-bundle/7c5444169792bca4.woff2 +0 -0
- package/dist/remotion-bundle/7c86bddd9d997212.woff2 +0 -0
- package/dist/remotion-bundle/7e1284684767f584.woff2 +0 -0
- package/dist/remotion-bundle/7e81c17522d182b2.woff2 +0 -0
- package/dist/remotion-bundle/7eb87be198f7858c.woff2 +0 -0
- package/dist/remotion-bundle/8060c928f948aab5.woff2 +0 -0
- package/dist/remotion-bundle/80bc9dfbea2b35ae.woff2 +0 -0
- package/dist/remotion-bundle/811b83f69963bb48.woff2 +0 -0
- package/dist/remotion-bundle/813.bundle.js +117511 -0
- package/dist/remotion-bundle/813.bundle.js.map +1 -0
- package/dist/remotion-bundle/84df492e349f82e9.woff2 +0 -0
- package/dist/remotion-bundle/8501bfd73eb36f2b.woff2 +0 -0
- package/dist/remotion-bundle/854236a8376093fe.woff2 +0 -0
- package/dist/remotion-bundle/8571d74529082753.woff2 +0 -0
- package/dist/remotion-bundle/860bf44f8e6f4b5d.woff2 +0 -0
- package/dist/remotion-bundle/879.bundle.js +64 -0
- package/dist/remotion-bundle/879.bundle.js.map +1 -0
- package/dist/remotion-bundle/887dd482f848d56f.woff2 +0 -0
- package/dist/remotion-bundle/89b2132e85fbbb5a.woff2 +0 -0
- package/dist/remotion-bundle/8ba60d6c306010c2.woff2 +0 -0
- package/dist/remotion-bundle/8c7c4dadea897806.woff2 +0 -0
- package/dist/remotion-bundle/8c943f9999706f61.woff2 +0 -0
- package/dist/remotion-bundle/8f2a718c90575cc9.woff2 +0 -0
- package/dist/remotion-bundle/906b6edb3e1772c9.woff2 +0 -0
- package/dist/remotion-bundle/930ff9daccdf14eb.woff2 +0 -0
- package/dist/remotion-bundle/934db2f1c403c4d0.woff2 +0 -0
- package/dist/remotion-bundle/938.bundle.js +451 -0
- package/dist/remotion-bundle/938.bundle.js.map +1 -0
- package/dist/remotion-bundle/967.bundle.js +4462 -0
- package/dist/remotion-bundle/967.bundle.js.map +1 -0
- package/dist/remotion-bundle/9684a1093d3c02ce.woff2 +0 -0
- package/dist/remotion-bundle/973dcd0faa6116cc.woff2 +0 -0
- package/dist/remotion-bundle/9745400694e76cd8.woff2 +0 -0
- package/dist/remotion-bundle/999ef957bed3bdca.woff2 +0 -0
- package/dist/remotion-bundle/99a3d67c8b0f43e3.woff2 +0 -0
- package/dist/remotion-bundle/a0586c3e03127283.woff2 +0 -0
- package/dist/remotion-bundle/a0eb654fdae46269.woff2 +0 -0
- package/dist/remotion-bundle/a20e35d3b08f7994.woff2 +0 -0
- package/dist/remotion-bundle/a2dcaced7c8c25ab.woff2 +0 -0
- package/dist/remotion-bundle/a79255a972a2681a.woff2 +0 -0
- package/dist/remotion-bundle/a804b352cb9fec1a.woff2 +0 -0
- package/dist/remotion-bundle/aae7117164e1eabc.woff2 +0 -0
- package/dist/remotion-bundle/affd121385d0442d.woff2 +0 -0
- package/dist/remotion-bundle/b19a6083987ee0d7.woff2 +0 -0
- package/dist/remotion-bundle/b1b2bd04d8637981.woff2 +0 -0
- package/dist/remotion-bundle/b2c07f341486be87.woff2 +0 -0
- package/dist/remotion-bundle/b33d8f82e575c4ce.woff2 +0 -0
- package/dist/remotion-bundle/b366c0bed35ef491.woff2 +0 -0
- package/dist/remotion-bundle/b41e857ec1b85642.woff2 +0 -0
- package/dist/remotion-bundle/b420bb34ccf23e7f.woff2 +0 -0
- package/dist/remotion-bundle/b4f7bf4efb0c0ccf.woff2 +0 -0
- package/dist/remotion-bundle/b60fe5eca03cff93.woff2 +0 -0
- package/dist/remotion-bundle/b6bd31a336e64bce.woff2 +0 -0
- package/dist/remotion-bundle/b6d2befba3dfefeb.woff2 +0 -0
- package/dist/remotion-bundle/b75f39ab06c43bf4.woff2 +0 -0
- package/dist/remotion-bundle/b77880e8c413d4fd.woff2 +0 -0
- package/dist/remotion-bundle/b7e38ec441e4a77a.woff2 +0 -0
- package/dist/remotion-bundle/b83baa383ff0bf2b.woff2 +0 -0
- package/dist/remotion-bundle/b9ad7b6c0a11450a.woff2 +0 -0
- package/dist/remotion-bundle/baf84486e8ae3aaf.woff2 +0 -0
- package/dist/remotion-bundle/bc047b1f6869cffa.woff2 +0 -0
- package/dist/remotion-bundle/bf4f3ac6e93f33aa.woff2 +0 -0
- package/dist/remotion-bundle/bf6835ffec5897a2.woff2 +0 -0
- package/dist/remotion-bundle/bf8885f581eb1724.woff2 +0 -0
- package/dist/remotion-bundle/bundle.js +83376 -0
- package/dist/remotion-bundle/bundle.js.map +1 -0
- package/dist/remotion-bundle/c03f046bccd789d0.woff2 +0 -0
- package/dist/remotion-bundle/c0bb1f8962b73bc3.woff2 +0 -0
- package/dist/remotion-bundle/c1003f9a7db6e1cf.woff2 +0 -0
- package/dist/remotion-bundle/c15d83fb1e199515.woff2 +0 -0
- package/dist/remotion-bundle/c28e7e5d310f73ef.woff2 +0 -0
- package/dist/remotion-bundle/c2b840274db78aea.woff2 +0 -0
- package/dist/remotion-bundle/c3000e3299d4e45f.woff2 +0 -0
- package/dist/remotion-bundle/c83ce886e5288510.woff2 +0 -0
- package/dist/remotion-bundle/c87a5a64d4ac0918.woff2 +0 -0
- package/dist/remotion-bundle/c8a7e0d049e965fa.woff2 +0 -0
- package/dist/remotion-bundle/c949a35d3a3b1faf.woff2 +0 -0
- package/dist/remotion-bundle/c9618c9b9ac2bc78.woff2 +0 -0
- package/dist/remotion-bundle/ca3add3b84152d5b.woff2 +0 -0
- package/dist/remotion-bundle/cad9dd036408d707.woff2 +0 -0
- package/dist/remotion-bundle/cbb24916619df439.woff2 +0 -0
- package/dist/remotion-bundle/cc054f0b5514e177.woff2 +0 -0
- package/dist/remotion-bundle/ccc248ed9312bc71.woff2 +0 -0
- package/dist/remotion-bundle/cd9d623aa07af925.woff2 +0 -0
- package/dist/remotion-bundle/ce2ba7a321bd1247.woff2 +0 -0
- package/dist/remotion-bundle/cf72455f79a29b14.woff2 +0 -0
- package/dist/remotion-bundle/d267cbfefab452ac.woff2 +0 -0
- package/dist/remotion-bundle/d435cff46a64955f.woff +0 -0
- package/dist/remotion-bundle/d494d07f67e363f6.woff2 +0 -0
- package/dist/remotion-bundle/d7aa0cc1fa47bf38.woff2 +0 -0
- package/dist/remotion-bundle/d7c5ca93d885160a.woff2 +0 -0
- package/dist/remotion-bundle/d855d3e252db74e2.woff2 +0 -0
- package/dist/remotion-bundle/d8f13d47f02f82c2.woff2 +0 -0
- package/dist/remotion-bundle/d9567cce2ee11019.woff2 +0 -0
- package/dist/remotion-bundle/db8d4456fc75dd86.woff +0 -0
- package/dist/remotion-bundle/dc274628378c47ee.woff2 +0 -0
- package/dist/remotion-bundle/dc3e06947bb69903.woff2 +0 -0
- package/dist/remotion-bundle/dd67040ac3b6d523.woff2 +0 -0
- package/dist/remotion-bundle/e0b04bd488f953f4.woff2 +0 -0
- package/dist/remotion-bundle/e2a572ff95089370.woff2 +0 -0
- package/dist/remotion-bundle/e2e18a86b1c2b0cc.woff2 +0 -0
- package/dist/remotion-bundle/e3a78ee2fc9c6931.woff2 +0 -0
- package/dist/remotion-bundle/e654c9d547605a9f.woff2 +0 -0
- package/dist/remotion-bundle/e67a3a64c129927c.woff2 +0 -0
- package/dist/remotion-bundle/e6be28b4203cd6ce.woff2 +0 -0
- package/dist/remotion-bundle/e841907ad9b0a191.woff +0 -0
- package/dist/remotion-bundle/e889d1541c69fffa.woff2 +0 -0
- package/dist/remotion-bundle/e88ef8c76373a9e2.woff2 +0 -0
- package/dist/remotion-bundle/e9c72f4bc37defef.woff2 +0 -0
- package/dist/remotion-bundle/e9e35f863403a255.woff2 +0 -0
- package/dist/remotion-bundle/eb23b37b009375da.woff2 +0 -0
- package/dist/remotion-bundle/ee1342b741625721.woff2 +0 -0
- package/dist/remotion-bundle/f07da88543a57ec9.woff2 +0 -0
- package/dist/remotion-bundle/f522982115306f8a.woff2 +0 -0
- package/dist/remotion-bundle/f8449bd864e6d8bc.woff2 +0 -0
- package/dist/remotion-bundle/f906dd5bd95ff9ab.woff2 +0 -0
- package/dist/remotion-bundle/f9e9e9413e3c38bb.woff2 +0 -0
- package/dist/remotion-bundle/fa5a5b16280994a8.woff2 +0 -0
- package/dist/remotion-bundle/favicon.ico +0 -0
- package/dist/remotion-bundle/fb19c0517725599b.woff2 +0 -0
- package/dist/remotion-bundle/fcaf24232f684b9b.woff2 +0 -0
- package/dist/remotion-bundle/fe09e084a3eea8cf.woff2 +0 -0
- package/dist/remotion-bundle/ff38d5317df7345a.woff2 +0 -0
- package/dist/remotion-bundle/ffe7ea1ea08f455a.woff2 +0 -0
- package/dist/remotion-bundle/index.html +49 -0
- package/dist/remotion-bundle/public/paper-slide/female-kefu-xiaomei/communication/0.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/female-kefu-xiaomei/communication/1.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/female-kefu-xiaomei/communication/2.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/female-kefu-xiaomei/communication/3.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/female-kefu-xiaoxin/career/0.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/female-kefu-xiaoxin/career/1.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/female-kefu-xiaoxin/career/2.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/female-kefu-xiaoxin/career/3.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/female-kefu-xiaoyue/parenting/0.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/female-kefu-xiaoyue/parenting/1.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/female-kefu-xiaoyue/parenting/2.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/female-kefu-xiaoyue/parenting/3.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/male-kefu-xiaoxu/time-trap/0.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/male-kefu-xiaoxu/time-trap/1.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/male-kefu-xiaoxu/time-trap/2.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/male-kefu-xiaoxu/time-trap/3.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-female-A6b7WpG3/cognition/0.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-female-A6b7WpG3/cognition/1.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-female-A6b7WpG3/cognition/2.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-female-A6b7WpG3/cognition/3.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-female-A6b7WpG3/growth/0.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-female-A6b7WpG3/growth/1.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-female-A6b7WpG3/growth/2.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-female-A6b7WpG3/growth/3.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-female-A6b7WpG3/parenting/0.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-female-A6b7WpG3/parenting/1.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-female-A6b7WpG3/parenting/2.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-female-A6b7WpG3/parenting/3.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-female-A6b7WpG3/soothing/0.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-female-A6b7WpG3/soothing/1.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-female-A6b7WpG3/soothing/2.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-female-A6b7WpG3/soothing/3.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-female-R2s4N9qJ/cognition/0.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-female-R2s4N9qJ/cognition/1.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-female-R2s4N9qJ/cognition/2.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-female-R2s4N9qJ/cognition/3.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-male-Bk7vD3xP/decision/0.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-male-Bk7vD3xP/decision/1.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-male-Bk7vD3xP/decision/2.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-male-Bk7vD3xP/decision/3.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-male-W1tH9jVc/manager/0.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-male-W1tH9jVc/manager/1.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-male-W1tH9jVc/manager/2.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-male-W1tH9jVc/manager/3.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-male-W1tH9jVc/manager/4.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-male-s5NqE0rZ/founder/0.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-male-s5NqE0rZ/founder/1.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-male-s5NqE0rZ/founder/2.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide/v-male-s5NqE0rZ/founder/3.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/career-advice/0.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/career-advice/1.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/career-advice/2.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/career-advice/3.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/career-advice/4.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/founder-lesson/0.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/founder-lesson/1.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/founder-lesson/2.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/founder-lesson/3.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/founder-lesson/4.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/incident-review/0.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/incident-review/1.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/incident-review/2.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/incident-review/3.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/incident-review/4.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/learning-loop/0.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/learning-loop/1.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/learning-loop/2.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/learning-loop/3.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/learning-loop/4.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/meeting-closure/0.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/meeting-closure/1.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/meeting-closure/2.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/meeting-closure/3.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/product-update/0.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/product-update/1.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/product-update/2.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/product-update/3.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/research-reading/0.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/research-reading/1.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/research-reading/2.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/research-reading/3.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/sales-enablement/0.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/sales-enablement/1.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/sales-enablement/2.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/sales-enablement/3.mp3 +0 -0
- package/dist/remotion-bundle/public/paper-slide-experiments/sales-enablement/4.mp3 +0 -0
- package/dist/remotion-bundle/public/voiceover/ai-life/card-0.mp3 +0 -0
- package/dist/remotion-bundle/public/voiceover/ai-life/card-1.mp3 +0 -0
- package/dist/remotion-bundle/public/voiceover/ai-life/card-2.mp3 +0 -0
- package/dist/remotion-bundle/public/voiceover/ai-life/card-3.mp3 +0 -0
- package/dist/remotion-bundle/public/voiceover/ai-life/card-4.mp3 +0 -0
- package/dist/remotion-bundle/public/voiceover/ai-life/card-5.mp3 +0 -0
- package/dist/remotion-bundle/public/voiceover/coffee-science/card-0.mp3 +0 -0
- package/dist/remotion-bundle/public/voiceover/coffee-science/card-1.mp3 +0 -0
- package/dist/remotion-bundle/public/voiceover/coffee-science/card-2.mp3 +0 -0
- package/dist/remotion-bundle/public/voiceover/coffee-science/card-3.mp3 +0 -0
- package/dist/remotion-bundle/public/voiceover/coffee-science/card-4.mp3 +0 -0
- package/dist/remotion-bundle/public/voiceover/coffee-science/card-5.mp3 +0 -0
- package/dist/remotion-bundle/public/voiceover/coffee-science/card-6.mp3 +0 -0
- package/dist/remotion-bundle/public/voiceover/reading-secrets/card-0.mp3 +0 -0
- package/dist/remotion-bundle/public/voiceover/reading-secrets/card-1.mp3 +0 -0
- package/dist/remotion-bundle/public/voiceover/reading-secrets/card-2.mp3 +0 -0
- package/dist/remotion-bundle/public/voiceover/reading-secrets/card-3.mp3 +0 -0
- package/dist/remotion-bundle/public/voiceover/reading-secrets/card-4.mp3 +0 -0
- package/dist/remotion-bundle/public/voiceover/reading-secrets/card-5.mp3 +0 -0
- package/dist/remotion-bundle/public/voiceover/reading-secrets/card-6.mp3 +0 -0
- package/dist/remotion-bundle/public/voiceover/remote-work/card-0.mp3 +0 -0
- package/dist/remotion-bundle/public/voiceover/remote-work/card-1.mp3 +0 -0
- package/dist/remotion-bundle/public/voiceover/remote-work/card-2.mp3 +0 -0
- package/dist/remotion-bundle/public/voiceover/remote-work/card-3.mp3 +0 -0
- package/dist/remotion-bundle/public/voiceover/remote-work/card-4.mp3 +0 -0
- package/dist/remotion-bundle/public/voiceover/remote-work/card-5.mp3 +0 -0
- package/dist/remotion-bundle/source-map-helper.wasm +0 -0
- package/lib/cli.js +270 -0
- package/lib/commands/_registry.js +48 -0
- package/lib/commands/add.js +242 -0
- package/lib/commands/asr/azure-transcribe.js +336 -0
- package/lib/commands/asr/cloud-transcribe.js +384 -0
- package/lib/commands/asr/helpers.js +76 -0
- package/lib/commands/asr/index.js +236 -0
- package/lib/commands/asr/local-transcribe.js +125 -0
- package/lib/commands/asr-jobs.js +257 -0
- package/lib/commands/asr.js +11 -0
- package/lib/commands/auth-cmds.js +358 -0
- package/lib/commands/dub.js +542 -0
- package/lib/commands/explain.js +512 -0
- package/lib/commands/feedback.js +152 -0
- package/lib/commands/image.js +207 -0
- package/lib/commands/mcp-key.js +166 -0
- package/lib/commands/narrate.js +639 -0
- package/lib/commands/picstory-templates.js +276 -0
- package/lib/commands/picstory.js +547 -0
- package/lib/commands/podcast/dialogue.js +109 -0
- package/lib/commands/podcast/generate.js +127 -0
- package/lib/commands/podcast/index.js +561 -0
- package/lib/commands/podcast/synthesize.js +188 -0
- package/lib/commands/podcast.js +11 -0
- package/lib/commands/present.js +519 -0
- package/lib/commands/publish.js +415 -0
- package/lib/commands/skills.js +473 -0
- package/lib/commands/slice-preview.js +266 -0
- package/lib/commands/slice-render.js +282 -0
- package/lib/commands/slice-stage.js +264 -0
- package/lib/commands/slice.js +343 -0
- package/lib/commands/slides/constants.js +108 -0
- package/lib/commands/slides/html-renderer.js +338 -0
- package/lib/commands/slides/index.js +345 -0
- package/lib/commands/slides.js +11 -0
- package/lib/commands/story.js +302 -0
- package/lib/commands/summarize.js +532 -0
- package/lib/commands/synthesize.js +261 -0
- package/lib/commands/translate.js +593 -0
- package/lib/commands/upgrade.js +249 -0
- package/lib/commands/video-translate.js +577 -0
- package/lib/commands/voices.js +292 -0
- package/lib/core/agent-env.js +104 -0
- package/lib/core/args.js +107 -0
- package/lib/core/asr-client.js +448 -0
- package/lib/core/asr-jobs-client.js +126 -0
- package/lib/core/asr-jobs-store.js +105 -0
- package/lib/core/asr-r2-upload.js +181 -0
- package/lib/core/asr-upload.js +132 -0
- package/lib/core/audio-extract.js +150 -0
- package/lib/core/audio.js +219 -0
- package/lib/core/auth.js +880 -0
- package/lib/core/config.js +197 -0
- package/lib/core/feedback.js +64 -0
- package/lib/core/ffmpeg.js +476 -0
- package/lib/core/http.js +188 -0
- package/lib/core/image-client.js +55 -0
- package/lib/core/intent-params.js +11 -0
- package/lib/core/llm-client.js +76 -0
- package/lib/core/logger.js +208 -0
- package/lib/core/mic-recorder.js +182 -0
- package/lib/core/pause-markers.js +94 -0
- package/lib/core/podcast-pacing.js +118 -0
- package/lib/core/spinner.js +33 -0
- package/lib/core/srt.js +394 -0
- package/lib/core/telemetry.js +100 -0
- package/lib/core/timeline.js +92 -0
- package/lib/core/tts-synthesizer.js +70 -0
- package/lib/core/update-check.js +185 -0
- package/lib/core/url-download.js +148 -0
- package/lib/core/whisper-local.js +279 -0
- package/lib/internal/deck-validator.js +488 -0
- package/lib/internal/slice-themes.json +370 -0
- package/lib/stage-core/cloud-render.js +170 -0
- package/lib/stage-core/deck-format.js +133 -0
- package/lib/stage-core/edit-prompt.js +104 -0
- package/lib/stage-core/event-bus.js +31 -0
- package/lib/stage-core/port.js +46 -0
- package/lib/stage-core/server.js +352 -0
- package/lib/stage-core/snapshot-store.js +198 -0
- package/lib/stage-core/watcher.js +106 -0
- package/lib/stage-ui/slice/template.js +1672 -0
- package/package.json +9 -4
- package/skills/.claude-plugin/marketplace.json +22 -0
- package/skills/.claude-plugin/plugin.json +25 -0
- package/skills/LICENSE +21 -0
- package/skills/README.md +120 -0
- package/skills/hub/SKILL.md +317 -0
- package/skills/podcast/SKILL.md +146 -0
- package/skills/slice/SKILL.md +205 -0
- package/skills/slice/agents/openai.yaml +4 -0
- package/skills/slice/references/deck-schema.md +183 -0
- package/skills/slice/references/example-decks.md +108 -0
- package/skills/slice/references/themes.md +172 -0
- package/skills/transcribe/SKILL.md +473 -0
- package/skills/video/SKILL.md +261 -0
- package/skills/voxflow-slice/SKILL.md +271 -0
- package/skills/voxflow-slice/examples/article.md +13 -0
- package/skills/voxflow-slice/examples/expected-deck.json +39 -0
- package/skills/voxflow-slice/examples/validate.mjs +46 -0
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* `voxflow slice stage <deck.json>` — boot a local Stage server that watches a
|
|
5
|
+
* deck JSON file and hot-reloads a localhost preview page on every change.
|
|
6
|
+
*
|
|
7
|
+
* No `meta` export — this is reached only via `slice.js` dispatch (first
|
|
8
|
+
* positional arg `stage`), so it stays out of the registry parity test.
|
|
9
|
+
*
|
|
10
|
+
* Phase 1 scope: server + watcher + minimal deck-JSON UI.
|
|
11
|
+
* Phase 1.3+ will add: Remotion `renderStill` thumbnails, exports, publish.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const fs = require('fs');
|
|
15
|
+
const path = require('path');
|
|
16
|
+
|
|
17
|
+
const { startStageServer } = require('../stage-core/server');
|
|
18
|
+
const { watchDeckFile } = require('../stage-core/watcher');
|
|
19
|
+
const { createEventBus } = require('../stage-core/event-bus');
|
|
20
|
+
const { createSnapshotStore } = require('../stage-core/snapshot-store');
|
|
21
|
+
const { createCloudRenderClient } = require('../stage-core/cloud-render');
|
|
22
|
+
const { renderSliceStageHtml } = require('../stage-ui/slice/template');
|
|
23
|
+
const { emit: emitTelemetry } = require('../core/telemetry');
|
|
24
|
+
|
|
25
|
+
// Sourced from the canonical registry at repo root. Previously this list
|
|
26
|
+
// silently fell out of sync (lagged at 6 themes while the rest of the repo
|
|
27
|
+
// had 13) — the JSON import makes that impossible.
|
|
28
|
+
const SLICE_THEME_REGISTRY = require('../../../slice-themes.json');
|
|
29
|
+
const VALID_THEMES = SLICE_THEME_REGISTRY.themes.map((t) => t.id);
|
|
30
|
+
|
|
31
|
+
function loadInitialDeck(absPath) {
|
|
32
|
+
try {
|
|
33
|
+
const raw = fs.readFileSync(absPath, 'utf8');
|
|
34
|
+
return { deck: JSON.parse(raw), error: null };
|
|
35
|
+
} catch (err) {
|
|
36
|
+
return { deck: null, error: err.code === 'ENOENT'
|
|
37
|
+
? `File not found: ${absPath}. Run \`voxflow slice <article>\` first, or pass --output to write a deck.json.`
|
|
38
|
+
: `Could not read deck: ${err.message}` };
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Programmatic entry — used by tests + the CLI handler. Returns a handle that
|
|
44
|
+
* exposes the listening URL and a stop() that tears everything down.
|
|
45
|
+
*
|
|
46
|
+
* @param {object} opts
|
|
47
|
+
* @param {string} opts.deckPath
|
|
48
|
+
* @param {string} [opts.theme] Lock to a single theme (validated)
|
|
49
|
+
* @param {number} [opts.port=5180]
|
|
50
|
+
* @param {number} [opts.maxPortTries=10]
|
|
51
|
+
* @returns {Promise<{url:string, port:number, stop:() => Promise<void>}>}
|
|
52
|
+
*/
|
|
53
|
+
async function startSliceStage(opts) {
|
|
54
|
+
const deckPath = path.resolve(opts.deckPath);
|
|
55
|
+
const theme = opts.theme;
|
|
56
|
+
if (theme && !VALID_THEMES.includes(theme)) {
|
|
57
|
+
throw new Error(
|
|
58
|
+
`Unknown theme "${theme}". Valid: ${VALID_THEMES.join(', ')}`
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const bus = createEventBus();
|
|
63
|
+
let snapshot = loadInitialDeck(deckPath);
|
|
64
|
+
snapshot.sourcePath = deckPath;
|
|
65
|
+
|
|
66
|
+
// Per-deck history. Initial parseable load = baseline snapshot (v0). Every
|
|
67
|
+
// subsequent watcher event with parseable content gets snapshotted; the
|
|
68
|
+
// store dedups by content hash so atomic-write double events stay one
|
|
69
|
+
// version. A `restore` flag rides on the deck event so the UI knows to
|
|
70
|
+
// skip the just-changed diff highlight (otherwise restoring an old deck
|
|
71
|
+
// would flash every card as "changed").
|
|
72
|
+
const snapshotStore = createSnapshotStore(deckPath);
|
|
73
|
+
let pendingRestoreId = null;
|
|
74
|
+
if (snapshot.deck) snapshotStore.snapshotIfChanged(snapshot.deck);
|
|
75
|
+
|
|
76
|
+
const watcher = watchDeckFile({
|
|
77
|
+
filePath: deckPath,
|
|
78
|
+
onChange(deck, err) {
|
|
79
|
+
if (err && !deck) {
|
|
80
|
+
snapshot = { deck: null, error: err.message, sourcePath: deckPath };
|
|
81
|
+
bus.publish({ type: 'error', sourcePath: deckPath, error: err.message, ts: Date.now() });
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
snapshot = { deck, error: null, sourcePath: deckPath };
|
|
85
|
+
snapshotStore.snapshotIfChanged(deck);
|
|
86
|
+
const restoredFromId = pendingRestoreId;
|
|
87
|
+
pendingRestoreId = null;
|
|
88
|
+
bus.publish({
|
|
89
|
+
type: 'deck',
|
|
90
|
+
sourcePath: deckPath,
|
|
91
|
+
deck,
|
|
92
|
+
theme: theme || null,
|
|
93
|
+
ts: Date.now(),
|
|
94
|
+
restoredFrom: restoredFromId,
|
|
95
|
+
});
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// Server-side restore writer: writes the chosen snapshot bytes back to the
|
|
100
|
+
// live deck file, sets `pendingRestoreId` so the next watcher event tags
|
|
101
|
+
// the deck event with `restoredFrom`. Server.js passes through to this.
|
|
102
|
+
const snapshotsBridge = {
|
|
103
|
+
list: snapshotStore.list,
|
|
104
|
+
load: snapshotStore.load,
|
|
105
|
+
getStorePath: snapshotStore.getStorePath,
|
|
106
|
+
restore(id) {
|
|
107
|
+
const deck = snapshotStore.load(id);
|
|
108
|
+
if (!deck) return null;
|
|
109
|
+
pendingRestoreId = id;
|
|
110
|
+
try {
|
|
111
|
+
fs.writeFileSync(deckPath, JSON.stringify(deck, null, 2) + '\n', 'utf8');
|
|
112
|
+
} catch (err) {
|
|
113
|
+
pendingRestoreId = null;
|
|
114
|
+
throw err;
|
|
115
|
+
}
|
|
116
|
+
return { deck };
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const preferredPort = Number.isInteger(opts.port) && opts.port > 0 ? opts.port : 5180;
|
|
121
|
+
const maxPortTries = Number.isInteger(opts.maxPortTries) && opts.maxPortTries > 0
|
|
122
|
+
? opts.maxPortTries : 10;
|
|
123
|
+
|
|
124
|
+
// We have to start the server before knowing the final port (port-conflict
|
|
125
|
+
// retry happens inside startStageServer). So we render the HTML lazily,
|
|
126
|
+
// re-rendering each request with the actual port baked in.
|
|
127
|
+
let actualPort = preferredPort;
|
|
128
|
+
const uiHtml = () => renderSliceStageHtml({ sourcePath: deckPath, port: actualPort });
|
|
129
|
+
|
|
130
|
+
// startStageServer takes a string for uiHtml; we pass a stub that will be
|
|
131
|
+
// replaced once we know the port. Cheap workaround: render once with
|
|
132
|
+
// `preferredPort`, then patch after we get the real port back.
|
|
133
|
+
const initialHtml = renderSliceStageHtml({ sourcePath: deckPath, port: preferredPort });
|
|
134
|
+
|
|
135
|
+
// Cloud render bridge — proxies POST /api/render-mp4 (and friends) to the
|
|
136
|
+
// backend cloud-render endpoint. Token comes from the same CLI cache the
|
|
137
|
+
// user logged into; the page never sees the JWT.
|
|
138
|
+
const cloudRender = opts.cloudRender || createCloudRenderClient();
|
|
139
|
+
|
|
140
|
+
const handle = await startStageServer({
|
|
141
|
+
uiHtml: initialHtml,
|
|
142
|
+
getDeckSnapshot: () => snapshot,
|
|
143
|
+
subscribe: bus.subscribe,
|
|
144
|
+
snapshots: snapshotsBridge,
|
|
145
|
+
cloudRender,
|
|
146
|
+
preferredPort,
|
|
147
|
+
maxPortTries,
|
|
148
|
+
});
|
|
149
|
+
actualPort = handle.port;
|
|
150
|
+
|
|
151
|
+
// Single anonymous launch event — feeds the "should Stage get an MCP?"
|
|
152
|
+
// decision. Fire-and-forget; opt-out via VOXFLOW_TELEMETRY=0 / DO_NOT_TRACK.
|
|
153
|
+
emitTelemetry('cli.slice.stage.launched', { theme: theme || null });
|
|
154
|
+
|
|
155
|
+
// If the port shifted (5180 → 5181 due to conflict), re-render the HTML so
|
|
156
|
+
// the footer hint shows the truthy URL. We swap by replacing the listener
|
|
157
|
+
// on the server: simpler than re-injecting, just update the closure used
|
|
158
|
+
// by startStageServer's GET / branch via a tiny patch.
|
|
159
|
+
if (actualPort !== preferredPort) {
|
|
160
|
+
// The HTML is captured by closure inside startStageServer; rather than
|
|
161
|
+
// wiring a setter, we do nothing — the page only references the port in
|
|
162
|
+
// a footer hint, and the live SSE connection works regardless. This is
|
|
163
|
+
// acceptable for MVP; revisit if/when port mismatch becomes user-visible.
|
|
164
|
+
void uiHtml;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return {
|
|
168
|
+
url: handle.url,
|
|
169
|
+
port: handle.port,
|
|
170
|
+
sourcePath: deckPath,
|
|
171
|
+
async stop() {
|
|
172
|
+
watcher.stop();
|
|
173
|
+
await handle.close();
|
|
174
|
+
},
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// ─── CLI Handler ────────────────────────────────────────────────────────────
|
|
179
|
+
|
|
180
|
+
async function handle(args) {
|
|
181
|
+
const { parseFlag } = require('../core/args');
|
|
182
|
+
|
|
183
|
+
const portFlag = parseFlag(args, '--port');
|
|
184
|
+
const themeFlag = parseFlag(args, '--theme');
|
|
185
|
+
const noOpen = args.includes('--no-open');
|
|
186
|
+
|
|
187
|
+
// First positional arg that isn't a flag value
|
|
188
|
+
const valuedFlags = new Set(['--port', '--theme']);
|
|
189
|
+
let deckPath = null;
|
|
190
|
+
for (let i = 0; i < args.length; i++) {
|
|
191
|
+
if (args[i].startsWith('-')) {
|
|
192
|
+
if (valuedFlags.has(args[i])) i++;
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
deckPath = args[i];
|
|
196
|
+
break;
|
|
197
|
+
}
|
|
198
|
+
if (!deckPath) {
|
|
199
|
+
console.error('Error: provide a deck JSON file');
|
|
200
|
+
console.error('Usage: voxflow slice stage <deck.json> [--port <n>] [--theme <id>] [--no-open]');
|
|
201
|
+
process.exit(1);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
let port;
|
|
205
|
+
if (portFlag) {
|
|
206
|
+
const parsed = Number.parseInt(portFlag, 10);
|
|
207
|
+
if (!Number.isInteger(parsed) || parsed <= 0 || parsed > 65535) {
|
|
208
|
+
console.error(`Error: --port "${portFlag}" is not a valid TCP port`);
|
|
209
|
+
process.exit(1);
|
|
210
|
+
}
|
|
211
|
+
port = parsed;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
let stage;
|
|
215
|
+
try {
|
|
216
|
+
stage = await startSliceStage({
|
|
217
|
+
deckPath,
|
|
218
|
+
theme: themeFlag || undefined,
|
|
219
|
+
port,
|
|
220
|
+
});
|
|
221
|
+
} catch (err) {
|
|
222
|
+
console.error(`\x1b[31mError:\x1b[0m ${err.message}`);
|
|
223
|
+
process.exit(1);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
console.log('');
|
|
227
|
+
console.log('\x1b[36m=== VoxFlow Stage — Slice ===\x1b[0m');
|
|
228
|
+
console.log(`Watching: ${stage.sourcePath}`);
|
|
229
|
+
console.log(`Preview: ${stage.url}`);
|
|
230
|
+
if (themeFlag) console.log(`Theme: ${themeFlag}`);
|
|
231
|
+
console.log('');
|
|
232
|
+
console.log('Edit the deck JSON to hot-reload. Ctrl+C to stop.');
|
|
233
|
+
console.log('');
|
|
234
|
+
|
|
235
|
+
if (!noOpen && !process.env.CI) {
|
|
236
|
+
try {
|
|
237
|
+
const open = (await import('open')).default;
|
|
238
|
+
await open(stage.url);
|
|
239
|
+
} catch {
|
|
240
|
+
// Non-fatal — user can open the URL manually.
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Keep the process alive until SIGINT.
|
|
245
|
+
let stopping = false;
|
|
246
|
+
const stopAndExit = async (signal) => {
|
|
247
|
+
if (stopping) return;
|
|
248
|
+
stopping = true;
|
|
249
|
+
console.log(`\n\x1b[33m${signal} received — stopping Stage…\x1b[0m`);
|
|
250
|
+
try { await stage.stop(); } catch { /* best-effort */ }
|
|
251
|
+
process.exit(0);
|
|
252
|
+
};
|
|
253
|
+
process.on('SIGINT', () => stopAndExit('SIGINT'));
|
|
254
|
+
process.on('SIGTERM', () => stopAndExit('SIGTERM'));
|
|
255
|
+
|
|
256
|
+
// Park.
|
|
257
|
+
return new Promise(() => { /* never resolves; SIGINT exits */ });
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
module.exports = {
|
|
261
|
+
handle,
|
|
262
|
+
startSliceStage,
|
|
263
|
+
VALID_THEMES,
|
|
264
|
+
};
|
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VoxFlow CLI — Slice command
|
|
3
|
+
*
|
|
4
|
+
* Slice an article (markdown / plain text) into a 5–8 card narrated deck JSON
|
|
5
|
+
* via /api/slice/deck (renamed from /api/paper-slide/slice in #3307 Deploy 2).
|
|
6
|
+
* Charges `slice-deck` (200 quota) under the new mount; legacy `paper-slide-slice`
|
|
7
|
+
* op still exists for the soft-deprecated `/api/paper-slide/slice` path that
|
|
8
|
+
* stays mounted for ~30 days.
|
|
9
|
+
*
|
|
10
|
+
* The cloud renderer that turns this deck into a 1080×1920 mp4 lives in the
|
|
11
|
+
* web app (voxflow.studio/apps/slice) — this command only produces the
|
|
12
|
+
* structured deck JSON, suitable for piping into custom pipelines or for
|
|
13
|
+
* inspection / iteration on the AI's slicing quality.
|
|
14
|
+
*
|
|
15
|
+
* Thirty-three themes supported (paper-slide / editorial-mag / bold-poster /
|
|
16
|
+
* notion-card / brutalist / glass-dark / broadsheet / blueprint /
|
|
17
|
+
* daisy-pastel / showa-catalog / photo-feature / atmospheric / art-mag /
|
|
18
|
+
* tome-noir / flomo-mute / substack-drop / ink-scroll / podcast-clip /
|
|
19
|
+
* ink-wash / morandi-calm / douyin-data / highlighter-note / memphis-design /
|
|
20
|
+
* bauhaus-grid / riso-print / chrome-y2k / botanical-press / art-nouveau /
|
|
21
|
+
* tabloid-print / arcade-pixel / hand-lettered / stamp-collector /
|
|
22
|
+
* tropical-postcard) — must match backend's VALID_THEMES set
|
|
23
|
+
* (backend/services/paper-slide/deck-validator.js) or the API rejects with
|
|
24
|
+
* `invalid_theme`. Both lists derive from /slice-themes.json so they
|
|
25
|
+
* auto-sync.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
const fs = require('fs');
|
|
29
|
+
const path = require('path');
|
|
30
|
+
const { request, throwApiError, throwNetworkError, ApiError } = require('../core/http');
|
|
31
|
+
|
|
32
|
+
// Sourced from the canonical registry at repo root. The CLI gates user
|
|
33
|
+
// input against this list, so adding a theme to slice-themes.json is enough
|
|
34
|
+
// for `voxflow slice --theme xxx` to accept it.
|
|
35
|
+
const SLICE_THEME_REGISTRY = require('../../../slice-themes.json');
|
|
36
|
+
const VALID_THEMES = SLICE_THEME_REGISTRY.themes.map((t) => t.id);
|
|
37
|
+
const DEFAULT_THEME = SLICE_THEME_REGISTRY.default;
|
|
38
|
+
// Theme id -> array of valid variant ids (palette swaps within a theme).
|
|
39
|
+
// Mirrors backend/services/paper-slide/deck-validator.js#THEME_VARIANTS so
|
|
40
|
+
// the CLI rejects unknown variants client-side before paying a network round
|
|
41
|
+
// trip. Themes without variants get an empty array — passing --variant for
|
|
42
|
+
// them is a hard error too (theme typo'd or feature confusion).
|
|
43
|
+
const THEME_VARIANTS = Object.fromEntries(
|
|
44
|
+
SLICE_THEME_REGISTRY.themes.map((t) => [
|
|
45
|
+
t.id,
|
|
46
|
+
(t.variants || []).map((v) => v.id),
|
|
47
|
+
])
|
|
48
|
+
);
|
|
49
|
+
const MIN_ARTICLE_CHARS = 80;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Slice an article via the backend.
|
|
53
|
+
*
|
|
54
|
+
* @param {object} opts
|
|
55
|
+
* @param {string} opts.token JWT token
|
|
56
|
+
* @param {string} opts.api API base URL
|
|
57
|
+
* @param {string} opts.article Article text (≥ 80 chars)
|
|
58
|
+
* @param {string} [opts.theme] Slice theme id (default: paper-slide)
|
|
59
|
+
* @param {string} [opts.output] Output JSON path (default: stdout)
|
|
60
|
+
* @param {boolean} [opts.json] Pure-JSON mode (suppress logs / spinners)
|
|
61
|
+
* @returns {Promise<{deck: object, usage: object, quotaUsed: number, outputPath: string|null}>}
|
|
62
|
+
*/
|
|
63
|
+
async function slice(opts) {
|
|
64
|
+
const article = opts.article;
|
|
65
|
+
if (typeof article !== 'string' || article.trim().length < MIN_ARTICLE_CHARS) {
|
|
66
|
+
throw new Error(
|
|
67
|
+
`Article must be a non-empty string of at least ${MIN_ARTICLE_CHARS} characters ` +
|
|
68
|
+
`(got ${article ? article.trim().length : 0}).`
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const theme = opts.theme || DEFAULT_THEME;
|
|
73
|
+
if (!VALID_THEMES.includes(theme)) {
|
|
74
|
+
throw new Error(
|
|
75
|
+
`Unknown theme "${theme}". Valid: ${VALID_THEMES.join(', ')}`
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const variantId = opts.variant;
|
|
80
|
+
if (variantId != null) {
|
|
81
|
+
const validVariants = THEME_VARIANTS[theme] || [];
|
|
82
|
+
if (validVariants.length === 0) {
|
|
83
|
+
throw new Error(
|
|
84
|
+
`--variant set but theme "${theme}" declares no variants. ` +
|
|
85
|
+
`Themes with variants: ${
|
|
86
|
+
Object.entries(THEME_VARIANTS)
|
|
87
|
+
.filter(([, ids]) => ids.length > 0)
|
|
88
|
+
.map(([id]) => id)
|
|
89
|
+
.join(', ') || '(none yet)'
|
|
90
|
+
}`
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
if (!validVariants.includes(variantId)) {
|
|
94
|
+
throw new Error(
|
|
95
|
+
`Unknown variant "${variantId}" for theme "${theme}". Valid: ${validVariants.join(', ')}`
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const api = opts.api;
|
|
101
|
+
const token = opts.token;
|
|
102
|
+
const quiet = opts.json === true;
|
|
103
|
+
|
|
104
|
+
if (!quiet) {
|
|
105
|
+
console.log('\n=== VoxFlow Slice ===');
|
|
106
|
+
console.log(`Theme: ${theme}${variantId ? ` (variant: ${variantId})` : ''}`);
|
|
107
|
+
console.log(`Article: ${article.trim().length} chars`);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
let status, data;
|
|
111
|
+
try {
|
|
112
|
+
({ status, data } = await request(`${api}/api/slice/deck`, {
|
|
113
|
+
method: 'POST',
|
|
114
|
+
headers: {
|
|
115
|
+
'Content-Type': 'application/json',
|
|
116
|
+
'Authorization': `Bearer ${token}`,
|
|
117
|
+
},
|
|
118
|
+
// Slice runs a structured-output Claude call producing 5–8
|
|
119
|
+
// narrated cards. P50 is 10–20s but P95 spikes past the 60s
|
|
120
|
+
// default request timeout under upstream load (verified
|
|
121
|
+
// against api.voxflow.studio/api/slice/deck with the
|
|
122
|
+
// 1.11.1 client). Match the 180s convention used by every
|
|
123
|
+
// other LLM-bound CLI command (image / picstory / explain /
|
|
124
|
+
// present / story).
|
|
125
|
+
timeoutMs: 180_000,
|
|
126
|
+
}, {
|
|
127
|
+
article: article.trim(),
|
|
128
|
+
theme,
|
|
129
|
+
...(variantId ? { variantId } : {}),
|
|
130
|
+
}));
|
|
131
|
+
} catch (err) {
|
|
132
|
+
throwNetworkError(err, api);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (status !== 200 || !data || data.code !== 'success') {
|
|
136
|
+
throwApiError(status, data, 'slice');
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const deck = data.data?.deck;
|
|
140
|
+
const usage = data.data?.usage || {};
|
|
141
|
+
if (!deck) {
|
|
142
|
+
throw new Error('Slice response missing `data.deck`');
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Authoritative cost from quota response, fallback to catalog.
|
|
146
|
+
const PAPER_SLIDE_SLICE_CATALOG_COST = 200;
|
|
147
|
+
const quotaUsed = typeof data.quota?.costDelta === 'number'
|
|
148
|
+
? data.quota.costDelta
|
|
149
|
+
: PAPER_SLIDE_SLICE_CATALOG_COST;
|
|
150
|
+
|
|
151
|
+
const payload = JSON.stringify({ deck, usage, theme }, null, 2);
|
|
152
|
+
let outputPath = null;
|
|
153
|
+
if (opts.output) {
|
|
154
|
+
outputPath = path.resolve(opts.output);
|
|
155
|
+
const outDir = path.dirname(outputPath);
|
|
156
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
157
|
+
fs.writeFileSync(outputPath, payload + '\n', 'utf8');
|
|
158
|
+
if (!quiet) {
|
|
159
|
+
console.log(`\nWrote ${outputPath} (${(payload.length / 1024).toFixed(1)} KB)`);
|
|
160
|
+
}
|
|
161
|
+
} else if (quiet) {
|
|
162
|
+
process.stdout.write(payload + '\n');
|
|
163
|
+
} else {
|
|
164
|
+
console.log('\n--- Deck JSON ---');
|
|
165
|
+
console.log(payload);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (!quiet) {
|
|
169
|
+
const cardCount = Array.isArray(deck.cards) ? deck.cards.length : 0;
|
|
170
|
+
console.log('\n=== Done ===');
|
|
171
|
+
console.log(`Cards: ${cardCount}`);
|
|
172
|
+
console.log(`Quota: ${quotaUsed} used, ${data.quota?.remaining ?? '?'} remaining`);
|
|
173
|
+
// Two next-step paths: (1) iterate locally with stage if an output
|
|
174
|
+
// file exists (no quota cost, hot-reload); (2) ship via the web
|
|
175
|
+
// renderer. Only print the stage hint when we actually have a path
|
|
176
|
+
// to feed it — stdout-only runs have nowhere for stage to attach.
|
|
177
|
+
if (outputPath) {
|
|
178
|
+
console.log(`\nPreview locally: voxflow slice stage ${outputPath}`);
|
|
179
|
+
console.log(`Or render mp4: https://voxflow.studio/apps/slice (paste/import the JSON)`);
|
|
180
|
+
} else {
|
|
181
|
+
console.log(`\nNext: render the deck at https://voxflow.studio/apps/slice (paste the JSON or import).`);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return { deck, usage, quotaUsed, outputPath };
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// ─── CLI Handler ────────────────────────────────────────────────────────────
|
|
189
|
+
|
|
190
|
+
async function handle(args) {
|
|
191
|
+
// Sub-action dispatch: `voxflow slice stage <deck.json>` boots the local
|
|
192
|
+
// preview/iteration server instead of generating a deck. Kept as a
|
|
193
|
+
// first-positional-arg check so the legacy `voxflow slice <article>`
|
|
194
|
+
// signature is unchanged. slice-stage.js has no `meta` export of its
|
|
195
|
+
// own — it would otherwise need to be in _registry.js (parity test).
|
|
196
|
+
if (args.length > 0 && args[0] === 'stage') {
|
|
197
|
+
const sliceStage = require('./slice-stage');
|
|
198
|
+
return sliceStage.handle(args.slice(1));
|
|
199
|
+
}
|
|
200
|
+
// `voxflow slice render <deck.json>` — offline local Remotion render
|
|
201
|
+
// (Phase 0: silent video). Same first-positional dispatch pattern as
|
|
202
|
+
// `stage`; the subcommand file has no `meta` export so it stays out of
|
|
203
|
+
// _registry.js (parity test).
|
|
204
|
+
if (args.length > 0 && args[0] === 'render') {
|
|
205
|
+
const sliceRender = require('./slice-render');
|
|
206
|
+
return sliceRender.handle(args.slice(1));
|
|
207
|
+
}
|
|
208
|
+
// `voxflow slice preview <deck.json>` — localhost HTML page with a
|
|
209
|
+
// midpoint-frame still per card (no mp4, fraction of the render cost).
|
|
210
|
+
if (args.length > 0 && args[0] === 'preview') {
|
|
211
|
+
const slicePreview = require('./slice-preview');
|
|
212
|
+
return slicePreview.handle(args.slice(1));
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const { parseFlag, runWithRetry } = require('../core/args');
|
|
216
|
+
const { getToken, getTokenInfo } = require('../core/auth');
|
|
217
|
+
const { API_BASE } = require('../core/config');
|
|
218
|
+
|
|
219
|
+
const api = parseFlag(args, '--api') || API_BASE;
|
|
220
|
+
const explicitToken = parseFlag(args, '--token');
|
|
221
|
+
|
|
222
|
+
const inlineText = parseFlag(args, '--text');
|
|
223
|
+
const theme = parseFlag(args, '--theme');
|
|
224
|
+
const variant = parseFlag(args, '--variant');
|
|
225
|
+
const output = parseFlag(args, '--output', '-o');
|
|
226
|
+
const jsonMode = args.includes('--json');
|
|
227
|
+
|
|
228
|
+
let article;
|
|
229
|
+
if (inlineText) {
|
|
230
|
+
article = inlineText;
|
|
231
|
+
} else {
|
|
232
|
+
// First positional arg that isn't a flag value
|
|
233
|
+
const valuedFlags = new Set([
|
|
234
|
+
'--text', '--theme', '--variant', '--output', '-o',
|
|
235
|
+
'--token', '--api',
|
|
236
|
+
]);
|
|
237
|
+
let filePath;
|
|
238
|
+
for (let i = 0; i < args.length; i++) {
|
|
239
|
+
if (args[i].startsWith('-')) {
|
|
240
|
+
if (valuedFlags.has(args[i])) i++;
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
filePath = args[i];
|
|
244
|
+
break;
|
|
245
|
+
}
|
|
246
|
+
if (!filePath) {
|
|
247
|
+
console.error('Error: provide an article file or --text "..."');
|
|
248
|
+
console.error('Usage: voxflow slice <file> [--theme <id>] [-o <out.json>]');
|
|
249
|
+
process.exit(1);
|
|
250
|
+
}
|
|
251
|
+
try {
|
|
252
|
+
article = fs.readFileSync(path.resolve(filePath), 'utf8');
|
|
253
|
+
} catch (err) {
|
|
254
|
+
console.error(`Error: cannot read file "${filePath}": ${err.message}`);
|
|
255
|
+
process.exit(1);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (theme && !VALID_THEMES.includes(theme)) {
|
|
260
|
+
console.error(`Error: --theme "${theme}" not in: ${VALID_THEMES.join(', ')}`);
|
|
261
|
+
process.exit(1);
|
|
262
|
+
}
|
|
263
|
+
if (variant) {
|
|
264
|
+
const targetTheme = theme || DEFAULT_THEME;
|
|
265
|
+
const validVariants = THEME_VARIANTS[targetTheme] || [];
|
|
266
|
+
if (validVariants.length === 0) {
|
|
267
|
+
console.error(
|
|
268
|
+
`Error: --variant set but theme "${targetTheme}" declares no variants.`
|
|
269
|
+
);
|
|
270
|
+
const themesWithVariants = Object.entries(THEME_VARIANTS)
|
|
271
|
+
.filter(([, ids]) => ids.length > 0)
|
|
272
|
+
.map(([id]) => id);
|
|
273
|
+
if (themesWithVariants.length > 0) {
|
|
274
|
+
console.error(`Themes with variants: ${themesWithVariants.join(', ')}`);
|
|
275
|
+
}
|
|
276
|
+
process.exit(1);
|
|
277
|
+
}
|
|
278
|
+
if (!validVariants.includes(variant)) {
|
|
279
|
+
console.error(
|
|
280
|
+
`Error: --variant "${variant}" not in ${targetTheme}'s variants: ${validVariants.join(', ')}`
|
|
281
|
+
);
|
|
282
|
+
process.exit(1);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
let token;
|
|
287
|
+
if (explicitToken) {
|
|
288
|
+
token = explicitToken;
|
|
289
|
+
} else {
|
|
290
|
+
token = await getToken({ api });
|
|
291
|
+
if (!jsonMode) {
|
|
292
|
+
const info = getTokenInfo();
|
|
293
|
+
if (info) {
|
|
294
|
+
console.log(`\x1b[32mLogged in as ${info.email}\x1b[0m`);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const opts = {
|
|
300
|
+
token, api, article,
|
|
301
|
+
theme: theme || undefined,
|
|
302
|
+
variant: variant || undefined,
|
|
303
|
+
output: output || undefined,
|
|
304
|
+
json: jsonMode,
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
await runWithRetry(slice, opts, api, explicitToken);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const meta = {
|
|
311
|
+
slice: {
|
|
312
|
+
usage: '<file> [--theme <id>] [--variant <id>] [--text <s>] [-o <file>] [--json]',
|
|
313
|
+
description: 'Slice an article into a 5–8 card deck JSON (Slice / paper-slide AI; 200 quota)',
|
|
314
|
+
options: [
|
|
315
|
+
'<file> Article file (markdown / plain text, ≥ 80 chars)',
|
|
316
|
+
'--text <string> Pass article inline instead of reading a file',
|
|
317
|
+
`--theme <id> One of: ${VALID_THEMES.join(', ')} (default: ${DEFAULT_THEME})`,
|
|
318
|
+
'--variant <id> Palette swap within the theme (e.g. editorial-mag → cream / sepia / cold-grey). Themes without variants reject this flag.',
|
|
319
|
+
'-o, --output <path> Write deck JSON to file (default: print to stdout/console)',
|
|
320
|
+
'--json Pure JSON mode — suppress all logs, emit only the deck JSON to stdout (for piping)',
|
|
321
|
+
],
|
|
322
|
+
examples: [
|
|
323
|
+
'voxflow slice article.md',
|
|
324
|
+
'voxflow slice article.md --theme editorial-mag -o deck.json',
|
|
325
|
+
'voxflow slice article.md --theme editorial-mag --variant sepia -o deck.json',
|
|
326
|
+
'voxflow slice --text "..." --theme bold-poster --json | jq .deck',
|
|
327
|
+
'voxflow slice stage deck.json # local preview, hot-reload',
|
|
328
|
+
'voxflow slice stage deck.json --port 5180 --no-open',
|
|
329
|
+
'voxflow slice render deck.json --output out.mp4 # offline render (no quota, no cloud)',
|
|
330
|
+
],
|
|
331
|
+
},
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
module.exports = {
|
|
335
|
+
slice,
|
|
336
|
+
handle,
|
|
337
|
+
meta,
|
|
338
|
+
VALID_THEMES,
|
|
339
|
+
THEME_VARIANTS,
|
|
340
|
+
DEFAULT_THEME,
|
|
341
|
+
MIN_ARTICLE_CHARS,
|
|
342
|
+
ApiError,
|
|
343
|
+
};
|