@assistkick/create 1.10.0 → 1.11.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/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/migrations/0015_magenta_jazinda.sql +1 -0
  159. package/templates/assistkick-product-system/packages/shared/db/migrations/0016_giant_xorn.sql +1 -0
  160. package/templates/assistkick-product-system/packages/shared/db/migrations/0017_sloppy_mentor.sql +6 -0
  161. package/templates/assistkick-product-system/packages/shared/db/migrations/0018_vengeful_kabuki.sql +9 -0
  162. package/templates/assistkick-product-system/packages/shared/db/migrations/0019_careful_sentinels.sql +8 -0
  163. package/templates/assistkick-product-system/packages/shared/db/migrations/0020_clever_spot.sql +27 -0
  164. package/templates/assistkick-product-system/packages/shared/db/migrations/0021_graceful_hex.sql +1 -0
  165. package/templates/assistkick-product-system/packages/shared/db/migrations/0022_short_kingpin.sql +1 -0
  166. package/templates/assistkick-product-system/packages/shared/db/migrations/0023_ambiguous_sharon_carter.sql +1 -0
  167. package/templates/assistkick-product-system/packages/shared/db/migrations/0024_fat_unus.sql +1 -0
  168. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0015_snapshot.json +1552 -0
  169. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0016_snapshot.json +1560 -0
  170. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0017_snapshot.json +1598 -0
  171. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0018_snapshot.json +1657 -0
  172. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0019_snapshot.json +1709 -0
  173. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0020_snapshot.json +1733 -0
  174. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0021_snapshot.json +1740 -0
  175. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0022_snapshot.json +1755 -0
  176. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0023_snapshot.json +1762 -0
  177. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0024_snapshot.json +1769 -0
  178. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/_journal.json +70 -0
  179. package/templates/assistkick-product-system/packages/shared/db/schema.ts +40 -1
  180. package/templates/assistkick-product-system/packages/shared/lib/claude-service.test.ts +236 -0
  181. package/templates/assistkick-product-system/packages/shared/lib/claude-service.ts +46 -5
  182. package/templates/assistkick-product-system/packages/shared/lib/git_workflow.ts +65 -39
  183. package/templates/assistkick-product-system/packages/shared/lib/programmable_node_executor.test.ts +173 -0
  184. package/templates/assistkick-product-system/packages/shared/lib/programmable_node_executor.ts +213 -0
  185. package/templates/assistkick-product-system/packages/shared/lib/validator.test.ts +70 -0
  186. package/templates/assistkick-product-system/packages/shared/lib/validator.ts +17 -1
  187. package/templates/assistkick-product-system/packages/shared/lib/workflow_engine.test.ts +803 -27
  188. package/templates/assistkick-product-system/packages/shared/lib/workflow_engine.ts +502 -68
  189. package/templates/assistkick-product-system/packages/shared/lib/workflow_orchestrator.ts +4 -4
  190. package/templates/assistkick-product-system/packages/shared/package.json +2 -1
  191. package/templates/assistkick-product-system/packages/shared/test_fixtures/hanging_stream.mjs +46 -0
  192. package/templates/assistkick-product-system/packages/shared/tools/add_node.test.ts +44 -0
  193. package/templates/assistkick-product-system/packages/shared/tools/add_node.ts +7 -0
  194. package/templates/assistkick-product-system/packages/shared/tools/remove_node.ts +2 -1
  195. package/templates/assistkick-product-system/packages/shared/tools/resolve_question.ts +2 -1
  196. package/templates/assistkick-product-system/packages/shared/tools/update_node.ts +2 -1
  197. package/templates/assistkick-product-system/tests/message_queue.test.ts +178 -0
  198. package/templates/assistkick-product-system/tests/message_queue_per_session.test.ts +143 -0
  199. package/templates/skills/assistkick-bootstrap/SKILL.md +26 -26
  200. package/templates/skills/assistkick-code-reviewer/SKILL.md +45 -46
  201. package/templates/skills/assistkick-db-explorer/SKILL.md +13 -13
  202. package/templates/skills/assistkick-debugger/SKILL.md +23 -23
  203. package/templates/skills/assistkick-developer/SKILL.md +59 -63
  204. package/templates/skills/assistkick-interview/SKILL.md +26 -26
  205. package/templates/skills/assistkick-video-composition-agent/SKILL.md +231 -0
  206. package/templates/skills/assistkick-video-script-writer/SKILL.md +136 -0
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Skills routes — lists available skills from project workspace.
3
+ * GET /api/projects/:id/skills — scans .claude/skills/ directory
4
+ */
5
+
6
+ import { Router } from 'express';
7
+ import { join } from 'node:path';
8
+ import { readdir, readFile } from 'node:fs/promises';
9
+ import { existsSync } from 'node:fs';
10
+ import type { ProjectWorkspaceService } from '../services/project_workspace_service.js';
11
+
12
+ interface SkillRoutesDeps {
13
+ workspaceService: ProjectWorkspaceService;
14
+ log: (tag: string, ...args: unknown[]) => void;
15
+ }
16
+
17
+ export interface SkillInfo {
18
+ name: string;
19
+ description: string;
20
+ folderName: string;
21
+ }
22
+
23
+ /**
24
+ * Parse a SKILL.md file to extract name and description.
25
+ * Looks for a # heading for name and the first non-empty, non-heading line
26
+ * after for description.
27
+ */
28
+ export const parseSkillMd = (content: string): { name: string; description: string } => {
29
+ const lines = content.split('\n');
30
+ let name = '';
31
+ let description = '';
32
+
33
+ for (const line of lines) {
34
+ const trimmed = line.trim();
35
+ if (!name && trimmed.startsWith('# ')) {
36
+ name = trimmed.slice(2).trim();
37
+ } else if (name && !description && trimmed && !trimmed.startsWith('#')) {
38
+ description = trimmed;
39
+ }
40
+ if (name && description) break;
41
+ }
42
+
43
+ return { name, description };
44
+ };
45
+
46
+ export const createSkillRoutes = ({ workspaceService, log }: SkillRoutesDeps): Router => {
47
+ const router: Router = Router({ mergeParams: true });
48
+
49
+ router.get('/', async (req, res) => {
50
+ const { id } = req.params;
51
+ log('SKILLS', `GET /api/projects/${id}/skills`);
52
+
53
+ try {
54
+ const wsPath = workspaceService.getWorkspacePath(id);
55
+ const skillsDir = join(wsPath, '.claude', 'skills');
56
+
57
+ if (!existsSync(skillsDir)) {
58
+ res.json({ skills: [] });
59
+ return;
60
+ }
61
+
62
+ const entries = await readdir(skillsDir, { withFileTypes: true });
63
+ const skills: SkillInfo[] = [];
64
+
65
+ for (const entry of entries) {
66
+ if (!entry.isDirectory()) continue;
67
+
68
+ const skillMdPath = join(skillsDir, entry.name, 'SKILL.md');
69
+ let name = entry.name;
70
+ let description = '';
71
+
72
+ if (existsSync(skillMdPath)) {
73
+ try {
74
+ const content = await readFile(skillMdPath, 'utf-8');
75
+ const parsed = parseSkillMd(content);
76
+ if (parsed.name) name = parsed.name;
77
+ description = parsed.description;
78
+ } catch {
79
+ // Use folder name as fallback
80
+ }
81
+ }
82
+
83
+ skills.push({ name, description, folderName: entry.name });
84
+ }
85
+
86
+ res.json({ skills });
87
+ } catch (err: unknown) {
88
+ const message = err instanceof Error ? err.message : String(err);
89
+ log('SKILLS', `Failed to list skills: ${message}`);
90
+ res.status(500).json({ error: `Failed to list skills: ${message}` });
91
+ }
92
+ });
93
+
94
+ return router;
95
+ };
@@ -43,7 +43,7 @@ export const createTerminalRoutes = ({ ptyManager, log }: TerminalRoutesDeps): R
43
43
 
