@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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@assistkick/create",
3
- "version": "1.6.0",
3
+ "version": "1.8.0",
4
4
  "description": "Scaffold assistkick-product-system into any project",
5
5
  "type": "module",
6
6
  "bin": {
@@ -14,7 +14,7 @@
14
14
  "build": "tsc",
15
15
  "prepare_templates": "bash scripts/prepare_templates.sh",
16
16
  "prepublishOnly": "bash scripts/prepare_templates.sh && pnpm build",
17
- "publish": "npm publish",
17
+ "npm-publish": "npm publish",
18
18
  "test": "tsx --test tests/**/*.test.ts"
19
19
  },
20
20
  "devDependencies": {
@@ -6,3 +6,4 @@ NODE_ENV=development
6
6
  RESEND_API_KEY=your-resend-api-key
7
7
  APP_BASE_URL=http://localhost:3000
8
8
  EMAIL_FROM=noreply@yourdomain.com
9
+ WORKSPACES_DIR=./worktrees
File without changes
@@ -7,12 +7,14 @@
7
7
  "scripts": {
8
8
  "dev": "concurrently -n backend,frontend -c blue,green \"pnpm --filter @assistkick/backend dev\" \"pnpm --filter @assistkick/frontend dev\"",
9
9
  "build": "pnpm -r build",
10
- "start": "tsx packages/backend/src/server.ts",
10
+ "start": "concurrently -n backend,frontend -c blue,green \"pnpm --filter @assistkick/backend start\" \"pnpm --filter @assistkick/frontend dev\"",
11
11
  "test": "tsx --test tests/**/*.test.ts",
12
12
  "clean": "pnpm -r clean",
13
13
  "db:migrate": "pnpm --filter @assistkick/shared db:migrate",
14
14
  "db:rollback": "pnpm --filter @assistkick/shared db:rollback",
15
- "db:generate": "pnpm --filter @assistkick/shared db:generate"
15
+ "db:generate": "pnpm --filter @assistkick/shared db:generate",
16
+ "remotion:studio": "pnpm --filter @assistkick/video studio",
17
+ "remotion:render": "pnpm --filter @assistkick/video render"
16
18
  },
17
19
  "pnpm": {
18
20
  "onlyBuiltDependencies": ["esbuild", "node-pty"]
@@ -12,6 +12,7 @@
12
12
  },
13
13
  "dependencies": {
14
14
  "@assistkick/shared": "workspace:*",
15
+ "@remotion/renderer": "^4.0.434",
15
16
  "bcryptjs": "^3.0.3",
16
17
  "cookie-parser": "^1.4.7",
17
18
  "cors": "^2.8.5",
@@ -21,6 +22,7 @@
21
22
  "gray-matter": "^4.0.3",
22
23
  "jose": "^6.1.3",
23
24
  "node-pty": "^1.1.0",
25
+ "remotion": "^4.0.434",
24
26
  "resend": "^6.9.3",
25
27
  "ws": "^8.19.0"
26
28
  },
@@ -0,0 +1,165 @@
1
+ /**
2
+ * Agent routes — CRUD endpoints for agent management.
3
+ * GET /api/agents — list agents (scope=global|project, projectId)
4
+ * GET /api/agents/:id — get single agent
5
+ * POST /api/agents — create agent
6
+ * PUT /api/agents/:id — update agent
7
+ * DELETE /api/agents/:id — delete agent (409 if assigned or default)
8
+ * POST /api/agents/:id/reset — reset default agent to original prompt
9
+ */
10
+
11
+ import { Router } from 'express';
12
+ import type { AgentService } from '../services/agent_service.js';
13
+
14
+ interface AgentRoutesDeps {
15
+ agentService: AgentService;
16
+ log: (tag: string, ...args: any[]) => void;
17
+ }
18
+
19
+ export const createAgentRoutes = ({ agentService, log }: AgentRoutesDeps): Router => {
20
+ const router: Router = Router();
21
+
22
+ // GET /api/agents?scope=global|project&projectId=...
23
+ router.get('/', async (req, res) => {
24
+ const scope = (req.query.scope as string) || 'global';
25
+ const projectId = req.query.projectId as string | undefined;
26
+ log('AGENTS', `GET /api/agents scope=${scope} projectId=${projectId || 'none'}`);
27
+
28
+ if (scope !== 'global' && scope !== 'project') {
29
+ res.status(400).json({ error: 'scope must be "global" or "project"' });
30
+ return;
31
+ }
32
+
33
+ if (scope === 'project' && !projectId) {
34
+ res.status(400).json({ error: 'projectId is required for project scope' });
35
+ return;
36
+ }
37
+
38
+ try {
39
+ const agentList = await agentService.listAgents(scope, projectId);
40
+ res.json({ agents: agentList });
41
+ } catch (err: any) {
42
+ log('AGENTS', `List agents failed: ${err.message}`);
43
+ res.status(500).json({ error: 'Failed to list agents' });
44
+ }
45
+ });
46
+
47
+ // GET /api/agents/:id
48
+ router.get('/:id', async (req, res) => {
49
+ const { id } = req.params;
50
+ log('AGENTS', `GET /api/agents/${id}`);
51
+
52
+ try {
53
+ const agent = await agentService.getById(id);
54
+ if (!agent) {
55
+ res.status(404).json({ error: 'Agent not found' });
56
+ return;
57
+ }
58
+ res.json({ agent });
59
+ } catch (err: any) {
60
+ log('AGENTS', `Get agent failed: ${err.message}`);
61
+ res.status(500).json({ error: 'Failed to get agent' });
62
+ }
63
+ });
64
+
65
+ // POST /api/agents
66
+ router.post('/', async (req, res) => {
67
+ const { name, promptTemplate, projectId } = req.body;
68
+ log('AGENTS', `POST /api/agents name="${name}"`);
69
+
70
+ if (!name || typeof name !== 'string' || !name.trim()) {
71
+ res.status(400).json({ error: 'Agent name is required' });
72
+ return;
73
+ }
74
+
75
+ if (!promptTemplate || typeof promptTemplate !== 'string') {
76
+ res.status(400).json({ error: 'promptTemplate is required' });
77
+ return;
78
+ }
79
+
80
+ try {
81
+ const agent = await agentService.create({
82
+ name: name.trim(),
83
+ promptTemplate,
84
+ projectId: projectId || null,
85
+ });
86
+ res.status(201).json({ agent });
87
+ } catch (err: any) {
88
+ log('AGENTS', `Create agent failed: ${err.message}`);
89
+ res.status(500).json({ error: 'Failed to create agent' });
90
+ }
91
+ });
92
+
93
+ // PUT /api/agents/:id
94
+ router.put('/:id', async (req, res) => {
95
+ const { id } = req.params;
96
+ const { name, promptTemplate } = req.body;
97
+ log('AGENTS', `PUT /api/agents/${id}`);
98
+
99
+ if (name !== undefined && (typeof name !== 'string' || !name.trim())) {
100
+ res.status(400).json({ error: 'Agent name must be a non-empty string' });
101
+ return;
102
+ }
103
+
104
+ try {
105
+ const agent = await agentService.update(id, {
106
+ name: name?.trim(),
107
+ promptTemplate,
108
+ });
109
+ res.json({ agent });
110
+ } catch (err: any) {
111
+ log('AGENTS', `Update agent failed: ${err.message}`);
112
+ if (err.message === 'Agent not found') {
113
+ res.status(404).json({ error: err.message });
114
+ return;
115
+ }
116
+ res.status(500).json({ error: 'Failed to update agent' });
117
+ }
118
+ });
119
+
120
+ // DELETE /api/agents/:id
121
+ router.delete('/:id', async (req, res) => {
122
+ const { id } = req.params;
123
+ log('AGENTS', `DELETE /api/agents/${id}`);
124
+
125
+ try {
126
+ await agentService.delete(id);
127
+ res.json({ success: true });
128
+ } catch (err: any) {
129
+ log('AGENTS', `Delete agent failed: ${err.message}`);
130
+ if (err.message === 'Agent not found') {
131
+ res.status(404).json({ error: err.message });
132
+ return;
133
+ }
134
+ if (err.message === 'Cannot delete a default agent' || err.message === 'Cannot delete an agent that is assigned to a stage') {
135
+ res.status(409).json({ error: err.message });
136
+ return;
137
+ }
138
+ res.status(500).json({ error: 'Failed to delete agent' });
139
+ }
140
+ });
141
+
142
+ // POST /api/agents/:id/reset
143
+ router.post('/:id/reset', async (req, res) => {
144
+ const { id } = req.params;
145
+ log('AGENTS', `POST /api/agents/${id}/reset`);
146
+
147
+ try {
148
+ const agent = await agentService.resetToDefault(id);
149
+ res.json({ agent });
150
+ } catch (err: any) {
151
+ log('AGENTS', `Reset agent failed: ${err.message}`);
152
+ if (err.message === 'Agent not found') {
153
+ res.status(404).json({ error: err.message });
154
+ return;
155
+ }
156
+ if (err.message === 'Only default agents can be reset') {
157
+ res.status(400).json({ error: err.message });
158
+ return;
159
+ }
160
+ res.status(500).json({ error: 'Failed to reset agent' });
161
+ }
162
+ });
163
+
164
+ return router;
165
+ };
@@ -0,0 +1,358 @@
1
+ import { describe, it, beforeEach, afterEach, mock } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { mkdtemp, writeFile, mkdir, rm } from 'node:fs/promises';
4
+ import { join } from 'node:path';
5
+ import { tmpdir } from 'node:os';
6
+ import { existsSync } from 'node:fs';
7
+ import { createFileRoutes } from './files.ts';
8
+ import express from 'express';
9
+ import type { Server } from 'node:http';
10
+
11
+ /** Create a test app with file routes mounted at /api/projects/:id/files */
12
+ const createTestApp = (workspacePath: string) => {
13
+ const workspaceService = {
14
+ getWorkspacePath: (_projectId: string) => workspacePath,
15
+ } as any;
16
+ const log = mock.fn();
17
+ const app = express();
18
+ app.use(express.json());
19
+ const fileRoutes = createFileRoutes({ workspaceService, log });
20
+ app.use('/api/projects/:id/files', fileRoutes);
21
+ return { app, log };
22
+ };
23
+
24
+ /** Simple HTTP request helper using built-in fetch against an Express server */
25
+ const request = (server: Server) => {
26
+ const addr = server.address() as { port: number };
27
+ const base = `http://127.0.0.1:${addr.port}`;
28
+ return {
29
+ get: async (path: string) => {
30
+ const res = await fetch(`${base}${path}`);
31
+ return { status: res.status, body: await res.json() };
32
+ },
33
+ post: async (path: string, body: any) => {
34
+ const res = await fetch(`${base}${path}`, {
35
+ method: 'POST',
36
+ headers: { 'Content-Type': 'application/json' },
37
+ body: JSON.stringify(body),
38
+ });
39
+ return { status: res.status, body: await res.json() };
40
+ },
41
+ put: async (path: string, body: any) => {
42
+ const res = await fetch(`${base}${path}`, {
43
+ method: 'PUT',
44
+ headers: { 'Content-Type': 'application/json' },
45
+ body: JSON.stringify(body),
46
+ });
47
+ return { status: res.status, body: await res.json() };
48
+ },
49
+ delete: async (path: string) => {
50
+ const res = await fetch(`${base}${path}`, { method: 'DELETE' });
51
+ return { status: res.status, body: await res.json() };
52
+ },
53
+ patch: async (path: string, body: any) => {
54
+ const res = await fetch(`${base}${path}`, {
55
+ method: 'PATCH',
56
+ headers: { 'Content-Type': 'application/json' },
57
+ body: JSON.stringify(body),
58
+ });
59
+ return { status: res.status, body: await res.json() };
60
+ },
61
+ };
62
+ };
63
+
64
+ describe('File Routes', () => {
65
+ let tmpDir: string;
66
+ let server: Server;
67
+ let http: ReturnType<typeof request>;
68
+
69
+ beforeEach(async () => {
70
+ tmpDir = await mkdtemp(join(tmpdir(), 'file-routes-test-'));
71
+ const { app } = createTestApp(tmpDir);
72
+ server = await new Promise<Server>((resolve) => {
73
+ const s = app.listen(0, '127.0.0.1', () => resolve(s));
74
+ });
75
+ http = request(server);
76
+ });
77
+
78
+ afterEach(async () => {
79
+ await new Promise<void>((resolve) => server.close(() => resolve()));
80
+ await rm(tmpDir, { recursive: true, force: true });
81
+ });
82
+
83
+ describe('GET / — directory tree', () => {
84
+ it('returns empty tree for empty workspace', async () => {
85
+ const { status, body } = await http.get('/api/projects/proj_1/files');
86
+ assert.equal(status, 200);
87
+ assert.deepEqual(body.tree, []);
88
+ });
89
+
90
+ it('returns recursive tree with files and directories', async () => {
91
+ await mkdir(join(tmpDir, 'src'));
92
+ await writeFile(join(tmpDir, 'src', 'index.ts'), 'console.log("hello")');
93
+ await writeFile(join(tmpDir, 'README.md'), '# Hello');
94
+
95
+ const { status, body } = await http.get('/api/projects/proj_1/files');
96
+ assert.equal(status, 200);
97
+ assert.equal(body.tree.length, 2);
98
+
99
+ // Directories come first
100
+ const srcEntry = body.tree.find((e: any) => e.name === 'src');
101
+ assert.ok(srcEntry);
102
+ assert.equal(srcEntry.type, 'directory');
103
+ assert.equal(srcEntry.children.length, 1);
104
+ assert.equal(srcEntry.children[0].name, 'index.ts');
105
+ assert.equal(srcEntry.children[0].type, 'file');
106
+
107
+ const readmeEntry = body.tree.find((e: any) => e.name === 'README.md');
108
+ assert.ok(readmeEntry);
109
+ assert.equal(readmeEntry.type, 'file');
110
+ });
111
+
112
+ it('skips hidden files and directories', async () => {
113
+ await mkdir(join(tmpDir, '.git'));
114
+ await writeFile(join(tmpDir, '.gitignore'), 'node_modules');
115
+ await writeFile(join(tmpDir, 'visible.ts'), 'export {}');
116
+
117
+ const { status, body } = await http.get('/api/projects/proj_1/files');
118
+ assert.equal(status, 200);
119
+ assert.equal(body.tree.length, 1);
120
+ assert.equal(body.tree[0].name, 'visible.ts');
121
+ });
122
+ });
123
+
124
+ describe('GET /content — read file', () => {
125
+ it('returns file content and language', async () => {
126
+ await writeFile(join(tmpDir, 'hello.ts'), 'const x = 1;');
127
+ const { status, body } = await http.get('/api/projects/proj_1/files/content?path=hello.ts');
128
+ assert.equal(status, 200);
129
+ assert.equal(body.content, 'const x = 1;');
130
+ assert.equal(body.language, 'typescript');
131
+ });
132
+
133
+ it('returns 404 for non-existent file', async () => {
134
+ const { status, body } = await http.get('/api/projects/proj_1/files/content?path=missing.ts');
135
+ assert.equal(status, 404);
136
+ assert.equal(body.error, 'File not found');
137
+ });
138
+
139
+ it('returns 400 for path traversal', async () => {
140
+ const { status, body } = await http.get('/api/projects/proj_1/files/content?path=../../../etc/passwd');
141
+ assert.equal(status, 400);
142
+ assert.equal(body.error, 'Invalid path');
143
+ });
144
+
145
+ it('returns 400 when path is missing', async () => {
146
+ const { status } = await http.get('/api/projects/proj_1/files/content');
147
+ assert.equal(status, 400);
148
+ });
149
+
150
+ it('returns binary flag for binary files', async () => {
151
+ await writeFile(join(tmpDir, 'image.png'), Buffer.from([0x89, 0x50, 0x4e, 0x47]));
152
+ const { status, body } = await http.get('/api/projects/proj_1/files/content?path=image.png');
153
+ assert.equal(status, 200);
154
+ assert.equal(body.binary, true);
155
+ assert.equal(typeof body.size, 'number');
156
+ assert.equal(body.content, undefined);
157
+ });
158
+
159
+ it('infers language from extension', async () => {
160
+ await writeFile(join(tmpDir, 'style.css'), 'body {}');
161
+ const { status, body } = await http.get('/api/projects/proj_1/files/content?path=style.css');
162
+ assert.equal(status, 200);
163
+ assert.equal(body.language, 'css');
164
+ });
165
+ });
166
+
167
+ describe('PUT /content — write file', () => {
168
+ it('writes content to an existing file', async () => {
169
+ await writeFile(join(tmpDir, 'test.ts'), 'old content');
170
+ const { status, body } = await http.put('/api/projects/proj_1/files/content', {
171
+ path: 'test.ts',
172
+ content: 'new content',
173
+ });
174
+ assert.equal(status, 200);
175
+ assert.equal(body.success, true);
176
+
177
+ // Verify content was written
178
+ const readRes = await http.get('/api/projects/proj_1/files/content?path=test.ts');
179
+ assert.equal(readRes.body.content, 'new content');
180
+ });
181
+
182
+ it('returns 404 for non-existent file', async () => {
183
+ const { status, body } = await http.put('/api/projects/proj_1/files/content', {
184
+ path: 'nonexistent.ts',
185
+ content: 'hello',
186
+ });
187
+ assert.equal(status, 404);
188
+ assert.equal(body.error, 'File not found');
189
+ });
190
+
191
+ it('returns 400 for path traversal', async () => {
192
+ const { status, body } = await http.put('/api/projects/proj_1/files/content', {
193
+ path: '../evil.ts',
194
+ content: 'hack',
195
+ });
196
+ assert.equal(status, 400);
197
+ assert.equal(body.error, 'Invalid path');
198
+ });
199
+
200
+ it('returns 400 when path or content is missing', async () => {
201
+ const { status } = await http.put('/api/projects/proj_1/files/content', { path: 'test.ts' });
202
+ assert.equal(status, 400);
203
+ });
204
+ });
205
+
206
+ describe('POST / — create file or folder', () => {
207
+ it('creates a new file', async () => {
208
+ const { status, body } = await http.post('/api/projects/proj_1/files', {
209
+ path: 'new_file.ts',
210
+ type: 'file',
211
+ });
212
+ assert.equal(status, 200);
213
+ assert.equal(body.success, true);
214
+ assert.ok(existsSync(join(tmpDir, 'new_file.ts')));
215
+ });
216
+
217
+ it('creates a new directory', async () => {
218
+ const { status, body } = await http.post('/api/projects/proj_1/files', {
219
+ path: 'new_dir',
220
+ type: 'directory',
221
+ });
222
+ assert.equal(status, 200);
223
+ assert.equal(body.success, true);
224
+ assert.ok(existsSync(join(tmpDir, 'new_dir')));
225
+ });
226
+
227
+ it('creates nested file with parent directories', async () => {
228
+ const { status, body } = await http.post('/api/projects/proj_1/files', {
229
+ path: 'deep/nested/file.ts',
230
+ type: 'file',
231
+ });
232
+ assert.equal(status, 200);
233
+ assert.equal(body.success, true);
234
+ assert.ok(existsSync(join(tmpDir, 'deep', 'nested', 'file.ts')));
235
+ });
236
+
237
+ it('returns 400 when path already exists', async () => {
238
+ await writeFile(join(tmpDir, 'existing.ts'), 'content');
239
+ const { status, body } = await http.post('/api/projects/proj_1/files', {
240
+ path: 'existing.ts',
241
+ type: 'file',
242
+ });
243
+ assert.equal(status, 400);
244
+ assert.equal(body.error, 'Path already exists');
245
+ });
246
+
247
+ it('returns 400 for path traversal', async () => {
248
+ const { status, body } = await http.post('/api/projects/proj_1/files', {
249
+ path: '../evil.ts',
250
+ type: 'file',
251
+ });
252
+ assert.equal(status, 400);
253
+ assert.equal(body.error, 'Invalid path');
254
+ });
255
+
256
+ it('returns 400 for invalid type', async () => {
257
+ const { status } = await http.post('/api/projects/proj_1/files', {
258
+ path: 'test.ts',
259
+ type: 'symlink',
260
+ });
261
+ assert.equal(status, 400);
262
+ });
263
+ });
264
+
265
+ describe('DELETE / — delete file or folder', () => {
266
+ it('deletes a file', async () => {
267
+ await writeFile(join(tmpDir, 'to_delete.ts'), 'bye');
268
+ const { status, body } = await http.delete('/api/projects/proj_1/files?path=to_delete.ts');
269
+ assert.equal(status, 200);
270
+ assert.equal(body.success, true);
271
+ assert.ok(!existsSync(join(tmpDir, 'to_delete.ts')));
272
+ });
273
+
274
+ it('deletes a directory recursively', async () => {
275
+ await mkdir(join(tmpDir, 'dir_to_delete', 'sub'), { recursive: true });
276
+ await writeFile(join(tmpDir, 'dir_to_delete', 'sub', 'file.ts'), 'nested');
277
+ const { status, body } = await http.delete('/api/projects/proj_1/files?path=dir_to_delete');
278
+ assert.equal(status, 200);
279
+ assert.equal(body.success, true);
280
+ assert.ok(!existsSync(join(tmpDir, 'dir_to_delete')));
281
+ });
282
+
283
+ it('returns 404 for non-existent file', async () => {
284
+ const { status, body } = await http.delete('/api/projects/proj_1/files?path=ghost.ts');
285
+ assert.equal(status, 404);
286
+ assert.equal(body.error, 'File not found');
287
+ });
288
+
289
+ it('returns 400 for path traversal', async () => {
290
+ const { status, body } = await http.delete('/api/projects/proj_1/files?path=../evil');
291
+ assert.equal(status, 400);
292
+ assert.equal(body.error, 'Invalid path');
293
+ });
294
+
295
+ it('returns 400 when path is missing', async () => {
296
+ const { status } = await http.delete('/api/projects/proj_1/files');
297
+ assert.equal(status, 400);
298
+ });
299
+ });
300
+
301
+ describe('PATCH / — rename/move', () => {
302
+ it('renames a file', async () => {
303
+ await writeFile(join(tmpDir, 'old_name.ts'), 'content');
304
+ const { status, body } = await http.patch('/api/projects/proj_1/files', {
305
+ oldPath: 'old_name.ts',
306
+ newPath: 'new_name.ts',
307
+ });
308
+ assert.equal(status, 200);
309
+ assert.equal(body.success, true);
310
+ assert.ok(!existsSync(join(tmpDir, 'old_name.ts')));
311
+ assert.ok(existsSync(join(tmpDir, 'new_name.ts')));
312
+ });
313
+
314
+ it('moves a file to a new directory', async () => {
315
+ await writeFile(join(tmpDir, 'movable.ts'), 'content');
316
+ const { status, body } = await http.patch('/api/projects/proj_1/files', {
317
+ oldPath: 'movable.ts',
318
+ newPath: 'subdir/movable.ts',
319
+ });
320
+ assert.equal(status, 200);
321
+ assert.equal(body.success, true);
322
+ assert.ok(existsSync(join(tmpDir, 'subdir', 'movable.ts')));
323
+ });
324
+
325
+ it('returns 404 when source does not exist', async () => {
326
+ const { status, body } = await http.patch('/api/projects/proj_1/files', {
327
+ oldPath: 'nonexistent.ts',
328
+ newPath: 'target.ts',
329
+ });
330
+ assert.equal(status, 404);
331
+ assert.equal(body.error, 'File not found');
332
+ });
333
+
334
+ it('returns 400 for path traversal in oldPath', async () => {
335
+ const { status, body } = await http.patch('/api/projects/proj_1/files', {
336
+ oldPath: '../evil.ts',
337
+ newPath: 'safe.ts',
338
+ });
339
+ assert.equal(status, 400);
340
+ assert.equal(body.error, 'Invalid path');
341
+ });
342
+
343
+ it('returns 400 for path traversal in newPath', async () => {
344
+ await writeFile(join(tmpDir, 'safe.ts'), 'content');
345
+ const { status, body } = await http.patch('/api/projects/proj_1/files', {
346
+ oldPath: 'safe.ts',
347
+ newPath: '../evil.ts',
348
+ });
349
+ assert.equal(status, 400);
350
+ assert.equal(body.error, 'Invalid path');
351
+ });
352
+
353
+ it('returns 400 when oldPath or newPath is missing', async () => {
354
+ const { status } = await http.patch('/api/projects/proj_1/files', { oldPath: 'test.ts' });
355
+ assert.equal(status, 400);
356
+ });
357
+ });
358
+ });