@assistkick/create 1.7.0 → 1.9.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 (206) hide show
  1. package/dist/bin/create.js +0 -0
  2. package/package.json +9 -7
  3. package/templates/assistkick-product-system/.env.example +1 -0
  4. package/templates/assistkick-product-system/local.db +0 -0
  5. package/templates/assistkick-product-system/package.json +4 -2
  6. package/templates/assistkick-product-system/packages/backend/package.json +2 -0
  7. package/templates/assistkick-product-system/packages/backend/src/routes/agents.ts +165 -0
  8. package/templates/assistkick-product-system/packages/backend/src/routes/files.test.ts +358 -0
  9. package/templates/assistkick-product-system/packages/backend/src/routes/files.ts +356 -0
  10. package/templates/assistkick-product-system/packages/backend/src/routes/git.ts +96 -1
  11. package/templates/assistkick-product-system/packages/backend/src/routes/graph.ts +1 -0
  12. package/templates/assistkick-product-system/packages/backend/src/routes/kanban.ts +61 -6
  13. package/templates/assistkick-product-system/packages/backend/src/routes/pipeline.ts +200 -84
  14. package/templates/assistkick-product-system/packages/backend/src/routes/projects.ts +6 -3
  15. package/templates/assistkick-product-system/packages/backend/src/routes/terminal.ts +53 -17
  16. package/templates/assistkick-product-system/packages/backend/src/routes/video.ts +218 -0
  17. package/templates/assistkick-product-system/packages/backend/src/routes/workflow_groups.ts +119 -0
  18. package/templates/assistkick-product-system/packages/backend/src/routes/workflows.ts +158 -0
  19. package/templates/assistkick-product-system/packages/backend/src/server.ts +60 -9
  20. package/templates/assistkick-product-system/packages/backend/src/services/agent_service.test.ts +489 -0
  21. package/templates/assistkick-product-system/packages/backend/src/services/agent_service.ts +416 -0
  22. package/templates/assistkick-product-system/packages/backend/src/services/bundle_service.test.ts +189 -0
  23. package/templates/assistkick-product-system/packages/backend/src/services/bundle_service.ts +182 -0
  24. package/templates/assistkick-product-system/packages/backend/src/services/init.ts +43 -77
  25. package/templates/assistkick-product-system/packages/backend/src/services/project_service.test.ts +16 -0
  26. package/templates/assistkick-product-system/packages/backend/src/services/project_service.ts +73 -2
  27. package/templates/assistkick-product-system/packages/backend/src/services/project_workspace_service.test.ts +4 -4
  28. package/templates/assistkick-product-system/packages/backend/src/services/project_workspace_service.ts +87 -11
  29. package/templates/assistkick-product-system/packages/backend/src/services/pty_session_manager.test.ts +210 -69
  30. package/templates/assistkick-product-system/packages/backend/src/services/pty_session_manager.ts +210 -215
  31. package/templates/assistkick-product-system/packages/backend/src/services/ssh_key_service.test.ts +162 -0
  32. package/templates/assistkick-product-system/packages/backend/src/services/ssh_key_service.ts +148 -0
  33. package/templates/assistkick-product-system/packages/backend/src/services/terminal_ws_handler.ts +11 -5
  34. package/templates/assistkick-product-system/packages/backend/src/services/tts_service.test.ts +64 -0
  35. package/templates/assistkick-product-system/packages/backend/src/services/tts_service.ts +134 -0
  36. package/templates/assistkick-product-system/packages/backend/src/services/video_render_service.test.ts +256 -0
  37. package/templates/assistkick-product-system/packages/backend/src/services/video_render_service.ts +258 -0
  38. package/templates/assistkick-product-system/packages/backend/src/services/workflow_group_service.ts +106 -0
  39. package/templates/assistkick-product-system/packages/backend/src/services/workflow_service.test.ts +275 -0
  40. package/templates/assistkick-product-system/packages/backend/src/services/workflow_service.ts +245 -0
  41. package/templates/assistkick-product-system/packages/frontend/package-lock.json +3455 -0
  42. package/templates/assistkick-product-system/packages/frontend/package.json +6 -0
  43. package/templates/assistkick-product-system/packages/frontend/src/App.tsx +8 -0
  44. package/templates/assistkick-product-system/packages/frontend/src/api/client.ts +458 -18
  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 +20 -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 +187 -56
  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/IterationCommentModal.tsx +80 -0
  62. package/templates/assistkick-product-system/packages/frontend/src/components/KanbanView.tsx +263 -167
  63. package/templates/assistkick-product-system/packages/frontend/src/components/LoginPage.tsx +14 -14
  64. package/templates/assistkick-product-system/packages/frontend/src/components/ProjectSelector.tsx +54 -33
  65. package/templates/assistkick-product-system/packages/frontend/src/components/QaIssueSheet.tsx +32 -49
  66. package/templates/assistkick-product-system/packages/frontend/src/components/SidePanel.tsx +43 -48
  67. package/templates/assistkick-product-system/packages/frontend/src/components/TerminalView.tsx +121 -52
  68. package/templates/assistkick-product-system/packages/frontend/src/components/Toolbar.tsx +20 -14
  69. package/templates/assistkick-product-system/packages/frontend/src/components/UsersView.tsx +52 -52
  70. package/templates/assistkick-product-system/packages/frontend/src/components/VideoGallery.tsx +313 -0
  71. package/templates/assistkick-product-system/packages/frontend/src/components/VideographyView.tsx +250 -0
  72. package/templates/assistkick-product-system/packages/frontend/src/components/WorkflowsView.tsx +474 -0
  73. package/templates/assistkick-product-system/packages/frontend/src/components/ds/AccentBorderList.tsx +53 -0
  74. package/templates/assistkick-product-system/packages/frontend/src/components/ds/Button.tsx +87 -0
  75. package/templates/assistkick-product-system/packages/frontend/src/components/ds/ButtonGroup.tsx +29 -0
  76. package/templates/assistkick-product-system/packages/frontend/src/components/ds/ButtonShowcase.tsx +221 -0
  77. package/templates/assistkick-product-system/packages/frontend/src/components/ds/CardGlass.tsx +141 -0
  78. package/templates/assistkick-product-system/packages/frontend/src/components/ds/CompletionRing.tsx +30 -0
  79. package/templates/assistkick-product-system/packages/frontend/src/components/ds/ContentCard.tsx +34 -0
  80. package/templates/assistkick-product-system/packages/frontend/src/components/ds/IconButton.tsx +74 -0
  81. package/templates/assistkick-product-system/packages/frontend/src/components/ds/KanbanCard.tsx +103 -87
  82. package/templates/assistkick-product-system/packages/frontend/src/components/ds/KanbanCardShowcase.tsx +9 -188
  83. package/templates/assistkick-product-system/packages/frontend/src/components/ds/Kbd.tsx +11 -0
  84. package/templates/assistkick-product-system/packages/frontend/src/components/ds/KindBadge.tsx +21 -0
  85. package/templates/assistkick-product-system/packages/frontend/src/components/ds/NavBarSidekick.tsx +81 -37
  86. package/templates/assistkick-product-system/packages/frontend/src/components/ds/SidePanelShowcase.tsx +370 -0
  87. package/templates/assistkick-product-system/packages/frontend/src/components/ds/SideSheet.tsx +64 -0
  88. package/templates/assistkick-product-system/packages/frontend/src/components/ds/StatusDot.tsx +18 -0
  89. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/CheckCardPositionNode.tsx +36 -0
  90. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/CheckCycleCountNode.tsx +60 -0
  91. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/EndNode.tsx +42 -0
  92. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/GenerateTTSNode.tsx +52 -0
  93. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/GroupNode.tsx +189 -0
  94. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/NodePalette.tsx +123 -0
  95. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/RebuildBundleNode.tsx +20 -0
  96. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/RenderVideoNode.tsx +72 -0
  97. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/RunAgentNode.tsx +51 -0
  98. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/SetCardMetadataNode.tsx +53 -0
  99. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/StartNode.tsx +18 -0
  100. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/TransitionCardNode.tsx +59 -0
  101. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/WorkflowCanvas.tsx +341 -0
  102. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/WorkflowMonitorModal.tsx +643 -0
  103. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/autoLayout.ts +103 -0
  104. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/edgeColors.ts +35 -0
  105. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/monitor_nodes.tsx +246 -0
  106. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/workflow_types.test.ts +119 -0
  107. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/workflow_types.ts +136 -0
  108. package/templates/assistkick-product-system/packages/frontend/src/constants/graph.ts +13 -11
  109. package/templates/assistkick-product-system/packages/frontend/src/hooks/useAutoSave.ts +75 -0
  110. package/templates/assistkick-product-system/packages/frontend/src/hooks/useToast.tsx +16 -3
  111. package/templates/assistkick-product-system/packages/frontend/src/pages/accept_invitation_page.tsx +30 -27
  112. package/templates/assistkick-product-system/packages/frontend/src/pages/forgot_password_page.tsx +18 -15
  113. package/templates/assistkick-product-system/packages/frontend/src/pages/register_page.tsx +21 -18
  114. package/templates/assistkick-product-system/packages/frontend/src/pages/reset_password_page.tsx +28 -25
  115. package/templates/assistkick-product-system/packages/frontend/src/routes/AgentsRoute.tsx +6 -0
  116. package/templates/assistkick-product-system/packages/frontend/src/routes/CoherenceRoute.tsx +1 -1
  117. package/templates/assistkick-product-system/packages/frontend/src/routes/DashboardLayout.tsx +2 -2
  118. package/templates/assistkick-product-system/packages/frontend/src/routes/FilesRoute.tsx +13 -0
  119. package/templates/assistkick-product-system/packages/frontend/src/routes/GraphRoute.tsx +2 -2
  120. package/templates/assistkick-product-system/packages/frontend/src/routes/VideographyRoute.tsx +13 -0
  121. package/templates/assistkick-product-system/packages/frontend/src/routes/WorkflowsRoute.tsx +6 -0
  122. package/templates/assistkick-product-system/packages/frontend/src/stores/useProjectStore.ts +6 -3
  123. package/templates/assistkick-product-system/packages/frontend/src/stores/useSidePanelStore.ts +4 -4
  124. package/templates/assistkick-product-system/packages/frontend/src/styles/index.css +275 -3535
  125. package/templates/assistkick-product-system/packages/frontend/src/utils/auto_save_service.test.ts +167 -0
  126. package/templates/assistkick-product-system/packages/frontend/src/utils/auto_save_service.ts +101 -0
  127. package/templates/assistkick-product-system/packages/frontend/src/utils/composition_matcher.test.ts +42 -0
  128. package/templates/assistkick-product-system/packages/frontend/src/utils/composition_matcher.ts +17 -0
  129. package/templates/assistkick-product-system/packages/frontend/src/utils/file_utils.test.ts +145 -0
  130. package/templates/assistkick-product-system/packages/frontend/src/utils/file_utils.ts +42 -0
  131. package/templates/assistkick-product-system/packages/frontend/src/utils/task_status.test.ts +4 -10
  132. package/templates/assistkick-product-system/packages/frontend/src/utils/task_status.ts +19 -1
  133. package/templates/assistkick-product-system/packages/frontend/vite.config.ts +5 -0
  134. package/templates/assistkick-product-system/packages/shared/db/local.db +0 -0
  135. package/templates/assistkick-product-system/packages/shared/db/migrations/0004_tidy_matthew_murdock.sql +9 -0
  136. package/templates/assistkick-product-system/packages/shared/db/migrations/0005_mysterious_falcon.sql +692 -0
  137. package/templates/assistkick-product-system/packages/shared/db/migrations/0006_next_venom.sql +9 -0
  138. package/templates/assistkick-product-system/packages/shared/db/migrations/0007_deep_barracuda.sql +39 -0
  139. package/templates/assistkick-product-system/packages/shared/db/migrations/0008_puzzling_hannibal_king.sql +1 -0
  140. package/templates/assistkick-product-system/packages/shared/db/migrations/0009_amused_beast.sql +8 -0
  141. package/templates/assistkick-product-system/packages/shared/db/migrations/0010_spotty_moira_mactaggert.sql +9 -0
  142. package/templates/assistkick-product-system/packages/shared/db/migrations/0011_goofy_snowbird.sql +3 -0
  143. package/templates/assistkick-product-system/packages/shared/db/migrations/0011_supreme_doctor_octopus.sql +3 -0
  144. package/templates/assistkick-product-system/packages/shared/db/migrations/0013_reflective_prowler.sql +15 -0
  145. package/templates/assistkick-product-system/packages/shared/db/migrations/0014_nifty_punisher.sql +15 -0
  146. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0004_snapshot.json +921 -0
  147. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0005_snapshot.json +1042 -0
  148. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0006_snapshot.json +1101 -0
  149. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0007_snapshot.json +1336 -0
  150. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0008_snapshot.json +1275 -0
  151. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0009_snapshot.json +1327 -0
  152. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0010_snapshot.json +1393 -0
  153. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0011_snapshot.json +1436 -0
  154. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0013_snapshot.json +1538 -0
  155. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0014_snapshot.json +1545 -0
  156. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/_journal.json +77 -0
  157. package/templates/assistkick-product-system/packages/shared/db/schema.ts +114 -0
  158. package/templates/assistkick-product-system/packages/shared/lib/claude-service.ts +32 -7
  159. package/templates/assistkick-product-system/packages/shared/lib/constants.ts +9 -0
  160. package/templates/assistkick-product-system/packages/shared/lib/git_workflow.ts +12 -4
  161. package/templates/assistkick-product-system/packages/shared/lib/graph.ts +5 -0
  162. package/templates/assistkick-product-system/packages/shared/lib/workflow_engine.test.ts +1999 -0
  163. package/templates/assistkick-product-system/packages/shared/lib/workflow_engine.ts +1437 -0
  164. package/templates/assistkick-product-system/packages/shared/lib/workflow_orchestrator.ts +211 -0
  165. package/templates/assistkick-product-system/packages/shared/tools/add_node.test.ts +43 -0
  166. package/templates/assistkick-product-system/packages/shared/tools/add_node.ts +13 -2
  167. package/templates/assistkick-product-system/packages/shared/tools/get_kanban.ts +1 -1
  168. package/templates/assistkick-product-system/packages/shared/tools/migrate_epics.test.ts +226 -0
  169. package/templates/assistkick-product-system/packages/shared/tools/migrate_epics.ts +251 -0
  170. package/templates/assistkick-product-system/packages/shared/tools/update_node.ts +2 -2
  171. package/templates/assistkick-product-system/packages/shared/utils/hello_workflow.test.ts +10 -0
  172. package/templates/assistkick-product-system/packages/shared/utils/hello_workflow.ts +6 -0
  173. package/templates/assistkick-product-system/packages/video/Root.tsx +85 -0
  174. package/templates/assistkick-product-system/packages/video/components/email_scene.tsx +231 -0
  175. package/templates/assistkick-product-system/packages/video/components/outro_scene.tsx +153 -0
  176. package/templates/assistkick-product-system/packages/video/components/part_divider.tsx +90 -0
  177. package/templates/assistkick-product-system/packages/video/components/scene.tsx +226 -0
  178. package/templates/assistkick-product-system/packages/video/components/theme.ts +22 -0
  179. package/templates/assistkick-product-system/packages/video/components/title_scene.tsx +169 -0
  180. package/templates/assistkick-product-system/packages/video/components/video_split_layout.tsx +84 -0
  181. package/templates/assistkick-product-system/packages/video/compositions/.gitkeep +0 -0
  182. package/templates/assistkick-product-system/packages/video/index.ts +4 -0
  183. package/templates/assistkick-product-system/packages/video/package.json +28 -0
  184. package/templates/assistkick-product-system/packages/video/remotion.config.ts +11 -0
  185. package/templates/assistkick-product-system/packages/video/scripts/process_script.test.ts +326 -0
  186. package/templates/assistkick-product-system/packages/video/scripts/process_script.ts +630 -0
  187. package/templates/assistkick-product-system/packages/video/style.css +1 -0
  188. package/templates/assistkick-product-system/packages/video/tsconfig.json +18 -0
  189. package/templates/assistkick-product-system/tests/graph_legend.test.ts +2 -1
  190. package/templates/assistkick-product-system/tests/video_render_service.test.ts +181 -0
  191. package/templates/assistkick-product-system/tests/web_terminal.test.ts +219 -455
  192. package/templates/assistkick-product-system/tests/workflow_integration.test.ts +341 -0
  193. package/templates/skills/assistkick-developer/SKILL.md +3 -0
  194. package/templates/skills/assistkick-developer/references/react_development_guidelines.md +225 -0
  195. package/templates/skills/product-system/graph.json +1890 -0
  196. package/templates/skills/product-system/kanban.json +304 -0
  197. package/templates/skills/product-system/nodes/comp_001.md +56 -0
  198. package/templates/skills/product-system/nodes/comp_002.md +57 -0
  199. package/templates/skills/product-system/nodes/data_001.md +51 -0
  200. package/templates/skills/product-system/nodes/data_002.md +40 -0
  201. package/templates/skills/product-system/nodes/data_004.md +38 -0
  202. package/templates/skills/product-system/nodes/dec_001.md +34 -0
  203. package/templates/skills/product-system/nodes/dec_016.md +32 -0
  204. package/templates/skills/product-system/nodes/feat_008.md +30 -0
  205. package/templates/skills/video-composition-agent/SKILL.md +232 -0
  206. 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
+ });