44
44
  // POST /api/terminal/sessions
45
45
  router.post('/sessions', async (req, res) => {
46
- const { projectId, projectName } = req.body;
46
+ const { projectId, projectName, sessionType } = req.body;
47
47
 
48
48
  if (!projectId || typeof projectId !== 'string') {
49
49
  res.status(400).json({ error: 'projectId is required' });
@@ -54,8 +54,9 @@ export const createTerminalRoutes = ({ ptyManager, log }: TerminalRoutesDeps): R
54
54
  return;
55
55
  }
56
56
 
57
- const session = await ptyManager.createSession(projectId.trim(), projectName.trim(), 80, 24);
58
- log('TERMINAL', `Created session "${session.name}" for project ${projectId}`);
57
+ const type = sessionType === 'terminal' ? 'terminal' as const : 'claude' as const;
58
+ const session = await ptyManager.createSession(projectId.trim(), projectName.trim(), 80, 24, type);
59
+ log('TERMINAL', `Created ${type} session "${session.name}" for project ${projectId}`);
59
60
  res.status(201).json({ session });
60
61
  });
61
62
 
@@ -93,7 +94,7 @@ export const createTerminalRoutes = ({ ptyManager, log }: TerminalRoutesDeps): R
93
94
  log('TERMINAL', `Uploaded image: ${filePath} (${body.length} bytes)`);
