@assistkick/create 1.10.0 → 1.12.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 (209) hide show
  1. package/dist/src/scaffolder.d.ts +12 -1
  2. package/dist/src/scaffolder.js +40 -3
  3. package/dist/src/scaffolder.js.map +1 -1
  4. package/package.json +1 -1
  5. package/templates/assistkick-product-system/package.json +1 -1
  6. package/templates/assistkick-product-system/packages/backend/package.json +1 -0
  7. package/templates/assistkick-product-system/packages/backend/src/mcp/permission_mcp_server.ts +196 -0
  8. package/templates/assistkick-product-system/packages/backend/src/routes/agents.ts +31 -7
  9. package/templates/assistkick-product-system/packages/backend/src/routes/auth.ts +15 -12
  10. package/templates/assistkick-product-system/packages/backend/src/routes/chat_files.test.ts +95 -0
  11. package/templates/assistkick-product-system/packages/backend/src/routes/chat_files.ts +97 -0
  12. package/templates/assistkick-product-system/packages/backend/src/routes/chat_permission.ts +94 -0
  13. package/templates/assistkick-product-system/packages/backend/src/routes/chat_sessions.ts +189 -0
  14. package/templates/assistkick-product-system/packages/backend/src/routes/chat_upload.test.ts +131 -0
  15. package/templates/assistkick-product-system/packages/backend/src/routes/chat_upload.ts +94 -0
  16. package/templates/assistkick-product-system/packages/backend/src/routes/files.test.ts +12 -3
  17. package/templates/assistkick-product-system/packages/backend/src/routes/files.ts +2 -2
  18. package/templates/assistkick-product-system/packages/backend/src/routes/git.ts +390 -22
  19. package/templates/assistkick-product-system/packages/backend/src/routes/git_branches.test.ts +306 -0
  20. package/templates/assistkick-product-system/packages/backend/src/routes/git_connect.test.ts +133 -0
  21. package/templates/assistkick-product-system/packages/backend/src/routes/pipeline.ts +66 -9
  22. package/templates/assistkick-product-system/packages/backend/src/routes/preview.ts +204 -0
  23. package/templates/assistkick-product-system/packages/backend/src/routes/projects.test.ts +205 -0
  24. package/templates/assistkick-product-system/packages/backend/src/routes/projects.ts +37 -9
  25. package/templates/assistkick-product-system/packages/backend/src/routes/skills.test.ts +139 -0
  26. package/templates/assistkick-product-system/packages/backend/src/routes/skills.ts +95 -0
  27. package/templates/assistkick-product-system/packages/backend/src/routes/terminal.ts +5 -4
  28. package/templates/assistkick-product-system/packages/backend/src/routes/users.ts +4 -4
  29. package/templates/assistkick-product-system/packages/backend/src/routes/video.ts +8 -8
  30. package/templates/assistkick-product-system/packages/backend/src/routes/workflow_groups.ts +5 -5
  31. package/templates/assistkick-product-system/packages/backend/src/routes/workflows.ts +6 -6
  32. package/templates/assistkick-product-system/packages/backend/src/server.ts +107 -27
  33. package/templates/assistkick-product-system/packages/backend/src/services/agent_service.test.ts +105 -203
  34. package/templates/assistkick-product-system/packages/backend/src/services/agent_service.ts +76 -266
  35. package/templates/assistkick-product-system/packages/backend/src/services/chat_cli_bridge.test.ts +427 -0
  36. package/templates/assistkick-product-system/packages/backend/src/services/chat_cli_bridge.ts +345 -0
  37. package/templates/assistkick-product-system/packages/backend/src/services/chat_message_repository.test.ts +170 -0
  38. package/templates/assistkick-product-system/packages/backend/src/services/chat_message_repository.ts +106 -0
  39. package/templates/assistkick-product-system/packages/backend/src/services/chat_session_service.test.ts +217 -0
  40. package/templates/assistkick-product-system/packages/backend/src/services/chat_session_service.ts +188 -0
  41. package/templates/assistkick-product-system/packages/backend/src/services/chat_ws_handler.test.ts +1243 -0
  42. package/templates/assistkick-product-system/packages/backend/src/services/chat_ws_handler.ts +894 -0
  43. package/templates/assistkick-product-system/packages/backend/src/services/coherence-review.ts +3 -3
  44. package/templates/assistkick-product-system/packages/backend/src/services/dev_command_detector.test.ts +85 -0
  45. package/templates/assistkick-product-system/packages/backend/src/services/dev_command_detector.ts +54 -0
  46. package/templates/assistkick-product-system/packages/backend/src/services/email_service.ts +13 -10
  47. package/templates/assistkick-product-system/packages/backend/src/services/init.ts +11 -3
  48. package/templates/assistkick-product-system/packages/backend/src/services/invitation_service.ts +1 -1
  49. package/templates/assistkick-product-system/packages/backend/src/services/password_reset_service.ts +1 -1
  50. package/templates/assistkick-product-system/packages/backend/src/services/permission_service.test.ts +243 -0
  51. package/templates/assistkick-product-system/packages/backend/src/services/permission_service.ts +259 -0
  52. package/templates/assistkick-product-system/packages/backend/src/services/preview_server_manager.test.ts +172 -0
  53. package/templates/assistkick-product-system/packages/backend/src/services/preview_server_manager.ts +225 -0
  54. package/templates/assistkick-product-system/packages/backend/src/services/project_service.test.ts +29 -0
  55. package/templates/assistkick-product-system/packages/backend/src/services/project_service.ts +17 -0
  56. package/templates/assistkick-product-system/packages/backend/src/services/project_workspace_service.test.ts +255 -0
  57. package/templates/assistkick-product-system/packages/backend/src/services/project_workspace_service.ts +300 -25
  58. package/templates/assistkick-product-system/packages/backend/src/services/pty_session_manager.test.ts +44 -0
  59. package/templates/assistkick-product-system/packages/backend/src/services/pty_session_manager.ts +62 -7
  60. package/templates/assistkick-product-system/packages/backend/src/services/ssh_key_service.test.ts +77 -6
  61. package/templates/assistkick-product-system/packages/backend/src/services/ssh_key_service.ts +129 -8
  62. package/templates/assistkick-product-system/packages/backend/src/services/terminal_ws_handler.ts +2 -1
  63. package/templates/assistkick-product-system/packages/backend/src/services/title_generator_service.test.ts +45 -0
  64. package/templates/assistkick-product-system/packages/backend/src/services/title_generator_service.ts +157 -0
  65. package/templates/assistkick-product-system/packages/backend/src/services/tts_service.ts +4 -3
  66. package/templates/assistkick-product-system/packages/backend/src/services/video_render_service.ts +3 -3
  67. package/templates/assistkick-product-system/packages/frontend/package.json +5 -0
  68. package/templates/assistkick-product-system/packages/frontend/src/App.tsx +2 -0
  69. package/templates/assistkick-product-system/packages/frontend/src/api/client.ts +336 -5
  70. package/templates/assistkick-product-system/packages/frontend/src/components/AgentsView.tsx +192 -12
  71. package/templates/assistkick-product-system/packages/frontend/src/components/AttachmentPreviewList.tsx +98 -0
  72. package/templates/assistkick-product-system/packages/frontend/src/components/AutocompleteDropdown.tsx +65 -0
  73. package/templates/assistkick-product-system/packages/frontend/src/components/ChatAttachButton.tsx +56 -0
  74. package/templates/assistkick-product-system/packages/frontend/src/components/ChatDropZone.tsx +80 -0
  75. package/templates/assistkick-product-system/packages/frontend/src/components/ChatMessageBubble.tsx +155 -0
  76. package/templates/assistkick-product-system/packages/frontend/src/components/ChatMessageContent.tsx +182 -0
  77. package/templates/assistkick-product-system/packages/frontend/src/components/ChatMessageInput.tsx +233 -0
  78. package/templates/assistkick-product-system/packages/frontend/src/components/ChatSessionSidebar.tsx +218 -0
  79. package/templates/assistkick-product-system/packages/frontend/src/components/ChatStopButton.tsx +32 -0
  80. package/templates/assistkick-product-system/packages/frontend/src/components/ChatTodoSidebar.tsx +113 -0
  81. package/templates/assistkick-product-system/packages/frontend/src/components/ChatView.tsx +842 -0
  82. package/templates/assistkick-product-system/packages/frontend/src/components/CommitMessageModal.tsx +82 -0
  83. package/templates/assistkick-product-system/packages/frontend/src/components/DiagramOverlay.tsx +160 -0
  84. package/templates/assistkick-product-system/packages/frontend/src/components/EditorTabBar.tsx +5 -5
  85. package/templates/assistkick-product-system/packages/frontend/src/components/FileTree.tsx +9 -10
  86. package/templates/assistkick-product-system/packages/frontend/src/components/FileTreeInlineInput.tsx +5 -5
  87. package/templates/assistkick-product-system/packages/frontend/src/components/FilesView.tsx +112 -41
  88. package/templates/assistkick-product-system/packages/frontend/src/components/GraphLegend.tsx +2 -2
  89. package/templates/assistkick-product-system/packages/frontend/src/components/HighlightedText.tsx +87 -0
  90. package/templates/assistkick-product-system/packages/frontend/src/components/ImageLightbox.tsx +192 -0
  91. package/templates/assistkick-product-system/packages/frontend/src/components/KanbanView.tsx +2 -2
  92. package/templates/assistkick-product-system/packages/frontend/src/components/MentionPill.tsx +33 -0
  93. package/templates/assistkick-product-system/packages/frontend/src/components/MermaidBlock.tsx +148 -0
  94. package/templates/assistkick-product-system/packages/frontend/src/components/PermissionDialog.tsx +91 -0
  95. package/templates/assistkick-product-system/packages/frontend/src/components/PermissionModeSelector.tsx +229 -0
  96. package/templates/assistkick-product-system/packages/frontend/src/components/ProjectSelector.tsx +249 -83
  97. package/templates/assistkick-product-system/packages/frontend/src/components/QueuedMessageBubble.tsx +38 -0
  98. package/templates/assistkick-product-system/packages/frontend/src/components/SidePanel.tsx +212 -117
  99. package/templates/assistkick-product-system/packages/frontend/src/components/SystemPromptAccordion.tsx +48 -0
  100. package/templates/assistkick-product-system/packages/frontend/src/components/TaskIcon.tsx +11 -0
  101. package/templates/assistkick-product-system/packages/frontend/src/components/TerminalView.tsx +25 -9
  102. package/templates/assistkick-product-system/packages/frontend/src/components/ToolDiffView.tsx +114 -0
  103. package/templates/assistkick-product-system/packages/frontend/src/components/ToolResultCard.tsx +87 -0
  104. package/templates/assistkick-product-system/packages/frontend/src/components/ToolUseCard.tsx +149 -0
  105. package/templates/assistkick-product-system/packages/frontend/src/components/Toolbar.tsx +25 -8
  106. package/templates/assistkick-product-system/packages/frontend/src/components/UnifiedGitWidget.tsx +722 -0
  107. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/GroupNode.tsx +2 -0
  108. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/NodePalette.tsx +2 -1
  109. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/ProgrammableNode.tsx +178 -0
  110. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/WorkflowCanvas.tsx +3 -0
  111. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/WorkflowMonitorModal.tsx +103 -9
  112. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/monitor_nodes.tsx +26 -2
  113. package/templates/assistkick-product-system/packages/frontend/src/components/workflow/workflow_types.ts +42 -1
  114. package/templates/assistkick-product-system/packages/frontend/src/hooks/useDocumentTitle.ts +11 -0
  115. package/templates/assistkick-product-system/packages/frontend/src/hooks/useProjects.ts +1 -0
  116. package/templates/assistkick-product-system/packages/frontend/src/hooks/use_chat_stream.ts +826 -0
  117. package/templates/assistkick-product-system/packages/frontend/src/hooks/use_file_tree_cache.ts +69 -0
  118. package/templates/assistkick-product-system/packages/frontend/src/hooks/use_mention_autocomplete.ts +284 -0
  119. package/templates/assistkick-product-system/packages/frontend/src/lib/attachment_manager.test.ts +183 -0
  120. package/templates/assistkick-product-system/packages/frontend/src/lib/attachment_manager.ts +150 -0
  121. package/templates/assistkick-product-system/packages/frontend/src/lib/chat_message_helpers.test.ts +305 -0
  122. package/templates/assistkick-product-system/packages/frontend/src/lib/chat_message_helpers.ts +113 -0
  123. package/templates/assistkick-product-system/packages/frontend/src/lib/context_usage_helpers.test.ts +157 -0
  124. package/templates/assistkick-product-system/packages/frontend/src/lib/context_usage_helpers.ts +95 -0
  125. package/templates/assistkick-product-system/packages/frontend/src/lib/mermaid_helpers.test.ts +65 -0
  126. package/templates/assistkick-product-system/packages/frontend/src/lib/mermaid_helpers.ts +110 -0
  127. package/templates/assistkick-product-system/packages/frontend/src/lib/message_queue.ts +66 -0
  128. package/templates/assistkick-product-system/packages/frontend/src/lib/tool_use_summary.test.ts +124 -0
  129. package/templates/assistkick-product-system/packages/frontend/src/lib/tool_use_summary.ts +112 -0
  130. package/templates/assistkick-product-system/packages/frontend/src/routes/AgentsRoute.tsx +2 -0
  131. package/templates/assistkick-product-system/packages/frontend/src/routes/ChatRoute.tsx +8 -0
  132. package/templates/assistkick-product-system/packages/frontend/src/routes/CoherenceRoute.tsx +2 -0
  133. package/templates/assistkick-product-system/packages/frontend/src/routes/DashboardLayout.tsx +0 -4
  134. package/templates/assistkick-product-system/packages/frontend/src/routes/DesignSystemRoute.tsx +2 -0
  135. package/templates/assistkick-product-system/packages/frontend/src/routes/FilesRoute.tsx +2 -0
  136. package/templates/assistkick-product-system/packages/frontend/src/routes/GraphRoute.tsx +2 -0
  137. package/templates/assistkick-product-system/packages/frontend/src/routes/KanbanRoute.tsx +2 -0
  138. package/templates/assistkick-product-system/packages/frontend/src/routes/TerminalRoute.tsx +2 -0
  139. package/templates/assistkick-product-system/packages/frontend/src/routes/UsersRoute.tsx +2 -0
  140. package/templates/assistkick-product-system/packages/frontend/src/routes/VideographyRoute.tsx +2 -0
  141. package/templates/assistkick-product-system/packages/frontend/src/routes/WorkflowsRoute.tsx +2 -0
  142. package/templates/assistkick-product-system/packages/frontend/src/routes/accept_invitation.tsx +2 -0
  143. package/templates/assistkick-product-system/packages/frontend/src/routes/forgot_password.tsx +2 -0
  144. package/templates/assistkick-product-system/packages/frontend/src/routes/login.tsx +2 -0
  145. package/templates/assistkick-product-system/packages/frontend/src/routes/register.tsx +2 -0
  146. package/templates/assistkick-product-system/packages/frontend/src/routes/reset_password.tsx +2 -0
  147. package/templates/assistkick-product-system/packages/frontend/src/stores/useAttachmentStore.ts +66 -0
  148. package/templates/assistkick-product-system/packages/frontend/src/stores/useChatSessionStore.ts +107 -0
  149. package/templates/assistkick-product-system/packages/frontend/src/stores/useMessageQueueStore.ts +110 -0
  150. package/templates/assistkick-product-system/packages/frontend/src/stores/usePreviewStore.ts +78 -0
  151. package/templates/assistkick-product-system/packages/frontend/src/stores/useProjectStore.ts +7 -0
  152. package/templates/assistkick-product-system/packages/frontend/src/stores/useSidePanelStore.ts +6 -1
  153. package/templates/assistkick-product-system/packages/frontend/src/styles/index.css +30 -357
  154. package/templates/assistkick-product-system/packages/frontend/src/utils/parse_node_markdown.test.ts +115 -0
  155. package/templates/assistkick-product-system/packages/frontend/src/utils/parse_node_markdown.ts +91 -0
  156. package/templates/assistkick-product-system/packages/frontend/src/utils/preview_utils.test.ts +30 -0
  157. package/templates/assistkick-product-system/packages/frontend/src/utils/preview_utils.ts +3 -0
  158. package/templates/assistkick-product-system/packages/shared/db/migrate.ts +82 -0
  159. package/templates/assistkick-product-system/packages/shared/db/migrations/0000_outgoing_ultron.sql +277 -0
  160. package/templates/assistkick-product-system/packages/shared/db/migrations/0015_magenta_jazinda.sql +1 -0
  161. package/templates/assistkick-product-system/packages/shared/db/migrations/0016_giant_xorn.sql +1 -0
  162. package/templates/assistkick-product-system/packages/shared/db/migrations/0017_sloppy_mentor.sql +6 -0
  163. package/templates/assistkick-product-system/packages/shared/db/migrations/0018_vengeful_kabuki.sql +9 -0
  164. package/templates/assistkick-product-system/packages/shared/db/migrations/0019_careful_sentinels.sql +8 -0
  165. package/templates/assistkick-product-system/packages/shared/db/migrations/0020_clever_spot.sql +27 -0
  166. package/templates/assistkick-product-system/packages/shared/db/migrations/0021_graceful_hex.sql +1 -0
  167. package/templates/assistkick-product-system/packages/shared/db/migrations/0022_short_kingpin.sql +1 -0
  168. package/templates/assistkick-product-system/packages/shared/db/migrations/0023_ambiguous_sharon_carter.sql +1 -0
  169. package/templates/assistkick-product-system/packages/shared/db/migrations/0024_fat_unus.sql +1 -0
  170. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0000_snapshot.json +972 -22
  171. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0015_snapshot.json +1552 -0
  172. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0016_snapshot.json +1560 -0
  173. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0017_snapshot.json +1598 -0
  174. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0018_snapshot.json +1657 -0
  175. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0019_snapshot.json +1709 -0
  176. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0020_snapshot.json +1733 -0
  177. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0021_snapshot.json +1740 -0
  178. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0022_snapshot.json +1755 -0
  179. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0023_snapshot.json +1762 -0
  180. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0024_snapshot.json +1769 -0
  181. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/_journal.json +2 -100
  182. package/templates/assistkick-product-system/packages/shared/db/schema.ts +40 -1
  183. package/templates/assistkick-product-system/packages/shared/lib/claude-service.test.ts +236 -0
  184. package/templates/assistkick-product-system/packages/shared/lib/claude-service.ts +46 -5
  185. package/templates/assistkick-product-system/packages/shared/lib/git_workflow.ts +65 -39
  186. package/templates/assistkick-product-system/packages/shared/lib/programmable_node_executor.test.ts +173 -0
  187. package/templates/assistkick-product-system/packages/shared/lib/programmable_node_executor.ts +213 -0
  188. package/templates/assistkick-product-system/packages/shared/lib/validator.test.ts +70 -0
  189. package/templates/assistkick-product-system/packages/shared/lib/validator.ts +17 -1
  190. package/templates/assistkick-product-system/packages/shared/lib/workflow_engine.test.ts +803 -27
  191. package/templates/assistkick-product-system/packages/shared/lib/workflow_engine.ts +502 -68
  192. package/templates/assistkick-product-system/packages/shared/lib/workflow_orchestrator.ts +4 -4
  193. package/templates/assistkick-product-system/packages/shared/package.json +2 -1
  194. package/templates/assistkick-product-system/packages/shared/test_fixtures/hanging_stream.mjs +46 -0
  195. package/templates/assistkick-product-system/packages/shared/tools/add_node.test.ts +44 -0
  196. package/templates/assistkick-product-system/packages/shared/tools/add_node.ts +7 -0
  197. package/templates/assistkick-product-system/packages/shared/tools/remove_node.ts +2 -1
  198. package/templates/assistkick-product-system/packages/shared/tools/resolve_question.ts +2 -1
  199. package/templates/assistkick-product-system/packages/shared/tools/update_node.ts +2 -1
  200. package/templates/assistkick-product-system/tests/message_queue.test.ts +178 -0
  201. package/templates/assistkick-product-system/tests/message_queue_per_session.test.ts +143 -0
  202. package/templates/skills/assistkick-bootstrap/SKILL.md +26 -26
  203. package/templates/skills/assistkick-code-reviewer/SKILL.md +45 -46
  204. package/templates/skills/assistkick-db-explorer/SKILL.md +13 -13
  205. package/templates/skills/assistkick-debugger/SKILL.md +23 -23
  206. package/templates/skills/assistkick-developer/SKILL.md +59 -63
  207. package/templates/skills/assistkick-interview/SKILL.md +26 -26
  208. package/templates/skills/assistkick-video-composition-agent/SKILL.md +231 -0
  209. package/templates/skills/assistkick-video-script-writer/SKILL.md +136 -0
