@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,164 @@
1
+ import { NextRequest, NextResponse } from "next/server";
2
+ import { isGitRepo } from "@/lib/git-status";
3
+ import {
4
+ checkGhCli,
5
+ getCommitsSinceBase,
6
+ generatePRTitle,
7
+ generatePRBody,
8
+ getPRForBranch,
9
+ createPR,
10
+ getCurrentBranch,
11
+ getBaseBranch,
12
+ } from "@/lib/pr";
13
+ import { generatePRContent } from "@/lib/pr-generation";
14
+
15
+ // GET /api/git/pr - Get PR status (fast - no AI generation)
16
+ // Use ?generate=true to also generate suggested title/body (slow - uses Claude CLI)
17
+ export async function GET(request: NextRequest) {
18
+ const searchParams = request.nextUrl.searchParams;
19
+ const path = searchParams.get("path");
20
+ const shouldGenerate = searchParams.get("generate") === "true";
21
+
22
+ if (!path) {
23
+ return NextResponse.json({ error: "Path is required" }, { status: 400 });
24
+ }
25
+
26
+ if (!isGitRepo(path)) {
27
+ return NextResponse.json(
28
+ { error: "Not a git repository" },
29
+ { status: 400 }
30
+ );
31
+ }
32
+
33
+ if (!checkGhCli()) {
34
+ return NextResponse.json(
35
+ {
36
+ error:
37
+ "GitHub CLI not installed or not authenticated. Run 'gh auth login' first.",
38
+ },
39
+ { status: 400 }
40
+ );
41
+ }
42
+
43
+ try {
44
+ const branch = getCurrentBranch(path);
45
+ const baseBranch = getBaseBranch(path);
46
+
47
+ // Check if on main/master (can't create PR from there)
48
+ if (branch === "main" || branch === "master") {
49
+ return NextResponse.json(
50
+ { error: "Cannot create PR from main/master branch" },
51
+ { status: 400 }
52
+ );
53
+ }
54
+
55
+ // Check if PR already exists
56
+ const existingPR = getPRForBranch(path, branch);
57
+
58
+ // Get commits for listing
59
+ const commits = getCommitsSinceBase(path, baseBranch);
60
+
61
+ // Only generate suggested content if explicitly requested (for PR creation flow)
62
+ let suggestedTitle: string | undefined;
63
+ let suggestedBody: string | undefined;
64
+
65
+ if (shouldGenerate) {
66
+ try {
67
+ const generated = await generatePRContent(path, baseBranch);
68
+ suggestedTitle = generated.title;
69
+ suggestedBody = generated.description;
70
+ } catch {
71
+ // Fallback to simple heuristic
72
+ suggestedTitle = generatePRTitle(commits, branch);
73
+ suggestedBody = generatePRBody(commits);
74
+ }
75
+ }
76
+
77
+ return NextResponse.json({
78
+ branch,
79
+ baseBranch,
80
+ existingPR,
81
+ commits: commits.map((c) => ({ hash: c.hash, subject: c.subject })),
82
+ ...(suggestedTitle && { suggestedTitle }),
83
+ ...(suggestedBody && { suggestedBody }),
84
+ });
85
+ } catch (error) {
86
+ return NextResponse.json(
87
+ {
88
+ error: error instanceof Error ? error.message : "Failed to get PR info",
89
+ },
90
+ { status: 500 }
91
+ );
92
+ }
93
+ }
94
+
95
+ // POST /api/git/pr - Create a new PR
96
+ export async function POST(request: NextRequest) {
97
+ try {
98
+ const body = await request.json();
99
+ const {
100
+ path,
101
+ title,
102
+ description,
103
+ baseBranch: customBase,
104
+ } = body as {
105
+ path: string;
106
+ title: string;
107
+ description: string;
108
+ baseBranch?: string;
109
+ };
110
+
111
+ if (!path) {
112
+ return NextResponse.json({ error: "Path is required" }, { status: 400 });
113
+ }
114
+
115
+ if (!title) {
116
+ return NextResponse.json({ error: "Title is required" }, { status: 400 });
117
+ }
118
+
119
+ if (!isGitRepo(path)) {
120
+ return NextResponse.json(
121
+ { error: "Not a git repository" },
122
+ { status: 400 }
123
+ );
124
+ }
125
+
126
+ if (!checkGhCli()) {
127
+ return NextResponse.json(
128
+ { error: "GitHub CLI not installed or not authenticated" },
129
+ { status: 400 }
130
+ );
131
+ }
132
+
133
+ const branch = getCurrentBranch(path);
134
+ const baseBranch = customBase || getBaseBranch(path);
135
+
136
+ // Check if on main/master
137
+ if (branch === "main" || branch === "master") {
138
+ return NextResponse.json(
139
+ { error: "Cannot create PR from main/master branch" },
140
+ { status: 400 }
141
+ );
142
+ }
143
+
144
+ // Check if PR already exists
145
+ const existingPR = getPRForBranch(path, branch);
146
+ if (existingPR) {
147
+ return NextResponse.json(
148
+ { error: "PR already exists for this branch", pr: existingPR },
149
+ { status: 409 }
150
+ );
151
+ }
152
+
153
+ // Create the PR
154
+ const pr = createPR(path, branch, baseBranch, title, description || "");
155
+
156
+ return NextResponse.json({ pr }, { status: 201 });
157
+ } catch (error) {
158
+ const message = error instanceof Error ? error.message : "Unknown error";
159
+ return NextResponse.json(
160
+ { error: `Failed to create PR: ${message}` },
161
+ { status: 500 }
162
+ );
163
+ }
164
+ }
@@ -0,0 +1,64 @@
1
+ import { NextRequest, NextResponse } from "next/server";
2
+ import {
3
+ push,
4
+ isGitRepo,
5
+ hasUpstream,
6
+ getRemoteUrl,
7
+ getGitStatus,
8
+ expandPath,
9
+ } from "@/lib/git-status";
10
+
11
+ export async function POST(request: NextRequest) {
12
+ try {
13
+ const body = await request.json();
14
+ const { path: rawPath } = body as { path: string };
15
+
16
+ if (!rawPath) {
17
+ return NextResponse.json({ error: "Path is required" }, { status: 400 });
18
+ }
19
+
20
+ const path = expandPath(rawPath);
21
+
22
+ if (!isGitRepo(path)) {
23
+ return NextResponse.json(
24
+ { error: "Not a git repository" },
25
+ { status: 400 }
26
+ );
27
+ }
28
+
29
+ // Check if remote exists
30
+ const remoteUrl = getRemoteUrl(path);
31
+ if (!remoteUrl) {
32
+ return NextResponse.json(
33
+ { error: "No remote origin configured" },
34
+ { status: 400 }
35
+ );
36
+ }
37
+
38
+ // Check if there are commits to push
39
+ const status = getGitStatus(path);
40
+ if (status.ahead === 0) {
41
+ return NextResponse.json({
42
+ success: true,
43
+ message: "Already up to date",
44
+ pushed: false,
45
+ });
46
+ }
47
+
48
+ // Push (set upstream if needed)
49
+ const needsUpstream = !hasUpstream(path);
50
+ const output = push(path, needsUpstream);
51
+
52
+ return NextResponse.json({
53
+ success: true,
54
+ output,
55
+ pushed: true,
56
+ setUpstream: needsUpstream,
57
+ });
58
+ } catch (error) {
59
+ return NextResponse.json(
60
+ { error: error instanceof Error ? error.message : "Failed to push" },
61
+ { status: 500 }
62
+ );
63
+ }
64
+ }
@@ -0,0 +1,40 @@
1
+ import { NextRequest, NextResponse } from "next/server";
2
+ import { stageFile, stageAll, isGitRepo, expandPath } from "@/lib/git-status";
3
+
4
+ export async function POST(request: NextRequest) {
5
+ try {
6
+ const body = await request.json();
7
+ const { path: rawPath, files } = body as { path: string; files?: string[] };
8
+
9
+ if (!rawPath) {
10
+ return NextResponse.json({ error: "Path is required" }, { status: 400 });
11
+ }
12
+
13
+ const path = expandPath(rawPath);
14
+
15
+ if (!isGitRepo(path)) {
16
+ return NextResponse.json(
17
+ { error: "Not a git repository" },
18
+ { status: 400 }
19
+ );
20
+ }
21
+
22
+ // Stage specific files or all
23
+ if (files && files.length > 0) {
24
+ for (const file of files) {
25
+ stageFile(path, file);
26
+ }
27
+ } else {
28
+ stageAll(path);
29
+ }
30
+
31
+ return NextResponse.json({ success: true });
32
+ } catch (error) {
33
+ return NextResponse.json(
34
+ {
35
+ error: error instanceof Error ? error.message : "Failed to stage files",
36
+ },
37
+ { status: 500 }
38
+ );
39
+ }
40
+ }
@@ -0,0 +1,51 @@
1
+ import { NextRequest, NextResponse } from "next/server";
2
+ import {
3
+ getGitStatus,
4
+ isGitRepo,
5
+ getFileDiff,
6
+ getUntrackedFileDiff,
7
+ expandPath,
8
+ } from "@/lib/git-status";
9
+
10
+ export async function GET(request: NextRequest) {
11
+ const searchParams = request.nextUrl.searchParams;
12
+ const rawPath = searchParams.get("path");
13
+ const filePath = searchParams.get("file");
14
+ const staged = searchParams.get("staged") === "true";
15
+
16
+ if (!rawPath) {
17
+ return NextResponse.json({ error: "Path is required" }, { status: 400 });
18
+ }
19
+
20
+ const path = expandPath(rawPath);
21
+
22
+ if (!isGitRepo(path)) {
23
+ return NextResponse.json(
24
+ { error: "Not a git repository" },
25
+ { status: 400 }
26
+ );
27
+ }
28
+
29
+ try {
30
+ // If file is specified, return diff for that file
31
+ if (filePath) {
32
+ const isUntracked = searchParams.get("untracked") === "true";
33
+ const diff = isUntracked
34
+ ? getUntrackedFileDiff(path, filePath)
35
+ : getFileDiff(path, filePath, staged);
36
+ return NextResponse.json({ diff });
37
+ }
38
+
39
+ // Otherwise return full status
40
+ const status = getGitStatus(path);
41
+ return NextResponse.json(status);
42
+ } catch (error) {
43
+ return NextResponse.json(
44
+ {
45
+ error:
46
+ error instanceof Error ? error.message : "Failed to get git status",
47
+ },
48
+ { status: 500 }
49
+ );
50
+ }
51
+ }
@@ -0,0 +1,46 @@
1
+ import { NextRequest, NextResponse } from "next/server";
2
+ import {
3
+ unstageFile,
4
+ unstageAll,
5
+ isGitRepo,
6
+ expandPath,
7
+ } from "@/lib/git-status";
8
+
9
+ export async function POST(request: NextRequest) {
10
+ try {
11
+ const body = await request.json();
12
+ const { path: rawPath, files } = body as { path: string; files?: string[] };
13
+
14
+ if (!rawPath) {
15
+ return NextResponse.json({ error: "Path is required" }, { status: 400 });
16
+ }
17
+
18
+ const path = expandPath(rawPath);
19
+
20
+ if (!isGitRepo(path)) {
21
+ return NextResponse.json(
22
+ { error: "Not a git repository" },
23
+ { status: 400 }
24
+ );
25
+ }
26
+
27
+ // Unstage specific files or all
28
+ if (files && files.length > 0) {
29
+ for (const file of files) {
30
+ unstageFile(path, file);
31
+ }
32
+ } else {
33
+ unstageAll(path);
34
+ }
35
+
36
+ return NextResponse.json({ success: true });
37
+ } catch (error) {
38
+ return NextResponse.json(
39
+ {
40
+ error:
41
+ error instanceof Error ? error.message : "Failed to unstage files",
42
+ },
43
+ { status: 500 }
44
+ );
45
+ }
46
+ }
@@ -0,0 +1,136 @@
1
+ import { NextResponse } from "next/server";
2
+ import { queries, type Group } from "@/lib/db";
3
+
4
+ // GET /api/groups/[...path] - Get a single group
5
+ export async function GET(
6
+ request: Request,
7
+ { params }: { params: Promise<{ path: string[] }> }
8
+ ) {
9
+ const { path: pathParts } = await params;
10
+ const path = pathParts.join("/");
11
+
12
+ try {
13
+ const group = await queries.getGroup(path) as Group | undefined;
14
+
15
+ if (!group) {
16
+ return NextResponse.json({ error: "Group not found" }, { status: 404 });
17
+ }
18
+
19
+ return NextResponse.json({
20
+ group: { ...group, expanded: Boolean(group.expanded) },
21
+ });
22
+ } catch (error) {
23
+ console.error("Error fetching group:", error);
24
+ return NextResponse.json(
25
+ { error: "Failed to fetch group" },
26
+ { status: 500 }
27
+ );
28
+ }
29
+ }
30
+
31
+ // PATCH /api/groups/[...path] - Update group (name, expanded, order)
32
+ export async function PATCH(
33
+ request: Request,
34
+ { params }: { params: Promise<{ path: string[] }> }
35
+ ) {
36
+ const { path: pathParts } = await params;
37
+ const path = pathParts.join("/");
38
+
39
+ try {
40
+ const body = await request.json();
41
+ const { name, expanded, sort_order } = body;
42
+
43
+ // Check if group exists
44
+ const group = await queries.getGroup(path) as Group | undefined;
45
+ if (!group) {
46
+ return NextResponse.json({ error: "Group not found" }, { status: 404 });
47
+ }
48
+
49
+ // Protect default group from being renamed
50
+ if (path === "sessions" && name !== undefined && name !== "Sessions") {
51
+ return NextResponse.json(
52
+ { error: "Cannot rename the default group" },
53
+ { status: 400 }
54
+ );
55
+ }
56
+
57
+ // Update name
58
+ if (name !== undefined) {
59
+ await queries.updateGroupName(name, path);
60
+ }
61
+
62
+ // Update expanded state
63
+ if (expanded !== undefined) {
64
+ await queries.updateGroupExpanded(!!expanded, path);
65
+ }
66
+
67
+ // Update sort order
68
+ if (sort_order !== undefined) {
69
+ await queries.updateGroupOrder(sort_order, path);
70
+ }
71
+
72
+ const updatedGroup = await queries.getGroup(path) as Group;
73
+ return NextResponse.json({
74
+ group: { ...updatedGroup, expanded: Boolean(updatedGroup.expanded) },
75
+ });
76
+ } catch (error) {
77
+ console.error("Error updating group:", error);
78
+ return NextResponse.json(
79
+ { error: "Failed to update group" },
80
+ { status: 500 }
81
+ );
82
+ }
83
+ }
84
+
85
+ // DELETE /api/groups/[...path] - Delete group (moves sessions to parent or default)
86
+ export async function DELETE(
87
+ request: Request,
88
+ { params }: { params: Promise<{ path: string[] }> }
89
+ ) {
90
+ const { path: pathParts } = await params;
91
+ const path = pathParts.join("/");
92
+
93
+ try {
94
+ // Protect default group
95
+ if (path === "sessions") {
96
+ return NextResponse.json(
97
+ { error: "Cannot delete the default group" },
98
+ { status: 400 }
99
+ );
100
+ }
101
+
102
+ // Check if group exists
103
+ const group = await queries.getGroup(path) as Group | undefined;
104
+ if (!group) {
105
+ return NextResponse.json({ error: "Group not found" }, { status: 404 });
106
+ }
107
+
108
+ // Find parent group or use default
109
+ const pathParts2 = path.split("/");
110
+ pathParts2.pop();
111
+ const parentPath =
112
+ pathParts2.length > 0 ? pathParts2.join("/") : "sessions";
113
+
114
+ // Move all sessions in this group to parent
115
+ await queries.moveSessionsToGroup(parentPath, path);
116
+
117
+ // Also move sessions from any subgroups
118
+ const allGroups = await queries.getAllGroups() as Group[];
119
+ const subgroups = allGroups.filter((g) => g.path.startsWith(path + "/"));
120
+ for (const subgroup of subgroups) {
121
+ await queries.moveSessionsToGroup(parentPath, subgroup.path);
122
+ await queries.deleteGroup(subgroup.path);
123
+ }
124
+
125
+ // Delete the group
126
+ await queries.deleteGroup(path);
127
+
128
+ return NextResponse.json({ success: true, movedTo: parentPath });
129
+ } catch (error) {
130
+ console.error("Error deleting group:", error);
131
+ return NextResponse.json(
132
+ { error: "Failed to delete group" },
133
+ { status: 500 }
134
+ );
135
+ }
136
+ }
@@ -0,0 +1,93 @@
1
+ import { NextResponse } from "next/server";
2
+ import { queries, type Group } from "@/lib/db";
3
+
4
+ // GET /api/groups - List all groups
5
+ export async function GET() {
6
+ try {
7
+ const groups = await queries.getAllGroups() as Group[];
8
+
9
+ // Convert expanded from 0/1 to boolean
10
+ const formattedGroups = groups.map((g) => ({
11
+ ...g,
12
+ expanded: Boolean(g.expanded),
13
+ }));
14
+
15
+ return NextResponse.json({ groups: formattedGroups });
16
+ } catch (error) {
17
+ console.error("Error fetching groups:", error);
18
+ return NextResponse.json(
19
+ { error: "Failed to fetch groups" },
20
+ { status: 500 }
21
+ );
22
+ }
23
+ }
24
+
25
+ // POST /api/groups - Create a new group
26
+ export async function POST(request: Request) {
27
+ try {
28
+ const body = await request.json();
29
+ const { name, parentPath } = body;
30
+
31
+ if (!name || typeof name !== "string") {
32
+ return NextResponse.json({ error: "Name is required" }, { status: 400 });
33
+ }
34
+
35
+ // Sanitize name to create path
36
+ const sanitizedName = name
37
+ .toLowerCase()
38
+ .replace(/[^a-z0-9-]/g, "-")
39
+ .replace(/-+/g, "-")
40
+ .replace(/^-|-$/g, "");
41
+
42
+ if (!sanitizedName) {
43
+ return NextResponse.json(
44
+ { error: "Invalid group name" },
45
+ { status: 400 }
46
+ );
47
+ }
48
+
49
+ // Build full path
50
+ const path = parentPath ? `${parentPath}/${sanitizedName}` : sanitizedName;
51
+
52
+ // Check if group already exists
53
+ const existing = await queries.getGroup(path) as Group | undefined;
54
+ if (existing) {
55
+ return NextResponse.json(
56
+ { error: "Group already exists", group: existing },
57
+ { status: 409 }
58
+ );
59
+ }
60
+
61
+ // If parent path specified, ensure parent exists
62
+ if (parentPath) {
63
+ const parent = await queries.getGroup(parentPath) as Group | undefined;
64
+ if (!parent) {
65
+ return NextResponse.json(
66
+ { error: "Parent group does not exist" },
67
+ { status: 400 }
68
+ );
69
+ }
70
+ }
71
+
72
+ // Get max sort order for new group
73
+ const groups = await queries.getAllGroups() as Group[];
74
+ const maxOrder = groups.reduce((max, g) => Math.max(max, g.sort_order), 0);
75
+
76
+ // Create the group
77
+ await queries.createGroup(path, name, maxOrder + 1);
78
+
79
+ const newGroup = await queries.getGroup(path) as Group;
80
+ return NextResponse.json(
81
+ {
82
+ group: { ...newGroup, expanded: Boolean(newGroup.expanded) },
83
+ },
84
+ { status: 201 }
85
+ );
86
+ } catch (error) {
87
+ console.error("Error creating group:", error);
88
+ return NextResponse.json(
89
+ { error: "Failed to create group" },
90
+ { status: 500 }
91
+ );
92
+ }
93
+ }
@@ -0,0 +1,45 @@
1
+ import { NextResponse } from "next/server";
2
+ import { spawnWorker } from "@/lib/orchestration";
3
+
4
+ export async function POST(request: Request) {
5
+ try {
6
+ const body = await request.json();
7
+ const {
8
+ conductorSessionId,
9
+ task,
10
+ workingDirectory,
11
+ branchName,
12
+ useWorktree = true,
13
+ model = "sonnet",
14
+ agentType = "claude",
15
+ } = body;
16
+
17
+ if (!conductorSessionId || !task || !workingDirectory) {
18
+ return NextResponse.json(
19
+ {
20
+ error:
21
+ "Missing required fields: conductorSessionId, task, workingDirectory",
22
+ },
23
+ { status: 400 }
24
+ );
25
+ }
26
+
27
+ const session = await spawnWorker({
28
+ conductorSessionId,
29
+ task,
30
+ workingDirectory,
31
+ branchName,
32
+ useWorktree,
33
+ model,
34
+ agentType,
35
+ });
36
+
37
+ return NextResponse.json({ session });
38
+ } catch (error) {
39
+ console.error("Failed to spawn worker:", error);
40
+ return NextResponse.json(
41
+ { error: "Failed to spawn worker" },
42
+ { status: 500 }
43
+ );
44
+ }
45
+ }