@atercates/claude-deck 0.2.1

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 (293) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +123 -0
  3. package/app/api/claude/hidden/route.ts +66 -0
  4. package/app/api/claude/projects/[name]/sessions/route.ts +71 -0
  5. package/app/api/claude/projects/route.ts +44 -0
  6. package/app/api/code-search/available/route.ts +12 -0
  7. package/app/api/code-search/route.ts +47 -0
  8. package/app/api/dev-servers/[id]/logs/route.ts +23 -0
  9. package/app/api/dev-servers/[id]/restart/route.ts +20 -0
  10. package/app/api/dev-servers/[id]/route.ts +51 -0
  11. package/app/api/dev-servers/[id]/stop/route.ts +20 -0
  12. package/app/api/dev-servers/detect/route.ts +39 -0
  13. package/app/api/dev-servers/route.ts +48 -0
  14. package/app/api/exec/route.ts +60 -0
  15. package/app/api/files/content/route.ts +76 -0
  16. package/app/api/files/route.ts +37 -0
  17. package/app/api/files/upload-temp/route.ts +41 -0
  18. package/app/api/git/check/route.ts +54 -0
  19. package/app/api/git/clone/route.ts +99 -0
  20. package/app/api/git/commit/route.ts +75 -0
  21. package/app/api/git/discard/route.ts +38 -0
  22. package/app/api/git/file-content/route.ts +64 -0
  23. package/app/api/git/history/[hash]/diff/route.ts +38 -0
  24. package/app/api/git/history/[hash]/route.ts +34 -0
  25. package/app/api/git/history/route.ts +27 -0
  26. package/app/api/git/multi-status/route.ts +46 -0
  27. package/app/api/git/pr/route.ts +164 -0
  28. package/app/api/git/push/route.ts +64 -0
  29. package/app/api/git/stage/route.ts +40 -0
  30. package/app/api/git/status/route.ts +51 -0
  31. package/app/api/git/unstage/route.ts +46 -0
  32. package/app/api/groups/[...path]/route.ts +136 -0
  33. package/app/api/groups/route.ts +93 -0
  34. package/app/api/orchestrate/spawn/route.ts +45 -0
  35. package/app/api/orchestrate/workers/[id]/route.ts +89 -0
  36. package/app/api/orchestrate/workers/route.ts +31 -0
  37. package/app/api/projects/[id]/detect/route.ts +27 -0
  38. package/app/api/projects/[id]/dev-servers/[dsId]/route.ts +66 -0
  39. package/app/api/projects/[id]/dev-servers/route.ts +51 -0
  40. package/app/api/projects/[id]/repositories/[repoId]/route.ts +67 -0
  41. package/app/api/projects/[id]/repositories/route.ts +74 -0
  42. package/app/api/projects/[id]/route.ts +108 -0
  43. package/app/api/projects/detect/route.ts +33 -0
  44. package/app/api/projects/route.ts +59 -0
  45. package/app/api/sessions/[id]/claude-session/route.ts +42 -0
  46. package/app/api/sessions/[id]/fork/route.ts +74 -0
  47. package/app/api/sessions/[id]/mcp-config/route.ts +34 -0
  48. package/app/api/sessions/[id]/messages/route.ts +60 -0
  49. package/app/api/sessions/[id]/pr/route.ts +188 -0
  50. package/app/api/sessions/[id]/preview/route.ts +42 -0
  51. package/app/api/sessions/[id]/route.ts +229 -0
  52. package/app/api/sessions/[id]/send-keys/route.ts +119 -0
  53. package/app/api/sessions/[id]/summarize/route.ts +331 -0
  54. package/app/api/sessions/init-script/route.ts +84 -0
  55. package/app/api/sessions/route.ts +209 -0
  56. package/app/api/sessions/status/route.ts +237 -0
  57. package/app/api/system/route.ts +9 -0
  58. package/app/api/tmux/kill-all/route.ts +57 -0
  59. package/app/api/tmux/rename/route.ts +30 -0
  60. package/app/globals.css +174 -0
  61. package/app/icon.svg +11 -0
  62. package/app/layout.tsx +122 -0
  63. package/app/page.tsx +629 -0
  64. package/components/ChatMessage.tsx +65 -0
  65. package/components/ChatView.tsx +276 -0
  66. package/components/ClaudeProjects/ClaudeProjectCard.tsx +195 -0
  67. package/components/ClaudeProjects/ClaudeProjectsSection.tsx +89 -0
  68. package/components/ClaudeProjects/ClaudeSessionCard.tsx +100 -0
  69. package/components/ClaudeProjects/index.ts +1 -0
  70. package/components/CodeSearch/CodeSearchResults.tsx +177 -0
  71. package/components/ConductorPanel.tsx +256 -0
  72. package/components/DevServers/DevServerCard.tsx +311 -0
  73. package/components/DevServers/DevServersSection.tsx +91 -0
  74. package/components/DevServers/ServerLogsModal.tsx +151 -0
  75. package/components/DevServers/StartServerDialog.tsx +359 -0
  76. package/components/DevServers/index.ts +4 -0
  77. package/components/DiffViewer/DiffModal.tsx +151 -0
  78. package/components/DiffViewer/UnifiedDiff.tsx +185 -0
  79. package/components/DiffViewer/index.tsx +2 -0
  80. package/components/DirectoryPicker.tsx +355 -0
  81. package/components/FileExplorer/FileEditor.tsx +276 -0
  82. package/components/FileExplorer/FileTabs.tsx +118 -0
  83. package/components/FileExplorer/FileTree.tsx +214 -0
  84. package/components/FileExplorer/HtmlRenderer.tsx +16 -0
  85. package/components/FileExplorer/MarkdownRenderer.tsx +18 -0
  86. package/components/FileExplorer/index.tsx +520 -0
  87. package/components/FilePicker.tsx +339 -0
  88. package/components/FolderPicker.tsx +201 -0
  89. package/components/GitDrawer/FileEditDialog.tsx +400 -0
  90. package/components/GitDrawer/index.tsx +464 -0
  91. package/components/GitPanel/CommitForm.tsx +205 -0
  92. package/components/GitPanel/CommitHistory.tsx +174 -0
  93. package/components/GitPanel/CommitItem.tsx +196 -0
  94. package/components/GitPanel/FileChanges.tsx +414 -0
  95. package/components/GitPanel/GitPanelTabs.tsx +39 -0
  96. package/components/GitPanel/index.tsx +817 -0
  97. package/components/MessageInput.tsx +82 -0
  98. package/components/NewClaudeSessionDialog.tsx +166 -0
  99. package/components/NewSessionDialog/AdvancedSettings.tsx +78 -0
  100. package/components/NewSessionDialog/AgentSelector.tsx +37 -0
  101. package/components/NewSessionDialog/CreatingOverlay.tsx +94 -0
  102. package/components/NewSessionDialog/NewSessionDialog.types.ts +136 -0
  103. package/components/NewSessionDialog/ProjectSelector.tsx +146 -0
  104. package/components/NewSessionDialog/WorkingDirectoryInput.tsx +55 -0
  105. package/components/NewSessionDialog/WorktreeSection.tsx +92 -0
  106. package/components/NewSessionDialog/hooks/useNewSessionForm.ts +370 -0
  107. package/components/NewSessionDialog/index.tsx +106 -0
  108. package/components/NotificationSettings.tsx +127 -0
  109. package/components/PRCreationModal.tsx +272 -0
  110. package/components/Pane/DesktopTabBar.tsx +353 -0
  111. package/components/Pane/MobileTabBar.tsx +210 -0
  112. package/components/Pane/OpenInVSCode.tsx +69 -0
  113. package/components/Pane/PaneSkeletons.tsx +57 -0
  114. package/components/Pane/index.tsx +558 -0
  115. package/components/PaneLayout.tsx +60 -0
  116. package/components/Projects/DevServersSection.tsx +140 -0
  117. package/components/Projects/DirectoryField.tsx +92 -0
  118. package/components/Projects/NewProjectDialog.tsx +188 -0
  119. package/components/Projects/NewProjectDialog.types.ts +46 -0
  120. package/components/Projects/ProjectCard.tsx +276 -0
  121. package/components/Projects/ProjectSettingsDialog.tsx +811 -0
  122. package/components/Projects/hooks/useNewProjectForm.ts +249 -0
  123. package/components/Projects/index.ts +3 -0
  124. package/components/Providers.tsx +49 -0
  125. package/components/QuickSwitcher.tsx +306 -0
  126. package/components/SessionList/KillAllConfirm.tsx +46 -0
  127. package/components/SessionList/SelectionToolbar.tsx +164 -0
  128. package/components/SessionList/SessionList.types.ts +37 -0
  129. package/components/SessionList/SessionListHeader.tsx +71 -0
  130. package/components/SessionList/hooks/useSessionListMutations.ts +269 -0
  131. package/components/SessionList/index.tsx +189 -0
  132. package/components/ShellDrawer/index.tsx +106 -0
  133. package/components/SidebarFooter.tsx +55 -0
  134. package/components/Terminal/KeybarToggleButton.tsx +45 -0
  135. package/components/Terminal/ScrollToBottomButton.tsx +32 -0
  136. package/components/Terminal/SearchBar.tsx +71 -0
  137. package/components/Terminal/TerminalToolbar.tsx +551 -0
  138. package/components/Terminal/VirtualKeyboard.tsx +711 -0
  139. package/components/Terminal/constants.ts +20 -0
  140. package/components/Terminal/hooks/index.ts +5 -0
  141. package/components/Terminal/hooks/resize-handlers.ts +140 -0
  142. package/components/Terminal/hooks/terminal-init.ts +151 -0
  143. package/components/Terminal/hooks/touch-scroll.ts +155 -0
  144. package/components/Terminal/hooks/useTerminalConnection.ts +282 -0
  145. package/components/Terminal/hooks/useTerminalConnection.types.ts +39 -0
  146. package/components/Terminal/hooks/useTerminalSearch.ts +103 -0
  147. package/components/Terminal/hooks/websocket-connection.ts +274 -0
  148. package/components/Terminal/index.tsx +320 -0
  149. package/components/ThemeToggle.tsx +168 -0
  150. package/components/TmuxSessions.tsx +132 -0
  151. package/components/ToolCallDisplay.tsx +71 -0
  152. package/components/WorkerCard.tsx +245 -0
  153. package/components/a/ABadge.tsx +115 -0
  154. package/components/a/AButton.tsx +163 -0
  155. package/components/a/ADialog.tsx +93 -0
  156. package/components/a/ADropdownMenu.tsx +279 -0
  157. package/components/a/AIconButton.tsx +190 -0
  158. package/components/a/ASheet.tsx +150 -0
  159. package/components/a/ATooltip.tsx +77 -0
  160. package/components/a/index.ts +64 -0
  161. package/components/mobile/SwipeSidebar.tsx +122 -0
  162. package/components/ui/badge.tsx +41 -0
  163. package/components/ui/button.tsx +60 -0
  164. package/components/ui/context-menu.tsx +197 -0
  165. package/components/ui/dialog.tsx +143 -0
  166. package/components/ui/dropdown-menu.tsx +257 -0
  167. package/components/ui/input.tsx +21 -0
  168. package/components/ui/scroll-area.tsx +52 -0
  169. package/components/ui/select.tsx +159 -0
  170. package/components/ui/skeleton.tsx +111 -0
  171. package/components/ui/switch.tsx +31 -0
  172. package/components/ui/textarea.tsx +21 -0
  173. package/components/ui/tooltip.tsx +32 -0
  174. package/components/views/DesktopView.tsx +244 -0
  175. package/components/views/MobileView.tsx +110 -0
  176. package/components/views/types.ts +75 -0
  177. package/contexts/PaneContext.tsx +336 -0
  178. package/data/claude/index.ts +9 -0
  179. package/data/claude/keys.ts +6 -0
  180. package/data/claude/queries.ts +120 -0
  181. package/data/claude/useClaudeUpdates.ts +37 -0
  182. package/data/code-search/index.ts +2 -0
  183. package/data/code-search/keys.ts +7 -0
  184. package/data/code-search/queries.ts +61 -0
  185. package/data/dev-servers/index.ts +8 -0
  186. package/data/dev-servers/keys.ts +4 -0
  187. package/data/dev-servers/queries.ts +104 -0
  188. package/data/files/index.ts +3 -0
  189. package/data/files/keys.ts +4 -0
  190. package/data/files/queries.ts +25 -0
  191. package/data/git/keys.ts +15 -0
  192. package/data/git/queries.ts +395 -0
  193. package/data/groups/index.ts +1 -0
  194. package/data/groups/mutations.ts +95 -0
  195. package/data/projects/index.ts +10 -0
  196. package/data/projects/keys.ts +4 -0
  197. package/data/projects/queries.ts +193 -0
  198. package/data/repositories/index.ts +7 -0
  199. package/data/repositories/keys.ts +5 -0
  200. package/data/repositories/queries.ts +122 -0
  201. package/data/sessions/index.ts +12 -0
  202. package/data/sessions/keys.ts +8 -0
  203. package/data/sessions/queries.ts +218 -0
  204. package/data/statuses/index.ts +1 -0
  205. package/data/statuses/queries.ts +69 -0
  206. package/hooks/useCopyToClipboard.ts +48 -0
  207. package/hooks/useDevServersManager.ts +73 -0
  208. package/hooks/useDirectoryBrowser.ts +90 -0
  209. package/hooks/useDrawerAnimation.ts +27 -0
  210. package/hooks/useFileDrop.ts +87 -0
  211. package/hooks/useFileEditor.ts +184 -0
  212. package/hooks/useGroups.ts +37 -0
  213. package/hooks/useHomePath.ts +34 -0
  214. package/hooks/useKeyRepeat.ts +55 -0
  215. package/hooks/useKeybarVisibility.ts +42 -0
  216. package/hooks/useNotifications.ts +257 -0
  217. package/hooks/useProjects.ts +53 -0
  218. package/hooks/useSessionStatuses.ts +30 -0
  219. package/hooks/useSessions.ts +86 -0
  220. package/hooks/useSpeechRecognition.ts +124 -0
  221. package/hooks/useViewport.ts +32 -0
  222. package/hooks/useViewportHeight.ts +50 -0
  223. package/lib/async-operations.ts +35 -0
  224. package/lib/banner.ts +81 -0
  225. package/lib/claude/jsonl-cache.ts +86 -0
  226. package/lib/claude/jsonl-reader.ts +271 -0
  227. package/lib/claude/process-manager.ts +278 -0
  228. package/lib/claude/stream-parser.ts +173 -0
  229. package/lib/claude/types.ts +154 -0
  230. package/lib/claude/watcher.ts +71 -0
  231. package/lib/client/session-registry.ts +111 -0
  232. package/lib/code-search.ts +121 -0
  233. package/lib/db/index.ts +48 -0
  234. package/lib/db/migrations.ts +45 -0
  235. package/lib/db/queries.ts +460 -0
  236. package/lib/db/schema.ts +114 -0
  237. package/lib/db/types.ts +92 -0
  238. package/lib/db.ts +2 -0
  239. package/lib/dev-servers.ts +509 -0
  240. package/lib/diff-parser.ts +221 -0
  241. package/lib/env-setup.ts +285 -0
  242. package/lib/file-upload.ts +34 -0
  243. package/lib/file-utils.ts +50 -0
  244. package/lib/files.ts +207 -0
  245. package/lib/git-history.ts +294 -0
  246. package/lib/git-status.ts +391 -0
  247. package/lib/git.ts +257 -0
  248. package/lib/mcp-config.ts +81 -0
  249. package/lib/multi-repo-git.ts +179 -0
  250. package/lib/notifications.ts +219 -0
  251. package/lib/orchestration.ts +448 -0
  252. package/lib/panes.ts +232 -0
  253. package/lib/ports.ts +97 -0
  254. package/lib/pr-generation.ts +307 -0
  255. package/lib/pr.ts +234 -0
  256. package/lib/projects.ts +578 -0
  257. package/lib/providers/registry.ts +70 -0
  258. package/lib/providers.ts +121 -0
  259. package/lib/query-client.ts +14 -0
  260. package/lib/rangeSelectionUtils.ts +65 -0
  261. package/lib/status-detector.ts +375 -0
  262. package/lib/terminal-themes.ts +265 -0
  263. package/lib/theme-config.ts +327 -0
  264. package/lib/utils.ts +6 -0
  265. package/lib/worktrees.ts +262 -0
  266. package/mcp/orchestration-server.ts +438 -0
  267. package/package.json +139 -0
  268. package/postcss.config.mjs +7 -0
  269. package/public/icon.svg +10 -0
  270. package/public/icons/icon-128x128.png +0 -0
  271. package/public/icons/icon-144x144.png +0 -0
  272. package/public/icons/icon-152x152.png +0 -0
  273. package/public/icons/icon-192x192.png +0 -0
  274. package/public/icons/icon-384x384.png +0 -0
  275. package/public/icons/icon-512x512.png +0 -0
  276. package/public/icons/icon-72x72.png +0 -0
  277. package/public/icons/icon-96x96.png +0 -0
  278. package/public/manifest.json +61 -0
  279. package/public/sw.js +64 -0
  280. package/scripts/agent-os +91 -0
  281. package/scripts/install.sh +48 -0
  282. package/scripts/lib/ai-clis.sh +132 -0
  283. package/scripts/lib/commands.sh +487 -0
  284. package/scripts/lib/common.sh +89 -0
  285. package/scripts/lib/prerequisites.sh +462 -0
  286. package/scripts/setup.sh +134 -0
  287. package/server.ts +155 -0
  288. package/stores/fileOpen.ts +26 -0
  289. package/stores/index.ts +1 -0
  290. package/stores/initialPrompt.ts +24 -0
  291. package/stores/sessionSelection.ts +48 -0
  292. package/styles/themes.css +603 -0
  293. package/tsconfig.json +33 -0