@@ -1,21 +1,31 @@
1
1
  /**
2
2
  * Git repository routes — connect/manage git repos for projects.
3
- * POST /api/projects/:id/git/connect — connect a git repo (clone URL + GitHub App)
4
- * POST /api/projects/:id/git/init — initialize a new local git repo
5
- * POST /api/projects/:id/git/disconnect — disconnect a git repo
6
- * GET /api/projects/:id/git/status — get git repo status
7
- * POST /api/projects/:id/git/test — test GitHub App connection
8
- * GET /api/projects/:id/git/installations — list GitHub App installations
9
- * GET /api/projects/:id/git/repos — list repos for an installation
10
- * POST /api/projects/:id/git/ssh/generate — generate SSH keypair for project
11
- * POST /api/projects/:id/git/ssh/connect — connect repo via SSH clone URL
3
+ * POST /api/projects/:id/git/connect — connect a git repo (remote add + fetch)
4
+ * POST /api/projects/:id/git/init — initialize a new local git repo
5
+ * POST /api/projects/:id/git/disconnect — disconnect a git repo
6
+ * GET /api/projects/:id/git/status — get git repo status
7
+ * POST /api/projects/:id/git/test — test GitHub App connection
8
+ * GET /api/projects/:id/git/installations — list GitHub App installations
9
+ * GET /api/projects/:id/git/repos — list repos for an installation
10
+ * POST /api/projects/:id/git/ssh/generate — generate SSH keypair for project
11
+ * POST /api/projects/:id/git/ssh/connect — connect repo via SSH (remote add + fetch)
12
+ * GET /api/projects/:id/git/branches — list local + remote branches with current
13
+ * POST /api/projects/:id/git/checkout — switch branch (optional commit message for dirty state)
14
+ * POST /api/projects/:id/git/branches — create and checkout a new branch
15
+ * GET /api/projects/:id/git/remotes — list configured remotes
16
+ * POST /api/projects/:id/git/remotes — add a new remote
17
+ * POST /api/projects/:id/git/fetch — fetch from remote
18
+ * POST /api/projects/:id/git/pull — pull current branch from remote
19
+ * POST /api/projects/:id/git/push — push current branch to remote
20
+ * GET /api/projects/:id/git/ahead-behind — get ahead/behind commit counts
21
+ * GET /api/projects/:id/git/initial-commit — check if only initial empty commit exists
12
22
  */