94
95
  res.json({ path: filePath });
95
96
  } catch (err: any) {
96
- log('TERMINAL', `Image upload failed: ${err.message}`);
97
+ log('TERMINAL', `Image upload failed:`, err.stack || err.message);
97
98
  res.status(500).json({ error: 'Failed to save image' });
98
99
  }
99
100
  },
@@ -37,7 +37,7 @@ export const createUserRoutes = ({ userManagementService, log }: UserRoutesDeps)
37
37
  const userList = await userManagementService.listUsers();
38
38
  res.json({ users: userList });
39
39
  } catch (err: any) {
40
- log('USERS', `List users failed: ${err.message}`);
40
+ log('USERS', `List users failed:`, err.stack || err.message);
41
41
  res.status(500).json({ error: 'Failed to list users' });
42
42
  }
43
43
  });
@@ -52,7 +52,7 @@ export const createUserRoutes = ({ userManagementService, log }: UserRoutesDeps)
52
52
  await userManagementService.deleteUser(id, user.id);
53
53
  res.json({ message: 'User deleted' });
54
54
  } catch (err: any) {
55
- log('USERS', `Delete user failed: ${err.message}`);
55
+ log('USERS', `Delete user failed:`, err.stack || err.message);
56
56
  if (err.message === 'Cannot delete your own account') {
57
57
  return res.status(400).json({ error: err.message });
58
58
  }
@@ -71,7 +71,7 @@ export const createUserRoutes = ({ userManagementService, log }: UserRoutesDeps)
71
71
  const invitationList = await userManagementService.listInvitations();
72
72
  res.json({ invitations: invitationList });
73
73
  } catch (err: any) {
74
- log('USERS', `List invitations failed: ${err.message}`);
74
+ log('USERS', `List invitations failed:`, err.stack || err.message);
75
75
  res.status(500).json({ error: 'Failed to list invitations' });
76
76
  }
77
77
  });
@@ -85,7 +85,7 @@ export const createUserRoutes = ({ userManagementService, log }: UserRoutesDeps)
85
85
  await userManagementService.deleteInvitation(id);
86
86
  res.json({ message: 'Invitation deleted' });
87
87
  } catch (err: any) {
88
- log('USERS', `Delete invitation failed: ${err.message}`);
88
+ log('USERS', `Delete invitation failed:`, err.stack || err.message);
89
89
  if (err.message === 'Invitation not found') {
90
90
  return res.status(404).json({ error: err.message });
91
91
  }
@@ -65,7 +65,7 @@ export const createVideoRoutes = ({ ttsService, bundleService, videoRenderServic
65
65
 
66
66
  res.json(result);
67
67
  } catch (err: any) {
68
- log('VIDEO', `TTS generation failed: ${err.message}`);
68
+ log('VIDEO', `TTS generation failed:`, err.stack || err.message);
69
69
  res.status(500).json({ error: err.message });
70
70
  }
71
71
  });
@@ -77,7 +77,7 @@ export const createVideoRoutes = ({ ttsService, bundleService, videoRenderServic
77
77
  const compositions = bundleService.listCompositions();
78
78
  res.json({ compositions });
79
79
  } catch (err: any) {
80
- log('VIDEO', `Failed to list compositions: ${err.message}`);
80
+ log('VIDEO', `Failed to list compositions:`, err.stack || err.message);
81
81
  res.status(500).json({ error: 'Failed to list compositions' });
82
82
  }
83
83
  });
@@ -95,7 +95,7 @@ export const createVideoRoutes = ({ ttsService, bundleService, videoRenderServic
95
95
  const status = await bundleService.buildBundle();
96
96
  res.json(status);
97
97
  } catch (err: any) {
98
- log('VIDEO', `Bundle rebuild failed: ${err.message}`);
98
+ log('VIDEO', `Bundle rebuild failed:`, err.stack || err.message);
99
99
  res.status(500).json({ error: 'Bundle rebuild failed' });
100
100
  }
101
101
  });
@@ -114,7 +114,7 @@ export const createVideoRoutes = ({ ttsService, bundleService, videoRenderServic
114
114
  const renders = await videoRenderService.listRenders(projectId);
115
115
  res.json({ renders });
116
116
  } catch (err: any) {
117
- log('VIDEO', `Failed to list renders: ${err.message}`);
117
+ log('VIDEO', `Failed to list renders:`, err.stack || err.message);
118
118
  res.status(500).json({ error: 'Failed to list renders' });
119
119
  }
120
120
  });
