@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,25 @@
|
|
|
1
|
+
import { useQuery } from "@tanstack/react-query";
|
|
2
|
+
import { fileKeys } from "./keys";
|
|
3
|
+
import type { FileNode } from "@/lib/file-utils";
|
|
4
|
+
|
|
5
|
+
export { fileKeys };
|
|
6
|
+
|
|
7
|
+
export interface DirectoryData {
|
|
8
|
+
files: FileNode[];
|
|
9
|
+
resolvedPath: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async function fetchDirectory(path: string): Promise<DirectoryData> {
|
|
13
|
+
const res = await fetch(`/api/files?path=${encodeURIComponent(path)}`);
|
|
14
|
+
const data = await res.json();
|
|
15
|
+
if (data.error) throw new Error(data.error);
|
|
16
|
+
return { files: data.files || [], resolvedPath: data.path || path };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function useDirectoryFilesQuery(path: string) {
|
|
20
|
+
return useQuery({
|
|
21
|
+
queryKey: fileKeys.list(path),
|
|
22
|
+
queryFn: () => fetchDirectory(path),
|
|
23
|
+
staleTime: 10000,
|
|
24
|
+
});
|
|
25
|
+
}
|
package/data/git/keys.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export const gitKeys = {
|
|
2
|
+
all: ["git"] as const,
|
|
3
|
+
check: (path: string) => [...gitKeys.all, "check", path] as const,
|
|
4
|
+
status: (workingDir: string) =>
|
|
5
|
+
[...gitKeys.all, "status", workingDir] as const,
|
|
6
|
+
multiStatus: (projectId: string, fallbackPath?: string) =>
|
|
7
|
+
[...gitKeys.all, "multi-status", projectId, fallbackPath || ""] as const,
|
|
8
|
+
pr: (workingDir: string) => [...gitKeys.all, "pr", workingDir] as const,
|
|
9
|
+
history: (workingDir: string) =>
|
|
10
|
+
[...gitKeys.all, "history", workingDir] as const,
|
|
11
|
+
commitDetail: (workingDir: string, hash: string) =>
|
|
12
|
+
[...gitKeys.all, "commit", workingDir, hash] as const,
|
|
13
|
+
commitFileDiff: (workingDir: string, hash: string, file: string) =>
|
|
14
|
+
[...gitKeys.all, "diff", workingDir, hash, file] as const,
|
|
15
|
+
};
|
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
|
2
|
+
import { gitKeys } from "./keys";
|
|
3
|
+
|
|
4
|
+
// Re-export for convenience
|
|
5
|
+
export { gitKeys };
|
|
6
|
+
import type { CommitSummary, CommitDetail } from "@/lib/git-history";
|
|
7
|
+
import type { GitStatus } from "@/lib/git-status";
|
|
8
|
+
import type { MultiRepoGitStatus } from "@/lib/multi-repo-git";
|
|
9
|
+
import type { ProjectRepository } from "@/lib/db";
|
|
10
|
+
|
|
11
|
+
export interface PRInfo {
|
|
12
|
+
number: number;
|
|
13
|
+
url: string;
|
|
14
|
+
state: string;
|
|
15
|
+
title: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface PRData {
|
|
19
|
+
branch: string;
|
|
20
|
+
baseBranch: string;
|
|
21
|
+
existingPR: PRInfo | null;
|
|
22
|
+
commits: { hash: string; subject: string }[];
|
|
23
|
+
suggestedTitle: string;
|
|
24
|
+
suggestedBody: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// --- Git Check ---
|
|
28
|
+
|
|
29
|
+
async function fetchGitCheck(path: string): Promise<{ isGitRepo: boolean }> {
|
|
30
|
+
const res = await fetch("/api/git/check", {
|
|
31
|
+
method: "POST",
|
|
32
|
+
headers: { "Content-Type": "application/json" },
|
|
33
|
+
body: JSON.stringify({ path }),
|
|
34
|
+
});
|
|
35
|
+
return res.json();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function useGitCheck(path: string) {
|
|
39
|
+
return useQuery({
|
|
40
|
+
queryKey: gitKeys.check(path),
|
|
41
|
+
queryFn: () => fetchGitCheck(path),
|
|
42
|
+
staleTime: 10000,
|
|
43
|
+
enabled: !!path && path !== "~",
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// --- Git Clone ---
|
|
48
|
+
|
|
49
|
+
async function cloneRepo(data: {
|
|
50
|
+
url: string;
|
|
51
|
+
directory: string;
|
|
52
|
+
}): Promise<{ path: string; name: string }> {
|
|
53
|
+
const res = await fetch("/api/git/clone", {
|
|
54
|
+
method: "POST",
|
|
55
|
+
headers: { "Content-Type": "application/json" },
|
|
56
|
+
body: JSON.stringify(data),
|
|
57
|
+
});
|
|
58
|
+
if (!res.ok) {
|
|
59
|
+
const err = await res.json();
|
|
60
|
+
throw new Error(err.error || "Failed to clone repository");
|
|
61
|
+
}
|
|
62
|
+
return res.json();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function useCloneRepo() {
|
|
66
|
+
return useMutation({ mutationFn: cloneRepo });
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// --- Git Status ---
|
|
70
|
+
|
|
71
|
+
async function fetchGitStatus(workingDir: string): Promise<GitStatus> {
|
|
72
|
+
const res = await fetch(
|
|
73
|
+
`/api/git/status?path=${encodeURIComponent(workingDir)}`
|
|
74
|
+
);
|
|
75
|
+
const data = await res.json();
|
|
76
|
+
if (data.error) throw new Error(data.error);
|
|
77
|
+
return data;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function useGitStatus(
|
|
81
|
+
workingDir: string,
|
|
82
|
+
options?: { enabled?: boolean }
|
|
83
|
+
) {
|
|
84
|
+
return useQuery({
|
|
85
|
+
queryKey: gitKeys.status(workingDir),
|
|
86
|
+
queryFn: () => fetchGitStatus(workingDir),
|
|
87
|
+
staleTime: 10000, // Consider fresh for 10s
|
|
88
|
+
refetchInterval: 15000, // Poll every 15s (was 3s)
|
|
89
|
+
enabled: !!workingDir && (options?.enabled ?? true),
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// --- PR Status ---
|
|
94
|
+
|
|
95
|
+
async function fetchPRData(workingDir: string): Promise<PRData | null> {
|
|
96
|
+
const res = await fetch(`/api/git/pr?path=${encodeURIComponent(workingDir)}`);
|
|
97
|
+
const data = await res.json();
|
|
98
|
+
if (data.error) return null;
|
|
99
|
+
return data;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function usePRStatus(workingDir: string) {
|
|
103
|
+
return useQuery({
|
|
104
|
+
queryKey: gitKeys.pr(workingDir),
|
|
105
|
+
queryFn: () => fetchPRData(workingDir),
|
|
106
|
+
staleTime: 60000, // 1 minute - PR status doesn't change often
|
|
107
|
+
gcTime: 5 * 60 * 1000, // Keep in cache for 5 minutes
|
|
108
|
+
enabled: !!workingDir,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// --- Mutations ---
|
|
113
|
+
|
|
114
|
+
export function useCreatePR(workingDir: string) {
|
|
115
|
+
const queryClient = useQueryClient();
|
|
116
|
+
|
|
117
|
+
return useMutation({
|
|
118
|
+
mutationFn: async () => {
|
|
119
|
+
// First get suggested content (with generate=true for AI generation)
|
|
120
|
+
const infoRes = await fetch(
|
|
121
|
+
`/api/git/pr?path=${encodeURIComponent(workingDir)}&generate=true`
|
|
122
|
+
);
|
|
123
|
+
const info = await infoRes.json();
|
|
124
|
+
|
|
125
|
+
if (info.error) throw new Error(info.error);
|
|
126
|
+
|
|
127
|
+
if (info.existingPR) {
|
|
128
|
+
// PR already exists, just return it
|
|
129
|
+
return { pr: info.existingPR, created: false };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Create the PR with auto-generated content
|
|
133
|
+
const createRes = await fetch("/api/git/pr", {
|
|
134
|
+
method: "POST",
|
|
135
|
+
headers: { "Content-Type": "application/json" },
|
|
136
|
+
body: JSON.stringify({
|
|
137
|
+
path: workingDir,
|
|
138
|
+
title: info.suggestedTitle,
|
|
139
|
+
description: info.suggestedBody,
|
|
140
|
+
baseBranch: info.baseBranch,
|
|
141
|
+
}),
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
const result = await createRes.json();
|
|
145
|
+
if (result.error) throw new Error(result.error);
|
|
146
|
+
|
|
147
|
+
return { pr: result.pr, created: true };
|
|
148
|
+
},
|
|
149
|
+
onSuccess: (data) => {
|
|
150
|
+
// Open PR in browser
|
|
151
|
+
if (data.pr?.url) {
|
|
152
|
+
window.open(data.pr.url, "_blank");
|
|
153
|
+
}
|
|
154
|
+
// Invalidate PR status
|
|
155
|
+
queryClient.invalidateQueries({ queryKey: gitKeys.pr(workingDir) });
|
|
156
|
+
},
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export function useStageFiles(workingDir: string) {
|
|
161
|
+
const queryClient = useQueryClient();
|
|
162
|
+
|
|
163
|
+
return useMutation({
|
|
164
|
+
mutationFn: async (files?: string[]) => {
|
|
165
|
+
const res = await fetch("/api/git/stage", {
|
|
166
|
+
method: "POST",
|
|
167
|
+
headers: { "Content-Type": "application/json" },
|
|
168
|
+
body: JSON.stringify({ path: workingDir, files }),
|
|
169
|
+
});
|
|
170
|
+
const data = await res.json();
|
|
171
|
+
if (data.error) throw new Error(data.error);
|
|
172
|
+
return data;
|
|
173
|
+
},
|
|
174
|
+
onSuccess: () => {
|
|
175
|
+
queryClient.invalidateQueries({ queryKey: gitKeys.status(workingDir) });
|
|
176
|
+
},
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export function useUnstageFiles(workingDir: string) {
|
|
181
|
+
const queryClient = useQueryClient();
|
|
182
|
+
|
|
183
|
+
return useMutation({
|
|
184
|
+
mutationFn: async (files?: string[]) => {
|
|
185
|
+
const res = await fetch("/api/git/unstage", {
|
|
186
|
+
method: "POST",
|
|
187
|
+
headers: { "Content-Type": "application/json" },
|
|
188
|
+
body: JSON.stringify({ path: workingDir, files }),
|
|
189
|
+
});
|
|
190
|
+
const data = await res.json();
|
|
191
|
+
if (data.error) throw new Error(data.error);
|
|
192
|
+
return data;
|
|
193
|
+
},
|
|
194
|
+
onSuccess: () => {
|
|
195
|
+
queryClient.invalidateQueries({ queryKey: gitKeys.status(workingDir) });
|
|
196
|
+
},
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export function useCommitAndPush(workingDir: string) {
|
|
201
|
+
const queryClient = useQueryClient();
|
|
202
|
+
|
|
203
|
+
return useMutation({
|
|
204
|
+
mutationFn: async ({
|
|
205
|
+
message,
|
|
206
|
+
branchName,
|
|
207
|
+
push = true,
|
|
208
|
+
}: {
|
|
209
|
+
message: string;
|
|
210
|
+
branchName?: string;
|
|
211
|
+
push?: boolean;
|
|
212
|
+
}) => {
|
|
213
|
+
// Commit
|
|
214
|
+
const commitRes = await fetch("/api/git/commit", {
|
|
215
|
+
method: "POST",
|
|
216
|
+
headers: { "Content-Type": "application/json" },
|
|
217
|
+
body: JSON.stringify({
|
|
218
|
+
path: workingDir,
|
|
219
|
+
message,
|
|
220
|
+
branchName,
|
|
221
|
+
}),
|
|
222
|
+
});
|
|
223
|
+
const commitData = await commitRes.json();
|
|
224
|
+
if (!commitRes.ok || commitData.error) {
|
|
225
|
+
throw new Error(commitData.error || "Commit failed");
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Push if requested
|
|
229
|
+
if (push) {
|
|
230
|
+
const pushRes = await fetch("/api/git/push", {
|
|
231
|
+
method: "POST",
|
|
232
|
+
headers: { "Content-Type": "application/json" },
|
|
233
|
+
body: JSON.stringify({ path: workingDir }),
|
|
234
|
+
});
|
|
235
|
+
const pushData = await pushRes.json();
|
|
236
|
+
if (!pushRes.ok || pushData.error) {
|
|
237
|
+
throw new Error(pushData.error || "Push failed");
|
|
238
|
+
}
|
|
239
|
+
return { commit: commitData, push: pushData };
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return { commit: commitData };
|
|
243
|
+
},
|
|
244
|
+
onSuccess: () => {
|
|
245
|
+
queryClient.invalidateQueries({ queryKey: gitKeys.status(workingDir) });
|
|
246
|
+
queryClient.invalidateQueries({ queryKey: gitKeys.pr(workingDir) });
|
|
247
|
+
queryClient.invalidateQueries({ queryKey: gitKeys.history(workingDir) });
|
|
248
|
+
},
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
async function fetchCommitHistory(
|
|
253
|
+
workingDir: string,
|
|
254
|
+
limit: number = 30
|
|
255
|
+
): Promise<CommitSummary[]> {
|
|
256
|
+
const res = await fetch(
|
|
257
|
+
`/api/git/history?path=${encodeURIComponent(workingDir)}&limit=${limit}`
|
|
258
|
+
);
|
|
259
|
+
if (!res.ok) throw new Error("Failed to fetch commit history");
|
|
260
|
+
const data = await res.json();
|
|
261
|
+
return data.commits || [];
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
async function fetchCommitDetail(
|
|
265
|
+
workingDir: string,
|
|
266
|
+
hash: string
|
|
267
|
+
): Promise<CommitDetail> {
|
|
268
|
+
const res = await fetch(
|
|
269
|
+
`/api/git/history/${hash}?path=${encodeURIComponent(workingDir)}`
|
|
270
|
+
);
|
|
271
|
+
if (!res.ok) throw new Error("Failed to fetch commit detail");
|
|
272
|
+
const data = await res.json();
|
|
273
|
+
return data.commit;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
async function fetchCommitFileDiff(
|
|
277
|
+
workingDir: string,
|
|
278
|
+
hash: string,
|
|
279
|
+
file: string
|
|
280
|
+
): Promise<string> {
|
|
281
|
+
const res = await fetch(
|
|
282
|
+
`/api/git/history/${hash}/diff?path=${encodeURIComponent(workingDir)}&file=${encodeURIComponent(file)}`
|
|
283
|
+
);
|
|
284
|
+
if (!res.ok) throw new Error("Failed to fetch commit file diff");
|
|
285
|
+
const data = await res.json();
|
|
286
|
+
return data.diff || "";
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
export function useCommitHistory(workingDir: string, limit: number = 30) {
|
|
290
|
+
return useQuery({
|
|
291
|
+
queryKey: gitKeys.history(workingDir),
|
|
292
|
+
queryFn: () => fetchCommitHistory(workingDir, limit),
|
|
293
|
+
staleTime: 30000,
|
|
294
|
+
enabled: !!workingDir,
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
export function useCommitDetail(workingDir: string, hash: string | null) {
|
|
299
|
+
return useQuery({
|
|
300
|
+
queryKey: gitKeys.commitDetail(workingDir, hash || ""),
|
|
301
|
+
queryFn: () => fetchCommitDetail(workingDir, hash!),
|
|
302
|
+
staleTime: 60000, // Commit details don't change
|
|
303
|
+
enabled: !!workingDir && !!hash,
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
export function useCommitFileDiff(
|
|
308
|
+
workingDir: string,
|
|
309
|
+
hash: string | null,
|
|
310
|
+
file: string | null
|
|
311
|
+
) {
|
|
312
|
+
return useQuery({
|
|
313
|
+
queryKey: gitKeys.commitFileDiff(workingDir, hash || "", file || ""),
|
|
314
|
+
queryFn: () => fetchCommitFileDiff(workingDir, hash!, file!),
|
|
315
|
+
staleTime: 60000, // Diffs don't change
|
|
316
|
+
enabled: !!workingDir && !!hash && !!file,
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// --- Multi-repo Git Status ---
|
|
321
|
+
|
|
322
|
+
async function fetchMultiRepoGitStatus(
|
|
323
|
+
projectId?: string,
|
|
324
|
+
fallbackPath?: string
|
|
325
|
+
): Promise<MultiRepoGitStatus> {
|
|
326
|
+
const params = new URLSearchParams();
|
|
327
|
+
if (projectId) params.set("projectId", projectId);
|
|
328
|
+
if (fallbackPath) params.set("fallbackPath", fallbackPath);
|
|
329
|
+
|
|
330
|
+
const res = await fetch(`/api/git/multi-status?${params}`);
|
|
331
|
+
const data = await res.json();
|
|
332
|
+
if (data.error) throw new Error(data.error);
|
|
333
|
+
return data;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
export function useMultiRepoGitStatus(
|
|
337
|
+
projectId?: string,
|
|
338
|
+
fallbackPath?: string,
|
|
339
|
+
options?: { enabled?: boolean }
|
|
340
|
+
) {
|
|
341
|
+
return useQuery({
|
|
342
|
+
queryKey: gitKeys.multiStatus(projectId || "", fallbackPath),
|
|
343
|
+
queryFn: () => fetchMultiRepoGitStatus(projectId, fallbackPath),
|
|
344
|
+
staleTime: 10000, // Consider fresh for 10s
|
|
345
|
+
refetchInterval: 15000, // Poll every 15s
|
|
346
|
+
enabled: (!!projectId || !!fallbackPath) && (options?.enabled ?? true),
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// Multi-repo stage/unstage mutations
|
|
351
|
+
export function useMultiRepoStageFiles(repoPath: string) {
|
|
352
|
+
const queryClient = useQueryClient();
|
|
353
|
+
|
|
354
|
+
return useMutation({
|
|
355
|
+
mutationFn: async (files?: string[]) => {
|
|
356
|
+
const res = await fetch("/api/git/stage", {
|
|
357
|
+
method: "POST",
|
|
358
|
+
headers: { "Content-Type": "application/json" },
|
|
359
|
+
body: JSON.stringify({ path: repoPath, files }),
|
|
360
|
+
});
|
|
361
|
+
const data = await res.json();
|
|
362
|
+
if (data.error) throw new Error(data.error);
|
|
363
|
+
return data;
|
|
364
|
+
},
|
|
365
|
+
onSuccess: () => {
|
|
366
|
+
// Invalidate all multi-status queries since we don't know which project
|
|
367
|
+
queryClient.invalidateQueries({
|
|
368
|
+
queryKey: gitKeys.all,
|
|
369
|
+
});
|
|
370
|
+
},
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
export function useMultiRepoUnstageFiles(repoPath: string) {
|
|
375
|
+
const queryClient = useQueryClient();
|
|
376
|
+
|
|
377
|
+
return useMutation({
|
|
378
|
+
mutationFn: async (files?: string[]) => {
|
|
379
|
+
const res = await fetch("/api/git/unstage", {
|
|
380
|
+
method: "POST",
|
|
381
|
+
headers: { "Content-Type": "application/json" },
|
|
382
|
+
body: JSON.stringify({ path: repoPath, files }),
|
|
383
|
+
});
|
|
384
|
+
const data = await res.json();
|
|
385
|
+
if (data.error) throw new Error(data.error);
|
|
386
|
+
return data;
|
|
387
|
+
},
|
|
388
|
+
onSuccess: () => {
|
|
389
|
+
// Invalidate all multi-status queries since we don't know which project
|
|
390
|
+
queryClient.invalidateQueries({
|
|
391
|
+
queryKey: gitKeys.all,
|
|
392
|
+
});
|
|
393
|
+
},
|
|
394
|
+
});
|
|
395
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { useToggleGroup, useCreateGroup, useDeleteGroup } from "./mutations";
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
|
2
|
+
import { sessionKeys } from "../sessions/keys";
|
|
3
|
+
|
|
4
|
+
export function useToggleGroup() {
|
|
5
|
+
const queryClient = useQueryClient();
|
|
6
|
+
|
|
7
|
+
return useMutation({
|
|
8
|
+
mutationFn: async ({
|
|
9
|
+
path,
|
|
10
|
+
expanded,
|
|
11
|
+
}: {
|
|
12
|
+
path: string;
|
|
13
|
+
expanded: boolean;
|
|
14
|
+
}) => {
|
|
15
|
+
const res = await fetch(`/api/groups/${encodeURIComponent(path)}`, {
|
|
16
|
+
method: "PATCH",
|
|
17
|
+
headers: { "Content-Type": "application/json" },
|
|
18
|
+
body: JSON.stringify({ expanded }),
|
|
19
|
+
});
|
|
20
|
+
if (!res.ok) throw new Error("Failed to toggle group");
|
|
21
|
+
return res.json();
|
|
22
|
+
},
|
|
23
|
+
onMutate: async ({ path, expanded }) => {
|
|
24
|
+
await queryClient.cancelQueries({ queryKey: sessionKeys.list() });
|
|
25
|
+
const previous = queryClient.getQueryData(sessionKeys.list());
|
|
26
|
+
queryClient.setQueryData(
|
|
27
|
+
sessionKeys.list(),
|
|
28
|
+
(
|
|
29
|
+
old:
|
|
30
|
+
| {
|
|
31
|
+
sessions: unknown[];
|
|
32
|
+
groups: Array<{ path: string; expanded: boolean }>;
|
|
33
|
+
}
|
|
34
|
+
| undefined
|
|
35
|
+
) =>
|
|
36
|
+
old
|
|
37
|
+
? {
|
|
38
|
+
...old,
|
|
39
|
+
groups: old.groups.map((g) =>
|
|
40
|
+
g.path === path ? { ...g, expanded } : g
|
|
41
|
+
),
|
|
42
|
+
}
|
|
43
|
+
: old
|
|
44
|
+
);
|
|
45
|
+
return { previous };
|
|
46
|
+
},
|
|
47
|
+
onError: (_, __, context) => {
|
|
48
|
+
if (context?.previous) {
|
|
49
|
+
queryClient.setQueryData(sessionKeys.list(), context.previous);
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function useCreateGroup() {
|
|
56
|
+
const queryClient = useQueryClient();
|
|
57
|
+
|
|
58
|
+
return useMutation({
|
|
59
|
+
mutationFn: async ({
|
|
60
|
+
name,
|
|
61
|
+
parentPath,
|
|
62
|
+
}: {
|
|
63
|
+
name: string;
|
|
64
|
+
parentPath?: string;
|
|
65
|
+
}) => {
|
|
66
|
+
const res = await fetch("/api/groups", {
|
|
67
|
+
method: "POST",
|
|
68
|
+
headers: { "Content-Type": "application/json" },
|
|
69
|
+
body: JSON.stringify({ name, parentPath }),
|
|
70
|
+
});
|
|
71
|
+
if (!res.ok) throw new Error("Failed to create group");
|
|
72
|
+
return res.json();
|
|
73
|
+
},
|
|
74
|
+
onSuccess: () => {
|
|
75
|
+
queryClient.invalidateQueries({ queryKey: sessionKeys.list() });
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function useDeleteGroup() {
|
|
81
|
+
const queryClient = useQueryClient();
|
|
82
|
+
|
|
83
|
+
return useMutation({
|
|
84
|
+
mutationFn: async (path: string) => {
|
|
85
|
+
const res = await fetch(`/api/groups/${encodeURIComponent(path)}`, {
|
|
86
|
+
method: "DELETE",
|
|
87
|
+
});
|
|
88
|
+
if (!res.ok) throw new Error("Failed to delete group");
|
|
89
|
+
return res.json();
|
|
90
|
+
},
|
|
91
|
+
onSuccess: () => {
|
|
92
|
+
queryClient.invalidateQueries({ queryKey: sessionKeys.list() });
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
}
|