13
23
 
14
24
  import { Router } from 'express';
15
25
  import type { ProjectService } from '../services/project_service.js';
16
26
  import type { GitHubAppService } from '../services/github_app_service.js';
17
27
  import type { ProjectWorkspaceService } from '../services/project_workspace_service.js';
18
- import type { SshKeyService } from '../services/ssh_key_service.js';
28
+ import { SshKeyService } from '../services/ssh_key_service.js';
19
29
 
20
30
  interface GitRoutesDeps {
21
31
  projectService: ProjectService;
@@ -56,24 +66,39 @@ export const createGitRoutes = ({ projectService, githubAppService, workspaceSer
56
66
  }
57
67
  }
58
68
 
59
- // Clone the repo into the workspace
69
+ // Add remote and fetch (replaces old rm -rf + clone pattern)
60
70
  await workspaceService.cloneRepo(id, cloneUrl);
61
71
 
62
72
  // Detect default branch
63
73
  const detectedBranch = await workspaceService.getDefaultBranch(id);
64
74
  const effectiveBranch = baseBranch || detectedBranch;
65
75
 
76
+ // If local repo only has the initial empty commit, reset to remote content
77
+ const onlyInitial = await workspaceService.hasOnlyInitialCommit(id);
78
+ if (onlyInitial) {
79
+ try {
80
+ await workspaceService.resetToRemoteBranch(id, `origin/${effectiveBranch}`);
81
+ } catch {
82
+ // Remote might be empty too — that's fine
83
+ }
84
+ }
85
+
66
86
  // Save repo metadata to project
67
- const updated = await projectService.connectRepo(id, {
87
+ let updated = await projectService.connectRepo(id, {
68
88
  repoUrl: githubRepoFullName ? `https://github.com/${githubRepoFullName}.git` : repoUrl,
69
89
  githubInstallationId,
70
90
  githubRepoFullName,
71
91
  baseBranch: effectiveBranch,
72
92
  });
73
93
 
94
+ // Set auth method to 'github_app' when connecting via GitHub App
95
+ if (githubInstallationId) {
96
+ updated = await projectService.setGitHubAppAuth(id);
97
+ }
98
+
74
99
  res.json({ project: updated });
75
100
  } catch (err: any) {
76
- log('GIT', `Connect repo failed: ${err.message}`);
101
+ log('GIT', `Connect repo failed:`, err.stack || err.message);
77
102
  res.status(500).json({ error: `Failed to connect repo: ${err.message}` });
78
103
  }
79
104
  });
