@assistkick/create 1.6.0 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (214) hide show
  1. package/package.json +2 -2
  2. package/templates/assistkick-product-system/.env.example +1 -0
  3. package/templates/assistkick-product-system/local.db +0 -0
  4. package/templates/assistkick-product-system/package.json +4 -2
  5. package/templates/assistkick-product-system/packages/backend/package.json +2 -0
  6. package/templates/assistkick-product-system/packages/backend/src/routes/agents.ts +165 -0
  7. package/templates/assistkick-product-system/packages/backend/src/routes/files.test.ts +358 -0
  8. package/templates/assistkick-product-system/packages/backend/src/routes/files.ts +356 -0
  9. package/templates/assistkick-product-system/packages/backend/src/routes/git.ts +96 -1
  10. package/templates/assistkick-product-system/packages/backend/src/routes/graph.ts +1 -0
  11. package/templates/assistkick-product-system/packages/backend/src/routes/kanban.ts +43 -4
  12. package/templates/assistkick-product-system/packages/backend/src/routes/pipeline.ts +200 -84
  13. package/templates/assistkick-product-system/packages/backend/src/routes/projects.ts +6 -3
  14. package/templates/assistkick-product-system/packages/backend/src/routes/terminal.ts +53 -17
  15. package/templates/assistkick-product-system/packages/backend/src/routes/video.ts +218 -0
  16. package/templates/assistkick-product-system/packages/backend/src/routes/workflow_groups.ts +119 -0
  17. package/templates/assistkick-product-system/packages/backend/src/routes/workflows.ts +154 -0
  18. package/templates/assistkick-product-system/packages/backend/src/server.ts +81 -9
  19. package/templates/assistkick-product-system/packages/backend/src/services/agent_service.test.ts +489 -0
  20. package/templates/assistkick-product-system/packages/backend/src/services/agent_service.ts +416 -0
  21. package/templates/assistkick-product-system/packages/backend/src/services/bundle_service.test.ts +189 -0
  22. package/templates/assistkick-product-system/packages/backend/src/services/bundle_service.ts +182 -0
  23. package/templates/assistkick-product-system/packages/backend/src/services/init.ts +28 -78
  24. package/templates/assistkick-product-system/packages/backend/src/services/project_service.test.ts +16 -0
  25. package/templates/assistkick-product-system/packages/backend/src/services/project_service.ts +73 -2
  26. package/templates/assistkick-product-system/packages/backend/src/services/project_workspace_service.test.ts +4 -4
  27. package/templates/assistkick-product-system/packages/backend/src/services/project_workspace_service.ts +87 -11
  28. package/templates/assistkick-product-system/packages/backend/src/services/pty_session_manager.test.ts +210 -69
  29. package/templates/assistkick-product-system/packages/backend/src/services/pty_session_manager.ts +210 -215
  30. package/templates/assistkick-product-system/packages/backend/src/services/ssh_key_service.test.ts +162 -0
  31. package/templates/assistkick-product-system/packages/backend/src/services/ssh_key_service.ts +148 -0
  32. package/templates/assistkick-product-system/packages/backend/src/services/terminal_ws_handler.ts +11 -5
  33. package/templates/assistkick-product-system/packages/backend/src/services/tts_service.test.ts +64 -0
  34. package/templates/assistkick-product-system/packages/backend/src/services/tts_service.ts +134 -0
  35. package/templates/assistkick-product-system/packages/backend/src/services/video_render_service.test.ts +256 -0
  36. package/templates/assistkick-product-system/packages/backend/src/services/video_render_service.ts +258 -0
  37. package/templates/assistkick-product-system/packages/backend/src/services/workflow_group_service.ts +106 -0
  38. package/templates/assistkick-product-system/packages/backend/src/services/workflow_service.test.ts +275 -0
  39. package/templates/assistkick-product-system/packages/backend/src/services/workflow_service.ts +222 -0
  40. package/templates/assistkick-product-system/packages/frontend/index.html +3 -0
  41. package/templates/assistkick-product-system/packages/frontend/package-lock.json +800 -11
  42. package/templates/assistkick-product-system/packages/frontend/package.json +11 -1
  43. package/templates/assistkick-product-system/packages/frontend/src/App.tsx +24 -7
  44. package/templates/assistkick-product-system/packages/frontend/src/api/client.ts +456 -16
  45. package/templates/assistkick-product-system/packages/frontend/src/api/client_files.test.ts +172 -0
  46. package/templates/assistkick-product-system/packages/frontend/src/api/client_video.test.ts +238 -0
  47. package/templates/assistkick-product-system/packages/frontend/src/components/AgentsView.tsx +307 -0
  48. package/templates/assistkick-product-system/packages/frontend/src/components/CoherenceView.tsx +82 -66
  49. package/templates/assistkick-product-system/packages/frontend/src/components/CompositionPlaceholder.tsx +97 -0
  50. package/templates/assistkick-product-system/packages/frontend/src/components/DesignSystemView.tsx +383 -0
  51. package/templates/assistkick-product-system/packages/frontend/src/components/EditorTabBar.tsx +57 -0
  52. package/templates/assistkick-product-system/packages/frontend/src/components/FileTree.tsx +313 -0
  53. package/templates/assistkick-product-system/packages/frontend/src/components/FileTreeContextMenu.tsx +61 -0
  54. package/templates/assistkick-product-system/packages/frontend/src/components/FileTreeInlineInput.tsx +73 -0
  55. package/templates/assistkick-product-system/packages/frontend/src/components/FilesView.tsx +404 -0
  56. package/templates/assistkick-product-system/packages/frontend/src/components/GitRepoModal.tsx +193 -64
  57. package/templates/assistkick-product-system/packages/frontend/src/components/GraphLegend.tsx +71 -73
  58. package/templates/assistkick-product-system/packages/frontend/src/components/GraphSettings.tsx +8 -8
  59. package/templates/assistkick-product-system/packages/frontend/src/components/GraphView.tsx +1 -1
  60. package/templates/assistkick-product-system/packages/frontend/src/components/InviteUserDialog.tsx +15 -11
  61. package/templates/assistkick-product-system/packages/frontend/src/components/KanbanView.tsx +226 -291
  62. package/templates/assistkick-product-system/packages/frontend/src/components/LoginPage.tsx +14 -14
  63. package/templates/assistkick-product-system/packages/frontend/src/components/ProjectSelector.tsx +54 -33
  64. package/templates/assistkick-product-system/packages/frontend/src/components/QaIssueSheet.tsx +40 -66
  65. package/templates/assistkick-product-system/packages/frontend/src/components/SidePanel.tsx +55 -115
  66. package/templates/assistkick-product-system/packages/frontend/src/components/TerminalView.tsx +121 -52
  67. package/templates/assistkick-product-system/packages/frontend/src/components/Toolbar.tsx +155 -77
  68. package/templates/assistkick-product-system/packages/frontend/src/components/UsersView.tsx +52 -52
  69. package/templates/assistkick-product-system/packages/frontend/src/components/VideoGallery.tsx +313 -0
  70. package/templates/assistkick-product-system/packages/frontend/src/components/VideographyView.tsx +250 -0
  71. package/templates/assistkick-product-system/packages/frontend/src/components/WorkflowsView.tsx +474 -0
  72. package/templates/assistkick-product-system/packages/frontend/src/components/ds/AccentBorderList.tsx +53 -0
  73. package/templates/assistkick-product-system/packages/frontend/src/components/ds/Button.tsx +87 -0
  74. package/templates/assistkick-product-system/packages/frontend/src/components/ds/ButtonGroup.tsx +29 -0
  75. package/templates/assistkick-product-system/packages/frontend/src/components/ds/ButtonShowcase.tsx +221 -0
  76. package/templates/assistkick-product-system/packages/frontend/src/components/ds/CardGlass.tsx +141 -0
  77. package/templates/assistkick-product-system/packages/frontend/src/components/ds/CompletionRing.tsx +30 -0
  78. package/templates/assistkick-product-system/packages/frontend/src/components/ds/ContentCard.tsx +34 -0
  79. package/templates/assistkick-product-system/packages/frontend/src/components/ds/IconButton.tsx +74 -0
  80. package/templates/assistkick-product-system/packages/frontend/src/components/ds/KanbanCard.tsx +270 -0
  81. package/templates/assistkick-product-system/packages/frontend/src/components/ds/KanbanCardShowcase.tsx +37 -0
  82. package/templates/assistkick-product-system/packages/frontend/src/components/ds/Kbd.tsx +11 -0
  83. package/templates/assistkick-product-system/packages/frontend/src/components/ds/KindBadge.tsx +21 -0
  84. package/templates/assistkick-product-system/packages/frontend/src/components/ds/NavBarSidekick.tsx +207 -0
  85. package/templates/assistkick-product-system/packages/frontend/src/components/ds/SidePanelShowcase.tsx +370 -0
  86. package/templates/assistkick-product-system/packages/frontend/src/components/ds/SideSheet.tsx +64 -0
  87. package/templates/assistkick-product-system/packages/frontend/src/components/ds/StatusDot.tsx +18 -0
  88. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/CheckCardPositionNode.tsx +36 -0
  89. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/CheckCycleCountNode.tsx +60 -0
  90. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/EndNode.tsx +42 -0
  91. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/GroupNode.tsx +189 -0
  92. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/NodePalette.tsx +123 -0
  93. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/RunAgentNode.tsx +51 -0
  94. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/SetCardMetadataNode.tsx +53 -0
  95. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/StartNode.tsx +18 -0
  96. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/TransitionCardNode.tsx +59 -0
  97. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/WorkflowCanvas.tsx +335 -0
  98. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/WorkflowMonitorModal.tsx +634 -0
  99. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/autoLayout.ts +103 -0
  100. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/edgeColors.ts +35 -0
  101. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/monitor_nodes.tsx +208 -0
  102. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/workflow_types.test.ts +119 -0
  103. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/workflow_types.ts +107 -0
  104. package/templates/assistkick-product-system/packages/frontend/src/constants/graph.ts +13 -11
  105. package/templates/assistkick-product-system/packages/frontend/src/hooks/useAutoSave.ts +75 -0
  106. package/templates/assistkick-product-system/packages/frontend/src/hooks/useGraph.ts +6 -21
  107. package/templates/assistkick-product-system/packages/frontend/src/hooks/useProjects.ts +15 -80
  108. package/templates/assistkick-product-system/packages/frontend/src/hooks/useToast.tsx +16 -3
  109. package/templates/assistkick-product-system/packages/frontend/src/pages/accept_invitation_page.tsx +30 -27
  110. package/templates/assistkick-product-system/packages/frontend/src/pages/forgot_password_page.tsx +18 -15
  111. package/templates/assistkick-product-system/packages/frontend/src/pages/register_page.tsx +21 -18
  112. package/templates/assistkick-product-system/packages/frontend/src/pages/reset_password_page.tsx +28 -25
  113. package/templates/assistkick-product-system/packages/frontend/src/routes/AgentsRoute.tsx +6 -0
  114. package/templates/assistkick-product-system/packages/frontend/src/routes/CoherenceRoute.tsx +19 -0
  115. package/templates/assistkick-product-system/packages/frontend/src/routes/DashboardLayout.tsx +54 -0
  116. package/templates/assistkick-product-system/packages/frontend/src/routes/DesignSystemRoute.tsx +6 -0
  117. package/templates/assistkick-product-system/packages/frontend/src/routes/FilesRoute.tsx +13 -0
  118. package/templates/assistkick-product-system/packages/frontend/src/routes/GraphRoute.tsx +93 -0
  119. package/templates/assistkick-product-system/packages/frontend/src/routes/KanbanRoute.tsx +30 -0
  120. package/templates/assistkick-product-system/packages/frontend/src/routes/TerminalRoute.tsx +9 -0
  121. package/templates/assistkick-product-system/packages/frontend/src/routes/UsersRoute.tsx +6 -0
  122. package/templates/assistkick-product-system/packages/frontend/src/routes/VideographyRoute.tsx +13 -0
  123. package/templates/assistkick-product-system/packages/frontend/src/routes/WorkflowsRoute.tsx +6 -0
  124. package/templates/assistkick-product-system/packages/frontend/src/stores/useGitModalStore.ts +14 -0
  125. package/templates/assistkick-product-system/packages/frontend/src/stores/useGraphStore.ts +36 -0
  126. package/templates/assistkick-product-system/packages/frontend/src/stores/useGraphUIStore.ts +25 -0
  127. package/templates/assistkick-product-system/packages/frontend/src/stores/useProjectStore.ts +90 -0
  128. package/templates/assistkick-product-system/packages/frontend/src/stores/useQaSheetStore.ts +27 -0
  129. package/templates/assistkick-product-system/packages/frontend/src/stores/useSidePanelStore.ts +76 -0
  130. package/templates/assistkick-product-system/packages/frontend/src/styles/index.css +336 -3632
  131. package/templates/assistkick-product-system/packages/frontend/src/utils/auto_save_service.test.ts +167 -0
  132. package/templates/assistkick-product-system/packages/frontend/src/utils/auto_save_service.ts +101 -0
  133. package/templates/assistkick-product-system/packages/frontend/src/utils/composition_matcher.test.ts +42 -0
  134. package/templates/assistkick-product-system/packages/frontend/src/utils/composition_matcher.ts +17 -0
  135. package/templates/assistkick-product-system/packages/frontend/src/utils/file_utils.test.ts +145 -0
  136. package/templates/assistkick-product-system/packages/frontend/src/utils/file_utils.ts +42 -0
  137. package/templates/assistkick-product-system/packages/frontend/src/utils/task_status.test.ts +4 -10
  138. package/templates/assistkick-product-system/packages/frontend/src/utils/task_status.ts +19 -1
  139. package/templates/assistkick-product-system/packages/frontend/vite.config.ts +7 -1
  140. package/templates/assistkick-product-system/packages/shared/db/local.db +0 -0
  141. package/templates/assistkick-product-system/packages/shared/db/migrations/0004_tidy_matthew_murdock.sql +9 -0
  142. package/templates/assistkick-product-system/packages/shared/db/migrations/0005_mysterious_falcon.sql +692 -0
  143. package/templates/assistkick-product-system/packages/shared/db/migrations/0006_next_venom.sql +9 -0
  144. package/templates/assistkick-product-system/packages/shared/db/migrations/0007_deep_barracuda.sql +39 -0
  145. package/templates/assistkick-product-system/packages/shared/db/migrations/0008_puzzling_hannibal_king.sql +1 -0
  146. package/templates/assistkick-product-system/packages/shared/db/migrations/0009_amused_beast.sql +8 -0
  147. package/templates/assistkick-product-system/packages/shared/db/migrations/0010_spotty_moira_mactaggert.sql +9 -0
  148. package/templates/assistkick-product-system/packages/shared/db/migrations/0011_goofy_snowbird.sql +3 -0
  149. package/templates/assistkick-product-system/packages/shared/db/migrations/0011_supreme_doctor_octopus.sql +3 -0
  150. package/templates/assistkick-product-system/packages/shared/db/migrations/0013_reflective_prowler.sql +15 -0
  151. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0004_snapshot.json +921 -0
  152. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0005_snapshot.json +1042 -0
  153. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0006_snapshot.json +1101 -0
  154. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0007_snapshot.json +1336 -0
  155. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0008_snapshot.json +1275 -0
  156. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0009_snapshot.json +1327 -0
  157. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0010_snapshot.json +1393 -0
  158. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0011_snapshot.json +1436 -0
  159. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0013_snapshot.json +1538 -0
  160. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/_journal.json +70 -0
  161. package/templates/assistkick-product-system/packages/shared/db/schema.ts +113 -0
  162. package/templates/assistkick-product-system/packages/shared/lib/claude-service.ts +32 -7
  163. package/templates/assistkick-product-system/packages/shared/lib/constants.ts +9 -0
  164. package/templates/assistkick-product-system/packages/shared/lib/git_workflow.ts +12 -4
  165. package/templates/assistkick-product-system/packages/shared/lib/graph.ts +16 -5
  166. package/templates/assistkick-product-system/packages/shared/lib/workflow_engine.test.ts +1753 -0
  167. package/templates/assistkick-product-system/packages/shared/lib/workflow_engine.ts +1281 -0
  168. package/templates/assistkick-product-system/packages/shared/lib/workflow_orchestrator.ts +211 -0
  169. package/templates/assistkick-product-system/packages/shared/tools/add_node.test.ts +43 -0
  170. package/templates/assistkick-product-system/packages/shared/tools/add_node.ts +13 -2
  171. package/templates/assistkick-product-system/packages/shared/tools/get_kanban.ts +1 -1
  172. package/templates/assistkick-product-system/packages/shared/tools/migrate_epics.test.ts +226 -0
  173. package/templates/assistkick-product-system/packages/shared/tools/migrate_epics.ts +251 -0
  174. package/templates/assistkick-product-system/packages/shared/tools/update_node.ts +2 -2
  175. package/templates/assistkick-product-system/packages/shared/utils/hello_workflow.test.ts +10 -0
  176. package/templates/assistkick-product-system/packages/shared/utils/hello_workflow.ts +6 -0
  177. package/templates/assistkick-product-system/packages/video/Root.tsx +85 -0
  178. package/templates/assistkick-product-system/packages/video/components/email_scene.tsx +231 -0
  179. package/templates/assistkick-product-system/packages/video/components/outro_scene.tsx +153 -0
  180. package/templates/assistkick-product-system/packages/video/components/part_divider.tsx +90 -0
  181. package/templates/assistkick-product-system/packages/video/components/scene.tsx +226 -0
  182. package/templates/assistkick-product-system/packages/video/components/theme.ts +22 -0
  183. package/templates/assistkick-product-system/packages/video/components/title_scene.tsx +169 -0
  184. package/templates/assistkick-product-system/packages/video/components/video_split_layout.tsx +84 -0
  185. package/templates/assistkick-product-system/packages/video/compositions/.gitkeep +0 -0
  186. package/templates/assistkick-product-system/packages/video/index.ts +4 -0
  187. package/templates/assistkick-product-system/packages/video/package.json +28 -0
  188. package/templates/assistkick-product-system/packages/video/remotion.config.ts +11 -0
  189. package/templates/assistkick-product-system/packages/video/scripts/process_script.test.ts +326 -0
  190. package/templates/assistkick-product-system/packages/video/scripts/process_script.ts +630 -0
  191. package/templates/assistkick-product-system/packages/video/style.css +1 -0
  192. package/templates/assistkick-product-system/packages/video/tsconfig.json +18 -0
  193. package/templates/assistkick-product-system/tests/graph_legend.test.ts +2 -1
  194. package/templates/assistkick-product-system/tests/video_render_service.test.ts +179 -0
  195. package/templates/assistkick-product-system/tests/web_terminal.test.ts +219 -455
  196. package/templates/assistkick-product-system/tests/workflow_integration.test.ts +341 -0
  197. package/templates/skills/assistkick-bootstrap/SKILL.md +3 -3
  198. package/templates/skills/assistkick-code-reviewer/SKILL.md +2 -2
  199. package/templates/skills/assistkick-debugger/SKILL.md +2 -2
  200. package/templates/skills/assistkick-developer/SKILL.md +6 -3
  201. package/templates/skills/assistkick-developer/references/react_development_guidelines.md +225 -0
  202. package/templates/skills/assistkick-interview/SKILL.md +2 -2
  203. package/templates/skills/product-system/graph.json +1890 -0
  204. package/templates/skills/product-system/kanban.json +304 -0
  205. package/templates/skills/product-system/nodes/comp_001.md +56 -0
  206. package/templates/skills/product-system/nodes/comp_002.md +57 -0
  207. package/templates/skills/product-system/nodes/data_001.md +51 -0
  208. package/templates/skills/product-system/nodes/data_002.md +40 -0
  209. package/templates/skills/product-system/nodes/data_004.md +38 -0
  210. package/templates/skills/product-system/nodes/dec_001.md +34 -0
  211. package/templates/skills/product-system/nodes/dec_016.md +32 -0
  212. package/templates/skills/product-system/nodes/feat_008.md +30 -0
  213. package/templates/skills/video-composition-agent/SKILL.md +232 -0
  214. package/templates/skills/video-script-writer/SKILL.md +136 -0