@@ -151,7 +151,7 @@ export const createVideoRoutes = ({ ttsService, bundleService, videoRenderServic
151
151
 
152
152
  res.json(status);
153
153
  } catch (err: any) {
154
- log('VIDEO', `Render start failed: ${err.message}`);
154
+ log('VIDEO', `Render start failed:`, err.stack || err.message);
155
155
  res.status(500).json({ error: err.message });
156
156
  }
157
157
  });
@@ -169,7 +169,7 @@ export const createVideoRoutes = ({ ttsService, bundleService, videoRenderServic
169
169
  }
170
170
  res.json(status);
171
171
  } catch (err: any) {
172
- log('VIDEO', `Failed to get render status: ${err.message}`);
172
+ log('VIDEO', `Failed to get render status:`, err.stack || err.message);
173
173
  res.status(500).json({ error: err.message });
174
174
  }
175
175
  });
@@ -187,7 +187,7 @@ export const createVideoRoutes = ({ ttsService, bundleService, videoRenderServic
187
187
  }
188
188
  res.json({ ok: true });
189
189
  } catch (err: any) {
190
- log('VIDEO', `Failed to delete render: ${err.message}`);
190
+ log('VIDEO', `Failed to delete render:`, err.stack || err.message);
191
191
  res.status(500).json({ error: err.message });
192
192
  }
193
193
  });
@@ -205,7 +205,7 @@ export const createVideoRoutes = ({ ttsService, bundleService, videoRenderServic
205
205
  }
206
206
  res.sendFile(filePath);
207
207
  } catch (err: any) {
208
- log('VIDEO', `Failed to serve render file: ${err.message}`);
208
+ log('VIDEO', `Failed to serve render file:`, err.stack || err.message);
209
209
  res.status(500).json({ error: err.message });
210
210
  }
211
211
  });
@@ -27,7 +27,7 @@ export const createWorkflowGroupRoutes = ({ workflowGroupService, log }: Workflo
27
27
  const groups = await workflowGroupService.list(projectId);
28
28
  res.json({ groups });
29
29
  } catch (err: any) {
30
- log('WORKFLOW_GROUPS', `List groups failed: ${err.message}`);
30
+ log('WORKFLOW_GROUPS', `List groups failed:`, err.stack || err.message);
31
31
  res.status(500).json({ error: 'Failed to list workflow groups' });
32
32
  }
33
33
  });
@@ -45,7 +45,7 @@ export const createWorkflowGroupRoutes = ({ workflowGroupService, log }: Workflo
45
45
  }
46
46
  res.json({ group });
47
47
  } catch (err: any) {
48
- log('WORKFLOW_GROUPS', `Get group failed: ${err.message}`);
48
+ log('WORKFLOW_GROUPS', `Get group failed:`, err.stack || err.message);
49
49
  res.status(500).json({ error: 'Failed to get workflow group' });
50
50
  }
51
51
  });
@@ -73,7 +73,7 @@ export const createWorkflowGroupRoutes = ({ workflowGroupService, log }: Workflo
73
73
  });
74
74
  res.status(201).json({ group });
75
75
  } catch (err: any) {
76
- log('WORKFLOW_GROUPS', `Create group failed: ${err.message}`);
76
+ log('WORKFLOW_GROUPS', `Create group failed:`, err.stack || err.message);
77
77
  res.status(500).json({ error: 'Failed to create workflow group' });
78
78
  }
79
79
  });
@@ -88,7 +88,7 @@ export const createWorkflowGroupRoutes = ({ workflowGroupService, log }: Workflo
88
88
  const group = await workflowGroupService.update(id, { name, graphData });
89
89
  res.json({ group });
90
90
  } catch (err: any) {
91
- log('WORKFLOW_GROUPS', `Update group failed: ${err.message}`);
91
+ log('WORKFLOW_GROUPS', `Update group failed:`, err.stack || err.message);
92
92
  if (err.message === 'Workflow group not found') {
93
93
  res.status(404).json({ error: err.message });
94
94
  return;
@@ -106,7 +106,7 @@ export const createWorkflowGroupRoutes = ({ workflowGroupService, log }: Workflo
106
106
  await workflowGroupService.delete(id);
107
107
  res.json({ success: true });
108
108
  } catch (err: any) {
109
- log('WORKFLOW_GROUPS', `Delete group failed: ${err.message}`);
109
+ log('WORKFLOW_GROUPS', `Delete group failed:`, err.stack || err.message);
110
110
  if (err.message === 'Workflow group not found') {
111
111
  res.status(404).json({ error: err.message });
112
112
  return;
@@ -30,7 +30,7 @@ export const createWorkflowRoutes = ({ workflowService, log }: WorkflowRoutesDep
30
30
  const workflowList = await workflowService.list(projectId, featureType, triggerColumn);
31
31
  res.json({ workflows: workflowList });
32
32
  } catch (err: any) {
33
- log('WORKFLOWS', `List workflows failed: ${err.message}`);
33
+ log('WORKFLOWS', `List workflows failed:`, err.stack || err.message);
34
34
  res.status(500).json({ error: 'Failed to list workflows' });
35
35
  }
36
36
  });
@@ -48,7 +48,7 @@ export const createWorkflowRoutes = ({ workflowService, log }: WorkflowRoutesDep
48
48
  }
49
49
  res.json({ workflow });
50
50
  } catch (err: any) {
51
- log('WORKFLOWS', `Get workflow failed: ${err.message}`);
51
+ log('WORKFLOWS', `Get workflow failed:`, err.stack || err.message);
52
52
  res.status(500).json({ error: 'Failed to get workflow' });
53
53
  }
54
54
  });