@@ -101,7 +126,7 @@ export const createGitRoutes = ({ projectService, githubAppService, workspaceSer
101
126
 
102
127
  res.json({ project: updated });
103
128
  } catch (err: any) {
104
- log('GIT', `Init repo failed: ${err.message}`);
129
+ log('GIT', `Init repo failed:`, err.stack || err.message);
105
130
  res.status(500).json({ error: `Failed to initialize repo: ${err.message}` });
106
131
  }
107
132
  });
@@ -115,7 +140,7 @@ export const createGitRoutes = ({ projectService, githubAppService, workspaceSer
115
140
  const updated = await projectService.disconnectRepo(id);
116
141
  res.json({ project: updated });
117
142
  } catch (err: any) {
118
- log('GIT', `Disconnect repo failed: ${err.message}`);
143
+ log('GIT', `Disconnect repo failed:`, err.stack || err.message);
119
144
  if (err.message === 'Project not found') {
120
145
  res.status(404).json({ error: err.message });
121
146
  return;
@@ -148,7 +173,7 @@ export const createGitRoutes = ({ projectService, githubAppService, workspaceSer
148
173
  sshConfigured: sshKeyService.isConfigured(),
149
174
  });
150
175
  } catch (err: any) {
151
- log('GIT', `Get git status failed: ${err.message}`);
176
+ log('GIT', `Get git status failed:`, err.stack || err.message);
152
177
  res.status(500).json({ error: `Failed to get git status: ${err.message}` });
153
178
  }
154
179
  });
