voxflow 1.15.0 → 1.15.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -0
- 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-render.js +282 -0
- package/lib/commands/slice-stage.js +264 -0
- package/lib/commands/slice.js +346 -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,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VoxFlow CLI — Timeline audio builder
|
|
3
|
+
*
|
|
4
|
+
* Places audio segments at precise millisecond positions on a timeline,
|
|
5
|
+
* filling gaps with silence. Used for dubbing where each caption must
|
|
6
|
+
* start at its exact SRT timestamp.
|
|
7
|
+
*
|
|
8
|
+
* Audio format: 24kHz, 16-bit, mono PCM (matches TTS API output).
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const { buildWav } = require('./audio');
|
|
12
|
+
|
|
13
|
+
const SAMPLE_RATE = 24000;
|
|
14
|
+
const BYTES_PER_SAMPLE = 2; // 16-bit
|
|
15
|
+
const BYTES_PER_MS = (SAMPLE_RATE * BYTES_PER_SAMPLE) / 1000; // 48 bytes/ms
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Convert milliseconds to PCM byte offset.
|
|
19
|
+
* @param {number} ms - Time in milliseconds
|
|
20
|
+
* @returns {number} Byte offset (even number, aligned to sample boundary)
|
|
21
|
+
*/
|
|
22
|
+
function msToBytes(ms) {
|
|
23
|
+
const bytes = Math.round(ms * BYTES_PER_MS);
|
|
24
|
+
// Ensure alignment to 2-byte (sample) boundary
|
|
25
|
+
return bytes - (bytes % BYTES_PER_SAMPLE);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Build a single PCM buffer with audio segments placed at their timeline positions.
|
|
30
|
+
*
|
|
31
|
+
* @param {Array<{startMs: number, endMs: number, audioBuffer: Buffer}>} segments
|
|
32
|
+
* Audio segments with their target start/end times.
|
|
33
|
+
* audioBuffer is raw PCM data (24kHz, 16-bit, mono).
|
|
34
|
+
* @param {number} [totalDurationMs] - Total timeline duration in ms.
|
|
35
|
+
* If omitted, uses the endMs of the last segment.
|
|
36
|
+
* @returns {{ pcm: Buffer, durationMs: number }}
|
|
37
|
+
*/
|
|
38
|
+
function buildTimelinePcm(segments, totalDurationMs) {
|
|
39
|
+
if (!segments || segments.length === 0) {
|
|
40
|
+
return { pcm: Buffer.alloc(0), durationMs: 0 };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Determine total length
|
|
44
|
+
const lastEndMs = Math.max(...segments.map((s) => s.endMs));
|
|
45
|
+
const durationMs = totalDurationMs || lastEndMs;
|
|
46
|
+
const totalBytes = msToBytes(durationMs);
|
|
47
|
+
|
|
48
|
+
// Allocate zero-filled buffer (silence by default)
|
|
49
|
+
const pcm = Buffer.alloc(totalBytes, 0);
|
|
50
|
+
|
|
51
|
+
// Place each segment at its start position
|
|
52
|
+
for (const seg of segments) {
|
|
53
|
+
const startByte = msToBytes(seg.startMs);
|
|
54
|
+
const slotBytes = msToBytes(seg.endMs) - startByte;
|
|
55
|
+
|
|
56
|
+
// Determine how many bytes to copy (don't exceed slot or total buffer)
|
|
57
|
+
const copyLen = Math.min(seg.audioBuffer.length, slotBytes, totalBytes - startByte);
|
|
58
|
+
if (copyLen > 0 && startByte < totalBytes) {
|
|
59
|
+
seg.audioBuffer.copy(pcm, startByte, 0, copyLen);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return { pcm, durationMs };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Build a complete WAV file with audio segments placed at timeline positions.
|
|
68
|
+
*
|
|
69
|
+
* @param {Array<{startMs: number, endMs: number, audioBuffer: Buffer}>} segments
|
|
70
|
+
* @param {number} [totalDurationMs]
|
|
71
|
+
* @returns {{ wav: Buffer, duration: number }} wav buffer and duration in seconds
|
|
72
|
+
*/
|
|
73
|
+
function buildTimelineAudio(segments, totalDurationMs) {
|
|
74
|
+
const { pcm, durationMs } = buildTimelinePcm(segments, totalDurationMs);
|
|
75
|
+
|
|
76
|
+
if (pcm.length === 0) {
|
|
77
|
+
return { wav: Buffer.alloc(0), duration: 0 };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Use buildWav with a single PCM buffer and zero silence gap
|
|
81
|
+
const { wav } = buildWav([pcm], 0);
|
|
82
|
+
return { wav, duration: durationMs / 1000 };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
module.exports = {
|
|
86
|
+
buildTimelinePcm,
|
|
87
|
+
buildTimelineAudio,
|
|
88
|
+
msToBytes,
|
|
89
|
+
SAMPLE_RATE,
|
|
90
|
+
BYTES_PER_SAMPLE,
|
|
91
|
+
BYTES_PER_MS,
|
|
92
|
+
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VoxFlow CLI — Shared TTS synthesis function
|
|
3
|
+
*
|
|
4
|
+
* Consolidates the duplicated TTS API call pattern from narrate, dub, story,
|
|
5
|
+
* and podcast commands into a single reusable function.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const { request, throwApiError, throwNetworkError } = require('./http');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Synthesize text to audio via the TTS API.
|
|
12
|
+
*
|
|
13
|
+
* @param {object} opts
|
|
14
|
+
* @param {string} opts.apiBase - API base URL
|
|
15
|
+
* @param {string} opts.token - JWT auth token
|
|
16
|
+
* @param {string} opts.text - Text to synthesize
|
|
17
|
+
* @param {string} opts.voiceId - TTS voice ID
|
|
18
|
+
* @param {number} [opts.speed=1.0] - TTS speed (0.5-2.0)
|
|
19
|
+
* @param {number} [opts.volume=1.0] - TTS volume (0-2.0)
|
|
20
|
+
* @param {number} [opts.pitch] - TTS pitch adjustment
|
|
21
|
+
* @param {string} [opts.format='pcm'] - Audio format: pcm | wav | mp3
|
|
22
|
+
* @param {number} opts.index - Current segment index (0-based)
|
|
23
|
+
* @param {number} opts.total - Total number of segments
|
|
24
|
+
* @param {string} [opts.label] - Optional label for progress output (e.g. speaker name)
|
|
25
|
+
* @returns {Promise<{ audio: Buffer, quota: object }>}
|
|
26
|
+
*/
|
|
27
|
+
async function synthesizeTTS(opts) {
|
|
28
|
+
const {
|
|
29
|
+
apiBase, token, text, voiceId,
|
|
30
|
+
speed = 1.0, volume = 1.0, pitch,
|
|
31
|
+
format = 'pcm',
|
|
32
|
+
index, total, label,
|
|
33
|
+
} = opts;
|
|
34
|
+
|
|
35
|
+
// Progress output
|
|
36
|
+
const labelSuffix = label ? ` ${label}` : '';
|
|
37
|
+
process.stdout.write(` TTS [${index + 1}/${total}]${labelSuffix}...`);
|
|
38
|
+
|
|
39
|
+
// Build request body
|
|
40
|
+
const body = { text, voiceId, speed, volume, format };
|
|
41
|
+
if (pitch != null && pitch !== 0) body.pitch = pitch;
|
|
42
|
+
|
|
43
|
+
let status, data, headers;
|
|
44
|
+
try {
|
|
45
|
+
// 180s — server-side TTS synthesis for a long paragraph can take ~60-90s
|
|
46
|
+
// on first hit (cold edge) and the previous 60s default was timing out
|
|
47
|
+
// the very first segment of `voxflow story` (issue #2916).
|
|
48
|
+
({ status, data, headers } = await request(`${apiBase}/api/tts/synthesize`, {
|
|
49
|
+
method: 'POST',
|
|
50
|
+
headers: {
|
|
51
|
+
'Content-Type': 'application/json',
|
|
52
|
+
'Authorization': `Bearer ${token}`,
|
|
53
|
+
},
|
|
54
|
+
timeoutMs: 180_000,
|
|
55
|
+
}, body));
|
|
56
|
+
} catch (err) {
|
|
57
|
+
console.log(' FAIL');
|
|
58
|
+
throwNetworkError(err, apiBase);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (status !== 200 || data.code !== 'success') {
|
|
62
|
+
console.log(' FAIL');
|
|
63
|
+
throwApiError(status, data, `TTS segment ${index + 1}`, headers);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const audio = Buffer.from(data.audio, 'base64');
|
|
67
|
+
return { audio, quota: data.quota };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
module.exports = { synthesizeTTS };
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VoxFlow CLI — update-notifier
|
|
3
|
+
*
|
|
4
|
+
* Background check against the npm registry; prints a one-line stderr hint at
|
|
5
|
+
* process exit when a newer version is available. Zero runtime deps.
|
|
6
|
+
*
|
|
7
|
+
* Design:
|
|
8
|
+
* - Cached at `<configDir>/update-check.json`, refreshed at most once / 24h.
|
|
9
|
+
* - Fire-and-forget: never blocks the CLI command. Errors are swallowed.
|
|
10
|
+
* - Skipped when stderr is not a TTY, in CI, or when `VOXFLOW_NO_UPDATE_CHECK=1`.
|
|
11
|
+
* - The hint is printed at `process.on('exit')`, so it never interleaves with
|
|
12
|
+
* the command's own output (which finishes synchronously before exit).
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const fs = require('fs');
|
|
16
|
+
const path = require('path');
|
|
17
|
+
const https = require('https');
|
|
18
|
+
|
|
19
|
+
const { getConfigDir } = require('./config');
|
|
20
|
+
|
|
21
|
+
const CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000; // 24h
|
|
22
|
+
const REGISTRY_URL = 'https://registry.npmjs.org/voxflow/latest';
|
|
23
|
+
const NETWORK_TIMEOUT_MS = 2_000;
|
|
24
|
+
|
|
25
|
+
function cachePath() {
|
|
26
|
+
return path.join(getConfigDir(), 'update-check.json');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function readCache() {
|
|
30
|
+
try {
|
|
31
|
+
return JSON.parse(fs.readFileSync(cachePath(), 'utf8'));
|
|
32
|
+
} catch {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function writeCache(payload) {
|
|
38
|
+
try {
|
|
39
|
+
fs.mkdirSync(getConfigDir(), { recursive: true });
|
|
40
|
+
fs.writeFileSync(cachePath(), JSON.stringify(payload), 'utf8');
|
|
41
|
+
} catch {
|
|
42
|
+
// Cache is best-effort; ignore failures.
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Detect whether an AI agent is reading this CLI's stderr.
|
|
48
|
+
* We use a single explicit signal: `CLAUDECODE=1`. Other agents that want the
|
|
49
|
+
* structured hint can set `VOXFLOW_AGENT=1`. We intentionally do NOT treat
|
|
50
|
+
* generic `CI=true` as "agent reading" — many CI users still benefit from a
|
|
51
|
+
* fail-silent CLI, not a banner on every job.
|
|
52
|
+
*/
|
|
53
|
+
function isAgentChannel() {
|
|
54
|
+
return process.env.CLAUDECODE === '1' || process.env.VOXFLOW_AGENT === '1';
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Decide whether the update check should run at all. Agents skip the human
|
|
59
|
+
* `▲` banner but still get the structured `<voxflow-hint>` tag; the test
|
|
60
|
+
* suite + NO_UPDATE_NOTIFIER opt-out skip everything.
|
|
61
|
+
*/
|
|
62
|
+
function shouldSkip() {
|
|
63
|
+
if (process.env.VOXFLOW_NO_UPDATE_CHECK === '1') return true;
|
|
64
|
+
if (process.env.NO_UPDATE_NOTIFIER === '1') return true; // industry standard (npm, update-notifier)
|
|
65
|
+
if (process.env.NODE_ENV === 'test') return true;
|
|
66
|
+
// Skip when neither a human TTY nor an agent is reading stderr.
|
|
67
|
+
if (!process.stderr.isTTY && !isAgentChannel()) return true;
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Compare semver-ish versions. Returns positive if a > b, 0 if equal, negative
|
|
73
|
+
* if a < b. Ignores pre-release suffixes (good enough for "is there an update").
|
|
74
|
+
*/
|
|
75
|
+
function cmpVersion(a, b) {
|
|
76
|
+
const parse = (v) => String(v).split('-')[0].split('.').map((n) => parseInt(n, 10) || 0);
|
|
77
|
+
const pa = parse(a);
|
|
78
|
+
const pb = parse(b);
|
|
79
|
+
for (let i = 0; i < 3; i++) {
|
|
80
|
+
const diff = (pa[i] || 0) - (pb[i] || 0);
|
|
81
|
+
if (diff !== 0) return diff;
|
|
82
|
+
}
|
|
83
|
+
return 0;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function fetchLatest() {
|
|
87
|
+
return new Promise((resolve, reject) => {
|
|
88
|
+
const req = https.get(REGISTRY_URL, { timeout: NETWORK_TIMEOUT_MS }, (res) => {
|
|
89
|
+
if (res.statusCode !== 200) {
|
|
90
|
+
res.resume();
|
|
91
|
+
reject(new Error(`registry HTTP ${res.statusCode}`));
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
let body = '';
|
|
95
|
+
res.setEncoding('utf8');
|
|
96
|
+
res.on('data', (chunk) => { body += chunk; });
|
|
97
|
+
res.on('end', () => {
|
|
98
|
+
try {
|
|
99
|
+
const json = JSON.parse(body);
|
|
100
|
+
if (typeof json.version === 'string') resolve(json.version);
|
|
101
|
+
else reject(new Error('no version field'));
|
|
102
|
+
} catch (err) {
|
|
103
|
+
reject(err);
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
req.on('error', reject);
|
|
108
|
+
req.on('timeout', () => { req.destroy(new Error('timeout')); });
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Kick off an update check. Non-blocking. Schedules a stderr hint at exit
|
|
114
|
+
* if a newer version is available (either from cache or a fresh fetch).
|
|
115
|
+
*
|
|
116
|
+
* @param {string} currentVersion - from package.json
|
|
117
|
+
*/
|
|
118
|
+
function scheduleUpdateCheck(currentVersion) {
|
|
119
|
+
if (shouldSkip() || !currentVersion) return;
|
|
120
|
+
|
|
121
|
+
const cached = readCache();
|
|
122
|
+
const now = Date.now();
|
|
123
|
+
const fresh = cached && typeof cached.checkedAt === 'number' && (now - cached.checkedAt) < CHECK_INTERVAL_MS;
|
|
124
|
+
|
|
125
|
+
// If cache is fresh and reports a newer version, queue the hint now.
|
|
126
|
+
if (fresh && cached.latest && cmpVersion(cached.latest, currentVersion) > 0) {
|
|
127
|
+
queueHint(currentVersion, cached.latest);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Otherwise, fetch in the background. Don't await — the process can exit
|
|
132
|
+
// earlier; that's fine, we'll just check again on the next run.
|
|
133
|
+
fetchLatest()
|
|
134
|
+
.then((latest) => {
|
|
135
|
+
writeCache({ latest, checkedAt: now });
|
|
136
|
+
if (cmpVersion(latest, currentVersion) > 0) queueHint(currentVersion, latest);
|
|
137
|
+
})
|
|
138
|
+
.catch(() => { /* swallow — update check is best-effort */ });
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
let hintQueued = false;
|
|
142
|
+
/**
|
|
143
|
+
* Render the hint line(s) for a (current, latest) version pair.
|
|
144
|
+
* Returns the exact stderr string (or empty string when nothing to emit).
|
|
145
|
+
*
|
|
146
|
+
* The agent line is a stable, regex-parseable contract — SKILL.md grep-rules
|
|
147
|
+
* depend on this format. Don't reorder attrs or change tag name without
|
|
148
|
+
* updating cli/skills/hub/SKILL.md in the same commit.
|
|
149
|
+
*/
|
|
150
|
+
function renderHintLines(current, latest, { agent, human }) {
|
|
151
|
+
const lines = [];
|
|
152
|
+
if (agent) {
|
|
153
|
+
// Mirrors the shape of Anthropic's <claude-code-hint v="1" type="plugin" .../>
|
|
154
|
+
// (the only structured agent-targeted hint protocol in the wild today).
|
|
155
|
+
// type="cli-outdated" is our own, forward-compatible; Anthropic may
|
|
156
|
+
// someday standardize a variant we can swap in.
|
|
157
|
+
lines.push(
|
|
158
|
+
`<voxflow-hint v="1" type="cli-outdated" current="${current}" latest="${latest}" action="voxflow upgrade -y" />`
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
if (human) {
|
|
162
|
+
lines.push(
|
|
163
|
+
`\x1b[2m▲ voxflow ${current} → ${latest} available · run \`voxflow upgrade -y\` (or \`npm i -g voxflow@latest\`)\x1b[0m`
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
return lines.length ? '\n' + lines.join('\n') + '\n' : '';
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function queueHint(current, latest) {
|
|
170
|
+
if (hintQueued) return;
|
|
171
|
+
hintQueued = true;
|
|
172
|
+
process.on('exit', () => {
|
|
173
|
+
const out = renderHintLines(current, latest, {
|
|
174
|
+
agent: isAgentChannel(),
|
|
175
|
+
human: process.stderr.isTTY && !isAgentChannel(),
|
|
176
|
+
});
|
|
177
|
+
if (out) process.stderr.write(out);
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
module.exports = {
|
|
182
|
+
scheduleUpdateCheck,
|
|
183
|
+
// Exposed for tests:
|
|
184
|
+
_test: { cmpVersion, shouldSkip, cachePath, readCache, writeCache, isAgentChannel, renderHintLines },
|
|
185
|
+
};
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Stream a remote URL to a temp file.
|
|
5
|
+
*
|
|
6
|
+
* Used by `voxflow asr --url` to work around Tencent Flash ASR not being
|
|
7
|
+
* able to resolve arbitrary external hosts — we fetch the file ourselves,
|
|
8
|
+
* then re-upload it via the existing Supabase Storage path.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const os = require('os');
|
|
13
|
+
const path = require('path');
|
|
14
|
+
const http = require('http');
|
|
15
|
+
const https = require('https');
|
|
16
|
+
|
|
17
|
+
const MAX_DOWNLOAD_BYTES = 500 * 1024 * 1024; // 500 MB hard cap
|
|
18
|
+
|
|
19
|
+
function pickClient(urlObj) {
|
|
20
|
+
if (urlObj.protocol === 'https:') return https;
|
|
21
|
+
if (urlObj.protocol === 'http:') return http;
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function suggestExt(urlObj, contentType) {
|
|
26
|
+
const fromPath = path.extname(urlObj.pathname);
|
|
27
|
+
if (fromPath && fromPath.length <= 5) return fromPath;
|
|
28
|
+
if (contentType) {
|
|
29
|
+
const ct = contentType.split(';')[0].trim().toLowerCase();
|
|
30
|
+
if (ct === 'audio/mpeg') return '.mp3';
|
|
31
|
+
if (ct === 'audio/wav' || ct === 'audio/x-wav') return '.wav';
|
|
32
|
+
if (ct === 'audio/ogg') return '.ogg';
|
|
33
|
+
if (ct === 'audio/mp4' || ct === 'audio/m4a') return '.m4a';
|
|
34
|
+
if (ct === 'audio/flac') return '.flac';
|
|
35
|
+
if (ct === 'video/mp4') return '.mp4';
|
|
36
|
+
}
|
|
37
|
+
return '.bin';
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Download `url` to a temp file. Follows up to 5 redirects.
|
|
42
|
+
*
|
|
43
|
+
* @param {string} url
|
|
44
|
+
* @param {object} [opts]
|
|
45
|
+
* @param {(received: number, total: number|null) => void} [opts.onProgress]
|
|
46
|
+
* @param {number} [opts.maxBytes] hard cap, default 500 MB
|
|
47
|
+
* @returns {Promise<{ filePath: string, size: number }>}
|
|
48
|
+
*/
|
|
49
|
+
async function downloadUrlToTempFile(url, opts = {}) {
|
|
50
|
+
const maxBytes = opts.maxBytes ?? MAX_DOWNLOAD_BYTES;
|
|
51
|
+
const onProgress = opts.onProgress || (() => {});
|
|
52
|
+
|
|
53
|
+
return new Promise((resolve, reject) => {
|
|
54
|
+
const visited = new Set();
|
|
55
|
+
const start = (target, redirectsLeft) => {
|
|
56
|
+
let urlObj;
|
|
57
|
+
try {
|
|
58
|
+
urlObj = new URL(target);
|
|
59
|
+
} catch {
|
|
60
|
+
reject(new Error(`Invalid URL: ${target}`));
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const client = pickClient(urlObj);
|
|
65
|
+
if (!client) {
|
|
66
|
+
reject(new Error(`Unsupported URL scheme: ${urlObj.protocol} (use http or https)`));
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (visited.has(target)) {
|
|
71
|
+
reject(new Error(`Redirect loop detected at ${target}`));
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
visited.add(target);
|
|
75
|
+
|
|
76
|
+
const req = client.get(target, { headers: { 'User-Agent': 'voxflow-cli' } }, (res) => {
|
|
77
|
+
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
78
|
+
if (redirectsLeft <= 0) {
|
|
79
|
+
res.resume();
|
|
80
|
+
reject(new Error('Too many redirects (>5)'));
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
const next = new URL(res.headers.location, target).toString();
|
|
84
|
+
res.resume();
|
|
85
|
+
start(next, redirectsLeft - 1);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
if (res.statusCode !== 200) {
|
|
89
|
+
res.resume();
|
|
90
|
+
reject(new Error(`Download failed: HTTP ${res.statusCode} for ${target}`));
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const contentLength = res.headers['content-length']
|
|
95
|
+
? parseInt(res.headers['content-length'], 10)
|
|
96
|
+
: null;
|
|
97
|
+
if (contentLength != null && contentLength > maxBytes) {
|
|
98
|
+
res.resume();
|
|
99
|
+
reject(new Error(`Remote file too large (${contentLength} bytes, max ${maxBytes})`));
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const ext = suggestExt(urlObj, res.headers['content-type']);
|
|
104
|
+
const filePath = path.join(
|
|
105
|
+
os.tmpdir(),
|
|
106
|
+
`voxflow-asr-${Date.now()}-${Math.random().toString(36).slice(2, 8)}${ext}`
|
|
107
|
+
);
|
|
108
|
+
const out = fs.createWriteStream(filePath);
|
|
109
|
+
let received = 0;
|
|
110
|
+
let aborted = false;
|
|
111
|
+
|
|
112
|
+
res.on('data', (chunk) => {
|
|
113
|
+
if (aborted) return;
|
|
114
|
+
received += chunk.length;
|
|
115
|
+
if (received > maxBytes) {
|
|
116
|
+
aborted = true;
|
|
117
|
+
out.destroy();
|
|
118
|
+
res.destroy();
|
|
119
|
+
try { fs.unlinkSync(filePath); } catch { /* ignore */ }
|
|
120
|
+
reject(new Error(`Remote file exceeded ${maxBytes} bytes during download`));
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
onProgress(received, contentLength);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
res.pipe(out);
|
|
127
|
+
|
|
128
|
+
out.on('finish', () => {
|
|
129
|
+
if (aborted) return;
|
|
130
|
+
out.close(() => resolve({ filePath, size: received }));
|
|
131
|
+
});
|
|
132
|
+
out.on('error', (err) => {
|
|
133
|
+
try { fs.unlinkSync(filePath); } catch { /* ignore */ }
|
|
134
|
+
reject(err);
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
req.on('error', reject);
|
|
139
|
+
req.setTimeout(60_000, () => {
|
|
140
|
+
req.destroy(new Error(`Download timed out after 60s: ${target}`));
|
|
141
|
+
});
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
start(url, 5);
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
module.exports = { downloadUrlToTempFile, MAX_DOWNLOAD_BYTES };
|