@@ -79,7 +79,7 @@ export const createWorkflowRoutes = ({ workflowService, log }: WorkflowRoutesDep
79
79
  });
80
80
  res.status(201).json({ workflow });
81
81
  } catch (err: any) {
82
- log('WORKFLOWS', `Create workflow failed: ${err.message}`);
82
+ log('WORKFLOWS', `Create workflow failed:`, err.stack || err.message);
83
83
  res.status(500).json({ error: 'Failed to create workflow' });
84
84
  }
85
85
  });
@@ -105,7 +105,7 @@ export const createWorkflowRoutes = ({ workflowService, log }: WorkflowRoutesDep
105
105
  });
106
106
  res.json({ workflow });
107
107
  } catch (err: any) {
108
- log('WORKFLOWS', `Update workflow failed: ${err.message}`);
108
+ log('WORKFLOWS', `Update workflow failed:`, err.stack || err.message);
109
109
  if (err.message === 'Workflow not found') {
110
110
  res.status(404).json({ error: err.message });
111
111
  return;
@@ -123,7 +123,7 @@ export const createWorkflowRoutes = ({ workflowService, log }: WorkflowRoutesDep
123
123
  await workflowService.delete(id);
124
124
  res.json({ success: true });
125
125
  } catch (err: any) {
126
- log('WORKFLOWS', `Delete workflow failed: ${err.message}`);
126
+ log('WORKFLOWS', `Delete workflow failed:`, err.stack || err.message);
127
127
  if (err.message === 'Workflow not found') {
128
128
  res.status(404).json({ error: err.message });
129
129
  return;
@@ -145,7 +145,7 @@ export const createWorkflowRoutes = ({ workflowService, log }: WorkflowRoutesDep
145
145
  const workflow = await workflowService.setDefault(id);
146
146
  res.json({ workflow });
147
147
  } catch (err: any) {
148
- log('WORKFLOWS', `Set default workflow failed: ${err.message}`);
148
+ log('WORKFLOWS', `Set default workflow failed:`, err.stack || err.message);
149
149
  if (err.message === 'Workflow not found') {
150
150
  res.status(404).json({ error: err.message });
151
151
  return;
@@ -16,13 +16,17 @@ import { fileURLToPath } from 'node:url';
16
16
  import { existsSync } from 'node:fs';
17
17
  import { WebSocketServer } from 'ws';
18
18
  import * as pty from 'node-pty';
19
- import { initServices, log, githubAppService, sshKeyService, workspaceService, workflowEngine, orchestrator, workflowService as initWorkflowService, workflowGroupService as initWorkflowGroupService, bundleService, ttsService, videoRenderService } from './services/init.js';
19
+ import { initServices, log, logError, paths, githubAppService, sshKeyService, workspaceService, workflowEngine, orchestrator, workflowService as initWorkflowService, workflowGroupService as initWorkflowGroupService, bundleService, ttsService, videoRenderService } from './services/init.js';
20
20
  import { AuthService } from './services/auth_service.js';
21
21
  import { EmailService } from './services/email_service.js';
22
22
  import { PasswordResetService } from './services/password_reset_service.js';
23
23
  import { InvitationService } from './services/invitation_service.js';
24
24
  import { PtySessionManager } from './services/pty_session_manager.js';
25
25
  import { TerminalWsHandler } from './services/terminal_ws_handler.js';
26
+ import { ChatCliBridge } from './services/chat_cli_bridge.js';
27
+ import { ChatWsHandler } from './services/chat_ws_handler.js';
28
+ import { PermissionService } from './services/permission_service.js';
29
+ import { createChatPermissionRoutes } from './routes/chat_permission.js';
26
30
  import { UserManagementService } from './services/user_management_service.js';
27
31
  import { ProjectService } from './services/project_service.js';
28
32
  import { AuthMiddleware } from './middleware/auth_middleware.js';
@@ -31,6 +35,7 @@ import { createUserRoutes } from './routes/users.js';
31
35
  import { createProjectRoutes } from './routes/projects.js';
32
36
  import { createTerminalRoutes } from './routes/terminal.js';
33
37
  import { getDb } from '@assistkick/shared/lib/db.js';
38
+ import { sql } from 'drizzle-orm';
34
39
  import graphRoutes from './routes/graph.js';
35
40
  import kanbanRoutes from './routes/kanban.js';
36
41
  import { createWorkflowExecutionRoutes } from './routes/pipeline.js';
@@ -38,10 +43,20 @@ import coherenceRoutes from './routes/coherence.js';
38
43
  import { createGitRoutes } from './routes/git.js';
39
44
  import { createFileRoutes } from './routes/files.js';
40
45
  import { AgentService } from './services/agent_service.js';
41
- import { createAgentRoutes } from './routes/agents.js';
46
+ import { createAgentRoutes, createSkillListRoutes } from './routes/agents.js';
42
47
  import { createWorkflowRoutes } from './routes/workflows.js';
43
48
  import { createWorkflowGroupRoutes } from './routes/workflow_groups.js';
44
49
  import { createVideoRoutes } from './routes/video.js';
50
+ import { ChatSessionService } from './services/chat_session_service.js';
51
+ import { ChatMessageRepository } from './services/chat_message_repository.js';
52
+ import { TitleGeneratorService } from './services/title_generator_service.js';
53
+ import { createChatSessionRoutes } from './routes/chat_sessions.js';
54
+ import { createChatUploadRoutes } from './routes/chat_upload.js';
55
+ import { createChatFilesRoutes } from './routes/chat_files.js';
56
+ import { createSkillRoutes } from './routes/skills.js';
57
+ import { DevCommandDetector } from './services/dev_command_detector.js';
58
+ import { PreviewServerManager } from './services/preview_server_manager.js';
59
+ import { createPreviewApiRoutes, createPreviewProxyMiddleware } from './routes/preview.js';
45
60
 
46
61
  const __dirname = dirname(fileURLToPath(import.meta.url));
47
62
  const DEFAULT_PORT = parseInt(process.env.PORT || '3000', 10);
@@ -68,7 +83,7 @@ initServices(args.verbose);
68
83
  const app: Express = express();
69
84
 
70
85
  app.use(cors());
71
- app.use(express.json());
86
+ app.use(express.json({ limit: '10mb' }));
72
87
  app.use(cookieParser());
73
88
 
74
89
  // Request/response logging
@@ -90,7 +105,7 @@ const authService = new AuthService({ jwtSecret, isProduction });
90
105
  const resendApiKey = process.env.RESEND_API_KEY || '';
91
106
  const appBaseUrl = process.env.APP_BASE_URL || 'https://assistkick.localhost';
92
107
  const emailFromAddress = process.env.EMAIL_FROM || 'noreply@example.com';
93
- const emailService = new EmailService({ apiKey: resendApiKey, fromAddress: emailFromAddress });
108
+ const emailService = new EmailService({ apiKey: resendApiKey, fromAddress: emailFromAddress, log });
94
109
  const passwordResetService = new PasswordResetService({ getDb, emailService, authService, appBaseUrl, log });
95
110
  const invitationService = new InvitationService({ getDb, emailService, authService, appBaseUrl, log });
96
111
  const authRoutes = createAuthRoutes({ getDb, authService, passwordResetService, invitationService, log });
@@ -106,7 +121,7 @@ app.use('/api/users', authMiddleware.requireAuth, userRoutes);
106
121
 
107
122
  // Project management routes (authenticated)
108
123
  const projectService = new ProjectService({ getDb, log });
109
- const projectRoutes = createProjectRoutes({ projectService, log });
124
+ const projectRoutes = createProjectRoutes({ projectService, workspaceService, log });
110
125
  app.use('/api/projects', authMiddleware.requireAuth, projectRoutes);
111
126
 
112
127
  // Git repository routes (nested under /api/projects/:id/git)
@@ -117,22 +132,20 @@ app.use('/api/projects/:id/git', authMiddleware.requireAuth, gitRoutes);
117
132
  const fileRoutes = createFileRoutes({ workspaceService, log });
118
133
  app.use('/api/projects/:id/files', authMiddleware.requireAuth, fileRoutes);
119
134
 
135
+ // Skills routes (nested under /api/projects/:id/skills)
136
+ const skillRoutes = createSkillRoutes({ workspaceService, log });
137
+ app.use('/api/projects/:id/skills', authMiddleware.requireAuth, skillRoutes);
138
+
120
139
  // Agent management routes (authenticated)
121
140
  const SKILLS_DIR = join(__dirname, '..', '..', '..', '..', '.claude', 'skills');
122
- const agentService = new AgentService({
123
- getDb,
124
- log,
125
- skillPaths: {
126
- developer: join(SKILLS_DIR, 'assistkick-developer', 'SKILL.md'),
127
- reviewer: join(SKILLS_DIR, 'assistkick-code-reviewer', 'SKILL.md'),
128
- debugger: join(SKILLS_DIR, 'assistkick-debugger', 'SKILL.md'),
129
- videoScriptWriter: join(SKILLS_DIR, 'video-script-writer', 'SKILL.md'),
130
- videoCompositionAgent: join(SKILLS_DIR, 'video-composition-agent', 'SKILL.md'),
131
- },
132
- });
141
+ const agentService = new AgentService({ getDb, log, skillsDir: SKILLS_DIR });
133
142
  const agentRoutes = createAgentRoutes({ agentService, log });
134
143
  app.use('/api/agents', authMiddleware.requireAuth, agentRoutes);
135
144
 
145
+ // Skills listing route (authenticated) — scans skills directory on disk
146
+ const skillListRoutes = createSkillListRoutes({ agentService, log });
147
+ app.use('/api/skills', authMiddleware.requireAuth, skillListRoutes);
148
+
136
149
  // Workflow management routes (authenticated)
137
150
  const workflowRoutes = createWorkflowRoutes({ workflowService: initWorkflowService, log });
138
151
  app.use('/api/workflows', authMiddleware.requireAuth, workflowRoutes);
@@ -145,16 +158,61 @@ app.use('/api/workflow-groups', authMiddleware.requireAuth, workflowGroupRoutes)
145
158
  const videoRoutes = createVideoRoutes({ ttsService, bundleService, videoRenderService, log });
146
159
  app.use('/api/video', authMiddleware.requireAuth, videoRoutes);
147
160
 
148
- // Ensure default project exists and assign orphan nodes on startup
149
- projectService.ensureDefaultAndAssignOrphans().catch((err: any) => {
150
- log('STARTUP', `Failed to ensure default project: ${err.message}`);
151
- });
161
+ // Wait for DB connection to be ready before running startup tasks.
162
+ // Turso cold-starts can cause the first query to fail if fired immediately.
163
+ const waitForDb = async (maxRetries = 5, delayMs = 500): Promise<void> => {
164
+ for (let i = 0; i < maxRetries; i++) {
165
+ try {
166
+ const db = getDb();
167
+ await db.select({ one: sql`1` }).from(sql`sqlite_master`).limit(1);
168
+ return;
169
+ } catch (err: any) {
170
+ if (i < maxRetries - 1) {
171
+ log('STARTUP', `DB not ready, retrying in ${delayMs}ms (${i + 1}/${maxRetries}):`, err.message);
172
+ await new Promise(r => setTimeout(r, delayMs));
173
+ }
174
+ }
175
+ }
176
+ log('STARTUP', 'DB warmup failed after retries — startup tasks may fail');
177
+ };
152
178
 
153
- // Sync default agent prompts with latest SKILL.md + template code
154
- agentService.syncDefaults().catch((err: any) => {
155
- log('STARTUP', `Failed to sync default agents: ${err.message}`);
179
+ waitForDb().then(async () => {
180
+ try {
181
+ await projectService.ensureDefaultAndAssignOrphans();
182
+ } catch (err: any) {
183
+ logError('STARTUP', 'Failed to ensure default project', err);
184
+ }
185
+ }).catch((err: any) => {
186
+ logError('STARTUP', 'Startup tasks failed', err);
156
187
  });
157
188
 
189
+ // Chat v2 session management routes (authenticated)
190
+ const chatSessionService = new ChatSessionService({ getDb, log });
191
+ const chatMessageRepository = new ChatMessageRepository({ getDb, log });
192
+ const chatSessionRoutes = createChatSessionRoutes({ chatSessionService, chatMessageRepository, log });
193
+ app.use('/api/chat-sessions', authMiddleware.requireAuth, chatSessionRoutes);
194
+
195
+ // Chat v2 file upload routes (authenticated) — base64 file upload for chat attachments
196
+ // Uses a larger body size limit since files are base64-encoded in JSON
197
+ const chatUploadRoutes = createChatUploadRoutes({ workspaceService, log });
198
+ app.use('/api/chat-upload', authMiddleware.requireAuth, express.json({ limit: '15mb' }), chatUploadRoutes);
199
+
200
+ // Chat v2 file serve routes (authenticated) — serves uploaded chat attachment files with caching
201
+ const chatFilesRoutes = createChatFilesRoutes({ workspaceService, log });
202
+ app.use('/api/chat-files', authMiddleware.requireAuth, chatFilesRoutes);
203
+
204
+ // Preview server manager — manages dev server lifecycle
205
+ const devCommandDetector = new DevCommandDetector({ log });
206
+ const previewManager = new PreviewServerManager({ devCommandDetector, log });
207
+
208
+ // Preview API routes (authenticated)
209
+ const previewApiRoutes = createPreviewApiRoutes({ previewManager, workspaceService, projectService, log });
210
+ app.use('/api/preview', authMiddleware.requireAuth, previewApiRoutes);
211
+
212
+ // Preview proxy middleware — must be before SPA fallback so /apps/* is intercepted
213
+ const previewProxy = createPreviewProxyMiddleware({ previewManager, authMiddleware, log });
214
+ app.use(previewProxy);
215
+
158
216
  // PTY session manager — initialized early so terminal REST routes can reference it
159
217
  // Resolve project root: from packages/backend/src → assistkick-product-system → repo root
160
218
  const PROJECT_ROOT = join(__dirname, '..', '..', '..', '..');
@@ -196,16 +254,37 @@ if (existsSync(FRONTEND_DIST)) {
196
254
  const server = createServer(app);
197
255
 
198
256
  // Set up WebSocket for terminal
199
- const wss = new WebSocketServer({ noServer: true });
200
- const terminalHandler = new TerminalWsHandler({ wss, authService, ptyManager, log });
257
+ const terminalWss = new WebSocketServer({ noServer: true });
258
+ const terminalHandler = new TerminalWsHandler({ wss: terminalWss, authService, ptyManager, log });
259
+
260
+ // Set up WebSocket for Chat v2 streaming with permission management
261
+ const chatWss = new WebSocketServer({ noServer: true });
262
+ const chatCliBridge = new ChatCliBridge({ workspacesDir: paths.workspacesDir, log });
263
+ const permissionService = new PermissionService({ getDb, log });
264
+ const titleGeneratorService = new TitleGeneratorService({ log });
265
+ const chatHandler = new ChatWsHandler({ wss: chatWss, authService, chatCliBridge, permissionService, chatMessageRepository, chatSessionService, titleGeneratorService, log });
266
+
267
+ // Chat permission routes — used by MCP permission server (no auth — internal only)
268
+ const chatPermissionRoutes = createChatPermissionRoutes({ permissionService, log });
269
+ app.use('/api/chat/permission', chatPermissionRoutes);
201
270
 
202
271
  server.on('upgrade', (req, socket, head) => {
203
- terminalHandler.handleUpgrade(req, socket, head);
272
+ const url = new URL(req.url || '', 'http://localhost');
273
+ if (url.pathname === '/api/chat') {
274
+ chatHandler.handleUpgrade(req, socket, head);
275
+ } else if (url.pathname.startsWith('/apps/')) {
276
+ // Let http-proxy-middleware handle WebSocket upgrades for preview apps (HMR)
277
+ // The proxy middleware registers its own upgrade handler on the server
278
+ } else {
279
+ terminalHandler.handleUpgrade(req, socket, head);
280
+ }
204
281
  });
205
282
 
206
- // Clean up PTY sessions on server shutdown
283
+ // Clean up PTY sessions and chat CLI processes on server shutdown
207
284
  process.on('SIGTERM', () => {
285
+ previewManager.stopAll();
208
286
  ptyManager.destroyAllPty();
287
+ chatCliBridge.killAll();
209
288
  server.close();
210
289
  });
211
290
 
@@ -213,6 +292,7 @@ server.listen(args.port, () => {
213
292
  log('SERVER', `API server running at http://localhost:${args.port}`);
214
293
  log('SERVER', `Verbose mode: ${args.verbose ? 'ON' : 'OFF'}`);
215
294
  log('SERVER', `WebSocket terminal endpoint: ws://localhost:${args.port}/api/terminal`);
295
+ log('SERVER', `WebSocket chat endpoint: ws://localhost:${args.port}/api/chat`);
216
296
  if (existsSync(FRONTEND_DIST)) {
217
297
  log('SERVER', `Serving frontend from ${FRONTEND_DIST}`);
218
298
  } else {