@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.
- package/LICENSE +21 -0
- package/README.md +123 -0
- package/app/api/claude/hidden/route.ts +66 -0
- package/app/api/claude/projects/[name]/sessions/route.ts +71 -0
- package/app/api/claude/projects/route.ts +44 -0
- package/app/api/code-search/available/route.ts +12 -0
- package/app/api/code-search/route.ts +47 -0
- package/app/api/dev-servers/[id]/logs/route.ts +23 -0
- package/app/api/dev-servers/[id]/restart/route.ts +20 -0
- package/app/api/dev-servers/[id]/route.ts +51 -0
- package/app/api/dev-servers/[id]/stop/route.ts +20 -0
- package/app/api/dev-servers/detect/route.ts +39 -0
- package/app/api/dev-servers/route.ts +48 -0
- package/app/api/exec/route.ts +60 -0
- package/app/api/files/content/route.ts +76 -0
- package/app/api/files/route.ts +37 -0
- package/app/api/files/upload-temp/route.ts +41 -0
- package/app/api/git/check/route.ts +54 -0
- package/app/api/git/clone/route.ts +99 -0
- package/app/api/git/commit/route.ts +75 -0
- package/app/api/git/discard/route.ts +38 -0
- package/app/api/git/file-content/route.ts +64 -0
- package/app/api/git/history/[hash]/diff/route.ts +38 -0
- package/app/api/git/history/[hash]/route.ts +34 -0
- package/app/api/git/history/route.ts +27 -0
- package/app/api/git/multi-status/route.ts +46 -0
- package/app/api/git/pr/route.ts +164 -0
- package/app/api/git/push/route.ts +64 -0
- package/app/api/git/stage/route.ts +40 -0
- package/app/api/git/status/route.ts +51 -0
- package/app/api/git/unstage/route.ts +46 -0
- package/app/api/groups/[...path]/route.ts +136 -0
- package/app/api/groups/route.ts +93 -0
- package/app/api/orchestrate/spawn/route.ts +45 -0
- package/app/api/orchestrate/workers/[id]/route.ts +89 -0
- package/app/api/orchestrate/workers/route.ts +31 -0
- package/app/api/projects/[id]/detect/route.ts +27 -0
- package/app/api/projects/[id]/dev-servers/[dsId]/route.ts +66 -0
- package/app/api/projects/[id]/dev-servers/route.ts +51 -0
- package/app/api/projects/[id]/repositories/[repoId]/route.ts +67 -0
- package/app/api/projects/[id]/repositories/route.ts +74 -0
- package/app/api/projects/[id]/route.ts +108 -0
- package/app/api/projects/detect/route.ts +33 -0
- package/app/api/projects/route.ts +59 -0
- package/app/api/sessions/[id]/claude-session/route.ts +42 -0
- package/app/api/sessions/[id]/fork/route.ts +74 -0
- package/app/api/sessions/[id]/mcp-config/route.ts +34 -0
- package/app/api/sessions/[id]/messages/route.ts +60 -0
- package/app/api/sessions/[id]/pr/route.ts +188 -0
- package/app/api/sessions/[id]/preview/route.ts +42 -0
- package/app/api/sessions/[id]/route.ts +229 -0
- package/app/api/sessions/[id]/send-keys/route.ts +119 -0
- package/app/api/sessions/[id]/summarize/route.ts +331 -0
- package/app/api/sessions/init-script/route.ts +84 -0
- package/app/api/sessions/route.ts +209 -0
- package/app/api/sessions/status/route.ts +237 -0
- package/app/api/system/route.ts +9 -0
- package/app/api/tmux/kill-all/route.ts +57 -0
- package/app/api/tmux/rename/route.ts +30 -0
- package/app/globals.css +174 -0
- package/app/icon.svg +11 -0
- package/app/layout.tsx +122 -0
- package/app/page.tsx +629 -0
- package/components/ChatMessage.tsx +65 -0
- package/components/ChatView.tsx +276 -0
- package/components/ClaudeProjects/ClaudeProjectCard.tsx +195 -0
- package/components/ClaudeProjects/ClaudeProjectsSection.tsx +89 -0
- package/components/ClaudeProjects/ClaudeSessionCard.tsx +100 -0
- package/components/ClaudeProjects/index.ts +1 -0
- package/components/CodeSearch/CodeSearchResults.tsx +177 -0
- package/components/ConductorPanel.tsx +256 -0
- package/components/DevServers/DevServerCard.tsx +311 -0
- package/components/DevServers/DevServersSection.tsx +91 -0
- package/components/DevServers/ServerLogsModal.tsx +151 -0
- package/components/DevServers/StartServerDialog.tsx +359 -0
- package/components/DevServers/index.ts +4 -0
- package/components/DiffViewer/DiffModal.tsx +151 -0
- package/components/DiffViewer/UnifiedDiff.tsx +185 -0
- package/components/DiffViewer/index.tsx +2 -0
- package/components/DirectoryPicker.tsx +355 -0
- package/components/FileExplorer/FileEditor.tsx +276 -0
- package/components/FileExplorer/FileTabs.tsx +118 -0
- package/components/FileExplorer/FileTree.tsx +214 -0
- package/components/FileExplorer/HtmlRenderer.tsx +16 -0
- package/components/FileExplorer/MarkdownRenderer.tsx +18 -0
- package/components/FileExplorer/index.tsx +520 -0
- package/components/FilePicker.tsx +339 -0
- package/components/FolderPicker.tsx +201 -0
- package/components/GitDrawer/FileEditDialog.tsx +400 -0
- package/components/GitDrawer/index.tsx +464 -0
- package/components/GitPanel/CommitForm.tsx +205 -0
- package/components/GitPanel/CommitHistory.tsx +174 -0
- package/components/GitPanel/CommitItem.tsx +196 -0
- package/components/GitPanel/FileChanges.tsx +414 -0
- package/components/GitPanel/GitPanelTabs.tsx +39 -0
- package/components/GitPanel/index.tsx +817 -0
- package/components/MessageInput.tsx +82 -0
- package/components/NewClaudeSessionDialog.tsx +166 -0
- package/components/NewSessionDialog/AdvancedSettings.tsx +78 -0
- package/components/NewSessionDialog/AgentSelector.tsx +37 -0
- package/components/NewSessionDialog/CreatingOverlay.tsx +94 -0
- package/components/NewSessionDialog/NewSessionDialog.types.ts +136 -0
- package/components/NewSessionDialog/ProjectSelector.tsx +146 -0
- package/components/NewSessionDialog/WorkingDirectoryInput.tsx +55 -0
- package/components/NewSessionDialog/WorktreeSection.tsx +92 -0
- package/components/NewSessionDialog/hooks/useNewSessionForm.ts +370 -0
- package/components/NewSessionDialog/index.tsx +106 -0
- package/components/NotificationSettings.tsx +127 -0
- package/components/PRCreationModal.tsx +272 -0
- package/components/Pane/DesktopTabBar.tsx +353 -0
- package/components/Pane/MobileTabBar.tsx +210 -0
- package/components/Pane/OpenInVSCode.tsx +69 -0
- package/components/Pane/PaneSkeletons.tsx +57 -0
- package/components/Pane/index.tsx +558 -0
- package/components/PaneLayout.tsx +60 -0
- package/components/Projects/DevServersSection.tsx +140 -0
- package/components/Projects/DirectoryField.tsx +92 -0
- package/components/Projects/NewProjectDialog.tsx +188 -0
- package/components/Projects/NewProjectDialog.types.ts +46 -0
- package/components/Projects/ProjectCard.tsx +276 -0
- package/components/Projects/ProjectSettingsDialog.tsx +811 -0
- package/components/Projects/hooks/useNewProjectForm.ts +249 -0
- package/components/Projects/index.ts +3 -0
- package/components/Providers.tsx +49 -0
- package/components/QuickSwitcher.tsx +306 -0
- package/components/SessionList/KillAllConfirm.tsx +46 -0
- package/components/SessionList/SelectionToolbar.tsx +164 -0
- package/components/SessionList/SessionList.types.ts +37 -0
- package/components/SessionList/SessionListHeader.tsx +71 -0
- package/components/SessionList/hooks/useSessionListMutations.ts +269 -0
- package/components/SessionList/index.tsx +189 -0
- package/components/ShellDrawer/index.tsx +106 -0
- package/components/SidebarFooter.tsx +55 -0
- package/components/Terminal/KeybarToggleButton.tsx +45 -0
- package/components/Terminal/ScrollToBottomButton.tsx +32 -0
- package/components/Terminal/SearchBar.tsx +71 -0
- package/components/Terminal/TerminalToolbar.tsx +551 -0
- package/components/Terminal/VirtualKeyboard.tsx +711 -0
- package/components/Terminal/constants.ts +20 -0
- package/components/Terminal/hooks/index.ts +5 -0
- package/components/Terminal/hooks/resize-handlers.ts +140 -0
- package/components/Terminal/hooks/terminal-init.ts +151 -0
- package/components/Terminal/hooks/touch-scroll.ts +155 -0
- package/components/Terminal/hooks/useTerminalConnection.ts +282 -0
- package/components/Terminal/hooks/useTerminalConnection.types.ts +39 -0
- package/components/Terminal/hooks/useTerminalSearch.ts +103 -0
- package/components/Terminal/hooks/websocket-connection.ts +274 -0
- package/components/Terminal/index.tsx +320 -0
- package/components/ThemeToggle.tsx +168 -0
- package/components/TmuxSessions.tsx +132 -0
- package/components/ToolCallDisplay.tsx +71 -0
- package/components/WorkerCard.tsx +245 -0
- package/components/a/ABadge.tsx +115 -0
- package/components/a/AButton.tsx +163 -0
- package/components/a/ADialog.tsx +93 -0
- package/components/a/ADropdownMenu.tsx +279 -0
- package/components/a/AIconButton.tsx +190 -0
- package/components/a/ASheet.tsx +150 -0
- package/components/a/ATooltip.tsx +77 -0
- package/components/a/index.ts +64 -0
- package/components/mobile/SwipeSidebar.tsx +122 -0
- package/components/ui/badge.tsx +41 -0
- package/components/ui/button.tsx +60 -0
- package/components/ui/context-menu.tsx +197 -0
- package/components/ui/dialog.tsx +143 -0
- package/components/ui/dropdown-menu.tsx +257 -0
- package/components/ui/input.tsx +21 -0
- package/components/ui/scroll-area.tsx +52 -0
- package/components/ui/select.tsx +159 -0
- package/components/ui/skeleton.tsx +111 -0
- package/components/ui/switch.tsx +31 -0
- package/components/ui/textarea.tsx +21 -0
- package/components/ui/tooltip.tsx +32 -0
- package/components/views/DesktopView.tsx +244 -0
- package/components/views/MobileView.tsx +110 -0
- package/components/views/types.ts +75 -0
- package/contexts/PaneContext.tsx +336 -0
- package/data/claude/index.ts +9 -0
- package/data/claude/keys.ts +6 -0
- package/data/claude/queries.ts +120 -0
- package/data/claude/useClaudeUpdates.ts +37 -0
- package/data/code-search/index.ts +2 -0
- package/data/code-search/keys.ts +7 -0
- package/data/code-search/queries.ts +61 -0
- package/data/dev-servers/index.ts +8 -0
- package/data/dev-servers/keys.ts +4 -0
- package/data/dev-servers/queries.ts +104 -0
- package/data/files/index.ts +3 -0
- package/data/files/keys.ts +4 -0
- package/data/files/queries.ts +25 -0
- package/data/git/keys.ts +15 -0
- package/data/git/queries.ts +395 -0
- package/data/groups/index.ts +1 -0
- package/data/groups/mutations.ts +95 -0
- package/data/projects/index.ts +10 -0
- package/data/projects/keys.ts +4 -0
- package/data/projects/queries.ts +193 -0
- package/data/repositories/index.ts +7 -0
- package/data/repositories/keys.ts +5 -0
- package/data/repositories/queries.ts +122 -0
- package/data/sessions/index.ts +12 -0
- package/data/sessions/keys.ts +8 -0
- package/data/sessions/queries.ts +218 -0
- package/data/statuses/index.ts +1 -0
- package/data/statuses/queries.ts +69 -0
- package/hooks/useCopyToClipboard.ts +48 -0
- package/hooks/useDevServersManager.ts +73 -0
- package/hooks/useDirectoryBrowser.ts +90 -0
- package/hooks/useDrawerAnimation.ts +27 -0
- package/hooks/useFileDrop.ts +87 -0
- package/hooks/useFileEditor.ts +184 -0
- package/hooks/useGroups.ts +37 -0
- package/hooks/useHomePath.ts +34 -0
- package/hooks/useKeyRepeat.ts +55 -0
- package/hooks/useKeybarVisibility.ts +42 -0
- package/hooks/useNotifications.ts +257 -0
- package/hooks/useProjects.ts +53 -0
- package/hooks/useSessionStatuses.ts +30 -0
- package/hooks/useSessions.ts +86 -0
- package/hooks/useSpeechRecognition.ts +124 -0
- package/hooks/useViewport.ts +32 -0
- package/hooks/useViewportHeight.ts +50 -0
- package/lib/async-operations.ts +35 -0
- package/lib/banner.ts +81 -0
- package/lib/claude/jsonl-cache.ts +86 -0
- package/lib/claude/jsonl-reader.ts +271 -0
- package/lib/claude/process-manager.ts +278 -0
- package/lib/claude/stream-parser.ts +173 -0
- package/lib/claude/types.ts +154 -0
- package/lib/claude/watcher.ts +71 -0
- package/lib/client/session-registry.ts +111 -0
- package/lib/code-search.ts +121 -0
- package/lib/db/index.ts +48 -0
- package/lib/db/migrations.ts +45 -0
- package/lib/db/queries.ts +460 -0
- package/lib/db/schema.ts +114 -0
- package/lib/db/types.ts +92 -0
- package/lib/db.ts +2 -0
- package/lib/dev-servers.ts +509 -0
- package/lib/diff-parser.ts +221 -0
- package/lib/env-setup.ts +285 -0
- package/lib/file-upload.ts +34 -0
- package/lib/file-utils.ts +50 -0
- package/lib/files.ts +207 -0
- package/lib/git-history.ts +294 -0
- package/lib/git-status.ts +391 -0
- package/lib/git.ts +257 -0
- package/lib/mcp-config.ts +81 -0
- package/lib/multi-repo-git.ts +179 -0
- package/lib/notifications.ts +219 -0
- package/lib/orchestration.ts +448 -0
- package/lib/panes.ts +232 -0
- package/lib/ports.ts +97 -0
- package/lib/pr-generation.ts +307 -0
- package/lib/pr.ts +234 -0
- package/lib/projects.ts +578 -0
- package/lib/providers/registry.ts +70 -0
- package/lib/providers.ts +121 -0
- package/lib/query-client.ts +14 -0
- package/lib/rangeSelectionUtils.ts +65 -0
- package/lib/status-detector.ts +375 -0
- package/lib/terminal-themes.ts +265 -0
- package/lib/theme-config.ts +327 -0
- package/lib/utils.ts +6 -0
- package/lib/worktrees.ts +262 -0
- package/mcp/orchestration-server.ts +438 -0
- package/package.json +139 -0
- package/postcss.config.mjs +7 -0
- package/public/icon.svg +10 -0
- package/public/icons/icon-128x128.png +0 -0
- package/public/icons/icon-144x144.png +0 -0
- package/public/icons/icon-152x152.png +0 -0
- package/public/icons/icon-192x192.png +0 -0
- package/public/icons/icon-384x384.png +0 -0
- package/public/icons/icon-512x512.png +0 -0
- package/public/icons/icon-72x72.png +0 -0
- package/public/icons/icon-96x96.png +0 -0
- package/public/manifest.json +61 -0
- package/public/sw.js +64 -0
- package/scripts/agent-os +91 -0
- package/scripts/install.sh +48 -0
- package/scripts/lib/ai-clis.sh +132 -0
- package/scripts/lib/commands.sh +487 -0
- package/scripts/lib/common.sh +89 -0
- package/scripts/lib/prerequisites.sh +462 -0
- package/scripts/setup.sh +134 -0
- package/server.ts +155 -0
- package/stores/fileOpen.ts +26 -0
- package/stores/index.ts +1 -0
- package/stores/initialPrompt.ts +24 -0
- package/stores/sessionSelection.ts +48 -0
- package/styles/themes.css +603 -0
- package/tsconfig.json +33 -0
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
|
2
|
+
import type {
|
|
3
|
+
ProjectWithRepositories,
|
|
4
|
+
DetectedDevServer,
|
|
5
|
+
} from "@/lib/projects";
|
|
6
|
+
import { projectKeys } from "./keys";
|
|
7
|
+
import { sessionKeys } from "../sessions/keys";
|
|
8
|
+
|
|
9
|
+
async function fetchProjects(): Promise<ProjectWithRepositories[]> {
|
|
10
|
+
const res = await fetch("/api/projects");
|
|
11
|
+
if (!res.ok) throw new Error("Failed to fetch projects");
|
|
12
|
+
const data = await res.json();
|
|
13
|
+
return data.projects || [];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function useProjectsQuery() {
|
|
17
|
+
return useQuery({
|
|
18
|
+
queryKey: projectKeys.list(),
|
|
19
|
+
queryFn: fetchProjects,
|
|
20
|
+
staleTime: 30000,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function useToggleProject() {
|
|
25
|
+
const queryClient = useQueryClient();
|
|
26
|
+
|
|
27
|
+
return useMutation({
|
|
28
|
+
mutationFn: async ({
|
|
29
|
+
projectId,
|
|
30
|
+
expanded,
|
|
31
|
+
}: {
|
|
32
|
+
projectId: string;
|
|
33
|
+
expanded: boolean;
|
|
34
|
+
}) => {
|
|
35
|
+
const res = await fetch(`/api/projects/${projectId}`, {
|
|
36
|
+
method: "PATCH",
|
|
37
|
+
headers: { "Content-Type": "application/json" },
|
|
38
|
+
body: JSON.stringify({ expanded }),
|
|
39
|
+
});
|
|
40
|
+
if (!res.ok) throw new Error("Failed to toggle project");
|
|
41
|
+
return res.json();
|
|
42
|
+
},
|
|
43
|
+
onMutate: async ({ projectId, expanded }) => {
|
|
44
|
+
await queryClient.cancelQueries({ queryKey: projectKeys.list() });
|
|
45
|
+
const previous = queryClient.getQueryData<ProjectWithRepositories[]>(
|
|
46
|
+
projectKeys.list()
|
|
47
|
+
);
|
|
48
|
+
queryClient.setQueryData<ProjectWithRepositories[]>(
|
|
49
|
+
projectKeys.list(),
|
|
50
|
+
(old) => old?.map((p) => (p.id === projectId ? { ...p, expanded } : p))
|
|
51
|
+
);
|
|
52
|
+
return { previous };
|
|
53
|
+
},
|
|
54
|
+
onError: (_, __, context) => {
|
|
55
|
+
if (context?.previous) {
|
|
56
|
+
queryClient.setQueryData(projectKeys.list(), context.previous);
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function useDeleteProject() {
|
|
63
|
+
const queryClient = useQueryClient();
|
|
64
|
+
|
|
65
|
+
return useMutation({
|
|
66
|
+
mutationFn: async (projectId: string) => {
|
|
67
|
+
const res = await fetch(`/api/projects/${projectId}`, {
|
|
68
|
+
method: "DELETE",
|
|
69
|
+
});
|
|
70
|
+
if (!res.ok) throw new Error("Failed to delete project");
|
|
71
|
+
return res.json();
|
|
72
|
+
},
|
|
73
|
+
onSuccess: () => {
|
|
74
|
+
queryClient.invalidateQueries({ queryKey: projectKeys.list() });
|
|
75
|
+
queryClient.invalidateQueries({ queryKey: sessionKeys.list() });
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function useRenameProject() {
|
|
81
|
+
const queryClient = useQueryClient();
|
|
82
|
+
|
|
83
|
+
return useMutation({
|
|
84
|
+
mutationFn: async ({
|
|
85
|
+
projectId,
|
|
86
|
+
newName,
|
|
87
|
+
}: {
|
|
88
|
+
projectId: string;
|
|
89
|
+
newName: string;
|
|
90
|
+
}) => {
|
|
91
|
+
const res = await fetch(`/api/projects/${projectId}`, {
|
|
92
|
+
method: "PATCH",
|
|
93
|
+
headers: { "Content-Type": "application/json" },
|
|
94
|
+
body: JSON.stringify({ name: newName }),
|
|
95
|
+
});
|
|
96
|
+
if (!res.ok) throw new Error("Failed to rename project");
|
|
97
|
+
return res.json();
|
|
98
|
+
},
|
|
99
|
+
onSuccess: () => {
|
|
100
|
+
queryClient.invalidateQueries({ queryKey: projectKeys.list() });
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function useUpdateProject() {
|
|
106
|
+
const queryClient = useQueryClient();
|
|
107
|
+
|
|
108
|
+
return useMutation({
|
|
109
|
+
mutationFn: async ({
|
|
110
|
+
projectId,
|
|
111
|
+
name,
|
|
112
|
+
workingDirectory,
|
|
113
|
+
agentType,
|
|
114
|
+
defaultModel,
|
|
115
|
+
initialPrompt,
|
|
116
|
+
}: {
|
|
117
|
+
projectId: string;
|
|
118
|
+
name?: string;
|
|
119
|
+
workingDirectory?: string;
|
|
120
|
+
agentType?: string;
|
|
121
|
+
defaultModel?: string;
|
|
122
|
+
initialPrompt?: string | null;
|
|
123
|
+
}) => {
|
|
124
|
+
const res = await fetch(`/api/projects/${projectId}`, {
|
|
125
|
+
method: "PATCH",
|
|
126
|
+
headers: { "Content-Type": "application/json" },
|
|
127
|
+
body: JSON.stringify({
|
|
128
|
+
name,
|
|
129
|
+
workingDirectory,
|
|
130
|
+
agentType,
|
|
131
|
+
defaultModel,
|
|
132
|
+
initialPrompt,
|
|
133
|
+
}),
|
|
134
|
+
});
|
|
135
|
+
if (!res.ok) throw new Error("Failed to update project");
|
|
136
|
+
return res.json();
|
|
137
|
+
},
|
|
138
|
+
onSuccess: () => {
|
|
139
|
+
queryClient.invalidateQueries({ queryKey: projectKeys.list() });
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function useCreateProject() {
|
|
145
|
+
const queryClient = useQueryClient();
|
|
146
|
+
|
|
147
|
+
return useMutation({
|
|
148
|
+
mutationFn: async (data: {
|
|
149
|
+
name: string;
|
|
150
|
+
workingDirectory: string;
|
|
151
|
+
agentType?: string;
|
|
152
|
+
defaultModel?: string;
|
|
153
|
+
devServers?: Array<{
|
|
154
|
+
name: string;
|
|
155
|
+
type: string;
|
|
156
|
+
command: string;
|
|
157
|
+
port?: number;
|
|
158
|
+
portEnvVar?: string;
|
|
159
|
+
}>;
|
|
160
|
+
}) => {
|
|
161
|
+
const res = await fetch("/api/projects", {
|
|
162
|
+
method: "POST",
|
|
163
|
+
headers: { "Content-Type": "application/json" },
|
|
164
|
+
body: JSON.stringify(data),
|
|
165
|
+
});
|
|
166
|
+
if (!res.ok) {
|
|
167
|
+
const err = await res.json();
|
|
168
|
+
throw new Error(err.error || "Failed to create project");
|
|
169
|
+
}
|
|
170
|
+
return res.json();
|
|
171
|
+
},
|
|
172
|
+
onSuccess: () => {
|
|
173
|
+
queryClient.invalidateQueries({ queryKey: projectKeys.list() });
|
|
174
|
+
},
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export function useDetectDevServers() {
|
|
179
|
+
return useMutation({
|
|
180
|
+
mutationFn: async (
|
|
181
|
+
workingDirectory: string
|
|
182
|
+
): Promise<DetectedDevServer[]> => {
|
|
183
|
+
const res = await fetch("/api/projects/detect", {
|
|
184
|
+
method: "POST",
|
|
185
|
+
headers: { "Content-Type": "application/json" },
|
|
186
|
+
body: JSON.stringify({ workingDirectory }),
|
|
187
|
+
});
|
|
188
|
+
if (!res.ok) throw new Error("Failed to detect dev servers");
|
|
189
|
+
const data = await res.json();
|
|
190
|
+
return data.detected || [];
|
|
191
|
+
},
|
|
192
|
+
});
|
|
193
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
|
2
|
+
import type { ProjectRepository } from "@/lib/db";
|
|
3
|
+
import { repositoryKeys } from "./keys";
|
|
4
|
+
import { projectKeys } from "@/data/projects/keys";
|
|
5
|
+
|
|
6
|
+
async function fetchProjectRepositories(
|
|
7
|
+
projectId: string
|
|
8
|
+
): Promise<ProjectRepository[]> {
|
|
9
|
+
const res = await fetch(`/api/projects/${projectId}/repositories`);
|
|
10
|
+
if (!res.ok) throw new Error("Failed to fetch repositories");
|
|
11
|
+
const data = await res.json();
|
|
12
|
+
return data.repositories || [];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function useProjectRepositories(projectId: string | undefined) {
|
|
16
|
+
return useQuery({
|
|
17
|
+
queryKey: repositoryKeys.list(projectId || ""),
|
|
18
|
+
queryFn: () => fetchProjectRepositories(projectId!),
|
|
19
|
+
enabled: !!projectId,
|
|
20
|
+
staleTime: 30000,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface AddRepositoryOptions {
|
|
25
|
+
projectId: string;
|
|
26
|
+
name: string;
|
|
27
|
+
path: string;
|
|
28
|
+
isPrimary?: boolean;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function useAddRepository() {
|
|
32
|
+
const queryClient = useQueryClient();
|
|
33
|
+
|
|
34
|
+
return useMutation({
|
|
35
|
+
mutationFn: async (opts: AddRepositoryOptions) => {
|
|
36
|
+
const res = await fetch(`/api/projects/${opts.projectId}/repositories`, {
|
|
37
|
+
method: "POST",
|
|
38
|
+
headers: { "Content-Type": "application/json" },
|
|
39
|
+
body: JSON.stringify({
|
|
40
|
+
name: opts.name,
|
|
41
|
+
path: opts.path,
|
|
42
|
+
isPrimary: opts.isPrimary,
|
|
43
|
+
}),
|
|
44
|
+
});
|
|
45
|
+
if (!res.ok) throw new Error("Failed to add repository");
|
|
46
|
+
return res.json();
|
|
47
|
+
},
|
|
48
|
+
onSuccess: (_, variables) => {
|
|
49
|
+
queryClient.invalidateQueries({
|
|
50
|
+
queryKey: repositoryKeys.list(variables.projectId),
|
|
51
|
+
});
|
|
52
|
+
queryClient.invalidateQueries({ queryKey: projectKeys.list() });
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
interface UpdateRepositoryOptions {
|
|
58
|
+
projectId: string;
|
|
59
|
+
repoId: string;
|
|
60
|
+
name?: string;
|
|
61
|
+
path?: string;
|
|
62
|
+
isPrimary?: boolean;
|
|
63
|
+
sortOrder?: number;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function useUpdateRepository() {
|
|
67
|
+
const queryClient = useQueryClient();
|
|
68
|
+
|
|
69
|
+
return useMutation({
|
|
70
|
+
mutationFn: async (opts: UpdateRepositoryOptions) => {
|
|
71
|
+
const res = await fetch(
|
|
72
|
+
`/api/projects/${opts.projectId}/repositories/${opts.repoId}`,
|
|
73
|
+
{
|
|
74
|
+
method: "PATCH",
|
|
75
|
+
headers: { "Content-Type": "application/json" },
|
|
76
|
+
body: JSON.stringify({
|
|
77
|
+
name: opts.name,
|
|
78
|
+
path: opts.path,
|
|
79
|
+
isPrimary: opts.isPrimary,
|
|
80
|
+
sortOrder: opts.sortOrder,
|
|
81
|
+
}),
|
|
82
|
+
}
|
|
83
|
+
);
|
|
84
|
+
if (!res.ok) throw new Error("Failed to update repository");
|
|
85
|
+
return res.json();
|
|
86
|
+
},
|
|
87
|
+
onSuccess: (_, variables) => {
|
|
88
|
+
queryClient.invalidateQueries({
|
|
89
|
+
queryKey: repositoryKeys.list(variables.projectId),
|
|
90
|
+
});
|
|
91
|
+
queryClient.invalidateQueries({ queryKey: projectKeys.list() });
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
interface DeleteRepositoryOptions {
|
|
97
|
+
projectId: string;
|
|
98
|
+
repoId: string;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function useDeleteRepository() {
|
|
102
|
+
const queryClient = useQueryClient();
|
|
103
|
+
|
|
104
|
+
return useMutation({
|
|
105
|
+
mutationFn: async (opts: DeleteRepositoryOptions) => {
|
|
106
|
+
const res = await fetch(
|
|
107
|
+
`/api/projects/${opts.projectId}/repositories/${opts.repoId}`,
|
|
108
|
+
{
|
|
109
|
+
method: "DELETE",
|
|
110
|
+
}
|
|
111
|
+
);
|
|
112
|
+
if (!res.ok) throw new Error("Failed to delete repository");
|
|
113
|
+
return res.json();
|
|
114
|
+
},
|
|
115
|
+
onSuccess: (_, variables) => {
|
|
116
|
+
queryClient.invalidateQueries({
|
|
117
|
+
queryKey: repositoryKeys.list(variables.projectId),
|
|
118
|
+
});
|
|
119
|
+
queryClient.invalidateQueries({ queryKey: projectKeys.list() });
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export { sessionKeys, statusKeys } from "./keys";
|
|
2
|
+
export {
|
|
3
|
+
useSessionsQuery,
|
|
4
|
+
useCreateSession,
|
|
5
|
+
useDeleteSession,
|
|
6
|
+
useRenameSession,
|
|
7
|
+
useForkSession,
|
|
8
|
+
useSummarizeSession,
|
|
9
|
+
useMoveSessionToGroup,
|
|
10
|
+
useMoveSessionToProject,
|
|
11
|
+
} from "./queries";
|
|
12
|
+
export type { CreateSessionInput } from "./queries";
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
|
2
|
+
import type { Session, Group } from "@/lib/db";
|
|
3
|
+
import type { AgentType } from "@/lib/providers";
|
|
4
|
+
import { sessionKeys } from "./keys";
|
|
5
|
+
|
|
6
|
+
interface SessionsResponse {
|
|
7
|
+
sessions: Session[];
|
|
8
|
+
groups: Group[];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async function fetchSessions(): Promise<SessionsResponse> {
|
|
12
|
+
const res = await fetch("/api/sessions");
|
|
13
|
+
if (!res.ok) throw new Error("Failed to fetch sessions");
|
|
14
|
+
return res.json();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function useSessionsQuery() {
|
|
18
|
+
return useQuery({
|
|
19
|
+
queryKey: sessionKeys.list(),
|
|
20
|
+
queryFn: fetchSessions,
|
|
21
|
+
staleTime: 5000,
|
|
22
|
+
refetchInterval: 10000,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function useDeleteSession() {
|
|
27
|
+
const queryClient = useQueryClient();
|
|
28
|
+
|
|
29
|
+
return useMutation({
|
|
30
|
+
mutationFn: async (sessionId: string) => {
|
|
31
|
+
const res = await fetch(`/api/sessions/${sessionId}`, {
|
|
32
|
+
method: "DELETE",
|
|
33
|
+
});
|
|
34
|
+
if (!res.ok) throw new Error("Failed to delete session");
|
|
35
|
+
return res.json();
|
|
36
|
+
},
|
|
37
|
+
onSuccess: () => {
|
|
38
|
+
queryClient.invalidateQueries({ queryKey: sessionKeys.list() });
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function useRenameSession() {
|
|
44
|
+
const queryClient = useQueryClient();
|
|
45
|
+
|
|
46
|
+
return useMutation({
|
|
47
|
+
mutationFn: async ({
|
|
48
|
+
sessionId,
|
|
49
|
+
newName,
|
|
50
|
+
}: {
|
|
51
|
+
sessionId: string;
|
|
52
|
+
newName: string;
|
|
53
|
+
}) => {
|
|
54
|
+
const res = await fetch(`/api/sessions/${sessionId}`, {
|
|
55
|
+
method: "PATCH",
|
|
56
|
+
headers: { "Content-Type": "application/json" },
|
|
57
|
+
body: JSON.stringify({ name: newName }),
|
|
58
|
+
});
|
|
59
|
+
if (!res.ok) throw new Error("Failed to rename session");
|
|
60
|
+
return res.json();
|
|
61
|
+
},
|
|
62
|
+
onMutate: async ({ sessionId, newName }) => {
|
|
63
|
+
await queryClient.cancelQueries({ queryKey: sessionKeys.list() });
|
|
64
|
+
const previous = queryClient.getQueryData<SessionsResponse>(
|
|
65
|
+
sessionKeys.list()
|
|
66
|
+
);
|
|
67
|
+
queryClient.setQueryData<SessionsResponse>(sessionKeys.list(), (old) =>
|
|
68
|
+
old
|
|
69
|
+
? {
|
|
70
|
+
...old,
|
|
71
|
+
sessions: old.sessions.map((s) =>
|
|
72
|
+
s.id === sessionId ? { ...s, name: newName } : s
|
|
73
|
+
),
|
|
74
|
+
}
|
|
75
|
+
: old
|
|
76
|
+
);
|
|
77
|
+
return { previous };
|
|
78
|
+
},
|
|
79
|
+
onError: (_, __, context) => {
|
|
80
|
+
if (context?.previous) {
|
|
81
|
+
queryClient.setQueryData(sessionKeys.list(), context.previous);
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
onSettled: () => {
|
|
85
|
+
queryClient.invalidateQueries({ queryKey: sessionKeys.list() });
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function useForkSession() {
|
|
91
|
+
const queryClient = useQueryClient();
|
|
92
|
+
|
|
93
|
+
return useMutation({
|
|
94
|
+
mutationFn: async (sessionId: string): Promise<Session | null> => {
|
|
95
|
+
const res = await fetch(`/api/sessions/${sessionId}/fork`, {
|
|
96
|
+
method: "POST",
|
|
97
|
+
});
|
|
98
|
+
const data = await res.json();
|
|
99
|
+
return data.session || null;
|
|
100
|
+
},
|
|
101
|
+
onSuccess: () => {
|
|
102
|
+
queryClient.invalidateQueries({ queryKey: sessionKeys.list() });
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function useSummarizeSession() {
|
|
108
|
+
const queryClient = useQueryClient();
|
|
109
|
+
|
|
110
|
+
return useMutation({
|
|
111
|
+
mutationFn: async (sessionId: string): Promise<Session | null> => {
|
|
112
|
+
const res = await fetch(`/api/sessions/${sessionId}/summarize`, {
|
|
113
|
+
method: "POST",
|
|
114
|
+
headers: { "Content-Type": "application/json" },
|
|
115
|
+
body: JSON.stringify({ createFork: true }),
|
|
116
|
+
});
|
|
117
|
+
const data = await res.json();
|
|
118
|
+
if (data.error) throw new Error(data.error);
|
|
119
|
+
return data.newSession || null;
|
|
120
|
+
},
|
|
121
|
+
onSuccess: () => {
|
|
122
|
+
queryClient.invalidateQueries({ queryKey: sessionKeys.list() });
|
|
123
|
+
},
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function useMoveSessionToGroup() {
|
|
128
|
+
const queryClient = useQueryClient();
|
|
129
|
+
|
|
130
|
+
return useMutation({
|
|
131
|
+
mutationFn: async ({
|
|
132
|
+
sessionId,
|
|
133
|
+
groupPath,
|
|
134
|
+
}: {
|
|
135
|
+
sessionId: string;
|
|
136
|
+
groupPath: string;
|
|
137
|
+
}) => {
|
|
138
|
+
const res = await fetch(`/api/sessions/${sessionId}`, {
|
|
139
|
+
method: "PATCH",
|
|
140
|
+
headers: { "Content-Type": "application/json" },
|
|
141
|
+
body: JSON.stringify({ groupPath }),
|
|
142
|
+
});
|
|
143
|
+
if (!res.ok) throw new Error("Failed to move session");
|
|
144
|
+
return res.json();
|
|
145
|
+
},
|
|
146
|
+
onSuccess: () => {
|
|
147
|
+
queryClient.invalidateQueries({ queryKey: sessionKeys.list() });
|
|
148
|
+
},
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export function useMoveSessionToProject() {
|
|
153
|
+
const queryClient = useQueryClient();
|
|
154
|
+
|
|
155
|
+
return useMutation({
|
|
156
|
+
mutationFn: async ({
|
|
157
|
+
sessionId,
|
|
158
|
+
projectId,
|
|
159
|
+
}: {
|
|
160
|
+
sessionId: string;
|
|
161
|
+
projectId: string;
|
|
162
|
+
}) => {
|
|
163
|
+
const res = await fetch(`/api/sessions/${sessionId}`, {
|
|
164
|
+
method: "PATCH",
|
|
165
|
+
headers: { "Content-Type": "application/json" },
|
|
166
|
+
body: JSON.stringify({ projectId }),
|
|
167
|
+
});
|
|
168
|
+
if (!res.ok) throw new Error("Failed to move session");
|
|
169
|
+
return res.json();
|
|
170
|
+
},
|
|
171
|
+
onSuccess: () => {
|
|
172
|
+
queryClient.invalidateQueries({ queryKey: sessionKeys.list() });
|
|
173
|
+
},
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export interface CreateSessionInput {
|
|
178
|
+
name?: string;
|
|
179
|
+
workingDirectory: string;
|
|
180
|
+
projectId: string | null;
|
|
181
|
+
agentType: AgentType;
|
|
182
|
+
useWorktree: boolean;
|
|
183
|
+
featureName: string | null;
|
|
184
|
+
baseBranch: string | null;
|
|
185
|
+
autoApprove: boolean;
|
|
186
|
+
useTmux: boolean;
|
|
187
|
+
initialPrompt: string | null;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
interface CreateSessionResponse {
|
|
191
|
+
session: Session;
|
|
192
|
+
initialPrompt?: string;
|
|
193
|
+
error?: string;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export function useCreateSession() {
|
|
197
|
+
const queryClient = useQueryClient();
|
|
198
|
+
|
|
199
|
+
return useMutation({
|
|
200
|
+
mutationFn: async (
|
|
201
|
+
input: CreateSessionInput
|
|
202
|
+
): Promise<CreateSessionResponse> => {
|
|
203
|
+
const res = await fetch("/api/sessions", {
|
|
204
|
+
method: "POST",
|
|
205
|
+
headers: { "Content-Type": "application/json" },
|
|
206
|
+
body: JSON.stringify(input),
|
|
207
|
+
});
|
|
208
|
+
const data = await res.json();
|
|
209
|
+
if (data.error) {
|
|
210
|
+
throw new Error(data.error);
|
|
211
|
+
}
|
|
212
|
+
return data;
|
|
213
|
+
},
|
|
214
|
+
onSuccess: () => {
|
|
215
|
+
queryClient.invalidateQueries({ queryKey: sessionKeys.list() });
|
|
216
|
+
},
|
|
217
|
+
});
|
|
218
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { useSessionStatusesQuery } from "./queries";
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { useQuery } from "@tanstack/react-query";
|
|
2
|
+
import { useEffect } from "react";
|
|
3
|
+
import type { Session } from "@/lib/db";
|
|
4
|
+
import type { SessionStatus } from "@/components/views/types";
|
|
5
|
+
import { statusKeys } from "../sessions/keys";
|
|
6
|
+
|
|
7
|
+
interface StatusResponse {
|
|
8
|
+
statuses: Record<string, SessionStatus>;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async function fetchStatuses(): Promise<StatusResponse> {
|
|
12
|
+
const res = await fetch("/api/sessions/status");
|
|
13
|
+
if (!res.ok) throw new Error("Failed to fetch statuses");
|
|
14
|
+
return res.json();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface UseSessionStatusesOptions {
|
|
18
|
+
sessions: Session[];
|
|
19
|
+
activeSessionId?: string | null;
|
|
20
|
+
checkStateChanges: (
|
|
21
|
+
states: Array<{
|
|
22
|
+
id: string;
|
|
23
|
+
name: string;
|
|
24
|
+
status: SessionStatus["status"];
|
|
25
|
+
}>,
|
|
26
|
+
activeSessionId?: string | null
|
|
27
|
+
) => void;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function useSessionStatusesQuery({
|
|
31
|
+
sessions,
|
|
32
|
+
activeSessionId,
|
|
33
|
+
checkStateChanges,
|
|
34
|
+
}: UseSessionStatusesOptions) {
|
|
35
|
+
const query = useQuery({
|
|
36
|
+
queryKey: statusKeys.all,
|
|
37
|
+
queryFn: fetchStatuses,
|
|
38
|
+
staleTime: 2000,
|
|
39
|
+
refetchInterval: (query) => {
|
|
40
|
+
const statuses = query.state.data?.statuses;
|
|
41
|
+
if (!statuses) return 5000;
|
|
42
|
+
|
|
43
|
+
const hasActive = Object.values(statuses).some(
|
|
44
|
+
(s) => s.status === "running" || s.status === "waiting"
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
return hasActive ? 5000 : 30000;
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
if (!query.data?.statuses) return;
|
|
53
|
+
|
|
54
|
+
const statuses = query.data.statuses;
|
|
55
|
+
|
|
56
|
+
const sessionStates = sessions.map((s) => ({
|
|
57
|
+
id: s.id,
|
|
58
|
+
name: s.name,
|
|
59
|
+
status: (statuses[s.id]?.status || "dead") as SessionStatus["status"],
|
|
60
|
+
}));
|
|
61
|
+
checkStateChanges(sessionStates, activeSessionId);
|
|
62
|
+
// Note: claude_session_id is now updated server-side in /api/sessions/status
|
|
63
|
+
}, [query.data, sessions, activeSessionId, checkStateChanges]);
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
sessionStatuses: query.data?.statuses ?? {},
|
|
67
|
+
isLoading: query.isLoading,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { useState, useCallback } from "react";
|
|
2
|
+
|
|
3
|
+
interface UseCopyToClipboardOptions {
|
|
4
|
+
/** Duration to show copied feedback (ms). Default: 1500 */
|
|
5
|
+
feedbackDuration?: number;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
interface UseCopyToClipboardReturn {
|
|
9
|
+
/** Whether the copy was successful (shows feedback) */
|
|
10
|
+
copied: boolean;
|
|
11
|
+
/** Copy text to clipboard */
|
|
12
|
+
copy: (text: string) => Promise<boolean>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Hook for copying text to clipboard with visual feedback.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* const { copied, copy } = useCopyToClipboard();
|
|
20
|
+
* <button onClick={() => copy(text)}>
|
|
21
|
+
* {copied ? <Check /> : <Copy />}
|
|
22
|
+
* </button>
|
|
23
|
+
*/
|
|
24
|
+
export function useCopyToClipboard(
|
|
25
|
+
options: UseCopyToClipboardOptions = {}
|
|
26
|
+
): UseCopyToClipboardReturn {
|
|
27
|
+
const { feedbackDuration = 1500 } = options;
|
|
28
|
+
const [copied, setCopied] = useState(false);
|
|
29
|
+
|
|
30
|
+
const copy = useCallback(
|
|
31
|
+
async (text: string): Promise<boolean> => {
|
|
32
|
+
if (!text) return false;
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
await navigator.clipboard.writeText(text);
|
|
36
|
+
setCopied(true);
|
|
37
|
+
setTimeout(() => setCopied(false), feedbackDuration);
|
|
38
|
+
return true;
|
|
39
|
+
} catch {
|
|
40
|
+
// Clipboard API failed or unavailable
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
[feedbackDuration]
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
return { copied, copy };
|
|
48
|
+
}
|