@@ -0,0 +1,84 @@
1
+ /** @jsxImportSource react */
2
+ import { AbsoluteFill } from "remotion";
3
+ import { Video } from "@remotion/media";
4
+ import { useCallback, useState } from "react";
5
+ import { fonts } from "./theme";
6
+
7
+ type VideoSplitLayoutProps = {
8
+ video?: string;
9
+ backgroundColor: string;
10
+ accentColor: string;
11
+ children: (hasVideo: boolean) => React.ReactNode;
12
+ };
13
+
14
+ export const VideoSplitLayout: React.FC<VideoSplitLayoutProps> = ({
15
+ video,
16
+ backgroundColor,
17
+ accentColor,
18
+ children,
19
+ }) => {
20
+ const [videoError, setVideoError] = useState(false);
21
+ const onVideoError = useCallback(() => {
22
+ setVideoError(true);
23
+ return "fail" as const;
24
+ }, []);
25
+ const hasVideo = !!video && !videoError;
26
+
27
+ const panelLeft = hasVideo ? "60%" : 0;
28
+ const panelPadding = hasVideo ? 48 : 80;
29
+
30
+ return (
31
+ <AbsoluteFill
32
+ style={{
33
+ backgroundColor,
34
+ fontFamily: fonts.sans,
35
+ }}
36
+ >
37
+ {hasVideo && (
38
+ <AbsoluteFill
39
+ style={{
40
+ width: "60%",
41
+ overflow: "hidden",
42
+ }}
43
+ >
44
+ <Video
45
+ src={video}
46
+ muted
47
+ onError={onVideoError}
48
+ style={{
49
+ width: "100%",
50
+ height: "100%",
51
+ objectFit: "contain",
52
+ }}
53
+ />
54
+ </AbsoluteFill>
55
+ )}
56
+
57
+ <div
58
+ style={{
59
+ position: "absolute",
60
+ top: 0,
61
+ left: panelLeft,
62
+ right: 0,
63
+ bottom: 0,
64
+ display: "flex",
65
+ flexDirection: "column",
66
+ justifyContent: "center",
67
+ padding: `60px ${panelPadding}px`,
68
+ }}
69
+ >
70
+ <div
71
+ style={{
72
+ position: "absolute",
73
+ top: 0,
74
+ left: 0,
75
+ right: 0,
76
+ height: 6,
77
+ backgroundColor: accentColor,
78
+ }}
79
+ />
80
+ {children(hasVideo)}
81
+ </div>
82
+ </AbsoluteFill>
83
+ );
84
+ };
@@ -0,0 +1,4 @@
1
+ import { registerRoot } from "remotion";
2
+ import { RemotionRoot } from "./Root";
3
+
4
+ registerRoot(RemotionRoot);
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "@assistkick/video",
3
+ "version": "1.0.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "studio": "remotion studio index.ts --config remotion.config.ts",
8
+ "render": "remotion render index.ts --config remotion.config.ts --log warn",
9
+ "process-script": "tsx scripts/process_script.ts"
10
+ },
11
+ "dependencies": {
12
+ "@remotion/cli": "^4.0.431",
13
+ "@remotion/media": "^4.0.431",
14
+ "@remotion/player": "^4.0.431",
15
+ "@remotion/tailwind-v4": "^4.0.431",
16
+ "@remotion/transitions": "^4.0.431",
17
+ "remotion": "^4.0.431",
18
+ "react": "^19.1.0",
19
+ "react-dom": "^19.1.0"
20
+ },
21
+ "devDependencies": {
22
+ "@types/react": "^19.1.0",
23
+ "@types/react-dom": "^19.1.0",
24
+ "tailwindcss": "^4.2.1",
25
+ "tsx": "^4.21.0",
26
+ "typescript": "^5.9.3"
27
+ }
28
+ }
@@ -0,0 +1,11 @@
1
+ import { Config } from "@remotion/cli/config";
2
+ import { enableTailwind } from "@remotion/tailwind-v4";
3
+
4
+ Config.setPublicDir("./public");
5
+ Config.setOutputLocation("/data/videos/");
6
+ Config.setConcurrency("100%");
7
+ Config.setOffthreadVideoCacheSizeInBytes(1024 * 1024 * 1024); // 1 GB
8
+
9
+ Config.overrideWebpackConfig((currentConfiguration) => {
10
+ return enableTailwind(currentConfiguration);
11
+ });
@@ -0,0 +1,326 @@
1
+ import { describe, it } from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { writeFileSync, unlinkSync, mkdirSync, rmSync, readFileSync } from "fs";
4
+ import { join } from "path";
5
+ import {
6
+ parseNarrations,
7
+ parseScreenCaptures,
8
+ hashText,
9
+ getAudioDuration,
10
+ getMp4Duration,
11
+ processScript,
12
+ } from "./process_script";
13
+
14
+ describe("parseNarrations", () => {
15
+ it("parses single narration directive", () => {
16
+ const md = `:::narration intro
17
+ Hello world, this is the intro.
18
+ :::`;
19
+
20
+ const result = parseNarrations(md);
21
+ assert.equal(result.length, 1);
22
+ assert.equal(result[0].id, "intro");
23
+ assert.equal(result[0].src, "audio/intro.mp3");
24
+ assert.equal(result[0].text, "Hello world, this is the intro.");
25
+ });
26
+
27
+ it("parses multiple narration directives", () => {
28
+ const md = `# Scene 1
29
+ :::narration title
30
+ Welcome to the video.
31
+ :::
32
+
33
+ # Scene 2
34
+ :::narration step1-login
35
+ First, log in to the dashboard.
36
+ Then enter your credentials.
37
+ :::`;
38
+
39
+ const result = parseNarrations(md);
40
+ assert.equal(result.length, 2);
41
+ assert.equal(result[0].id, "title");
42
+ assert.equal(result[0].text, "Welcome to the video.");
43
+ assert.equal(result[1].id, "step1-login");
44
+ assert.equal(result[1].text, "First, log in to the dashboard. Then enter your credentials.");
45
+ });
46
+
47
+ it("returns empty array when no directives found", () => {
48
+ const result = parseNarrations("# Just a heading\n\nSome text.");
49
+ assert.equal(result.length, 0);
50
+ });
51
+
52
+ it("handles hyphenated IDs", () => {
53
+ const md = `:::narration my-scene
54
+ Text
55
+ :::`;
56
+ const result = parseNarrations(md);
57
+ assert.equal(result[0].id, "my-scene");
58
+ assert.equal(result[0].src, "audio/my-scene.mp3");
59
+ });
60
+
61
+ it("collapses newlines in narration text", () => {
62
+ const md = `:::narration multi
63
+ Line one.
64
+
65
+ Line two.
66
+
67
+ Line three.
68
+ :::`;
69
+ const result = parseNarrations(md);
70
+ assert.equal(result[0].text, "Line one. Line two. Line three.");
71
+ });
72
+
73
+ it("trims whitespace from narration text", () => {
74
+ const md = `:::narration padded
75
+ Some padded text.
76
+ :::`;
77
+ const result = parseNarrations(md);
78
+ assert.equal(result[0].text, "Some padded text.");
79
+ });
80
+
81
+ it("does not match XML-style narration tags", () => {
82
+ const md = `<narration src="audio/old.mp3">Old format text</narration>`;
83
+ const result = parseNarrations(md);
84
+ assert.equal(result.length, 0);
85
+ });
86
+ });
87
+
88
+ describe("parseScreenCaptures", () => {
89
+ it("parses screencapture directives", () => {
90
+ const md = `:::screencapture step1
91
+ Screen recording of the login flow
92
+ :::`;
93
+
94
+ const result = parseScreenCaptures(md);
95
+ assert.equal(result.length, 1);
96
+ assert.equal(result[0].id, "step1");
97
+ assert.equal(result[0].src, "video/step1.mp4");
98
+ assert.equal(result[0].description, "Screen recording of the login flow");
99
+ });
100
+
101
+ it("parses multiple screencapture directives", () => {
102
+ const md = `:::screencapture a
103
+ Desc A
104
+ :::
105
+
106
+ :::screencapture b
107
+ Desc B
108
+ :::`;
109
+
110
+ const result = parseScreenCaptures(md);
111
+ assert.equal(result.length, 2);
112
+ assert.equal(result[0].id, "a");
113
+ assert.equal(result[1].id, "b");
114
+ });
115
+
116
+ it("returns empty array when no directives found", () => {
117
+ const result = parseScreenCaptures("No captures here.");
118
+ assert.equal(result.length, 0);
119
+ });
120
+
121
+ it("does not match XML-style screencapture tags", () => {
122
+ const md = `<screencapture src="video/old.mp4">Old format</screencapture>`;
123
+ const result = parseScreenCaptures(md);
124
+ assert.equal(result.length, 0);
125
+ });
126
+ });
127
+
128
+ describe("hashText", () => {
129
+ it("returns consistent hash for same input", () => {
130
+ const h1 = hashText("hello world");
131
+ const h2 = hashText("hello world");
132
+ assert.equal(h1, h2);
133
+ });
134
+
135
+ it("returns different hash for different input", () => {
136
+ const h1 = hashText("hello world");
137
+ const h2 = hashText("hello world!");
138
+ assert.notEqual(h1, h2);
139
+ });
140
+
141
+ it("returns 16-character hex string", () => {
142
+ const h = hashText("test");
143
+ assert.equal(h.length, 16);
144
+ assert.match(h, /^[0-9a-f]{16}$/);
145
+ });
146
+ });
147
+
148
+ describe("getAudioDuration", () => {
149
+ it("returns null for non-existent file", () => {
150
+ const result = getAudioDuration("/nonexistent/file.mp3");
151
+ assert.equal(result, null);
152
+ });
153
+
154
+ it("returns null for empty file", () => {
155
+ const tmpFile = join(import.meta.dirname!, "_test_empty.mp3");
156
+ writeFileSync(tmpFile, Buffer.alloc(0));
157
+ try {
158
+ const result = getAudioDuration(tmpFile);
159
+ assert.equal(result, null);
160
+ } finally {
161
+ unlinkSync(tmpFile);
162
+ }
163
+ });
164
+ });
165
+
166
+ describe("getMp4Duration", () => {
167
+ it("returns null for non-existent file", () => {
168
+ const result = getMp4Duration("/nonexistent/file.mp4");
169
+ assert.equal(result, null);
170
+ });
171
+
172
+ it("returns null for non-MP4 data", () => {
173
+ const tmpFile = join(import.meta.dirname!, "_test_bad.mp4");
174
+ writeFileSync(tmpFile, Buffer.from("not a real mp4 file"));
175
+ try {
176
+ const result = getMp4Duration(tmpFile);
177
+ assert.equal(result, null);
178
+ } finally {
179
+ unlinkSync(tmpFile);
180
+ }
181
+ });
182
+ });
183
+
184
+ describe("processScript", () => {
185
+ it("throws when script file does not exist", async () => {
186
+ await assert.rejects(
187
+ () => processScript({
188
+ scriptPath: "/nonexistent/script.md",
189
+ projectId: "proj_test",
190
+ featureId: "feat_test",
191
+ }),
192
+ { message: /Script file not found/ },
193
+ );
194
+ });
195
+
196
+ it("throws when no narration directives found", async () => {
197
+ const tmpDir = join(import.meta.dirname!, "_test_no_narration");
198
+ mkdirSync(tmpDir, { recursive: true });
199
+ const scriptFile = join(tmpDir, "script.md");
200
+ writeFileSync(scriptFile, "# Just a heading\n\nNo narrations here.");
201
+
202
+ try {
203
+ await assert.rejects(
204
+ () => processScript({
205
+ scriptPath: scriptFile,
206
+ projectId: "proj_test",
207
+ featureId: "feat_test",
208
+ mediaBase: tmpDir,
209
+ }),
210
+ { message: /No :::narration directives found/ },
211
+ );
212
+ } finally {
213
+ rmSync(tmpDir, { recursive: true, force: true });
214
+ }
215
+ });
216
+
217
+ it("processes script in dry-run mode without calling ElevenLabs", async () => {
218
+ const tmpDir = join(import.meta.dirname!, "_test_dry_run");
219
+ mkdirSync(tmpDir, { recursive: true });
220
+ const scriptFile = join(tmpDir, "script.md");
221
+ writeFileSync(scriptFile, `# Scene 1
222
+ :::narration intro
223
+ Welcome to the demo.
224
+ :::
225
+
226
+ :::narration step1
227
+ Now let's get started.
228
+ :::
229
+ `);
230
+
231
+ try {
232
+ const result = await processScript({
233
+ scriptPath: scriptFile,
234
+ projectId: "proj_test",
235
+ featureId: "feat_dry",
236
+ mediaBase: tmpDir,
237
+ dryRun: true,
238
+ });
239
+
240
+ assert.equal(result.processed, 2);
241
+ assert.equal(result.generated, 0); // dry run — nothing generated
242
+ assert.equal(result.skipped, 0);
243
+ assert.equal(result.errors.length, 0);
244
+
245
+ // Hashes should still be written
246
+ const hashesFile = join(tmpDir, "feat_dry", "audio", "hashes.json");
247
+ const hashes = JSON.parse(readFileSync(hashesFile, "utf-8"));
248
+ assert.equal(Object.keys(hashes).length, 2);
249
+ assert.ok(hashes["intro"]);
250
+ assert.ok(hashes["step1"]);
251
+ } finally {
252
+ rmSync(tmpDir, { recursive: true, force: true });
253
+ }
254
+ });
255
+
256
+ it("detects text changes via hash comparison", async () => {
257
+ const tmpDir = join(import.meta.dirname!, "_test_hash_change");
258
+ const audioDir = join(tmpDir, "feat_hash", "audio");
259
+ mkdirSync(audioDir, { recursive: true });
260
+
261
+ const scriptFile = join(tmpDir, "script.md");
262
+ writeFileSync(scriptFile, `:::narration intro
263
+ Original text.
264
+ :::
265
+ `);
266
+
267
+ // Write a pre-existing hashes.json with a different hash
268
+ const hashesFile = join(audioDir, "hashes.json");
269
+ writeFileSync(hashesFile, JSON.stringify({ intro: "differenthash12345" }));
270
+
271
+ // Write a dummy audio file so it's not "missing"
272
+ writeFileSync(join(audioDir, "intro.mp3"), Buffer.from("fake mp3"));
273
+
274
+ try {
275
+ const result = await processScript({
276
+ scriptPath: scriptFile,
277
+ projectId: "proj_test",
278
+ featureId: "feat_hash",
279
+ mediaBase: tmpDir,
280
+ dryRun: true,
281
+ });
282
+
283
+ // Should detect text changed (hash mismatch) → processed but not generated (dry run)
284
+ assert.equal(result.processed, 1);
285
+ assert.equal(result.generated, 0); // dry run
286
+ assert.equal(result.skipped, 0); // not skipped because hash changed
287
+ } finally {
288
+ rmSync(tmpDir, { recursive: true, force: true });
289
+ }
290
+ });
291
+
292
+ it("skips narration when hash matches and audio exists", async () => {
293
+ const tmpDir = join(import.meta.dirname!, "_test_skip");
294
+ const audioDir = join(tmpDir, "feat_skip", "audio");
295
+ mkdirSync(audioDir, { recursive: true });
296
+
297
+ const narrationText = "This text has not changed.";
298
+ const scriptFile = join(tmpDir, "script.md");
299
+ writeFileSync(scriptFile, `:::narration unchanged
300
+ ${narrationText}
301
+ :::
302
+ `);
303
+
304
+ // Write matching hash
305
+ const { createHash } = await import("crypto");
306
+ const hash = createHash("sha256").update(narrationText).digest("hex").slice(0, 16);
307
+ writeFileSync(join(audioDir, "hashes.json"), JSON.stringify({ unchanged: hash }));
308
+ writeFileSync(join(audioDir, "unchanged.mp3"), Buffer.from("fake mp3"));
309
+
310
+ try {
311
+ const result = await processScript({
312
+ scriptPath: scriptFile,
313
+ projectId: "proj_test",
314
+ featureId: "feat_skip",
315
+ mediaBase: tmpDir,
316
+ dryRun: true,
317
+ });
318
+
319
+ assert.equal(result.processed, 1);
320
+ assert.equal(result.skipped, 1);
321
+ assert.equal(result.generated, 0);
322
+ } finally {
323
+ rmSync(tmpDir, { recursive: true, force: true });
324
+ }
325
+ });
326
+ });