@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
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Saad Naveed
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,123 @@
1
+ # ClaudeDeck
2
+
3
+ A mobile-first web UI for managing AI coding sessions.
4
+
5
+ [![Discord](https://img.shields.io/badge/Discord-Join%20us-5865F2?logo=discord&logoColor=white)](https://discord.gg/cSjutkCGAh)
6
+
7
+ https://github.com/user-attachments/assets/0e2e66f7-037e-4739-99ec-608d1840df0a
8
+
9
+ ![ClaudeDeck Screenshot](screenshot-v2.png)
10
+
11
+ ## Installation
12
+
13
+ ### Via npm (Recommended)
14
+
15
+ If you already have Node.js 24+ installed:
16
+
17
+ ```bash
18
+ # Install globally
19
+ npm install -g @atercates/claude-deck
20
+
21
+ # Run setup (checks/installs tmux, ripgrep, builds app)
22
+ claude-deck install
23
+
24
+ # Start the server
25
+ claude-deck start
26
+ ```
27
+
28
+ ### Via curl (Installs everything)
29
+
30
+ For fresh installs without Node.js:
31
+
32
+ ```bash
33
+ curl -fsSL https://raw.githubusercontent.com/atercates/claude-deck/main/scripts/install.sh | bash
34
+ claude-deck start
35
+ ```
36
+
37
+ ### Desktop App
38
+
39
+ Download native desktop apps from [Releases](https://github.com/atercates/claude-deck/releases):
40
+
41
+ - macOS (Apple Silicon): `.dmg`
42
+ - Linux: `.deb` or `.AppImage`
43
+
44
+ > **Note:** The desktop app is a native wrapper around the web UI. You still need to install and run ClaudeDeck (via the installer script above) for the backend server. The desktop app just provides a convenient native window instead of using your browser.
45
+
46
+ > **Don't want to self-host?** Try [ClaudeDeck Cloud](https://runagentos.com) - pre-configured cloud VMs for AI coding.
47
+
48
+ ### Manual Install
49
+
50
+ ```bash
51
+ git clone https://github.com/atercates/claude-deck
52
+ cd claude-deck
53
+ npm install
54
+ npm run dev # http://localhost:3011
55
+ ```
56
+
57
+ ### Prerequisites
58
+
59
+ - Node.js 24+
60
+ - tmux
61
+ - [ripgrep](https://github.com/BurntSushi/ripgrep) (for code search - auto-installed by installer script, or run `claude-deck update`)
62
+ - At least one AI CLI: [Claude Code](https://github.com/anthropics/claude-code), [Codex](https://github.com/openai/codex), [OpenCode](https://github.com/anomalyco/opencode), [Gemini CLI](https://github.com/google-gemini/gemini-cli), [Aider](https://aider.chat/), or [Cursor CLI](https://cursor.com/cli)
63
+
64
+ ## Supported Agents
65
+
66
+ | Agent | Resume | Fork | Auto-Approve |
67
+ | ----------- | ------ | ---- | -------------------------------- |
68
+ | Claude Code | ✅ | ✅ | `--dangerously-skip-permissions` |
69
+ | Codex | ❌ | ❌ | `--approval-mode full-auto` |
70
+ | OpenCode | ❌ | ❌ | Config file |
71
+ | Gemini CLI | ❌ | ❌ | `--yolomode` |
72
+ | Aider | ❌ | ❌ | `--yes` |
73
+ | Cursor CLI | ❌ | ❌ | N/A |
74
+ | Amp | ❌ | ❌ | `--dangerously-allow-all` |
75
+ | Pi | ❌ | ❌ | N/A |
76
+ | Oh My Pi | ❌ | ❌ | N/A |
77
+
78
+ ## Features
79
+
80
+ - **Mobile-first** - Full functionality from your phone, not a dumbed-down responsive view
81
+ - **Voice-to-text** - Dictate prompts to your coding sessions hands-free
82
+ - **Multi-pane layout** - Run up to 4 sessions side-by-side
83
+ - **Code search** - Fast codebase search with syntax-highlighted results (Cmd+K)
84
+ - **File picker** - Browse and attach files to sessions, with direct upload from mobile
85
+ - **Clone from GitHub** - Clone repos directly from the UI when creating projects
86
+ - **Git integration** - Status, diffs, commits, PRs from the UI
87
+ - **Git worktrees** - Isolated branches with auto-setup
88
+ - **Dev servers** - Start/stop Node.js and Docker servers
89
+ - **Session orchestration** - Conductor/worker model via MCP
90
+
91
+ ## CLI Commands
92
+
93
+ ```bash
94
+ claude-deck run # Start and open browser
95
+ claude-deck start # Start in background
96
+ claude-deck stop # Stop server
97
+ claude-deck status # Show URLs
98
+ claude-deck logs # Tail logs
99
+ claude-deck update # Update to latest
100
+ ```
101
+
102
+ ## Mobile Access
103
+
104
+ Use [Tailscale](https://tailscale.com) for secure access from your phone:
105
+
106
+ 1. Install Tailscale on your dev machine and phone
107
+ 2. Sign in with the same account
108
+ 3. Access `http://100.x.x.x:3011` from your phone
109
+
110
+ ## Documentation
111
+
112
+ For configuration and advanced usage, see the [docs](https://www.runagentos.com/docs).
113
+
114
+ ## Related Projects
115
+
116
+ - **[aTerm](https://github.com/atercates/aTerm)** - A Tauri-based desktop terminal workspace for AI-assisted coding. While ClaudeDeck is a mobile-first web UI, aTerm is a native desktop app with multi-pane layouts optimized for running AI coding agents (Claude Code, Aider, OpenCode) alongside shells, dev servers, and a built-in git panel. Choose ClaudeDeck for mobile access and browser-based workflows, or aTerm for a native desktop terminal experience.
117
+ - **[LumifyHub](https://lumifyhub.io)** - Team collaboration platform with real-time chat and structured documentation. Useful alongside ClaudeDeck for coordinating multi-agent work across a team — share session context, document architectural decisions from coding sessions, and track progress across parallel agent workflows.
118
+
119
+ ## License
120
+
121
+ MIT License - Free and open source.
122
+
123
+ See [LICENSE](LICENSE) for full terms.
@@ -0,0 +1,66 @@
1
+ import { NextRequest, NextResponse } from "next/server";
2
+ import { queries } from "@/lib/db";
3
+
4
+ export async function GET() {
5
+ try {
6
+ const items = await queries.getAllHiddenItems();
7
+ return NextResponse.json({ items });
8
+ } catch (error) {
9
+ console.error("Error fetching hidden items:", error);
10
+ return NextResponse.json(
11
+ { error: "Failed to fetch hidden items" },
12
+ { status: 500 }
13
+ );
14
+ }
15
+ }
16
+
17
+ export async function POST(request: NextRequest) {
18
+ try {
19
+ const { itemType, itemId } = await request.json();
20
+
21
+ if (!itemType || !itemId) {
22
+ return NextResponse.json(
23
+ { error: "itemType and itemId are required" },
24
+ { status: 400 }
25
+ );
26
+ }
27
+
28
+ if (!["project", "session"].includes(itemType)) {
29
+ return NextResponse.json(
30
+ { error: "itemType must be 'project' or 'session'" },
31
+ { status: 400 }
32
+ );
33
+ }
34
+
35
+ await queries.hideItem(itemType, itemId);
36
+ return NextResponse.json({ hidden: true });
37
+ } catch (error) {
38
+ console.error("Error hiding item:", error);
39
+ return NextResponse.json(
40
+ { error: "Failed to hide item" },
41
+ { status: 500 }
42
+ );
43
+ }
44
+ }
45
+
46
+ export async function DELETE(request: NextRequest) {
47
+ try {
48
+ const { itemType, itemId } = await request.json();
49
+
50
+ if (!itemType || !itemId) {
51
+ return NextResponse.json(
52
+ { error: "itemType and itemId are required" },
53
+ { status: 400 }
54
+ );
55
+ }
56
+
57
+ await queries.unhideItem(itemType, itemId);
58
+ return NextResponse.json({ hidden: false });
59
+ } catch (error) {
60
+ console.error("Error unhiding item:", error);
61
+ return NextResponse.json(
62
+ { error: "Failed to unhide item" },
63
+ { status: 500 }
64
+ );
65
+ }
66
+ }
@@ -0,0 +1,71 @@
1
+ import { NextRequest, NextResponse } from "next/server";
2
+ import { getCachedSessions, getCachedProjects } from "@/lib/claude/jsonl-cache";
3
+ import { queries } from "@/lib/db";
4
+ import fs from "fs";
5
+ import path from "path";
6
+
7
+ function resolveValidCwd(
8
+ cwd: string | null,
9
+ projectDirectory: string | null
10
+ ): string {
11
+ if (!cwd && projectDirectory) return projectDirectory;
12
+ if (!cwd) return process.env.HOME || "/";
13
+ if (fs.existsSync(cwd)) return cwd;
14
+ let dir = cwd;
15
+ while (dir && dir !== "/" && !fs.existsSync(dir)) {
16
+ dir = path.dirname(dir);
17
+ }
18
+ if (dir && dir !== "/" && fs.existsSync(dir)) return dir;
19
+ return projectDirectory || process.env.HOME || "/";
20
+ }
21
+
22
+ interface RouteParams {
23
+ params: Promise<{ name: string }>;
24
+ }
25
+
26
+ export async function GET(request: NextRequest, { params }: RouteParams) {
27
+ try {
28
+ const { name } = await params;
29
+ const { searchParams } = new URL(request.url);
30
+ const limit = parseInt(searchParams.get("limit") || "50", 10);
31
+ const offset = parseInt(searchParams.get("offset") || "0", 10);
32
+ const includeHidden = searchParams.get("includeHidden") === "true";
33
+
34
+ const [allSessions, allProjects] = await Promise.all([
35
+ getCachedSessions(name),
36
+ getCachedProjects(),
37
+ ]);
38
+
39
+ const project = allProjects.find((p) => p.name === name);
40
+ const projectDir = project?.directory || null;
41
+
42
+ const hiddenItems = await queries.getHiddenItems("session");
43
+ const hiddenSet = new Set(hiddenItems.map((h) => h.item_id));
44
+
45
+ const enriched = allSessions
46
+ .filter((s) => s.messageCount > 2 || s.cwd)
47
+ .map((s) => ({
48
+ ...s,
49
+ cwd: resolveValidCwd(s.cwd, projectDir),
50
+ hidden: hiddenSet.has(s.sessionId),
51
+ }));
52
+
53
+ const filtered = includeHidden
54
+ ? enriched
55
+ : enriched.filter((s) => !s.hidden);
56
+
57
+ const paginated = filtered.slice(offset, offset + limit);
58
+
59
+ return NextResponse.json({
60
+ sessions: paginated,
61
+ total: filtered.length,
62
+ hasMore: offset + limit < filtered.length,
63
+ });
64
+ } catch (error) {
65
+ console.error("Error fetching sessions:", error);
66
+ return NextResponse.json(
67
+ { error: "Failed to fetch sessions" },
68
+ { status: 500 }
69
+ );
70
+ }
71
+ }
@@ -0,0 +1,44 @@
1
+ import { NextResponse } from "next/server";
2
+ import { getCachedProjects } from "@/lib/claude/jsonl-cache";
3
+ import { queries } from "@/lib/db";
4
+
5
+ export interface ClaudeProject {
6
+ name: string;
7
+ directory: string | null;
8
+ displayName: string;
9
+ sessionCount: number;
10
+ lastActivity: string | null;
11
+ hidden: boolean;
12
+ }
13
+
14
+ export async function GET() {
15
+ try {
16
+ const cachedProjects = await getCachedProjects();
17
+ const hiddenItems = await queries.getHiddenItems("project");
18
+ const hiddenSet = new Set(hiddenItems.map((h) => h.item_id));
19
+
20
+ const projects: ClaudeProject[] = cachedProjects.map((p) => ({
21
+ ...p,
22
+ hidden: hiddenSet.has(p.name),
23
+ }));
24
+
25
+ projects.sort((a, b) => {
26
+ if (a.hidden !== b.hidden) return a.hidden ? 1 : -1;
27
+ if (!a.lastActivity && !b.lastActivity) return 0;
28
+ if (!a.lastActivity) return 1;
29
+ if (!b.lastActivity) return -1;
30
+ return (
31
+ new Date(b.lastActivity).getTime() -
32
+ new Date(a.lastActivity).getTime()
33
+ );
34
+ });
35
+
36
+ return NextResponse.json({ projects });
37
+ } catch (error) {
38
+ console.error("Error discovering Claude projects:", error);
39
+ return NextResponse.json(
40
+ { error: "Failed to discover projects" },
41
+ { status: 500 }
42
+ );
43
+ }
44
+ }
@@ -0,0 +1,12 @@
1
+ import { NextResponse } from "next/server";
2
+ import { isRipgrepAvailable } from "@/lib/code-search";
3
+
4
+ export async function GET() {
5
+ try {
6
+ const available = isRipgrepAvailable();
7
+ return NextResponse.json({ available });
8
+ } catch (error) {
9
+ console.error("Error checking ripgrep availability:", error);
10
+ return NextResponse.json({ available: false });
11
+ }
12
+ }
@@ -0,0 +1,47 @@
1
+ import { NextRequest, NextResponse } from "next/server";
2
+ import { searchCode, formatSearchResults } from "@/lib/code-search";
3
+
4
+ export async function GET(request: NextRequest) {
5
+ try {
6
+ const { searchParams } = request.nextUrl;
7
+ const query = searchParams.get("query");
8
+ const path = searchParams.get("path");
9
+ const maxResults = parseInt(searchParams.get("maxResults") || "100");
10
+ const contextLines = parseInt(searchParams.get("contextLines") || "2");
11
+
12
+ if (!query) {
13
+ return NextResponse.json(
14
+ { error: "Query parameter is required" },
15
+ { status: 400 }
16
+ );
17
+ }
18
+
19
+ if (!path) {
20
+ return NextResponse.json(
21
+ { error: "Path parameter is required" },
22
+ { status: 400 }
23
+ );
24
+ }
25
+
26
+ const expandedPath = path.replace(/^~/, process.env.HOME || "");
27
+
28
+ const rawMatches = searchCode(expandedPath, query, {
29
+ maxResults,
30
+ contextLines,
31
+ });
32
+
33
+ const results = formatSearchResults(rawMatches);
34
+
35
+ return NextResponse.json({
36
+ results,
37
+ query,
38
+ path: expandedPath,
39
+ count: results.length,
40
+ });
41
+ } catch (error) {
42
+ console.error("Code search error:", error);
43
+ const message =
44
+ error instanceof Error ? error.message : "Failed to search code";
45
+ return NextResponse.json({ error: message }, { status: 500 });
46
+ }
47
+ }
@@ -0,0 +1,23 @@
1
+ import { NextRequest, NextResponse } from "next/server";
2
+ import { getServerLogs } from "@/lib/dev-servers";
3
+
4
+ // GET /api/dev-servers/[id]/logs - Get server logs
5
+ export async function GET(
6
+ request: NextRequest,
7
+ { params }: { params: Promise<{ id: string }> }
8
+ ) {
9
+ try {
10
+ const { id } = await params;
11
+ const { searchParams } = new URL(request.url);
12
+ const lines = parseInt(searchParams.get("lines") || "100", 10);
13
+
14
+ const logs = await getServerLogs(id, lines);
15
+ return NextResponse.json({ logs });
16
+ } catch (error) {
17
+ console.error("Error getting dev server logs:", error);
18
+ return NextResponse.json(
19
+ { error: "Failed to get dev server logs" },
20
+ { status: 500 }
21
+ );
22
+ }
23
+ }
@@ -0,0 +1,20 @@
1
+ import { NextRequest, NextResponse } from "next/server";
2
+ import { restartServer } from "@/lib/dev-servers";
3
+
4
+ // POST /api/dev-servers/[id]/restart - Restart a server
5
+ export async function POST(
6
+ request: NextRequest,
7
+ { params }: { params: Promise<{ id: string }> }
8
+ ) {
9
+ try {
10
+ const { id } = await params;
11
+ const server = await restartServer(id);
12
+ return NextResponse.json({ server });
13
+ } catch (error) {
14
+ console.error("Error restarting dev server:", error);
15
+ return NextResponse.json(
16
+ { error: "Failed to restart dev server" },
17
+ { status: 500 }
18
+ );
19
+ }
20
+ }
@@ -0,0 +1,51 @@
1
+ import { NextRequest, NextResponse } from "next/server";
2
+ import { queries, type DevServer } from "@/lib/db";
3
+ import { getServerStatus, removeServer } from "@/lib/dev-servers";
4
+
5
+ // GET /api/dev-servers/[id] - Get single server with live status
6
+ export async function GET(
7
+ request: NextRequest,
8
+ { params }: { params: Promise<{ id: string }> }
9
+ ) {
10
+ try {
11
+ const { id } = await params;
12
+ const server = await queries.getDevServer(id) as DevServer | undefined;
13
+
14
+ if (!server) {
15
+ return NextResponse.json({ error: "Server not found" }, { status: 404 });
16
+ }
17
+
18
+ // Get live status
19
+ const liveStatus = await getServerStatus(server);
20
+ if (liveStatus !== server.status) {
21
+ await queries.updateDevServerStatus(liveStatus, id);
22
+ server.status = liveStatus;
23
+ }
24
+
25
+ return NextResponse.json({ server });
26
+ } catch (error) {
27
+ console.error("Error getting dev server:", error);
28
+ return NextResponse.json(
29
+ { error: "Failed to get dev server" },
30
+ { status: 500 }
31
+ );
32
+ }
33
+ }
34
+
35
+ // DELETE /api/dev-servers/[id] - Stop and remove server
36
+ export async function DELETE(
37
+ request: NextRequest,
38
+ { params }: { params: Promise<{ id: string }> }
39
+ ) {
40
+ try {
41
+ const { id } = await params;
42
+ await removeServer(id);
43
+ return NextResponse.json({ success: true });
44
+ } catch (error) {
45
+ console.error("Error removing dev server:", error);
46
+ return NextResponse.json(
47
+ { error: "Failed to remove dev server" },
48
+ { status: 500 }
49
+ );
50
+ }
51
+ }
@@ -0,0 +1,20 @@
1
+ import { NextRequest, NextResponse } from "next/server";
2
+ import { stopServer } from "@/lib/dev-servers";
3
+
4
+ // POST /api/dev-servers/[id]/stop - Stop a server
5
+ export async function POST(
6
+ request: NextRequest,
7
+ { params }: { params: Promise<{ id: string }> }
8
+ ) {
9
+ try {
10
+ const { id } = await params;
11
+ await stopServer(id);
12
+ return NextResponse.json({ success: true });
13
+ } catch (error) {
14
+ console.error("Error stopping dev server:", error);
15
+ return NextResponse.json(
16
+ { error: "Failed to stop dev server" },
17
+ { status: 500 }
18
+ );
19
+ }
20
+ }
@@ -0,0 +1,39 @@
1
+ import { NextRequest, NextResponse } from "next/server";
2
+ import { detectServers } from "@/lib/dev-servers";
3
+ import { queries, type Project } from "@/lib/db";
4
+
5
+ // GET /api/dev-servers/detect?projectId=X - Auto-detect available dev servers
6
+ export async function GET(request: NextRequest) {
7
+ try {
8
+ const { searchParams } = new URL(request.url);
9
+ const projectId = searchParams.get("projectId");
10
+
11
+ if (!projectId) {
12
+ return NextResponse.json(
13
+ { error: "projectId is required" },
14
+ { status: 400 }
15
+ );
16
+ }
17
+
18
+ const project = await queries.getProject(projectId) as
19
+ | Project
20
+ | undefined;
21
+ if (!project) {
22
+ return NextResponse.json({ error: "Project not found" }, { status: 404 });
23
+ }
24
+
25
+ // Expand ~ to home directory
26
+ const expandedPath = project.working_directory.startsWith("~")
27
+ ? project.working_directory.replace("~", process.env.HOME || "")
28
+ : project.working_directory;
29
+
30
+ const servers = await detectServers(expandedPath);
31
+ return NextResponse.json({ servers, workingDirectory: expandedPath });
32
+ } catch (error) {
33
+ console.error("Error detecting dev servers:", error);
34
+ return NextResponse.json(
35
+ { error: "Failed to detect dev servers" },
36
+ { status: 500 }
37
+ );
38
+ }
39
+ }
@@ -0,0 +1,48 @@
1
+ import { NextRequest, NextResponse } from "next/server";
2
+ import { getAllServers, startServer } from "@/lib/dev-servers";
3
+
4
+ // GET /api/dev-servers - List all servers with live status
5
+ export async function GET() {
6
+ try {
7
+ const servers = await getAllServers();
8
+ return NextResponse.json({ servers });
9
+ } catch (error) {
10
+ console.error("Error getting dev servers:", error);
11
+ return NextResponse.json(
12
+ { error: "Failed to get dev servers" },
13
+ { status: 500 }
14
+ );
15
+ }
16
+ }
17
+
18
+ // POST /api/dev-servers - Start a new server
19
+ export async function POST(request: NextRequest) {
20
+ try {
21
+ const body = await request.json();
22
+ const { projectId, type, name, command, workingDirectory, ports } = body;
23
+
24
+ if (!projectId || !type || !name || !command || !workingDirectory) {
25
+ return NextResponse.json(
26
+ { error: "Missing required fields" },
27
+ { status: 400 }
28
+ );
29
+ }
30
+
31
+ const server = await startServer({
32
+ projectId,
33
+ type,
34
+ name,
35
+ command,
36
+ workingDirectory,
37
+ ports,
38
+ });
39
+
40
+ return NextResponse.json({ server });
41
+ } catch (error) {
42
+ console.error("Error starting dev server:", error);
43
+ return NextResponse.json(
44
+ { error: "Failed to start dev server" },
45
+ { status: 500 }
46
+ );
47
+ }
48
+ }
@@ -0,0 +1,60 @@
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
+ // Max execution time (10 seconds)
8
+ const TIMEOUT = 10000;
9
+
10
+ export async function POST(request: NextRequest) {
11
+ try {
12
+ const body = await request.json();
13
+ const { command } = body;
14
+
15
+ if (!command) {
16
+ return NextResponse.json(
17
+ { error: "No command specified" },
18
+ { status: 400 }
19
+ );
20
+ }
21
+
22
+ const startTime = Date.now();
23
+
24
+ try {
25
+ const { stdout, stderr } = await execAsync(command, {
26
+ timeout: TIMEOUT,
27
+ shell: "/bin/zsh",
28
+ env: {
29
+ ...process.env,
30
+ PATH: `/usr/local/bin:/opt/homebrew/bin:${process.env.PATH}`,
31
+ HOME: process.env.HOME,
32
+ },
33
+ });
34
+
35
+ const duration = Date.now() - startTime;
36
+
37
+ return NextResponse.json({
38
+ success: true,
39
+ output: stdout || stderr,
40
+ duration,
41
+ });
42
+ } catch (execError: unknown) {
43
+ const duration = Date.now() - startTime;
44
+ const error = execError as {
45
+ stdout?: string;
46
+ stderr?: string;
47
+ message?: string;
48
+ };
49
+
50
+ return NextResponse.json({
51
+ success: false,
52
+ output:
53
+ error.stderr || error.stdout || error.message || "Unknown error",
54
+ duration,
55
+ });
56
+ }
57
+ } catch {
58
+ return NextResponse.json({ error: "Invalid request" }, { status: 400 });
59
+ }
60
+ }