@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,182 @@
1
+ /**
2
+ * BundleService — manages the Remotion bundle for the video package.
3
+ *
4
+ * Responsibilities:
5
+ * - Build the Remotion bundle from the video package (via @remotion/bundler)
6
+ * - Track bundle status (ready, building, last built timestamp)
7
+ * - Scan the compositions directory for available compositions and metadata
8
+ * - Serve bundle path for static file serving
9
+ */
10
+
11
+ import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
12
+ import { join } from 'node:path';
13
+ import { execFile } from 'node:child_process';
14
+
15
+ interface BundleServiceDeps {
16
+ videoPackageDir: string;
17
+ bundleOutputDir: string;
18
+ log: (tag: string, ...args: any[]) => void;
19
+ }
20
+
21
+ export interface CompositionMeta {
22
+ id: string;
23
+ durationInFrames: number;
24
+ fps: number;
25
+ width: number;
26
+ height: number;
27
+ folder?: string;
28
+ }
29
+
30
+ export interface BundleStatus {
31
+ ready: boolean;
32
+ building: boolean;
33
+ bundlePath: string | null;
34
+ lastBuiltAt: string | null;
35
+ error: string | null;
36
+ }
37
+
38
+ export class BundleService {
39
+ private readonly videoPackageDir: string;
40
+ private readonly bundleOutputDir: string;
41
+ private readonly log: BundleServiceDeps['log'];
42
+ private building = false;
43
+ private lastBuiltAt: string | null = null;
44
+ private lastError: string | null = null;
45
+
46
+ constructor({ videoPackageDir, bundleOutputDir, log }: BundleServiceDeps) {
47
+ this.videoPackageDir = videoPackageDir;
48
+ this.bundleOutputDir = bundleOutputDir;
49
+ this.log = log;
50
+ }
51
+
52
+ /** Returns the directory path where the built bundle is stored. */
53
+ getBundleDir = (): string => {
54
+ return this.bundleOutputDir;
55
+ };
56
+
57
+ /** Check if a bundle has been built and exists on disk. */
58
+ isBundleReady = (): boolean => {
59
+ return existsSync(join(this.bundleOutputDir, 'index.html'));
60
+ };
61
+
62
+ /** Returns current bundle status. */
63
+ getStatus = (): BundleStatus => {
64
+ return {
65
+ ready: this.isBundleReady(),
66
+ building: this.building,
67
+ bundlePath: this.isBundleReady() ? this.bundleOutputDir : null,
68
+ lastBuiltAt: this.lastBuiltAt,
69
+ error: this.lastError,
70
+ };
71
+ };
72
+
73
+ /**
74
+ * Build the Remotion bundle from the video package.
75
+ * Uses the Remotion CLI to create a webpack bundle.
76
+ */
77
+ buildBundle = async (): Promise<BundleStatus> => {
78
+ if (this.building) {
79
+ this.log('BUNDLE', 'Bundle build already in progress');
80
+ return this.getStatus();
81
+ }
82
+
83
+ this.building = true;
84
+ this.lastError = null;
85
+ this.log('BUNDLE', `Building Remotion bundle from ${this.videoPackageDir}`);
86
+
87
+ try {
88
+ await this.spawnBundler();
89
+ this.lastBuiltAt = new Date().toISOString();
90
+ this.log('BUNDLE', `Bundle built successfully at ${this.lastBuiltAt}`);
91
+ } catch (err: any) {
92
+ this.lastError = err.message;
93
+ this.log('BUNDLE', `Bundle build failed: ${err.message}`);
94
+ } finally {
95
+ this.building = false;
96
+ }
97
+
98
+ return this.getStatus();
99
+ };
100
+
101
+ /**
102
+ * Scan the video package's compositions/ directory and extract metadata
103
+ * from each composition's index.tsx file.
104
+ */
105
+ listCompositions = (): CompositionMeta[] => {
106
+ const compositionsDir = join(this.videoPackageDir, 'compositions');
107
+ if (!existsSync(compositionsDir)) {
108
+ return [];
109
+ }
110
+
111
+ const entries = readdirSync(compositionsDir);
112
+ const compositions: CompositionMeta[] = [];
113
+
114
+ for (const entry of entries) {
115
+ const entryPath = join(compositionsDir, entry);
116
+ if (!statSync(entryPath).isDirectory()) continue;
117
+
118
+ const indexPath = join(entryPath, 'index.tsx');
119
+ if (!existsSync(indexPath)) continue;
120
+
121
+ const meta = this.parseCompositionMeta(indexPath);
122
+ if (meta) {
123
+ compositions.push(meta);
124
+ }
125
+ }
126
+
127
+ return compositions;
128
+ };
129
+
130
+ /**
131
+ * Parse a composition's index.tsx file to extract exported metadata.
132
+ * Uses regex to find exported const values for id, fps, width, height, etc.
133
+ */
134
+ private parseCompositionMeta = (filePath: string): CompositionMeta | null => {
135
+ try {
136
+ const content = readFileSync(filePath, 'utf-8');
137
+
138
+ const idMatch = content.match(/export\s+const\s+id\s*=\s*['"]([^'"]+)['"]/);
139
+ if (!idMatch) return null;
140
+
141
+ const durationMatch = content.match(/export\s+const\s+durationInFrames\s*=\s*(\d+)/);
142
+ const fpsMatch = content.match(/export\s+const\s+fps\s*=\s*(\d+)/);
143
+ const widthMatch = content.match(/export\s+const\s+width\s*=\s*(\d+)/);
144
+ const heightMatch = content.match(/export\s+const\s+height\s*=\s*(\d+)/);
145
+ const folderMatch = content.match(/export\s+const\s+folder\s*=\s*['"]([^'"]+)['"]/);
146
+
147
+ return {
148
+ id: idMatch[1],
149
+ durationInFrames: durationMatch ? parseInt(durationMatch[1], 10) : 300,
150
+ fps: fpsMatch ? parseInt(fpsMatch[1], 10) : 30,
151
+ width: widthMatch ? parseInt(widthMatch[1], 10) : 1920,
152
+ height: heightMatch ? parseInt(heightMatch[1], 10) : 1080,
153
+ folder: folderMatch ? folderMatch[1] : undefined,
154
+ };
155
+ } catch {
156
+ return null;
157
+ }
158
+ };
159
+
160
+ private spawnBundler = (): Promise<void> => {
161
+ return new Promise((resolve, reject) => {
162
+ const entryPoint = join(this.videoPackageDir, 'index.ts');
163
+ execFile('npx', [
164
+ 'remotion', 'bundle',
165
+ entryPoint,
166
+ '--out-dir', this.bundleOutputDir,
167
+ '--log', 'warn',
168
+ ], {
169
+ cwd: this.videoPackageDir,
170
+ timeout: 300_000,
171
+ maxBuffer: 10 * 1024 * 1024,
172
+ env: { ...process.env },
173
+ }, (error, _stdout, stderr) => {
174
+ if (error) {
175
+ reject(new Error(`Remotion bundle failed: ${error.message}${stderr ? `\n${stderr}` : ''}`));
176
+ return;
177
+ }
178
+ resolve();
179
+ });
180
+ });
181
+ };
182
+ }
@@ -1,24 +1,25 @@
1
1
  /**
2
- * Service initialization — creates pipeline, claude service, and related instances.
2
+ * Service initialization — creates workflow engine, claude service, and related instances.
3
3
  * Deferred until VERBOSE flag is set at startup.
4
4
  */
