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