@@ -177,7 +202,7 @@ export const createGitRoutes = ({ projectService, githubAppService, workspaceSer
177
202
  })),
178
203
  });
179
204
  } catch (err: any) {
180
- log('GIT', `Test GitHub App connection failed: ${err.message}`);
205
+ log('GIT', `Test GitHub App connection failed:`, err.stack || err.message);
181
206
  res.json({
182
207
  configured: true,
183
208
  error: `Connection test failed: ${err.message}`,
@@ -203,7 +228,7 @@ export const createGitRoutes = ({ projectService, githubAppService, workspaceSer
203
228
  })),
204
229
  });
205
230
  } catch (err: any) {
206
- log('GIT', `List installations failed: ${err.message}`);
231
+ log('GIT', `List installations failed:`, err.stack || err.message);
207
232
  res.status(500).json({ error: `Failed to list installations: ${err.message}` });
208
233
  }
209
234
  });
@@ -229,7 +254,7 @@ export const createGitRoutes = ({ projectService, githubAppService, workspaceSer
229
254
  })),
230
255
  });
231
256
  } catch (err: any) {
232
- log('GIT', `List repos failed: ${err.message}`);
257
+ log('GIT', `List repos failed:`, err.stack || err.message);
233
258
  res.status(500).json({ error: `Failed to list repos: ${err.message}` });
234
259
  }