5
5
 
6
6
  import { join, dirname } from 'node:path';
7
7
  import { fileURLToPath } from 'node:url';
8
- import { existsSync } from 'node:fs';
9
8
  import { createClaudeService } from '@assistkick/shared/lib/claude-service.js';
10
- import { Pipeline } from '@assistkick/shared/lib/pipeline.js';
11
- import { PipelineStateStore } from '@assistkick/shared/lib/pipeline-state-store.js';
12
- import { PromptBuilder } from '@assistkick/shared/lib/prompt_builder.js';
13
9
  import { GitWorkflow } from '@assistkick/shared/lib/git_workflow.js';
14
10
  import { getDb } from '@assistkick/shared/lib/db.js';
15
11
  import { loadKanban, getKanbanEntry, saveKanbanEntry } from '@assistkick/shared/lib/kanban.js';
16
- import { readGraph, getNode } from '@assistkick/shared/lib/graph.js';
17
- import { WorkSummaryParser } from '@assistkick/shared/lib/work_summary_parser.js';
18
- import { PipelineOrchestrator } from '@assistkick/shared/lib/pipeline_orchestrator.js';
12
+ import { readGraph } from '@assistkick/shared/lib/graph.js';
13
+ import { WorkflowEngine } from '@assistkick/shared/lib/workflow_engine.js';
14
+ import { WorkflowOrchestrator } from '@assistkick/shared/lib/workflow_orchestrator.js';
15
+ import { WorkflowService } from './workflow_service.js';
16
+ import { WorkflowGroupService } from './workflow_group_service.js';
19
17
  import { GitHubAppService } from './github_app_service.js';
