@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,106 @@
1
+ "use client";
2
+
3
+ import {
4
+ Dialog,
5
+ DialogContent,
6
+ DialogHeader,
7
+ DialogTitle,
8
+ DialogFooter,
9
+ } from "@/components/ui/dialog";
10
+ import { Button } from "@/components/ui/button";
11
+ import { Input } from "@/components/ui/input";
12
+ import { useNewSessionForm } from "./hooks/useNewSessionForm";
13
+ import { AgentSelector } from "./AgentSelector";
14
+ import { CreatingOverlay } from "./CreatingOverlay";
15
+ import type { NewSessionDialogProps } from "./NewSessionDialog.types";
16
+
17
+ export function NewSessionDialog({
18
+ open,
19
+ projects,
20
+ selectedProjectId,
21
+ onClose,
22
+ onCreated,
23
+ onCreateProject,
24
+ }: NewSessionDialogProps) {
25
+ const form = useNewSessionForm({
26
+ open,
27
+ projects,
28
+ selectedProjectId,
29
+ onCreated,
30
+ onClose,
31
+ onCreateProject,
32
+ });
33
+
34
+ return (
35
+ <>
36
+ <Dialog
37
+ open={open}
38
+ onOpenChange={(o) => !o && !form.isLoading && form.handleClose()}
39
+ >
40
+ <DialogContent
41
+ className="max-h-[85vh] overflow-y-auto"
42
+ onKeyDown={(e) => {
43
+ if (e.key === "Enter" && e.shiftKey && !form.isLoading) {
44
+ e.preventDefault();
45
+ form.handleSubmit(e as unknown as React.FormEvent);
46
+ }
47
+ }}
48
+ >
49
+ {form.isLoading && (
50
+ <CreatingOverlay
51
+ isWorktree={form.useWorktree}
52
+ step={form.creationStep}
53
+ />
54
+ )}
55
+ <DialogHeader>
56
+ <DialogTitle>New Session</DialogTitle>
57
+ </DialogHeader>
58
+ <form onSubmit={form.handleSubmit} className="space-y-4">
59
+ <AgentSelector
60
+ value={form.agentType}
61
+ onChange={form.handleAgentTypeChange}
62
+ />
63
+
64
+ <div className="space-y-2">
65
+ <label className="text-sm font-medium">
66
+ Name{" "}
67
+ <span className="text-muted-foreground font-normal">
68
+ (optional)
69
+ </span>
70
+ </label>
71
+ <Input
72
+ value={form.name}
73
+ onChange={(e) => form.setName(e.target.value)}
74
+ placeholder="Auto-generated if empty"
75
+ autoFocus
76
+ />
77
+ </div>
78
+
79
+ {form.error && <p className="text-sm text-red-500">{form.error}</p>}
80
+
81
+ <DialogFooter>
82
+ <Button
83
+ type="button"
84
+ variant="outline"
85
+ onClick={form.handleClose}
86
+ disabled={form.isLoading}
87
+ >
88
+ Cancel
89
+ </Button>
90
+ <Button
91
+ type="submit"
92
+ disabled={
93
+ form.isLoading ||
94
+ (form.useWorktree && !form.featureName.trim())
95
+ }
96
+ >
97
+ {form.isLoading ? "Creating..." : "Create"}
98
+ </Button>
99
+ </DialogFooter>
100
+ </form>
101
+ </DialogContent>
102
+ </Dialog>
103
+
104
+ </>
105
+ );
106
+ }
@@ -0,0 +1,127 @@
1
+ "use client";
2
+
3
+ import { Bell, Volume2, VolumeX, AlertCircle } from "lucide-react";
4
+ import { Button } from "./ui/button";
5
+ import {
6
+ DropdownMenu,
7
+ DropdownMenuContent,
8
+ DropdownMenuItem,
9
+ DropdownMenuLabel,
10
+ DropdownMenuSeparator,
11
+ DropdownMenuTrigger,
12
+ } from "./ui/dropdown-menu";
13
+ import { cn } from "@/lib/utils";
14
+ import type { NotificationSettings as NotificationSettingsType } from "@/lib/notifications";
15
+
16
+ interface WaitingSession {
17
+ id: string;
18
+ name: string;
19
+ }
20
+
21
+ interface NotificationSettingsProps {
22
+ open: boolean;
23
+ onOpenChange: (open: boolean) => void;
24
+ settings: NotificationSettingsType;
25
+ permissionGranted: boolean;
26
+ waitingSessions?: WaitingSession[];
27
+ onUpdateSettings: (settings: Partial<NotificationSettingsType>) => void;
28
+ onRequestPermission: () => Promise<boolean>;
29
+ onSelectSession?: (id: string) => void;
30
+ }
31
+
32
+ export function NotificationSettings({
33
+ open,
34
+ onOpenChange,
35
+ settings,
36
+ permissionGranted,
37
+ waitingSessions = [],
38
+ onUpdateSettings,
39
+ onRequestPermission,
40
+ onSelectSession,
41
+ }: NotificationSettingsProps) {
42
+ const waitingCount = waitingSessions.length;
43
+
44
+ return (
45
+ <DropdownMenu open={open} onOpenChange={onOpenChange}>
46
+ <DropdownMenuTrigger asChild>
47
+ <Button variant="ghost" size="icon-sm" className="relative">
48
+ <Bell
49
+ className={cn(
50
+ "h-4 w-4",
51
+ !settings.sound && "text-muted-foreground"
52
+ )}
53
+ />
54
+ {waitingCount > 0 && (
55
+ <span className="absolute -top-1 -right-1 flex h-4 w-4 items-center justify-center rounded-full bg-yellow-500 text-[10px] font-bold text-yellow-950">
56
+ {waitingCount}
57
+ </span>
58
+ )}
59
+ </Button>
60
+ </DropdownMenuTrigger>
61
+ <DropdownMenuContent align="end" className="w-48">
62
+ {/* Waiting sessions section */}
63
+ {waitingCount > 0 && (
64
+ <>
65
+ <DropdownMenuLabel className="flex items-center gap-2 text-xs text-yellow-500">
66
+ <AlertCircle className="h-3 w-3" />
67
+ Waiting for input
68
+ </DropdownMenuLabel>
69
+ {waitingSessions.map((session) => (
70
+ <DropdownMenuItem
71
+ key={session.id}
72
+ onClick={() => {
73
+ onSelectSession?.(session.id);
74
+ onOpenChange(false);
75
+ }}
76
+ className="text-sm"
77
+ >
78
+ {session.name}
79
+ </DropdownMenuItem>
80
+ ))}
81
+ <DropdownMenuSeparator />
82
+ </>
83
+ )}
84
+
85
+ {/* Sound toggle */}
86
+ <DropdownMenuItem
87
+ onClick={() => onUpdateSettings({ sound: !settings.sound })}
88
+ className="flex items-center justify-between"
89
+ >
90
+ <span className="flex items-center gap-2">
91
+ {settings.sound ? (
92
+ <Volume2 className="h-3 w-3" />
93
+ ) : (
94
+ <VolumeX className="text-muted-foreground h-3 w-3" />
95
+ )}
96
+ Sound
97
+ </span>
98
+ <span
99
+ className={cn(
100
+ "relative h-4 w-8 rounded-full transition-colors",
101
+ settings.sound ? "bg-primary" : "bg-muted"
102
+ )}
103
+ >
104
+ <span
105
+ className={cn(
106
+ "bg-background absolute top-0.5 h-3 w-3 rounded-full transition-transform",
107
+ settings.sound ? "translate-x-4" : "translate-x-0.5"
108
+ )}
109
+ />
110
+ </span>
111
+ </DropdownMenuItem>
112
+
113
+ {/* Browser notifications - only show if not granted */}
114
+ {!permissionGranted && (
115
+ <DropdownMenuItem
116
+ onClick={async () => {
117
+ await onRequestPermission();
118
+ }}
119
+ >
120
+ <Bell className="mr-2 h-3 w-3" />
121
+ <span className="text-xs">Enable browser alerts</span>
122
+ </DropdownMenuItem>
123
+ )}
124
+ </DropdownMenuContent>
125
+ </DropdownMenu>
126
+ );
127
+ }
@@ -0,0 +1,272 @@
1
+ "use client";
2
+
3
+ import { useState, useEffect } from "react";
4
+ import {
5
+ X,
6
+ GitPullRequest,
7
+ Loader2,
8
+ ExternalLink,
9
+ GitBranch,
10
+ ChevronLeft,
11
+ } from "lucide-react";
12
+ import { Button } from "@/components/ui/button";
13
+ import { cn } from "@/lib/utils";
14
+
15
+ interface PRData {
16
+ branch: string;
17
+ baseBranch: string;
18
+ existingPR: {
19
+ number: number;
20
+ url: string;
21
+ state: string;
22
+ title: string;
23
+ } | null;
24
+ commits: { hash: string; subject: string }[];
25
+ suggestedTitle: string;
26
+ suggestedBody: string;
27
+ }
28
+
29
+ interface PRCreationModalProps {
30
+ workingDirectory: string;
31
+ onClose: () => void;
32
+ onSuccess?: (prUrl: string) => void;
33
+ }
34
+
35
+ export function PRCreationModal({
36
+ workingDirectory,
37
+ onClose,
38
+ onSuccess,
39
+ }: PRCreationModalProps) {
40
+ const [loading, setLoading] = useState(true);
41
+ const [creating, setCreating] = useState(false);
42
+ const [error, setError] = useState<string | null>(null);
43
+ const [prData, setPrData] = useState<PRData | null>(null);
44
+ const [title, setTitle] = useState("");
45
+ const [body, setBody] = useState("");
46
+
47
+ // Fetch PR data on mount
48
+ useEffect(() => {
49
+ const fetchPRData = async () => {
50
+ try {
51
+ const res = await fetch(
52
+ `/api/git/pr?path=${encodeURIComponent(workingDirectory)}`
53
+ );
54
+ const data = await res.json();
55
+
56
+ if (data.error) {
57
+ setError(data.error);
58
+ } else {
59
+ setPrData(data);
60
+ setTitle(data.suggestedTitle);
61
+ setBody(data.suggestedBody);
62
+ }
63
+ } catch {
64
+ setError("Failed to fetch PR data");
65
+ } finally {
66
+ setLoading(false);
67
+ }
68
+ };
69
+
70
+ fetchPRData();
71
+ }, [workingDirectory]);
72
+
73
+ const handleCreate = async () => {
74
+ if (!title.trim()) {
75
+ setError("Title is required");
76
+ return;
77
+ }
78
+
79
+ setCreating(true);
80
+ setError(null);
81
+
82
+ try {
83
+ const res = await fetch("/api/git/pr", {
84
+ method: "POST",
85
+ headers: { "Content-Type": "application/json" },
86
+ body: JSON.stringify({
87
+ path: workingDirectory,
88
+ title: title.trim(),
89
+ description: body,
90
+ baseBranch: prData?.baseBranch,
91
+ }),
92
+ });
93
+
94
+ const data = await res.json();
95
+
96
+ if (data.error) {
97
+ setError(data.error);
98
+ return;
99
+ }
100
+
101
+ if (data.pr?.url) {
102
+ onSuccess?.(data.pr.url);
103
+ // Open PR in new tab
104
+ window.open(data.pr.url, "_blank");
105
+ onClose();
106
+ }
107
+ } catch {
108
+ setError("Failed to create PR");
109
+ } finally {
110
+ setCreating(false);
111
+ }
112
+ };
113
+
114
+ // Show existing PR
115
+ if (prData?.existingPR) {
116
+ return (
117
+ <div className="bg-background fixed inset-0 z-50 flex flex-col">
118
+ <Header onClose={onClose} />
119
+
120
+ <div className="flex flex-1 flex-col items-center justify-center p-4">
121
+ <GitPullRequest className="text-primary mb-4 h-12 w-12" />
122
+ <h2 className="mb-2 text-lg font-medium">PR Already Exists</h2>
123
+ <p className="text-muted-foreground mb-4 text-center text-sm">
124
+ #{prData.existingPR.number} - {prData.existingPR.title}
125
+ </p>
126
+ <Button
127
+ variant="default"
128
+ onClick={() => window.open(prData.existingPR!.url, "_blank")}
129
+ className="min-h-[44px]"
130
+ >
131
+ <ExternalLink className="mr-2 h-4 w-4" />
132
+ View Pull Request
133
+ </Button>
134
+ </div>
135
+ </div>
136
+ );
137
+ }
138
+
139
+ return (
140
+ <div className="bg-background fixed inset-0 z-50 flex flex-col">
141
+ <Header onClose={onClose} />
142
+
143
+ {loading ? (
144
+ <div className="flex flex-1 items-center justify-center">
145
+ <Loader2 className="text-muted-foreground h-8 w-8 animate-spin" />
146
+ </div>
147
+ ) : error && !prData ? (
148
+ <div className="flex flex-1 flex-col items-center justify-center p-4">
149
+ <p className="mb-4 text-sm text-red-500">{error}</p>
150
+ <Button variant="outline" onClick={onClose}>
151
+ Close
152
+ </Button>
153
+ </div>
154
+ ) : (
155
+ <>
156
+ <div className="flex-1 space-y-4 overflow-y-auto p-4">
157
+ {/* Branch info */}
158
+ <div className="text-muted-foreground flex items-center gap-2 text-sm">
159
+ <GitBranch className="h-4 w-4" />
160
+ <span>{prData?.branch}</span>
161
+ <span>→</span>
162
+ <span>{prData?.baseBranch}</span>
163
+ </div>
164
+
165
+ {/* Title input */}
166
+ <div className="space-y-1.5">
167
+ <label className="text-sm font-medium">Title</label>
168
+ <input
169
+ type="text"
170
+ value={title}
171
+ onChange={(e) => setTitle(e.target.value)}
172
+ placeholder="PR title..."
173
+ className={cn(
174
+ "w-full rounded-md px-3 py-2 text-sm",
175
+ "bg-muted/50 border-border border",
176
+ "focus:ring-primary/50 focus:ring-2 focus:outline-none",
177
+ "min-h-[44px]"
178
+ )}
179
+ />
180
+ </div>
181
+
182
+ {/* Body textarea */}
183
+ <div className="space-y-1.5">
184
+ <label className="text-sm font-medium">Description</label>
185
+ <textarea
186
+ value={body}
187
+ onChange={(e) => setBody(e.target.value)}
188
+ placeholder="Describe your changes..."
189
+ rows={12}
190
+ className={cn(
191
+ "w-full resize-none rounded-md px-3 py-2 font-mono text-sm",
192
+ "bg-muted/50 border-border border",
193
+ "focus:ring-primary/50 focus:ring-2 focus:outline-none"
194
+ )}
195
+ />
196
+ </div>
197
+
198
+ {/* Commits list */}
199
+ {prData && prData.commits.length > 0 && (
200
+ <div className="space-y-1.5">
201
+ <label className="text-muted-foreground text-sm font-medium">
202
+ Commits ({prData.commits.length})
203
+ </label>
204
+ <div className="text-muted-foreground space-y-1 text-xs">
205
+ {prData.commits.slice(0, 10).map((commit) => (
206
+ <div key={commit.hash} className="truncate">
207
+ <code className="text-primary/70">
208
+ {commit.hash.slice(0, 7)}
209
+ </code>{" "}
210
+ {commit.subject}
211
+ </div>
212
+ ))}
213
+ {prData.commits.length > 10 && (
214
+ <div className="text-muted-foreground/50">
215
+ +{prData.commits.length - 10} more commits
216
+ </div>
217
+ )}
218
+ </div>
219
+ </div>
220
+ )}
221
+
222
+ {/* Error message */}
223
+ {error && <p className="text-sm text-red-500">{error}</p>}
224
+ </div>
225
+
226
+ {/* Footer */}
227
+ <div className="border-border safe-area-bottom border-t p-4">
228
+ <Button
229
+ variant="default"
230
+ onClick={handleCreate}
231
+ disabled={creating || !title.trim()}
232
+ className="min-h-[44px] w-full"
233
+ >
234
+ {creating ? (
235
+ <Loader2 className="mr-2 h-4 w-4 animate-spin" />
236
+ ) : (
237
+ <GitPullRequest className="mr-2 h-4 w-4" />
238
+ )}
239
+ Create Pull Request
240
+ </Button>
241
+ </div>
242
+ </>
243
+ )}
244
+ </div>
245
+ );
246
+ }
247
+
248
+ function Header({ onClose }: { onClose: () => void }) {
249
+ return (
250
+ <div className="border-border flex items-center gap-2 border-b p-3">
251
+ <Button
252
+ variant="ghost"
253
+ size="icon-sm"
254
+ onClick={onClose}
255
+ className="h-9 w-9"
256
+ >
257
+ <ChevronLeft className="h-5 w-5" />
258
+ </Button>
259
+ <div className="flex-1">
260
+ <h2 className="text-sm font-medium">Create Pull Request</h2>
261
+ </div>
262
+ <Button
263
+ variant="ghost"
264
+ size="icon-sm"
265
+ onClick={onClose}
266
+ className="h-9 w-9 md:hidden"
267
+ >
268
+ <X className="h-4 w-4" />
269
+ </Button>
270
+ </div>
271
+ );
272
+ }