235
260
  });
@@ -254,6 +279,9 @@ export const createGitRoutes = ({ projectService, githubAppService, workspaceSer
254
279
  const keyPair = sshKeyService.generateKeyPair();
255
280
  const encryptedPrivateKey = sshKeyService.encrypt(keyPair.privateKey);
256
281
 
282
+ // Install the key to ~/.ssh/ so CLI git can use the same key
283
+ await sshKeyService.installKeyForCli(keyPair);
284
+
257
285
  const updated = await projectService.setSshKeys(id, {
258
286
  sshPrivateKeyEncrypted: encryptedPrivateKey,
259
287
  sshPublicKey: keyPair.publicKey,
@@ -267,7 +295,7 @@ export const createGitRoutes = ({ projectService, githubAppService, workspaceSer
267
295
  publicKey: keyPair.publicKey,
268
296
  });
269
297
  } catch (err: any) {
270
- log('GIT', `Generate SSH key failed: ${err.message}`);
298
+ log('GIT', `Generate SSH key failed:`, err.stack || err.message);
271
299
  res.status(500).json({ error: `Failed to generate SSH key: ${err.message}` });
272
300
  }
273
301
  });
@@ -295,13 +323,23 @@ export const createGitRoutes = ({ projectService, githubAppService, workspaceSer
295
323
  return;
296
324
  }
297
325
 
298
- // Clone using SSH
326
+ // Add remote and fetch using SSH (no rm -rf + clone)
299
327
  await workspaceService.cloneRepoSsh(id, repoUrl, project.sshPrivateKeyEncrypted);
300
328
 
301
329
  // Detect default branch
302
330
  const detectedBranch = await workspaceService.getDefaultBranch(id);
303
331
  const effectiveBranch = baseBranch || detectedBranch;
304
332
 
333
+ // If local repo only has the initial empty commit, reset to remote content
334
+ const onlyInitial = await workspaceService.hasOnlyInitialCommit(id);
335
+ if (onlyInitial) {
336
+ try {
337
+ await workspaceService.resetToRemoteBranch(id, `origin/${effectiveBranch}`);
338
+ } catch {
339
+ // Remote might be empty too — that's fine
340
+ }
341
+ }
342
+
305
343
  // Save repo metadata
306
344
  const updated = await projectService.connectRepo(id, {
307
345
  repoUrl,
@@ -317,10 +355,340 @@ export const createGitRoutes = ({ projectService, githubAppService, workspaceSer
317
355
  },
318
356
  });
319
357
  } catch (err: any) {
320
- log('GIT', `SSH connect repo failed: ${err.message}`);
358
+ log('GIT', `SSH connect repo failed:`, err.stack || err.message);
321
359
  res.status(500).json({ error: `Failed to connect repo via SSH: ${err.message}` });
322
360
  }
323
361
  });
324
362
 