20
18
  import { ProjectWorkspaceService } from './project_workspace_service.js';
21
- import { ProjectService } from './project_service.js';
19
+ import { SshKeyService } from './ssh_key_service.js';
20
+ import { BundleService } from './bundle_service.js';
21
+ import { TtsService } from './tts_service.js';
22
+ import { VideoRenderService } from './video_render_service.js';
22
23
 
23
24
  const __dirname = dirname(fileURLToPath(import.meta.url));
24
25
  // Navigate from packages/backend/src/services/ up to assistkick-product-system/
@@ -32,10 +33,13 @@ const TOOLS_DIR = IS_DEV ? join(SHARED_DIR, 'tools') : join(__dirname, '..', '..
32
33
  const DATA_DIR = SHARED_DIR;
33
34
  const PROJECT_ROOT = join(SKILL_ROOT, '..');
34
35
  const SKILLS_DIR = join(PROJECT_ROOT, '.claude', 'skills');
36
+ const WORKSPACES_DIR = process.env.WORKSPACES_DIR || join(PROJECT_ROOT, 'workspaces');
35
37
  const WORKTREES_DIR = join(PROJECT_ROOT, '.worktrees');
36
38
  const DEVELOPER_SKILL_PATH = join(SKILLS_DIR, 'product-developer', 'SKILL.md');
37
39
  const REVIEWER_SKILL_PATH = join(SKILLS_DIR, 'product-code-reviewer', 'SKILL.md');
38
40
  const DEBUGGER_SKILL_PATH = join(SKILLS_DIR, 'product-debugger', 'SKILL.md');
41
+ const VIDEO_PACKAGE_DIR = join(SKILL_ROOT, 'video');
42
+ const BUNDLE_OUTPUT_DIR = process.env.REMOTION_BUNDLE_DIR || join(PROJECT_ROOT, 'data', 'remotion-bundle');
39
43
 
40
44
  export const log = (tag: string, ...args: any[]) => {
41
45
  const ts = new Date().toISOString().slice(11, 23);
@@ -43,14 +47,20 @@ export const log = (tag: string, ...args: any[]) => {
43
47
  };
44
48
 
45
49
  export let claudeService: any;
46
- export let pipeline: any;
47
- export let pipelineStateStore: any;
48
- export let orchestrator: PipelineOrchestrator;
50
+ export let workflowEngine: WorkflowEngine;
51
+ export let orchestrator: WorkflowOrchestrator;
52
+ export let workflowService: WorkflowService;
53
+ export let workflowGroupService: WorkflowGroupService;
49
54
  export let githubAppService: GitHubAppService;
55
+ export let sshKeyService: SshKeyService;
50
56
  export let workspaceService: ProjectWorkspaceService;
57
+ export let bundleService: BundleService;
58
+ export let ttsService: TtsService;
59
+ export let videoRenderService: VideoRenderService;
51
60
 
52
61
  export const paths = {
53
62
  projectRoot: PROJECT_ROOT,
63
+ workspacesDir: WORKSPACES_DIR,
54
64
  worktreesDir: WORKTREES_DIR,
55
65
  skillsDir: SKILLS_DIR,
56
66
  toolsDir: TOOLS_DIR,
@@ -64,84 +74,40 @@ export const paths = {
64
74
 
65
75
  export const initServices = (verbose: boolean) => {
66
76
  claudeService = createClaudeService({ verbose, log });
67
- pipelineStateStore = new PipelineStateStore({ getDb });
68
- const promptBuilder = new PromptBuilder({ paths, log });
69
77
  const gitWorkflow = new GitWorkflow({ claudeService, projectRoot: PROJECT_ROOT, worktreesDir: WORKTREES_DIR, log });
70
- const workSummaryParser = new WorkSummaryParser();
71
78
 
72
79
  // New services for project git repo integration
73
80
  githubAppService = new GitHubAppService({ log });
74
- workspaceService = new ProjectWorkspaceService({ claudeService, projectRoot: PROJECT_ROOT, log });
75
- const projectService = new ProjectService({ getDb, log });
81
+ sshKeyService = new SshKeyService({ log });
82
+ workspaceService = new ProjectWorkspaceService({ claudeService, workspacesDir: WORKSPACES_DIR, log, sshKeyService });
76
83
 
77
- /**
78
- * Resolve a project-scoped workspace for the pipeline.
79
- * Returns a GitWorkflow pointing to the project workspace + an afterMerge hook
80
- * that pushes to the remote. Returns null if no workspace is configured.
81
- */
82
- const resolveProjectWorkspace = async (projectId: string) => {
83
- const project = await projectService.getById(projectId);
84
- if (!project) return null;
84
+ // Workflow service for CRUD + default resolution
85
+ workflowService = new WorkflowService({ getDb, log });
86
+ workflowGroupService = new WorkflowGroupService({ getDb, log });
85
87
 
86
- // Check if project has a workspace
87
- if (!workspaceService.hasWorkspace(projectId)) {
88
- // No workspace use default behavior
89
- return null;
90
- }
88
+ // Video services for workflow video node types
89
+ bundleService = new BundleService({ videoPackageDir: VIDEO_PACKAGE_DIR, bundleOutputDir: BUNDLE_OUTPUT_DIR, log });
90
+ ttsService = new TtsService({ workspacesDir: WORKSPACES_DIR, videoPackageDir: VIDEO_PACKAGE_DIR, log });
91
+ videoRenderService = new VideoRenderService({ getDb, workspacesDir: WORKSPACES_DIR, bundleOutputDir: BUNDLE_OUTPUT_DIR, log });
91
92
 
92
- const wsPath = workspaceService.getWorkspacePath(projectId);
93
- const wsWorktreesDir = workspaceService.getWorktreesDir(projectId);
94
-
95
- const projectGitWorkflow = new GitWorkflow({
96
- claudeService,
97
- projectRoot: wsPath,
98
- worktreesDir: wsWorktreesDir,
99
- log,
100
- });
101
-
102
- // Build afterMerge hook for pushing to remote
103
- let afterMerge = null;
104
- if (project.repoUrl && project.githubInstallationId && project.githubRepoFullName) {
105
- afterMerge = async () => {
106
- // Get a fresh token for push
107
- const token = await githubAppService.getInstallationToken(project.githubInstallationId!);
108
- const authUrl = `https://x-access-token:${token}@github.com/${project.githubRepoFullName}.git`;
109
- await projectGitWorkflow.setRemoteUrl(authUrl);
110
- try {
111
- const branch = project.baseBranch || 'main';
112
- await projectGitWorkflow.pushToRemote(branch);
113
- } finally {
114
- // Reset to non-authenticated URL
115
- await projectGitWorkflow.setRemoteUrl(`https://github.com/${project.githubRepoFullName}.git`).catch(() => {});
116
- }
117
- };
118
- }
119
-
120
- return { gitWorkflow: projectGitWorkflow, afterMerge };
121
- };
122
-
123
- pipeline = new Pipeline({
124
- promptBuilder,
125
- gitWorkflow,
126
- claudeService,
93
+ // WorkflowEngine replaces Pipeline
94
+ workflowEngine = new WorkflowEngine({
95
+ db: getDb(),
127
96
  kanban: { getKanbanEntry, saveKanbanEntry },
128
- paths,
97
+ claudeService,
98
+ gitWorkflow,
99
+ bundleService,
100
+ ttsService,
101
+ videoRenderService,
129
102
  log,
130
- stateStore: pipelineStateStore,
131
- workSummaryParser,
132
- getNode,
133
- resolveProjectWorkspace,
134
103
  });
135
104
 
136
- orchestrator = new PipelineOrchestrator({
137
- pipeline,
105
+ // WorkflowOrchestrator replaces PipelineOrchestrator
106
+ orchestrator = new WorkflowOrchestrator({
107
+ workflowEngine,
138
108
  loadKanban,
139
109
  readGraph,
110
+ resolveDefaultWorkflow: (projectId?: string) => workflowService.resolveDefault(projectId),
140
111
  log,
141
112
  });
142
-
143
- // Mark any active pipeline states as interrupted (server restart recovery)
144
- pipelineStateStore.markInterrupted().catch((err: any) => {
145
- log('STARTUP', `Failed to mark interrupted pipelines: ${err.message}`);
146
- });
147
113
  };
@@ -118,12 +118,28 @@ describe('ProjectService', () => {
118
118
 
119
119
  assert.ok(result.id.startsWith('proj_'));
120
120
  assert.equal(result.name, 'New Project');
121
+ assert.equal(result.type, 'software');
121
122
  assert.equal(result.isDefault, 0);
122
123
  assert.equal(result.archivedAt, null);
123
124
  assert.ok(result.createdAt);
124
125
  assert.ok(result.updatedAt);
125
126
  assert.equal(db.insert.mock.calls.length, 1);
126
127
  });
128
+
129
+ it('creates a project with video type', async () => {
130
+ const result = await service.create('Video Project', 'video');
131
+
132
+ assert.ok(result.id.startsWith('proj_'));
133
+ assert.equal(result.name, 'Video Project');
134
+ assert.equal(result.type, 'video');
135
+ assert.equal(result.isDefault, 0);
136
+ });
137
+
138
+ it('defaults to software type when no type specified', async () => {
139
+ const result = await service.create('Default Type');
140
+
141
+ assert.equal(result.type, 'software');
142
+ });
127
143
  });
128
144
 
129
145
  describe('rename', () => {
@@ -15,12 +15,16 @@ interface ProjectServiceDeps {
15
15
  export interface ProjectRecord {
16
16
  id: string;
17
17
  name: string;
18
+ type: string;
18
19
  isDefault: number;
19
20
  archivedAt: string | null;
20
21
  repoUrl: string | null;
21
22
  githubInstallationId: string | null;
22
23
  githubRepoFullName: string | null;
23
24
  baseBranch: string | null;
25
+ gitAuthMethod: string | null;
26
+ sshPrivateKeyEncrypted: string | null;
27
+ sshPublicKey: string | null;
24
28
  createdAt: string;
25
29
  updatedAt: string;
26
30
  }
@@ -58,7 +62,7 @@ export class ProjectService {
58
62
  return row || null;
59
63
  };
60
64
 
61
- create = async (name: string): Promise<ProjectRecord> => {
65
+ create = async (name: string, type: string = 'software'): Promise<ProjectRecord> => {
62
66
  const db = this.getDb();
63
67
  const now = new Date().toISOString();
64
68
  const id = this.generateId();
@@ -66,18 +70,22 @@ export class ProjectService {
66
70
  const record: ProjectRecord = {
67
71
  id,
68
72
  name,
73
+ type,
69
74
  isDefault: 0,
70
75
  archivedAt: null,
71
76
  repoUrl: null,
72
77
  githubInstallationId: null,
73
78
  githubRepoFullName: null,
74
79
  baseBranch: null,
80
+ gitAuthMethod: null,
81
+ sshPrivateKeyEncrypted: null,
82
+ sshPublicKey: null,
75
83
  createdAt: now,
76
84
  updatedAt: now,
77
85
  };
78
86
 
79
87
  await db.insert(projects).values(record);
80
- this.log('PROJECTS', `Created project: ${name} (${id})`);
88
+ this.log('PROJECTS', `Created project: ${name} (${id}) type=${type}`);
81
89
  return record;
82
90
  };
83
91
 
@@ -195,6 +203,9 @@ export class ProjectService {
195
203
  githubInstallationId: null,
196
204
  githubRepoFullName: null,
197
205
  baseBranch: null,
206
+ gitAuthMethod: null,
207
+ sshPrivateKeyEncrypted: null,
208
+ sshPublicKey: null,
198
209
  updatedAt: now,
199
210
  })
200
211
  .where(eq(projects.id, id));
@@ -206,6 +217,66 @@ export class ProjectService {
206
217
  githubInstallationId: null,
207
218
  githubRepoFullName: null,
208
219
  baseBranch: null,
220
+ gitAuthMethod: null,
221
+ sshPrivateKeyEncrypted: null,
222
+ sshPublicKey: null,
223
+ updatedAt: now,
224
+ };
225
+ };
226
+
227
+ /** Store SSH key data and set auth method to 'ssh_key'. */
228
+ setSshKeys = async (id: string, opts: {
229
+ sshPrivateKeyEncrypted: string;
230
+ sshPublicKey: string;
231
+ }): Promise<ProjectRecord> => {
232
+ const db = this.getDb();
233
+ const existing = await this.getById(id);
234
+ if (!existing) throw new Error('Project not found');
235
+
236
+ const now = new Date().toISOString();
237
+ await db
238
+ .update(projects)
239
+ .set({
240
+ gitAuthMethod: 'ssh_key',
241
+ sshPrivateKeyEncrypted: opts.sshPrivateKeyEncrypted,
242
+ sshPublicKey: opts.sshPublicKey,
243
+ updatedAt: now,
244
+ })
245
+ .where(eq(projects.id, id));
246
+
247
+ this.log('PROJECTS', `Set SSH keys for project ${id}`);
248
+ return {
249
+ ...existing,
250
+ gitAuthMethod: 'ssh_key',
251
+ sshPrivateKeyEncrypted: opts.sshPrivateKeyEncrypted,
252
+ sshPublicKey: opts.sshPublicKey,
253
+ updatedAt: now,
254
+ };
255
+ };
256
+
257
+ /** Set auth method to 'github_app' and clear SSH keys. */
258
+ setGitHubAppAuth = async (id: string): Promise<ProjectRecord> => {
259
+ const db = this.getDb();
260
+ const existing = await this.getById(id);
261
+ if (!existing) throw new Error('Project not found');
262
+
263
+ const now = new Date().toISOString();
264
+ await db
265
+ .update(projects)
266
+ .set({
267
+ gitAuthMethod: 'github_app',
268
+ sshPrivateKeyEncrypted: null,
269
+ sshPublicKey: null,
270
+ updatedAt: now,
271
+ })
272
+ .where(eq(projects.id, id));
273
+
274
+ this.log('PROJECTS', `Set GitHub App auth for project ${id}`);
275
+ return {
276
+ ...existing,
277
+ gitAuthMethod: 'github_app',
278
+ sshPrivateKeyEncrypted: null,
279
+ sshPublicKey: null,
209
280
  updatedAt: now,
210
281
  };
211
282
  };
@@ -3,7 +3,7 @@ import assert from 'node:assert/strict';
3
3
  import { ProjectWorkspaceService } from './project_workspace_service.ts';
4
4
  import { join } from 'node:path';
5
5
 
6
- const PROJECT_ROOT = '/tmp/test-project-root';
6
+ const WORKSPACES_DIR = '/tmp/test-workspaces';
7
7
 
8
8
  const createMockClaudeService = () => ({
9
9
  spawnCommand: mock.fn(async () => ''),
@@ -12,7 +12,7 @@ const createMockClaudeService = () => ({
12
12
  const createService = (claudeService: any) => {
13
13
  return new ProjectWorkspaceService({
14
14
  claudeService,
15
- projectRoot: PROJECT_ROOT,
15
+ workspacesDir: WORKSPACES_DIR,
16
16
  log: mock.fn(),
17
17
  });
18
18
  };
@@ -29,14 +29,14 @@ describe('ProjectWorkspaceService', () => {
29
29
  describe('getWorkspacePath', () => {
30
30
  it('returns correct workspace path for a project', () => {
31
31
  const path = service.getWorkspacePath('proj_abc123');
32
- assert.equal(path, join(PROJECT_ROOT, 'workspaces', 'proj_abc123'));
32
+ assert.equal(path, join(WORKSPACES_DIR, 'proj_abc123'));
33
33
  });
34
34
  });
35
35
 
36
36
  describe('getWorktreesDir', () => {
37
37
  it('returns correct worktrees dir inside workspace', () => {
38
38
  const path = service.getWorktreesDir('proj_abc123');
39
- assert.equal(path, join(PROJECT_ROOT, 'workspaces', 'proj_abc123', '.worktrees'));
39
+ assert.equal(path, join(WORKSPACES_DIR, 'proj_abc123', '.worktrees'));
40
40
  });
41
41
  });
42
42