@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
package/lib/git.ts
ADDED
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Git utilities for worktree management
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { exec } from "child_process";
|
|
6
|
+
import { promisify } from "util";
|
|
7
|
+
import * as path from "path";
|
|
8
|
+
import * as fs from "fs";
|
|
9
|
+
|
|
10
|
+
const execAsync = promisify(exec);
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Check if a directory is a git repository
|
|
14
|
+
*/
|
|
15
|
+
export async function isGitRepo(dirPath: string): Promise<boolean> {
|
|
16
|
+
try {
|
|
17
|
+
const resolvedPath = dirPath.replace(/^~/, process.env.HOME || "");
|
|
18
|
+
await execAsync(`git -C "${resolvedPath}" rev-parse --git-dir`, {
|
|
19
|
+
timeout: 5000,
|
|
20
|
+
});
|
|
21
|
+
return true;
|
|
22
|
+
} catch {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Get the current branch name
|
|
29
|
+
*/
|
|
30
|
+
export async function getCurrentBranch(dirPath: string): Promise<string> {
|
|
31
|
+
const resolvedPath = dirPath.replace(/^~/, process.env.HOME || "");
|
|
32
|
+
const { stdout } = await execAsync(
|
|
33
|
+
`git -C "${resolvedPath}" rev-parse --abbrev-ref HEAD`,
|
|
34
|
+
{ timeout: 5000 }
|
|
35
|
+
);
|
|
36
|
+
return stdout.trim();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Get the default branch (main or master)
|
|
41
|
+
*/
|
|
42
|
+
export async function getDefaultBranch(dirPath: string): Promise<string> {
|
|
43
|
+
const resolvedPath = dirPath.replace(/^~/, process.env.HOME || "");
|
|
44
|
+
try {
|
|
45
|
+
// Try to get the default branch from remote
|
|
46
|
+
const { stdout } = await execAsync(
|
|
47
|
+
`git -C "${resolvedPath}" symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's@^refs/remotes/origin/@@'`,
|
|
48
|
+
{ timeout: 5000 }
|
|
49
|
+
);
|
|
50
|
+
if (stdout.trim()) {
|
|
51
|
+
return stdout.trim();
|
|
52
|
+
}
|
|
53
|
+
} catch {
|
|
54
|
+
// Ignore
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Fallback: check if main or master exists
|
|
58
|
+
try {
|
|
59
|
+
await execAsync(`git -C "${resolvedPath}" rev-parse --verify main`, {
|
|
60
|
+
timeout: 5000,
|
|
61
|
+
});
|
|
62
|
+
return "main";
|
|
63
|
+
} catch {
|
|
64
|
+
try {
|
|
65
|
+
await execAsync(`git -C "${resolvedPath}" rev-parse --verify master`, {
|
|
66
|
+
timeout: 5000,
|
|
67
|
+
});
|
|
68
|
+
return "master";
|
|
69
|
+
} catch {
|
|
70
|
+
// Return current branch as fallback
|
|
71
|
+
return getCurrentBranch(resolvedPath);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Get list of local branches
|
|
78
|
+
*/
|
|
79
|
+
export async function getBranches(dirPath: string): Promise<string[]> {
|
|
80
|
+
const resolvedPath = dirPath.replace(/^~/, process.env.HOME || "");
|
|
81
|
+
const { stdout } = await execAsync(
|
|
82
|
+
`git -C "${resolvedPath}" branch --format='%(refname:short)'`,
|
|
83
|
+
{ timeout: 5000 }
|
|
84
|
+
);
|
|
85
|
+
return stdout
|
|
86
|
+
.trim()
|
|
87
|
+
.split("\n")
|
|
88
|
+
.filter((b) => b);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Check if a branch exists
|
|
93
|
+
*/
|
|
94
|
+
export async function branchExists(
|
|
95
|
+
dirPath: string,
|
|
96
|
+
branchName: string
|
|
97
|
+
): Promise<boolean> {
|
|
98
|
+
const resolvedPath = dirPath.replace(/^~/, process.env.HOME || "");
|
|
99
|
+
try {
|
|
100
|
+
await execAsync(
|
|
101
|
+
`git -C "${resolvedPath}" rev-parse --verify "${branchName}"`,
|
|
102
|
+
{ timeout: 5000 }
|
|
103
|
+
);
|
|
104
|
+
return true;
|
|
105
|
+
} catch {
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Get the repository name from path
|
|
112
|
+
*/
|
|
113
|
+
export function getRepoName(dirPath: string): string {
|
|
114
|
+
const resolvedPath = dirPath.replace(/^~/, process.env.HOME || "");
|
|
115
|
+
return path.basename(resolvedPath);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Slugify a string for use in branch names
|
|
120
|
+
*/
|
|
121
|
+
export function slugify(text: string): string {
|
|
122
|
+
return text
|
|
123
|
+
.toLowerCase()
|
|
124
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
125
|
+
.replace(/^-+|-+$/g, "")
|
|
126
|
+
.slice(0, 50);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Generate a branch name from a feature description
|
|
131
|
+
*/
|
|
132
|
+
export function generateBranchName(feature: string): string {
|
|
133
|
+
const slug = slugify(feature);
|
|
134
|
+
return `feature/${slug}`;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Check if a branch exists on remote
|
|
139
|
+
*/
|
|
140
|
+
export async function remoteBranchExists(
|
|
141
|
+
dirPath: string,
|
|
142
|
+
branchName: string
|
|
143
|
+
): Promise<boolean> {
|
|
144
|
+
const resolvedPath = dirPath.replace(/^~/, process.env.HOME || "");
|
|
145
|
+
try {
|
|
146
|
+
const { stdout } = await execAsync(
|
|
147
|
+
`git -C "${resolvedPath}" ls-remote --heads origin "${branchName}"`,
|
|
148
|
+
{ timeout: 10000 }
|
|
149
|
+
);
|
|
150
|
+
return stdout.trim().length > 0;
|
|
151
|
+
} catch {
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Rename a branch locally and optionally on remote
|
|
158
|
+
* Returns the new branch name or throws on error
|
|
159
|
+
*/
|
|
160
|
+
export async function renameBranch(
|
|
161
|
+
dirPath: string,
|
|
162
|
+
oldBranchName: string,
|
|
163
|
+
newBranchName: string
|
|
164
|
+
): Promise<{ renamed: boolean; remoteRenamed: boolean }> {
|
|
165
|
+
const resolvedPath = dirPath.replace(/^~/, process.env.HOME || "");
|
|
166
|
+
let renamed = false;
|
|
167
|
+
let remoteRenamed = false;
|
|
168
|
+
|
|
169
|
+
// Rename local branch
|
|
170
|
+
try {
|
|
171
|
+
await execAsync(
|
|
172
|
+
`git -C "${resolvedPath}" branch -m "${oldBranchName}" "${newBranchName}"`,
|
|
173
|
+
{ timeout: 10000 }
|
|
174
|
+
);
|
|
175
|
+
renamed = true;
|
|
176
|
+
} catch (error) {
|
|
177
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
178
|
+
throw new Error(`Failed to rename local branch: ${message}`);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Check if old branch exists on remote and rename there too
|
|
182
|
+
const hasRemote = await remoteBranchExists(dirPath, oldBranchName);
|
|
183
|
+
if (hasRemote) {
|
|
184
|
+
try {
|
|
185
|
+
// Push new branch to remote
|
|
186
|
+
await execAsync(
|
|
187
|
+
`git -C "${resolvedPath}" push origin "${newBranchName}" -u`,
|
|
188
|
+
{ timeout: 30000 }
|
|
189
|
+
);
|
|
190
|
+
// Delete old branch from remote
|
|
191
|
+
await execAsync(
|
|
192
|
+
`git -C "${resolvedPath}" push origin --delete "${oldBranchName}"`,
|
|
193
|
+
{ timeout: 30000 }
|
|
194
|
+
);
|
|
195
|
+
remoteRenamed = true;
|
|
196
|
+
} catch {
|
|
197
|
+
// Remote rename failed but local succeeded - that's okay
|
|
198
|
+
console.error(
|
|
199
|
+
`Warning: Local branch renamed but remote rename failed for ${oldBranchName}`
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return { renamed, remoteRenamed };
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Get git status summary (files changed, ahead/behind)
|
|
209
|
+
*/
|
|
210
|
+
export async function getGitStatus(dirPath: string): Promise<{
|
|
211
|
+
staged: number;
|
|
212
|
+
unstaged: number;
|
|
213
|
+
untracked: number;
|
|
214
|
+
ahead: number;
|
|
215
|
+
behind: number;
|
|
216
|
+
}> {
|
|
217
|
+
const resolvedPath = dirPath.replace(/^~/, process.env.HOME || "");
|
|
218
|
+
|
|
219
|
+
// Get file counts
|
|
220
|
+
const { stdout: statusOutput } = await execAsync(
|
|
221
|
+
`git -C "${resolvedPath}" status --porcelain`,
|
|
222
|
+
{ timeout: 5000 }
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
const lines = statusOutput.trim().split("\n").filter(Boolean);
|
|
226
|
+
let staged = 0;
|
|
227
|
+
let unstaged = 0;
|
|
228
|
+
let untracked = 0;
|
|
229
|
+
|
|
230
|
+
for (const line of lines) {
|
|
231
|
+
const index = line[0];
|
|
232
|
+
const worktree = line[1];
|
|
233
|
+
if (index === "?" && worktree === "?") {
|
|
234
|
+
untracked++;
|
|
235
|
+
} else {
|
|
236
|
+
if (index !== " " && index !== "?") staged++;
|
|
237
|
+
if (worktree !== " " && worktree !== "?") unstaged++;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Get ahead/behind counts
|
|
242
|
+
let ahead = 0;
|
|
243
|
+
let behind = 0;
|
|
244
|
+
try {
|
|
245
|
+
const { stdout: aheadBehind } = await execAsync(
|
|
246
|
+
`git -C "${resolvedPath}" rev-list --left-right --count HEAD...@{upstream} 2>/dev/null || echo "0 0"`,
|
|
247
|
+
{ timeout: 5000 }
|
|
248
|
+
);
|
|
249
|
+
const [a, b] = aheadBehind.trim().split(/\s+/);
|
|
250
|
+
ahead = parseInt(a, 10) || 0;
|
|
251
|
+
behind = parseInt(b, 10) || 0;
|
|
252
|
+
} catch {
|
|
253
|
+
// No upstream, ignore
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return { staged, unstaged, untracked, ahead, behind };
|
|
257
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Config Auto-Generation
|
|
3
|
+
*
|
|
4
|
+
* Writes a .mcp.json file to the session's working directory so Claude
|
|
5
|
+
* automatically picks up the orchestration tools with the session ID baked in.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { writeFileSync, existsSync, readFileSync } from "fs";
|
|
9
|
+
import path from "path";
|
|
10
|
+
|
|
11
|
+
const AGENTOS_URL = process.env.AGENTOS_URL || "http://localhost:3011";
|
|
12
|
+
|
|
13
|
+
interface McpConfig {
|
|
14
|
+
mcpServers: Record<
|
|
15
|
+
string,
|
|
16
|
+
{
|
|
17
|
+
command: string;
|
|
18
|
+
args: string[];
|
|
19
|
+
env?: Record<string, string>;
|
|
20
|
+
}
|
|
21
|
+
>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Write or update .mcp.json in the working directory with orchestration server config
|
|
26
|
+
*/
|
|
27
|
+
export function ensureMcpConfig(
|
|
28
|
+
workingDirectory: string,
|
|
29
|
+
sessionId: string
|
|
30
|
+
): void {
|
|
31
|
+
const configPath = path.join(workingDirectory, ".mcp.json");
|
|
32
|
+
const orchestrationServerPath = path.join(
|
|
33
|
+
process.cwd(),
|
|
34
|
+
"mcp",
|
|
35
|
+
"orchestration-server.ts"
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
let config: McpConfig = { mcpServers: {} };
|
|
39
|
+
|
|
40
|
+
// Read existing config if present
|
|
41
|
+
if (existsSync(configPath)) {
|
|
42
|
+
try {
|
|
43
|
+
const existing = readFileSync(configPath, "utf-8");
|
|
44
|
+
config = JSON.parse(existing);
|
|
45
|
+
if (!config.mcpServers) {
|
|
46
|
+
config.mcpServers = {};
|
|
47
|
+
}
|
|
48
|
+
} catch {
|
|
49
|
+
// Invalid JSON, start fresh
|
|
50
|
+
config = { mcpServers: {} };
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Add/update claude-deck orchestration server
|
|
55
|
+
config.mcpServers["claude-deck"] = {
|
|
56
|
+
command: "npx",
|
|
57
|
+
args: ["tsx", orchestrationServerPath],
|
|
58
|
+
env: {
|
|
59
|
+
AGENTOS_URL,
|
|
60
|
+
CONDUCTOR_SESSION_ID: sessionId,
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
// Write config
|
|
65
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Check if .mcp.json exists and has claude-deck configured
|
|
70
|
+
*/
|
|
71
|
+
export function hasMcpConfig(workingDirectory: string): boolean {
|
|
72
|
+
const configPath = path.join(workingDirectory, ".mcp.json");
|
|
73
|
+
if (!existsSync(configPath)) return false;
|
|
74
|
+
|
|
75
|
+
try {
|
|
76
|
+
const config = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
77
|
+
return !!config.mcpServers?.["claude-deck"];
|
|
78
|
+
} catch {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Multi-repository git status aggregation
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
getGitStatus,
|
|
7
|
+
isGitRepo,
|
|
8
|
+
expandPath,
|
|
9
|
+
type GitFile,
|
|
10
|
+
type GitStatus,
|
|
11
|
+
} from "./git-status";
|
|
12
|
+
import type { ProjectRepository } from "./db";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Extended git file with repository information
|
|
16
|
+
*/
|
|
17
|
+
export interface MultiRepoGitFile extends GitFile {
|
|
18
|
+
repoId: string;
|
|
19
|
+
repoName: string;
|
|
20
|
+
repoPath: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Repository status information
|
|
25
|
+
*/
|
|
26
|
+
export interface RepositoryStatus {
|
|
27
|
+
id: string;
|
|
28
|
+
name: string;
|
|
29
|
+
path: string;
|
|
30
|
+
branch: string;
|
|
31
|
+
ahead: number;
|
|
32
|
+
behind: number;
|
|
33
|
+
isValid: boolean;
|
|
34
|
+
error?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Aggregated multi-repository git status
|
|
39
|
+
*/
|
|
40
|
+
export interface MultiRepoGitStatus {
|
|
41
|
+
repositories: RepositoryStatus[];
|
|
42
|
+
staged: MultiRepoGitFile[];
|
|
43
|
+
unstaged: MultiRepoGitFile[];
|
|
44
|
+
untracked: MultiRepoGitFile[];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Get aggregated git status from multiple repositories
|
|
49
|
+
*/
|
|
50
|
+
export function getMultiRepoGitStatus(
|
|
51
|
+
repositories: ProjectRepository[],
|
|
52
|
+
fallbackPath?: string
|
|
53
|
+
): MultiRepoGitStatus {
|
|
54
|
+
const result: MultiRepoGitStatus = {
|
|
55
|
+
repositories: [],
|
|
56
|
+
staged: [],
|
|
57
|
+
unstaged: [],
|
|
58
|
+
untracked: [],
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
// If no repositories configured, use fallback path as single repo
|
|
62
|
+
const reposToCheck =
|
|
63
|
+
repositories.length > 0
|
|
64
|
+
? repositories
|
|
65
|
+
: fallbackPath
|
|
66
|
+
? [
|
|
67
|
+
{
|
|
68
|
+
id: "fallback",
|
|
69
|
+
project_id: "",
|
|
70
|
+
name: "Repository",
|
|
71
|
+
path: fallbackPath,
|
|
72
|
+
is_primary: true,
|
|
73
|
+
sort_order: 0,
|
|
74
|
+
},
|
|
75
|
+
]
|
|
76
|
+
: [];
|
|
77
|
+
|
|
78
|
+
for (const repo of reposToCheck) {
|
|
79
|
+
const expandedPath = expandPath(repo.path);
|
|
80
|
+
|
|
81
|
+
// Check if it's a valid git repo
|
|
82
|
+
if (!isGitRepo(expandedPath)) {
|
|
83
|
+
result.repositories.push({
|
|
84
|
+
id: repo.id,
|
|
85
|
+
name: repo.name,
|
|
86
|
+
path: repo.path,
|
|
87
|
+
branch: "",
|
|
88
|
+
ahead: 0,
|
|
89
|
+
behind: 0,
|
|
90
|
+
isValid: false,
|
|
91
|
+
error: "Not a git repository",
|
|
92
|
+
});
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
const status = getGitStatus(expandedPath);
|
|
98
|
+
|
|
99
|
+
// Add repository status
|
|
100
|
+
result.repositories.push({
|
|
101
|
+
id: repo.id,
|
|
102
|
+
name: repo.name,
|
|
103
|
+
path: repo.path,
|
|
104
|
+
branch: status.branch,
|
|
105
|
+
ahead: status.ahead,
|
|
106
|
+
behind: status.behind,
|
|
107
|
+
isValid: true,
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
// Add files with repo info
|
|
111
|
+
for (const file of status.staged) {
|
|
112
|
+
result.staged.push({
|
|
113
|
+
...file,
|
|
114
|
+
repoId: repo.id,
|
|
115
|
+
repoName: repo.name,
|
|
116
|
+
repoPath: repo.path,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
for (const file of status.unstaged) {
|
|
121
|
+
result.unstaged.push({
|
|
122
|
+
...file,
|
|
123
|
+
repoId: repo.id,
|
|
124
|
+
repoName: repo.name,
|
|
125
|
+
repoPath: repo.path,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
for (const file of status.untracked) {
|
|
130
|
+
result.untracked.push({
|
|
131
|
+
...file,
|
|
132
|
+
repoId: repo.id,
|
|
133
|
+
repoName: repo.name,
|
|
134
|
+
repoPath: repo.path,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
} catch (error) {
|
|
138
|
+
result.repositories.push({
|
|
139
|
+
id: repo.id,
|
|
140
|
+
name: repo.name,
|
|
141
|
+
path: repo.path,
|
|
142
|
+
branch: "",
|
|
143
|
+
ahead: 0,
|
|
144
|
+
behind: 0,
|
|
145
|
+
isValid: false,
|
|
146
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return result;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Group files by repository
|
|
156
|
+
*/
|
|
157
|
+
export function groupFilesByRepo(
|
|
158
|
+
files: MultiRepoGitFile[]
|
|
159
|
+
): Map<string, MultiRepoGitFile[]> {
|
|
160
|
+
const grouped = new Map<string, MultiRepoGitFile[]>();
|
|
161
|
+
|
|
162
|
+
for (const file of files) {
|
|
163
|
+
const existing = grouped.get(file.repoId) || [];
|
|
164
|
+
existing.push(file);
|
|
165
|
+
grouped.set(file.repoId, existing);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return grouped;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Get repositories with staged changes
|
|
173
|
+
*/
|
|
174
|
+
export function getReposWithStagedChanges(
|
|
175
|
+
status: MultiRepoGitStatus
|
|
176
|
+
): RepositoryStatus[] {
|
|
177
|
+
const repoIds = new Set(status.staged.map((f) => f.repoId));
|
|
178
|
+
return status.repositories.filter((r) => repoIds.has(r.id));
|
|
179
|
+
}
|