363
+ // GET /api/projects/:id/git/remotes — list configured remotes
364
+ router.get('/remotes', async (req, res) => {
365
+ const { id } = req.params;
366
+ log('GIT', `GET /api/projects/${id}/git/remotes`);
367
+
368
+ try {
369
+ if (!workspaceService.hasWorkspace(id)) {
370
+ res.status(404).json({ error: 'No git workspace found for this project' });
371
+ return;
372
+ }
373
+ const remotes = await workspaceService.listRemotes(id);
374
+ res.json({ remotes });
375
+ } catch (err: any) {
376
+ log('GIT', `List remotes failed: ${err.message}`);
377
+ res.status(500).json({ error: `Failed to list remotes: ${err.message}` });
378
+ }
379
+ });
380
+
381
+ // POST /api/projects/:id/git/remotes — add a new remote
382
+ router.post('/remotes', async (req, res) => {
383
+ const { id } = req.params;
384
+ const { name, url } = req.body;
385
+ log('GIT', `POST /api/projects/${id}/git/remotes name="${name}" url="${url}"`);
386
+
387
+ if (!url) {
388
+ res.status(400).json({ error: 'url is required' });
389
+ return;
390
+ }
391
+
392
+ const remoteName = name || 'origin';
393
+
394
+ try {
395
+ if (!workspaceService.hasWorkspace(id)) {
396
+ res.status(404).json({ error: 'No git workspace found for this project' });
397
+ return;
398
+ }
399
+ await workspaceService.addRemote(id, remoteName, url);
400
+
401
+ // Save repo URL to project metadata
402
+ if (remoteName === 'origin') {
403
+ await projectService.connectRepo(id, { repoUrl: url });
404
+ }
405
+
406
+ const remotes = await workspaceService.listRemotes(id);
407
+ res.json({ success: true, remotes });
408
+ } catch (err: any) {
409
+ log('GIT', `Add remote failed: ${err.message}`);
410
+ res.status(500).json({ error: `Failed to add remote: ${err.message}` });
411
+ }
412
+ });
413
+
414
+ // POST /api/projects/:id/git/fetch — fetch from remote
415
+ router.post('/fetch', async (req, res) => {
416
+ const { id } = req.params;
417
+ const { remote = 'origin' } = req.body || {};
418
+ log('GIT', `POST /api/projects/${id}/git/fetch remote="${remote}"`);
419
+
420
+ try {
421
+ if (!workspaceService.hasWorkspace(id)) {
422
+ res.status(404).json({ error: 'No git workspace found for this project' });
423
+ return;
424
+ }
425
+
426
+ const project = await projectService.getById(id);
427
+ if (!project) {
428
+ res.status(404).json({ error: 'Project not found' });
429
+ return;
430
+ }
431
+
432
+ if (project.gitAuthMethod === 'ssh_key' && project.sshPrivateKeyEncrypted) {
433
+ await workspaceService.fetchRemoteSsh(id, project.sshPrivateKeyEncrypted, remote);
434
+ } else if (project.gitAuthMethod === 'github_app' && project.githubInstallationId && project.githubRepoFullName) {
435
+ const token = await githubAppService.getInstallationToken(project.githubInstallationId);
436
+ const authUrl = `https://x-access-token:${token}@github.com/${project.githubRepoFullName}.git`;
437
+ const wsPath = workspaceService.getWorkspacePath(id);
438
+ await workspaceService['claudeService'].spawnCommand('git', ['remote', 'set-url', 'origin', authUrl], wsPath);
439
+ await workspaceService.fetchRemote(id, remote);
440
+ await workspaceService['claudeService'].spawnCommand(
441
+ 'git', ['remote', 'set-url', 'origin', `https://github.com/${project.githubRepoFullName}.git`], wsPath,
442
+ ).catch(() => {});
443
+ } else {
444
+ await workspaceService.fetchRemote(id, remote);
445
+ }
446
+
447
+ const aheadBehind = await workspaceService.getAheadBehind(id);
448
+ res.json({ success: true, ...aheadBehind });
449
+ } catch (err: any) {
450
+ log('GIT', `Fetch failed: ${err.message}`);
451
+ res.status(500).json({ error: `Failed to fetch: ${err.message}` });
452
+ }
453
+ });
454
+
455
+ // POST /api/projects/:id/git/pull — pull current branch from remote
456
+ router.post('/pull', async (req, res) => {
457
+ const { id } = req.params;
458
+ const { force } = req.body || {};
459
+ log('GIT', `POST /api/projects/${id}/git/pull force=${force}`);
460
+
461
+ try {
462
+ if (!workspaceService.hasWorkspace(id)) {
463
+ res.status(404).json({ error: 'No git workspace found for this project' });
464
+ return;
465
+ }
466
+
467
+ const project = await projectService.getById(id);
468
+ if (!project) {
469
+ res.status(404).json({ error: 'Project not found' });
470
+ return;
471
+ }
472
+
473
+ // If force=true, do a hard reset to the remote branch (for pulling into projects with only initial commit)
474
+ if (force) {
475
+ const branch = await workspaceService.getDefaultBranch(id);
476
+ await workspaceService.resetToRemoteBranch(id, `origin/${branch}`);
477
+ // Checkout the remote branch properly
478
+ try {
479
+ await workspaceService.checkoutBranch(id, branch);
480
+ } catch {
481
+ // Already on the branch
482
+ }
483
+ const aheadBehind = await workspaceService.getAheadBehind(id);
484
+ res.json({ success: true, ...aheadBehind });
485
+ return;
486
+ }
487
+
488
+ if (project.gitAuthMethod === 'ssh_key' && project.sshPrivateKeyEncrypted) {
489
+ await workspaceService.pullBranchSsh(id, project.sshPrivateKeyEncrypted);
490
+ } else if (project.gitAuthMethod === 'github_app' && project.githubInstallationId && project.githubRepoFullName) {
491
+ const token = await githubAppService.getInstallationToken(project.githubInstallationId);
492
+ await workspaceService.pullBranch(id, token, project.githubRepoFullName);
493
+ } else {
494
+ await workspaceService.pullBranch(id);
495
+ }
496
+
497
+ const aheadBehind = await workspaceService.getAheadBehind(id);
498
+ res.json({ success: true, ...aheadBehind });
499
+ } catch (err: any) {
500
+ log('GIT', `Pull failed: ${err.message}`);
501
+ res.status(500).json({ error: `Failed to pull: ${err.message}` });
502
+ }
503
+ });
504
+
505
+ // POST /api/projects/:id/git/push — push current branch to remote
506
+ router.post('/push', async (req, res) => {
507
+ const { id } = req.params;
508
+ log('GIT', `POST /api/projects/${id}/git/push`);
509
+
510
+ try {
511
+ if (!workspaceService.hasWorkspace(id)) {
512
+ res.status(404).json({ error: 'No git workspace found for this project' });
513
+ return;
514
+ }
515
+
516
+ const project = await projectService.getById(id);
517
+ if (!project) {
518
+ res.status(404).json({ error: 'Project not found' });
519
+ return;
520
+ }
521
+
522
+ if (project.gitAuthMethod === 'ssh_key' && project.sshPrivateKeyEncrypted) {
523
+ await workspaceService.pushBranchSsh(id, project.sshPrivateKeyEncrypted);
524
+ } else if (project.gitAuthMethod === 'github_app' && project.githubInstallationId && project.githubRepoFullName) {
525
+ const token = await githubAppService.getInstallationToken(project.githubInstallationId);
526
+ await workspaceService.pushBranch(id, token, project.githubRepoFullName);
527
+ } else {
528
+ await workspaceService.pushBranch(id);
529
+ }
530
+
531
+ const aheadBehind = await workspaceService.getAheadBehind(id);
532
+ res.json({ success: true, ...aheadBehind });
533
+ } catch (err: any) {
534
+ log('GIT', `Push failed: ${err.message}`);
535
+ res.status(500).json({ error: `Failed to push: ${err.message}` });
536
+ }
537
+ });
538
+
539
+ // GET /api/projects/:id/git/ahead-behind — get ahead/behind counts
540
+ router.get('/ahead-behind', async (req, res) => {
541
+ const { id } = req.params;
542
+
543
+ try {
544
+ if (!workspaceService.hasWorkspace(id)) {
545
+ res.json({ ahead: 0, behind: 0 });
546
+ return;
547
+ }
548
+ const result = await workspaceService.getAheadBehind(id);
549
+ res.json(result);
550
+ } catch (err: any) {
551
+ log('GIT', `Ahead/behind failed: ${err.message}`);
552
+ res.json({ ahead: 0, behind: 0 });
553
+ }
554
+ });
555
+
556
+ // GET /api/projects/:id/git/initial-commit — check if workspace has only the initial empty commit
557
+ router.get('/initial-commit', async (req, res) => {
558
+ const { id } = req.params;
559
+
560
+ try {
561
+ if (!workspaceService.hasWorkspace(id)) {
562
+ res.json({ onlyInitialCommit: true });
563
+ return;
564
+ }
565
+ const onlyInitialCommit = await workspaceService.hasOnlyInitialCommit(id);
566
+ res.json({ onlyInitialCommit });
567
+ } catch (err: any) {
568
+ log('GIT', `Initial commit check failed: ${err.message}`);
569
+ res.json({ onlyInitialCommit: true });
570
+ }
571
+ });
572
+
573
+ // GET /api/projects/:id/git/branches — list local + remote branches with current
574
+ router.get('/branches', async (req, res) => {
575
+ const { id } = req.params;
576
+ log('GIT', `GET /api/projects/${id}/git/branches`);
577
+
578
+ try {
579
+ if (!workspaceService.hasWorkspace(id)) {
580
+ res.status(404).json({ error: 'No git workspace found for this project' });
581
+ return;
582
+ }
583
+
584
+ const branches = await workspaceService.listBranches(id);
585
+ res.json(branches);
586
+ } catch (err: any) {
587
+ log('GIT', `List branches failed: ${err.message}`);
588
+ res.status(500).json({ error: `Failed to list branches: ${err.message}` });
589
+ }
590
+ });
591
+
592
+ // POST /api/projects/:id/git/checkout — switch branch
593
+ router.post('/checkout', async (req, res) => {
594
+ const { id } = req.params;
595
+ const { branch, commitMessage } = req.body;
596
+ log('GIT', `POST /api/projects/${id}/git/checkout branch="${branch}"`);
597
+
598
+ if (!branch) {
599
+ res.status(400).json({ error: 'branch is required' });
600
+ return;
601
+ }
602
+
603
+ try {
604
+ if (!workspaceService.hasWorkspace(id)) {
605
+ res.status(404).json({ error: 'No git workspace found for this project' });
606
+ return;
607
+ }
608
+
609
+ // If there are uncommitted changes and a commit message was provided, commit first
610
+ const isDirty = await workspaceService.hasUncommittedChanges(id);
611
+ if (isDirty) {
612
+ if (!commitMessage) {
613
+ res.status(409).json({ error: 'Uncommitted changes exist', dirty: true });
614
+ return;
615
+ }
616
+ await workspaceService.commitAll(id, commitMessage);
617
+ }
618
+
619
+ await workspaceService.checkoutBranch(id, branch);
620
+ const branches = await workspaceService.listBranches(id);
621
+ res.json({ success: true, ...branches });
622
+ } catch (err: any) {
623
+ log('GIT', `Checkout branch failed: ${err.message}`);
624
+ res.status(500).json({ error: `Failed to checkout branch: ${err.message}` });
625
+ }
626
+ });
627
+
628
+ // POST /api/projects/:id/git/branches — create and checkout a new branch
629
+ router.post('/branches', async (req, res) => {
630
+ const { id } = req.params;
631
+ const { name } = req.body;
632
+ log('GIT', `POST /api/projects/${id}/git/branches name="${name}"`);
633
+
634
+ if (!name) {
635
+ res.status(400).json({ error: 'name is required' });
636
+ return;
637
+ }
638
+
639
+ // Validate branch name (no spaces, no special chars that break git)
640
+ if (/[\s~^:?*\[\\]/.test(name) || name.startsWith('-') || name.endsWith('.') || name.includes('..')) {
641
+ res.status(400).json({ error: 'Invalid branch name' });
642
+ return;
643
+ }
644
+
645
+ try {
646
+ if (!workspaceService.hasWorkspace(id)) {
647
+ res.status(404).json({ error: 'No git workspace found for this project' });
648
+ return;
649
+ }
650
+
651
+ await workspaceService.createBranch(id, name);
652
+ const branches = await workspaceService.listBranches(id);
653
+ res.json({ success: true, ...branches });
654
+ } catch (err: any) {
655
+ log('GIT', `Create branch failed: ${err.message}`);
656
+ res.status(500).json({ error: `Failed to create branch: ${err.message}` });
657
+ }
658
+ });
659
+
660
+ // DELETE /api/projects/:id/git/branches — delete a local branch
661
+ router.delete('/branches', async (req, res) => {
662
+ const { id } = req.params;
663
+ const { name } = req.body;
664
+ log('GIT', `DELETE /api/projects/${id}/git/branches name="${name}"`);
665
+
666
+ if (!name) {
667
+ res.status(400).json({ error: 'name is required' });
668
+ return;
669
+ }
670
+
671
+ try {
672
+ if (!workspaceService.hasWorkspace(id)) {
673
+ res.status(404).json({ error: 'No git workspace found for this project' });
674
+ return;
675
+ }
676
+
677
+ // Prevent deleting the current branch
678
+ const branches = await workspaceService.listBranches(id);
679
+ if (branches.current === name) {
680
+ res.status(400).json({ error: 'Cannot delete the currently checked out branch' });
681
+ return;
682
+ }
683
+
684
+ await workspaceService.deleteBranch(id, name);
685
+ const updatedBranches = await workspaceService.listBranches(id);
686
+ res.json({ success: true, ...updatedBranches });
687
+ } catch (err: any) {
688
+ log('GIT', `Delete branch failed: ${err.message}`);
689
+ res.status(500).json({ error: `Failed to delete branch: ${err.message}` });
690
+ }
691
+ });
692
+
325
693
  return router;
326
694
  };