@@ -0,0 +1,209 @@
1
+ import { NextRequest, NextResponse } from "next/server";
2
+ import { randomUUID } from "crypto";
3
+ import { queries, type Session, type Group } from "@/lib/db";
4
+ import { isValidAgentType, type AgentType } from "@/lib/providers";
5
+ import { createWorktree } from "@/lib/worktrees";
6
+ import { setupWorktree, type SetupResult } from "@/lib/env-setup";
7
+ import { findAvailablePort } from "@/lib/ports";
8
+ import { runInBackground } from "@/lib/async-operations";
9
+ import { getProject } from "@/lib/projects";
10
+
11
+ // GET /api/sessions - List all sessions and groups
12
+ export async function GET() {
13
+ try {
14
+ const sessions = await queries.getAllSessions();
15
+ const groups = await queries.getAllGroups();
16
+
17
+ // Convert expanded from 0/1 to boolean
18
+ const formattedGroups = groups.map((g) => ({
19
+ ...g,
20
+ expanded: Boolean(g.expanded),
21
+ }));
22
+
23
+ return NextResponse.json({ sessions, groups: formattedGroups });
24
+ } catch (error) {
25
+ console.error("Error fetching sessions:", error);
26
+ return NextResponse.json(
27
+ { error: "Failed to fetch sessions" },
28
+ { status: 500 }
29
+ );
30
+ }
31
+ }
32
+
33
+ // Generate a unique session name
34
+ async function generateSessionName(): Promise<string> {
35
+ const sessions = await queries.getAllSessions();
36
+ const existingNumbers = sessions
37
+ .map((s) => {
38
+ const match = s.name.match(/^Session (\d+)$/);
39
+ return match ? parseInt(match[1], 10) : 0;
40
+ })
41
+ .filter((n) => n > 0);
42
+
43
+ const nextNumber =
44
+ existingNumbers.length > 0 ? Math.max(...existingNumbers) + 1 : 1;
45
+ return `Session ${nextNumber}`;
46
+ }
47
+
48
+ // POST /api/sessions - Create new session
49
+ export async function POST(request: NextRequest) {
50
+ try {
51
+ const body = await request.json();
52
+
53
+ const {
54
+ name: providedName,
55
+ workingDirectory = "~",
56
+ parentSessionId = null,
57
+ model = "sonnet",
58
+ systemPrompt = null,
59
+ groupPath = "sessions",
60
+ claudeSessionId = null,
61
+ agentType: rawAgentType = "claude",
62
+ autoApprove = false,
63
+ projectId = "uncategorized",
64
+ // Worktree options
65
+ useWorktree = false,
66
+ featureName = null,
67
+ baseBranch = "main",
68
+ // Tmux option
69
+ useTmux = true,
70
+ // Initial prompt to send when session starts
71
+ initialPrompt = null,
72
+ } = body;
73
+
74
+ // Validate agent type
75
+ const agentType: AgentType = isValidAgentType(rawAgentType)
76
+ ? rawAgentType
77
+ : "claude";
78
+
79
+ // Auto-generate name if not provided
80
+ const name =
81
+ providedName?.trim() ||
82
+ (featureName ? featureName : await generateSessionName());
83
+
84
+ const id = randomUUID();
85
+
86
+ // Handle worktree creation if requested
87
+ let worktreePath: string | null = null;
88
+ let branchName: string | null = null;
89
+ let actualWorkingDirectory = workingDirectory;
90
+ let port: number | null = null;
91
+ let setupResult: SetupResult | null = null;
92
+
93
+ if (useWorktree && featureName) {
94
+ try {
95
+ const worktreeInfo = await createWorktree({
96
+ projectPath: workingDirectory,
97
+ featureName,
98
+ baseBranch,
99
+ });
100
+ worktreePath = worktreeInfo.worktreePath;
101
+ branchName = worktreeInfo.branchName;
102
+ actualWorkingDirectory = worktreeInfo.worktreePath;
103
+
104
+ // Find an available port for the dev server
105
+ port = await findAvailablePort();
106
+
107
+ // Run environment setup in background (non-blocking)
108
+ // This allows instant UI feedback while npm install runs async
109
+ const capturedWorktreePath = worktreeInfo.worktreePath;
110
+ const capturedSourcePath = workingDirectory;
111
+ const capturedPort = port;
112
+ runInBackground(async () => {
113
+ const result = await setupWorktree({
114
+ worktreePath: capturedWorktreePath,
115
+ sourcePath: capturedSourcePath,
116
+ port: capturedPort,
117
+ });
118
+ console.log("Worktree setup completed:", {
119
+ port: capturedPort,
120
+ envFilesCopied: result.envFilesCopied,
121
+ stepsRun: result.steps.length,
122
+ success: result.success,
123
+ });
124
+ }, `setup-worktree-${id}`);
125
+ } catch (error) {
126
+ const message =
127
+ error instanceof Error ? error.message : "Unknown error";
128
+ return NextResponse.json(
129
+ { error: `Failed to create worktree: ${message}` },
130
+ { status: 400 }
131
+ );
132
+ }
133
+ }
134
+
135
+ const tmuxName = useTmux ? `${agentType}-${id}` : null;
136
+ await queries.createSession(
137
+ id,
138
+ name,
139
+ tmuxName,
140
+ actualWorkingDirectory,
141
+ parentSessionId,
142
+ model,
143
+ systemPrompt,
144
+ groupPath,
145
+ agentType,
146
+ autoApprove,
147
+ projectId
148
+ );
149
+
150
+ // Set worktree info if created
151
+ if (worktreePath) {
152
+ await queries.updateSessionWorktree(
153
+ worktreePath,
154
+ branchName,
155
+ baseBranch,
156
+ port,
157
+ id
158
+ );
159
+ }
160
+
161
+ // Set claude_session_id if provided (for importing external sessions)
162
+ if (claudeSessionId) {
163
+ const { getDb } = await import("@/lib/db");
164
+ getDb()
165
+ .prepare("UPDATE sessions SET claude_session_id = ? WHERE id = ?")
166
+ .run(claudeSessionId, id);
167
+ }
168
+
169
+ // Messages are no longer stored in our DB - skipping message copy for forked sessions
170
+
171
+ const session = await queries.getSession(id);
172
+
173
+ // Get project's initial prompt if available
174
+ const project = projectId ? await getProject(projectId) : null;
175
+ const projectInitialPrompt = project?.initial_prompt?.trim();
176
+ const sessionInitialPrompt = initialPrompt?.trim();
177
+
178
+ // Combine prompts: project prompt first, then session prompt
179
+ let combinedPrompt: string | undefined;
180
+ if (projectInitialPrompt && sessionInitialPrompt) {
181
+ combinedPrompt = `${projectInitialPrompt}\n\n${sessionInitialPrompt}`;
182
+ } else if (projectInitialPrompt) {
183
+ combinedPrompt = projectInitialPrompt;
184
+ } else if (sessionInitialPrompt) {
185
+ combinedPrompt = sessionInitialPrompt;
186
+ }
187
+
188
+ // Include setup result and initial prompt in response
189
+ const response: {
190
+ session: Session | null;
191
+ setup?: SetupResult;
192
+ initialPrompt?: string;
193
+ } = { session };
194
+ if (setupResult) {
195
+ response.setup = setupResult;
196
+ }
197
+ if (combinedPrompt) {
198
+ response.initialPrompt = combinedPrompt;
199
+ }
200
+
201
+ return NextResponse.json(response, { status: 201 });
202
+ } catch (error) {
203
+ console.error("Error creating session:", error);
204
+ return NextResponse.json(
205
+ { error: "Failed to create session" },
206
+ { status: 500 }
207
+ );
208
+ }
209
+ }
@@ -0,0 +1,237 @@
1
+ import { NextResponse } from "next/server";
2
+ import { exec } from "child_process";
3
+ import { promisify } from "util";
4
+ import * as fs from "fs";
5
+ import * as path from "path";
6
+ import * as os from "os";
7
+ import { statusDetector, type SessionStatus } from "@/lib/status-detector";
8
+ import type { AgentType } from "@/lib/providers";
9
+ import {
10
+ getManagedSessionPattern,
11
+ getProviderIdFromSessionName,
12
+ getSessionIdFromName,
13
+ } from "@/lib/providers/registry";
14
+ import { getDb } from "@/lib/db";
15
+
16
+ const execAsync = promisify(exec);
17
+
18
+ interface SessionStatusResponse {
19
+ sessionName: string;
20
+ status: SessionStatus;
21
+ lastLine?: string;
22
+ claudeSessionId?: string | null;
23
+ agentType?: AgentType;
24
+ }
25
+
26
+ async function getTmuxSessions(): Promise<string[]> {
27
+ try {
28
+ const { stdout } = await execAsync(
29
+ "tmux list-sessions -F '#{session_name}' 2>/dev/null || true"
30
+ );
31
+ return stdout.trim().split("\n").filter(Boolean);
32
+ } catch {
33
+ return [];
34
+ }
35
+ }
36
+
37
+ async function getTmuxSessionCwd(sessionName: string): Promise<string | null> {
38
+ try {
39
+ const { stdout } = await execAsync(
40
+ `tmux display-message -t "${sessionName}" -p "#{pane_current_path}" 2>/dev/null || echo ""`
41
+ );
42
+ const cwd = stdout.trim();
43
+ return cwd || null;
44
+ } catch {
45
+ return null;
46
+ }
47
+ }
48
+
49
+ // Get Claude session ID from tmux environment variable
50
+ async function getClaudeSessionIdFromEnv(
51
+ sessionName: string
52
+ ): Promise<string | null> {
53
+ try {
54
+ const { stdout } = await execAsync(
55
+ `tmux show-environment -t "${sessionName}" CLAUDE_SESSION_ID 2>/dev/null || echo ""`
56
+ );
57
+ const line = stdout.trim();
58
+ if (line.startsWith("CLAUDE_SESSION_ID=")) {
59
+ const sessionId = line.replace("CLAUDE_SESSION_ID=", "");
60
+ if (sessionId && sessionId !== "null") {
61
+ return sessionId;
62
+ }
63
+ }
64
+ return null;
65
+ } catch {
66
+ return null;
67
+ }
68
+ }
69
+
70
+ // Get Claude session ID by looking at session files on disk
71
+ function getClaudeSessionIdFromFiles(projectPath: string): string | null {
72
+ const home = os.homedir();
73
+ const claudeDir = process.env.CLAUDE_CONFIG_DIR || path.join(home, ".claude");
74
+ const projectDirName = projectPath.replace(/\//g, "-");
75
+ const projectDir = path.join(claudeDir, "projects", projectDirName);
76
+
77
+ if (!fs.existsSync(projectDir)) {
78
+ return null;
79
+ }
80
+
81
+ const uuidPattern =
82
+ /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.jsonl$/;
83
+
84
+ try {
85
+ const files = fs.readdirSync(projectDir);
86
+ let mostRecent: string | null = null;
87
+ let mostRecentTime = 0;
88
+
89
+ for (const file of files) {
90
+ if (file.startsWith("agent-")) continue;
91
+ if (!uuidPattern.test(file)) continue;
92
+
93
+ const filePath = path.join(projectDir, file);
94
+ const stat = fs.statSync(filePath);
95
+
96
+ if (stat.mtimeMs > mostRecentTime) {
97
+ mostRecentTime = stat.mtimeMs;
98
+ mostRecent = file.replace(".jsonl", "");
99
+ }
100
+ }
101
+
102
+ if (mostRecent && Date.now() - mostRecentTime < 5 * 60 * 1000) {
103
+ return mostRecent;
104
+ }
105
+
106
+ const configFile = path.join(claudeDir, ".claude.json");
107
+ if (fs.existsSync(configFile)) {
108
+ try {
109
+ const config = JSON.parse(fs.readFileSync(configFile, "utf-8"));
110
+ if (config.projects?.[projectPath]?.lastSessionId) {
111
+ return config.projects[projectPath].lastSessionId;
112
+ }
113
+ } catch {
114
+ // Ignore config parse errors
115
+ }
116
+ }
117
+
118
+ return null;
119
+ } catch {
120
+ return null;
121
+ }
122
+ }
123
+
124
+ async function getClaudeSessionId(sessionName: string): Promise<string | null> {
125
+ const envId = await getClaudeSessionIdFromEnv(sessionName);
126
+ if (envId) {
127
+ return envId;
128
+ }
129
+
130
+ const cwd = await getTmuxSessionCwd(sessionName);
131
+ if (cwd) {
132
+ return getClaudeSessionIdFromFiles(cwd);
133
+ }
134
+
135
+ return null;
136
+ }
137
+
138
+ async function getLastLine(sessionName: string): Promise<string> {
139
+ try {
140
+ const { stdout } = await execAsync(
141
+ `tmux capture-pane -t "${sessionName}" -p -S -5 2>/dev/null || echo ""`
142
+ );
143
+ const lines = stdout.trim().split("\n").filter(Boolean);
144
+ return lines.pop() || "";
145
+ } catch {
146
+ return "";
147
+ }
148
+ }
149
+
150
+ // UUID pattern for claude-deck managed sessions (derived from registry)
151
+ const UUID_PATTERN = getManagedSessionPattern();
152
+
153
+ // Track previous statuses to detect changes
154
+ const previousStatuses = new Map<string, SessionStatus>();
155
+
156
+ function getAgentTypeFromSessionName(sessionName: string): AgentType {
157
+ return getProviderIdFromSessionName(sessionName) || "claude";
158
+ }
159
+
160
+ export async function GET() {
161
+ try {
162
+ const sessions = await getTmuxSessions();
163
+
164
+ // Get status for claude-deck managed sessions
165
+ const managedSessions = sessions.filter((s) => UUID_PATTERN.test(s));
166
+
167
+ // Use the new status detector
168
+ const statusMap: Record<string, SessionStatusResponse> = {};
169
+
170
+ const db = getDb();
171
+ const sessionsToUpdate: string[] = [];
172
+
173
+ // Process all sessions in parallel for speed
174
+ const sessionPromises = managedSessions.map(async (sessionName) => {
175
+ const [status, claudeSessionId, lastLine] = await Promise.all([
176
+ statusDetector.getStatus(sessionName),
177
+ getClaudeSessionId(sessionName),
178
+ getLastLine(sessionName),
179
+ ]);
180
+ const id = getSessionIdFromName(sessionName);
181
+ const agentType = getAgentTypeFromSessionName(sessionName);
182
+
183
+ return { sessionName, id, status, claudeSessionId, lastLine, agentType };
184
+ });
185
+
186
+ const results = await Promise.all(sessionPromises);
187
+
188
+ for (const {
189
+ sessionName,
190
+ id,
191
+ status,
192
+ claudeSessionId,
193
+ lastLine,
194
+ agentType,
195
+ } of results) {
196
+ // Track status changes - update DB when session becomes active
197
+ const prevStatus = previousStatuses.get(id);
198
+ if (status === "running" || status === "waiting") {
199
+ if (prevStatus !== status) {
200
+ sessionsToUpdate.push(id);
201
+ }
202
+ }
203
+ previousStatuses.set(id, status);
204
+
205
+ statusMap[id] = {
206
+ sessionName,
207
+ status,
208
+ lastLine,
209
+ claudeSessionId,
210
+ agentType,
211
+ };
212
+ }
213
+
214
+ // Batch update sessions and claude_session_id
215
+ for (const id of sessionsToUpdate) {
216
+ db.prepare(
217
+ "UPDATE sessions SET updated_at = datetime('now') WHERE id = ?"
218
+ ).run(id);
219
+ }
220
+
221
+ for (const { id, claudeSessionId } of results) {
222
+ if (claudeSessionId) {
223
+ db.prepare(
224
+ "UPDATE sessions SET claude_session_id = ? WHERE id = ? AND (claude_session_id IS NULL OR claude_session_id != ?)"
225
+ ).run(claudeSessionId, id, claudeSessionId);
226
+ }
227
+ }
228
+
229
+ // Cleanup old trackers
230
+ statusDetector.cleanup();
231
+
232
+ return NextResponse.json({ statuses: statusMap });
233
+ } catch (error) {
234
+ console.error("Error getting session statuses:", error);
235
+ return NextResponse.json({ statuses: {} });
236
+ }
237
+ }
@@ -0,0 +1,9 @@
1
+ import { NextResponse } from "next/server";
2
+ import os from "os";
3
+
4
+ export async function GET() {
5
+ return NextResponse.json({
6
+ user: os.userInfo().username,
7
+ hostname: os.hostname(),
8
+ });
9
+ }
@@ -0,0 +1,57 @@
1
+ import { NextResponse } from "next/server";
2
+ import { exec } from "child_process";
3
+ import { promisify } from "util";
4
+ import { queries, type Session } from "@/lib/db";
5
+
6
+ const execAsync = promisify(exec);
7
+
8
+ // POST /api/tmux/kill-all - Kill all ClaudeDeck tmux sessions and remove from database
9
+ export async function POST() {
10
+ try {
11
+ // Get all tmux sessions
12
+ const { stdout } = await execAsync(
13
+ 'tmux list-sessions -F "#{session_name}" 2>/dev/null || echo ""',
14
+ { timeout: 5000 }
15
+ );
16
+
17
+ const tmuxSessions = stdout
18
+ .trim()
19
+ .split("\n")
20
+ .filter(
21
+ (s) => s && /^(claude|codex|opencode|gemini|aider|cursor)-/.test(s)
22
+ );
23
+
24
+ // Kill each tmux session
25
+ const killed: string[] = [];
26
+ for (const session of tmuxSessions) {
27
+ try {
28
+ await execAsync(`tmux kill-session -t "${session}"`, { timeout: 5000 });
29
+ killed.push(session);
30
+ } catch {
31
+ // Session might already be dead, continue
32
+ }
33
+ }
34
+
35
+ // Delete ALL sessions from database
36
+ const dbSessions = await queries.getAllSessions() as Session[];
37
+ for (const session of dbSessions) {
38
+ try {
39
+ await queries.deleteSession(session.id);
40
+ } catch {
41
+ // Continue on error
42
+ }
43
+ }
44
+
45
+ return NextResponse.json({
46
+ killed: killed.length,
47
+ sessions: killed,
48
+ deletedFromDb: dbSessions.length,
49
+ });
50
+ } catch (error) {
51
+ console.error("Error killing tmux sessions:", error);
52
+ return NextResponse.json(
53
+ { error: "Failed to kill sessions" },
54
+ { status: 500 }
55
+ );
56
+ }
57
+ }
@@ -0,0 +1,30 @@
1
+ import { NextRequest, NextResponse } from "next/server";
2
+ import { exec } from "child_process";
3
+ import { promisify } from "util";
4
+
5
+ const execAsync = promisify(exec);
6
+
7
+ // POST /api/tmux/rename - Rename a tmux session
8
+ export async function POST(request: NextRequest) {
9
+ try {
10
+ const { oldName, newName } = await request.json();
11
+
12
+ if (!oldName || !newName) {
13
+ return NextResponse.json(
14
+ { error: "oldName and newName are required" },
15
+ { status: 400 }
16
+ );
17
+ }
18
+
19
+ // Rename the tmux session
20
+ await execAsync(`tmux rename-session -t "${oldName}" "${newName}"`);
21
+
22
+ return NextResponse.json({ success: true, newName });
23
+ } catch (error) {
24
+ console.error("Error renaming tmux session:", error);
25
+ return NextResponse.json(
26
+ { error: "Failed to rename tmux session" },
27
+ { status: 500 }
28
+ );
29
+ }
30
+ }