@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,76 @@
1
+ import { NextRequest, NextResponse } from "next/server";
2
+ import { readFileContent, writeFileContent } from "@/lib/files";
3
+
4
+ /**
5
+ * GET /api/files/content?path=...
6
+ * Read file contents
7
+ */
8
+ export async function GET(request: NextRequest) {
9
+ try {
10
+ const searchParams = request.nextUrl.searchParams;
11
+ const path = searchParams.get("path");
12
+
13
+ if (!path) {
14
+ return NextResponse.json(
15
+ { error: "Path parameter is required" },
16
+ { status: 400 }
17
+ );
18
+ }
19
+
20
+ // Expand ~ to home directory
21
+ const expandedPath = path.replace(/^~/, process.env.HOME || "");
22
+
23
+ const result = readFileContent(expandedPath);
24
+
25
+ return NextResponse.json({
26
+ ...result,
27
+ path: expandedPath,
28
+ });
29
+ } catch (error) {
30
+ console.error("Error reading file:", error);
31
+ return NextResponse.json(
32
+ { error: error instanceof Error ? error.message : "Failed to read file" },
33
+ { status: 500 }
34
+ );
35
+ }
36
+ }
37
+
38
+ /**
39
+ * POST /api/files/content
40
+ * Write file contents
41
+ */
42
+ export async function POST(request: NextRequest) {
43
+ try {
44
+ const body = await request.json();
45
+ const { path, content } = body;
46
+
47
+ if (!path) {
48
+ return NextResponse.json({ error: "Path is required" }, { status: 400 });
49
+ }
50
+
51
+ if (content === undefined) {
52
+ return NextResponse.json(
53
+ { error: "Content is required" },
54
+ { status: 400 }
55
+ );
56
+ }
57
+
58
+ // Expand ~ to home directory
59
+ const expandedPath = path.replace(/^~/, process.env.HOME || "");
60
+
61
+ const result = writeFileContent(expandedPath, content);
62
+
63
+ return NextResponse.json({
64
+ ...result,
65
+ path: expandedPath,
66
+ });
67
+ } catch (error) {
68
+ console.error("Error writing file:", error);
69
+ return NextResponse.json(
70
+ {
71
+ error: error instanceof Error ? error.message : "Failed to write file",
72
+ },
73
+ { status: 500 }
74
+ );
75
+ }
76
+ }
@@ -0,0 +1,37 @@
1
+ import { NextRequest, NextResponse } from "next/server";
2
+ import { listDirectory } from "@/lib/files";
3
+
4
+ /**
5
+ * GET /api/files?path=...&recursive=true
6
+ * List directory contents
7
+ */
8
+ export async function GET(request: NextRequest) {
9
+ try {
10
+ const searchParams = request.nextUrl.searchParams;
11
+ const path = searchParams.get("path");
12
+ const recursive = searchParams.get("recursive") === "true";
13
+
14
+ if (!path) {
15
+ return NextResponse.json(
16
+ { error: "Path parameter is required" },
17
+ { status: 400 }
18
+ );
19
+ }
20
+
21
+ // Expand ~ to home directory
22
+ const expandedPath = path.replace(/^~/, process.env.HOME || "");
23
+
24
+ const files = listDirectory(expandedPath, {
25
+ recursive,
26
+ maxDepth: recursive ? 2 : 1,
27
+ });
28
+
29
+ return NextResponse.json({ files, path: expandedPath });
30
+ } catch (error) {
31
+ console.error("Error listing directory:", error);
32
+ return NextResponse.json(
33
+ { error: "Failed to list directory" },
34
+ { status: 500 }
35
+ );
36
+ }
37
+ }
@@ -0,0 +1,41 @@
1
+ import { NextResponse } from "next/server";
2
+ import fs from "fs";
3
+ import path from "path";
4
+ import os from "os";
5
+
6
+ export async function POST(request: Request) {
7
+ try {
8
+ const { filename, base64, mimeType } = await request.json();
9
+
10
+ if (!base64) {
11
+ return NextResponse.json({ error: "No image data" }, { status: 400 });
12
+ }
13
+
14
+ // Create temp directory for screenshots if it doesn't exist
15
+ const tempDir = path.join(os.tmpdir(), "claude-deck-screenshots");
16
+ if (!fs.existsSync(tempDir)) {
17
+ fs.mkdirSync(tempDir, { recursive: true });
18
+ }
19
+
20
+ // Generate unique filename
21
+ const ext = mimeType?.split("/")[1] || "png";
22
+ const safeName = filename?.replace(/[^a-zA-Z0-9.-]/g, "_") || "screenshot";
23
+ const uniqueName = `${Date.now()}-${safeName}`;
24
+ const finalName = uniqueName.endsWith(`.${ext}`)
25
+ ? uniqueName
26
+ : `${uniqueName}.${ext}`;
27
+ const filePath = path.join(tempDir, finalName);
28
+
29
+ // Decode base64 and write file
30
+ const buffer = Buffer.from(base64, "base64");
31
+ fs.writeFileSync(filePath, buffer);
32
+
33
+ return NextResponse.json({ path: filePath });
34
+ } catch (error) {
35
+ console.error("Upload error:", error);
36
+ return NextResponse.json(
37
+ { error: "Failed to save image" },
38
+ { status: 500 }
39
+ );
40
+ }
41
+ }
@@ -0,0 +1,54 @@
1
+ import { NextRequest, NextResponse } from "next/server";
2
+ import {
3
+ isGitRepo,
4
+ getBranches,
5
+ getDefaultBranch,
6
+ getCurrentBranch,
7
+ } from "@/lib/git";
8
+
9
+ /**
10
+ * POST /api/git/check
11
+ * Check if a path is a git repository and return branch info
12
+ */
13
+ export async function POST(request: NextRequest) {
14
+ try {
15
+ const body = await request.json();
16
+ const { path: dirPath } = body;
17
+
18
+ if (!dirPath) {
19
+ return NextResponse.json({ error: "Path is required" }, { status: 400 });
20
+ }
21
+
22
+ // Check if it's a git repo
23
+ const isRepo = await isGitRepo(dirPath);
24
+
25
+ if (!isRepo) {
26
+ return NextResponse.json({
27
+ isGitRepo: false,
28
+ branches: [],
29
+ defaultBranch: null,
30
+ currentBranch: null,
31
+ });
32
+ }
33
+
34
+ // Get branch info
35
+ const [branches, defaultBranch, currentBranch] = await Promise.all([
36
+ getBranches(dirPath),
37
+ getDefaultBranch(dirPath),
38
+ getCurrentBranch(dirPath),
39
+ ]);
40
+
41
+ return NextResponse.json({
42
+ isGitRepo: true,
43
+ branches,
44
+ defaultBranch,
45
+ currentBranch,
46
+ });
47
+ } catch (error) {
48
+ console.error("Error checking git repo:", error);
49
+ return NextResponse.json(
50
+ { error: "Failed to check git repository" },
51
+ { status: 500 }
52
+ );
53
+ }
54
+ }
@@ -0,0 +1,99 @@
1
+ import { NextRequest, NextResponse } from "next/server";
2
+ import { exec } from "child_process";
3
+ import { promisify } from "util";
4
+ import * as path from "path";
5
+ import * as fs from "fs/promises";
6
+
7
+ const execAsync = promisify(exec);
8
+
9
+ /**
10
+ * POST /api/git/clone
11
+ * Clone a git repository into a target directory
12
+ */
13
+ export async function POST(request: NextRequest) {
14
+ try {
15
+ const body = await request.json();
16
+ const { url, directory } = body;
17
+
18
+ if (!url) {
19
+ return NextResponse.json(
20
+ { error: "Repository URL is required" },
21
+ { status: 400 }
22
+ );
23
+ }
24
+
25
+ if (!directory) {
26
+ return NextResponse.json(
27
+ { error: "Target directory is required" },
28
+ { status: 400 }
29
+ );
30
+ }
31
+
32
+ // Resolve ~ to home directory
33
+ const resolvedDir = directory.replace(/^~/, process.env.HOME || "");
34
+
35
+ // Verify parent directory exists
36
+ try {
37
+ await fs.access(resolvedDir);
38
+ } catch {
39
+ return NextResponse.json(
40
+ { error: `Directory does not exist: ${directory}` },
41
+ { status: 400 }
42
+ );
43
+ }
44
+
45
+ // Extract repo name from URL for the clone target
46
+ const repoName = extractRepoName(url);
47
+ if (!repoName) {
48
+ return NextResponse.json(
49
+ { error: "Could not determine repository name from URL" },
50
+ { status: 400 }
51
+ );
52
+ }
53
+
54
+ const clonePath = path.join(resolvedDir, repoName);
55
+
56
+ // Check if target already exists
57
+ try {
58
+ await fs.access(clonePath);
59
+ return NextResponse.json(
60
+ { error: `Directory already exists: ${clonePath}` },
61
+ { status: 409 }
62
+ );
63
+ } catch {
64
+ // Good - doesn't exist yet
65
+ }
66
+
67
+ // Clone the repository
68
+ const { stderr } = await execAsync(`git clone "${url}" "${clonePath}"`, {
69
+ timeout: 120000,
70
+ });
71
+
72
+ // git clone outputs progress to stderr, not an error
73
+ if (stderr && stderr.includes("fatal:")) {
74
+ return NextResponse.json({ error: stderr.trim() }, { status: 500 });
75
+ }
76
+
77
+ return NextResponse.json({
78
+ path: clonePath,
79
+ name: repoName,
80
+ });
81
+ } catch (error) {
82
+ const message =
83
+ error instanceof Error ? error.message : "Failed to clone repository";
84
+ console.error("Error cloning repository:", message);
85
+ return NextResponse.json({ error: message }, { status: 500 });
86
+ }
87
+ }
88
+
89
+ function extractRepoName(url: string): string | null {
90
+ // https://github.com/user/repo.git or https://github.com/user/repo
91
+ // git@github.com:user/repo.git
92
+ const match = url.match(
93
+ /(?:[\w.-]+\/([\w.-]+?)(?:\.git)?$|:([\w.-]+\/)([\w.-]+?)(?:\.git)?$)/
94
+ );
95
+ if (match) {
96
+ return match[1] || match[3] || null;
97
+ }
98
+ return null;
99
+ }
@@ -0,0 +1,75 @@
1
+ import { NextRequest, NextResponse } from "next/server";
2
+ import {
3
+ commit,
4
+ isGitRepo,
5
+ isMainBranch,
6
+ createBranch,
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 {
15
+ path: rawPath,
16
+ message,
17
+ branchName,
18
+ } = body as {
19
+ path: string;
20
+ message: string;
21
+ branchName?: string;
22
+ };
23
+
24
+ if (!rawPath) {
25
+ return NextResponse.json({ error: "Path is required" }, { status: 400 });
26
+ }
27
+
28
+ if (!message) {
29
+ return NextResponse.json(
30
+ { error: "Commit message is required" },
31
+ { status: 400 }
32
+ );
33
+ }
34
+
35
+ const path = expandPath(rawPath);
36
+
37
+ if (!isGitRepo(path)) {
38
+ return NextResponse.json(
39
+ { error: "Not a git repository" },
40
+ { status: 400 }
41
+ );
42
+ }
43
+
44
+ // Check if there are staged changes
45
+ const status = getGitStatus(path);
46
+ if (status.staged.length === 0) {
47
+ return NextResponse.json(
48
+ { error: "No staged changes to commit" },
49
+ { status: 400 }
50
+ );
51
+ }
52
+
53
+ // Create new branch if on main/master and branch name provided
54
+ let newBranch = false;
55
+ if (branchName && isMainBranch(path)) {
56
+ createBranch(path, branchName);
57
+ newBranch = true;
58
+ }
59
+
60
+ // Commit
61
+ const output = commit(path, message);
62
+
63
+ return NextResponse.json({
64
+ success: true,
65
+ output,
66
+ newBranch,
67
+ branchName: newBranch ? branchName : undefined,
68
+ });
69
+ } catch (error) {
70
+ return NextResponse.json(
71
+ { error: error instanceof Error ? error.message : "Failed to commit" },
72
+ { status: 500 }
73
+ );
74
+ }
75
+ }
@@ -0,0 +1,38 @@
1
+ import { NextRequest, NextResponse } from "next/server";
2
+ import { discardChanges, 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, file } = body as { path: string; file: string };
8
+
9
+ if (!rawPath) {
10
+ return NextResponse.json({ error: "Path is required" }, { status: 400 });
11
+ }
12
+
13
+ if (!file) {
14
+ return NextResponse.json({ error: "File is required" }, { status: 400 });
15
+ }
16
+
17
+ const path = expandPath(rawPath);
18
+
19
+ if (!isGitRepo(path)) {
20
+ return NextResponse.json(
21
+ { error: "Not a git repository" },
22
+ { status: 400 }
23
+ );
24
+ }
25
+
26
+ discardChanges(path, file);
27
+
28
+ return NextResponse.json({ success: true });
29
+ } catch (error) {
30
+ return NextResponse.json(
31
+ {
32
+ error:
33
+ error instanceof Error ? error.message : "Failed to discard changes",
34
+ },
35
+ { status: 500 }
36
+ );
37
+ }
38
+ }
@@ -0,0 +1,64 @@
1
+ import { NextRequest, NextResponse } from "next/server";
2
+ import { execSync } from "child_process";
3
+ import { expandPath } from "@/lib/git-status";
4
+
5
+ /**
6
+ * GET /api/git/file-content?path=...&file=...
7
+ * Get file content from git HEAD (original version before changes)
8
+ */
9
+ export async function GET(request: NextRequest) {
10
+ try {
11
+ const searchParams = request.nextUrl.searchParams;
12
+ const path = searchParams.get("path");
13
+ const file = searchParams.get("file");
14
+
15
+ if (!path) {
16
+ return NextResponse.json(
17
+ { error: "Path parameter is required" },
18
+ { status: 400 }
19
+ );
20
+ }
21
+
22
+ if (!file) {
23
+ return NextResponse.json(
24
+ { error: "File parameter is required" },
25
+ { status: 400 }
26
+ );
27
+ }
28
+
29
+ const expandedPath = expandPath(path);
30
+
31
+ try {
32
+ // Get file content from HEAD
33
+ const content = execSync(`git show HEAD:"${file}"`, {
34
+ cwd: expandedPath,
35
+ encoding: "utf-8",
36
+ maxBuffer: 10 * 1024 * 1024, // 10MB
37
+ });
38
+
39
+ return NextResponse.json({ content });
40
+ } catch (gitError: unknown) {
41
+ // File might be new (not in HEAD)
42
+ const errorMessage =
43
+ gitError instanceof Error ? gitError.message : String(gitError);
44
+
45
+ if (
46
+ errorMessage.includes("does not exist") ||
47
+ errorMessage.includes("fatal:")
48
+ ) {
49
+ return NextResponse.json({ content: "", isNew: true });
50
+ }
51
+
52
+ throw gitError;
53
+ }
54
+ } catch (error) {
55
+ console.error("Error getting file content from git:", error);
56
+ return NextResponse.json(
57
+ {
58
+ error:
59
+ error instanceof Error ? error.message : "Failed to get file content",
60
+ },
61
+ { status: 500 }
62
+ );
63
+ }
64
+ }
@@ -0,0 +1,38 @@
1
+ import { NextRequest, NextResponse } from "next/server";
2
+ import { getCommitFileDiff } from "@/lib/git-history";
3
+
4
+ interface RouteParams {
5
+ params: Promise<{ hash: string }>;
6
+ }
7
+
8
+ export async function GET(request: NextRequest, { params }: RouteParams) {
9
+ try {
10
+ const { hash } = await params;
11
+ const searchParams = request.nextUrl.searchParams;
12
+ const path = searchParams.get("path");
13
+ const file = searchParams.get("file");
14
+
15
+ if (!path) {
16
+ return NextResponse.json(
17
+ { error: "Missing path parameter" },
18
+ { status: 400 }
19
+ );
20
+ }
21
+
22
+ if (!file) {
23
+ return NextResponse.json(
24
+ { error: "Missing file parameter" },
25
+ { status: 400 }
26
+ );
27
+ }
28
+
29
+ const diff = getCommitFileDiff(path, hash, file);
30
+ return NextResponse.json({ diff });
31
+ } catch (error) {
32
+ console.error("Error getting commit file diff:", error);
33
+ return NextResponse.json(
34
+ { error: "Failed to get commit file diff" },
35
+ { status: 500 }
36
+ );
37
+ }
38
+ }
@@ -0,0 +1,34 @@
1
+ import { NextRequest, NextResponse } from "next/server";
2
+ import { getCommitDetail } from "@/lib/git-history";
3
+
4
+ interface RouteParams {
5
+ params: Promise<{ hash: string }>;
6
+ }
7
+
8
+ export async function GET(request: NextRequest, { params }: RouteParams) {
9
+ try {
10
+ const { hash } = await params;
11
+ const searchParams = request.nextUrl.searchParams;
12
+ const path = searchParams.get("path");
13
+
14
+ if (!path) {
15
+ return NextResponse.json(
16
+ { error: "Missing path parameter" },
17
+ { status: 400 }
18
+ );
19
+ }
20
+
21
+ const commit = getCommitDetail(path, hash);
22
+ if (!commit) {
23
+ return NextResponse.json({ error: "Commit not found" }, { status: 404 });
24
+ }
25
+
26
+ return NextResponse.json({ commit });
27
+ } catch (error) {
28
+ console.error("Error getting commit detail:", error);
29
+ return NextResponse.json(
30
+ { error: "Failed to get commit detail" },
31
+ { status: 500 }
32
+ );
33
+ }
34
+ }
@@ -0,0 +1,27 @@
1
+ import { NextRequest, NextResponse } from "next/server";
2
+ import { getCommitHistory } from "@/lib/git-history";
3
+
4
+ export async function GET(request: NextRequest) {
5
+ try {
6
+ const searchParams = request.nextUrl.searchParams;
7
+ const path = searchParams.get("path");
8
+ const limitStr = searchParams.get("limit");
9
+ const limit = limitStr ? parseInt(limitStr, 10) : 30;
10
+
11
+ if (!path) {
12
+ return NextResponse.json(
13
+ { error: "Missing path parameter" },
14
+ { status: 400 }
15
+ );
16
+ }
17
+
18
+ const commits = getCommitHistory(path, limit);
19
+ return NextResponse.json({ commits });
20
+ } catch (error) {
21
+ console.error("Error getting commit history:", error);
22
+ return NextResponse.json(
23
+ { error: "Failed to get commit history" },
24
+ { status: 500 }
25
+ );
26
+ }
27
+ }
@@ -0,0 +1,46 @@
1
+ import { NextRequest, NextResponse } from "next/server";
2
+ import { getProjectRepositories, getProject } from "@/lib/projects";
3
+ import { getMultiRepoGitStatus } from "@/lib/multi-repo-git";
4
+ import { expandPath } from "@/lib/git-status";
5
+ import type { ProjectRepository } from "@/lib/db";
6
+
7
+ export async function GET(request: NextRequest) {
8
+ try {
9
+ const { searchParams } = new URL(request.url);
10
+ const projectId = searchParams.get("projectId");
11
+ const fallbackPath = searchParams.get("fallbackPath");
12
+
13
+ if (!projectId && !fallbackPath) {
14
+ return NextResponse.json(
15
+ { error: "Either projectId or fallbackPath is required" },
16
+ { status: 400 }
17
+ );
18
+ }
19
+
20
+ let repositories: ProjectRepository[] = [];
21
+
22
+ if (projectId) {
23
+ const project = await getProject(projectId);
24
+ if (!project) {
25
+ return NextResponse.json(
26
+ { error: "Project not found" },
27
+ { status: 404 }
28
+ );
29
+ }
30
+ repositories = await getProjectRepositories(projectId);
31
+ }
32
+
33
+ const expandedFallback = fallbackPath
34
+ ? expandPath(fallbackPath)
35
+ : undefined;
36
+ const status = getMultiRepoGitStatus(repositories, expandedFallback);
37
+
38
+ return NextResponse.json(status);
39
+ } catch (error) {
40
+ console.error("Error fetching multi-repo git status:", error);
41
+ return NextResponse.json(
42
+ { error: "Failed to fetch git status" },
43
+ { status: 500 }
44
+ );
45
+ }
46
+ }