@atercates/claude-deck 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (293) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +123 -0
  3. package/app/api/claude/hidden/route.ts +66 -0
  4. package/app/api/claude/projects/[name]/sessions/route.ts +71 -0
  5. package/app/api/claude/projects/route.ts +44 -0
  6. package/app/api/code-search/available/route.ts +12 -0
  7. package/app/api/code-search/route.ts +47 -0
  8. package/app/api/dev-servers/[id]/logs/route.ts +23 -0
  9. package/app/api/dev-servers/[id]/restart/route.ts +20 -0
  10. package/app/api/dev-servers/[id]/route.ts +51 -0
  11. package/app/api/dev-servers/[id]/stop/route.ts +20 -0
  12. package/app/api/dev-servers/detect/route.ts +39 -0
  13. package/app/api/dev-servers/route.ts +48 -0
  14. package/app/api/exec/route.ts +60 -0
  15. package/app/api/files/content/route.ts +76 -0
  16. package/app/api/files/route.ts +37 -0
  17. package/app/api/files/upload-temp/route.ts +41 -0
  18. package/app/api/git/check/route.ts +54 -0
  19. package/app/api/git/clone/route.ts +99 -0
  20. package/app/api/git/commit/route.ts +75 -0
  21. package/app/api/git/discard/route.ts +38 -0
  22. package/app/api/git/file-content/route.ts +64 -0
  23. package/app/api/git/history/[hash]/diff/route.ts +38 -0
  24. package/app/api/git/history/[hash]/route.ts +34 -0
  25. package/app/api/git/history/route.ts +27 -0
  26. package/app/api/git/multi-status/route.ts +46 -0
  27. package/app/api/git/pr/route.ts +164 -0
  28. package/app/api/git/push/route.ts +64 -0
  29. package/app/api/git/stage/route.ts +40 -0
  30. package/app/api/git/status/route.ts +51 -0
  31. package/app/api/git/unstage/route.ts +46 -0
  32. package/app/api/groups/[...path]/route.ts +136 -0
  33. package/app/api/groups/route.ts +93 -0
  34. package/app/api/orchestrate/spawn/route.ts +45 -0
  35. package/app/api/orchestrate/workers/[id]/route.ts +89 -0
  36. package/app/api/orchestrate/workers/route.ts +31 -0
  37. package/app/api/projects/[id]/detect/route.ts +27 -0
  38. package/app/api/projects/[id]/dev-servers/[dsId]/route.ts +66 -0
  39. package/app/api/projects/[id]/dev-servers/route.ts +51 -0
  40. package/app/api/projects/[id]/repositories/[repoId]/route.ts +67 -0
  41. package/app/api/projects/[id]/repositories/route.ts +74 -0
  42. package/app/api/projects/[id]/route.ts +108 -0
  43. package/app/api/projects/detect/route.ts +33 -0
  44. package/app/api/projects/route.ts +59 -0
  45. package/app/api/sessions/[id]/claude-session/route.ts +42 -0
  46. package/app/api/sessions/[id]/fork/route.ts +74 -0
  47. package/app/api/sessions/[id]/mcp-config/route.ts +34 -0
  48. package/app/api/sessions/[id]/messages/route.ts +60 -0
  49. package/app/api/sessions/[id]/pr/route.ts +188 -0
  50. package/app/api/sessions/[id]/preview/route.ts +42 -0
  51. package/app/api/sessions/[id]/route.ts +229 -0
  52. package/app/api/sessions/[id]/send-keys/route.ts +119 -0
  53. package/app/api/sessions/[id]/summarize/route.ts +331 -0
  54. package/app/api/sessions/init-script/route.ts +84 -0
  55. package/app/api/sessions/route.ts +209 -0
  56. package/app/api/sessions/status/route.ts +237 -0
  57. package/app/api/system/route.ts +9 -0
  58. package/app/api/tmux/kill-all/route.ts +57 -0
  59. package/app/api/tmux/rename/route.ts +30 -0
  60. package/app/globals.css +174 -0
  61. package/app/icon.svg +11 -0
  62. package/app/layout.tsx +122 -0
  63. package/app/page.tsx +629 -0
  64. package/components/ChatMessage.tsx +65 -0
  65. package/components/ChatView.tsx +276 -0
  66. package/components/ClaudeProjects/ClaudeProjectCard.tsx +195 -0
  67. package/components/ClaudeProjects/ClaudeProjectsSection.tsx +89 -0
  68. package/components/ClaudeProjects/ClaudeSessionCard.tsx +100 -0
  69. package/components/ClaudeProjects/index.ts +1 -0
  70. package/components/CodeSearch/CodeSearchResults.tsx +177 -0
  71. package/components/ConductorPanel.tsx +256 -0
  72. package/components/DevServers/DevServerCard.tsx +311 -0
  73. package/components/DevServers/DevServersSection.tsx +91 -0
  74. package/components/DevServers/ServerLogsModal.tsx +151 -0
  75. package/components/DevServers/StartServerDialog.tsx +359 -0
  76. package/components/DevServers/index.ts +4 -0
  77. package/components/DiffViewer/DiffModal.tsx +151 -0
  78. package/components/DiffViewer/UnifiedDiff.tsx +185 -0
  79. package/components/DiffViewer/index.tsx +2 -0
  80. package/components/DirectoryPicker.tsx +355 -0
  81. package/components/FileExplorer/FileEditor.tsx +276 -0
  82. package/components/FileExplorer/FileTabs.tsx +118 -0
  83. package/components/FileExplorer/FileTree.tsx +214 -0
  84. package/components/FileExplorer/HtmlRenderer.tsx +16 -0
  85. package/components/FileExplorer/MarkdownRenderer.tsx +18 -0
  86. package/components/FileExplorer/index.tsx +520 -0
  87. package/components/FilePicker.tsx +339 -0
  88. package/components/FolderPicker.tsx +201 -0
  89. package/components/GitDrawer/FileEditDialog.tsx +400 -0
  90. package/components/GitDrawer/index.tsx +464 -0
  91. package/components/GitPanel/CommitForm.tsx +205 -0
  92. package/components/GitPanel/CommitHistory.tsx +174 -0
  93. package/components/GitPanel/CommitItem.tsx +196 -0
  94. package/components/GitPanel/FileChanges.tsx +414 -0
  95. package/components/GitPanel/GitPanelTabs.tsx +39 -0
  96. package/components/GitPanel/index.tsx +817 -0
  97. package/components/MessageInput.tsx +82 -0
  98. package/components/NewClaudeSessionDialog.tsx +166 -0
  99. package/components/NewSessionDialog/AdvancedSettings.tsx +78 -0
  100. package/components/NewSessionDialog/AgentSelector.tsx +37 -0
  101. package/components/NewSessionDialog/CreatingOverlay.tsx +94 -0
  102. package/components/NewSessionDialog/NewSessionDialog.types.ts +136 -0
  103. package/components/NewSessionDialog/ProjectSelector.tsx +146 -0
  104. package/components/NewSessionDialog/WorkingDirectoryInput.tsx +55 -0
  105. package/components/NewSessionDialog/WorktreeSection.tsx +92 -0
  106. package/components/NewSessionDialog/hooks/useNewSessionForm.ts +370 -0
  107. package/components/NewSessionDialog/index.tsx +106 -0
  108. package/components/NotificationSettings.tsx +127 -0
  109. package/components/PRCreationModal.tsx +272 -0
  110. package/components/Pane/DesktopTabBar.tsx +353 -0
  111. package/components/Pane/MobileTabBar.tsx +210 -0
  112. package/components/Pane/OpenInVSCode.tsx +69 -0
  113. package/components/Pane/PaneSkeletons.tsx +57 -0
  114. package/components/Pane/index.tsx +558 -0
  115. package/components/PaneLayout.tsx +60 -0
  116. package/components/Projects/DevServersSection.tsx +140 -0
  117. package/components/Projects/DirectoryField.tsx +92 -0
  118. package/components/Projects/NewProjectDialog.tsx +188 -0
  119. package/components/Projects/NewProjectDialog.types.ts +46 -0
  120. package/components/Projects/ProjectCard.tsx +276 -0
  121. package/components/Projects/ProjectSettingsDialog.tsx +811 -0
  122. package/components/Projects/hooks/useNewProjectForm.ts +249 -0
  123. package/components/Projects/index.ts +3 -0
  124. package/components/Providers.tsx +49 -0
  125. package/components/QuickSwitcher.tsx +306 -0
  126. package/components/SessionList/KillAllConfirm.tsx +46 -0
  127. package/components/SessionList/SelectionToolbar.tsx +164 -0
  128. package/components/SessionList/SessionList.types.ts +37 -0
  129. package/components/SessionList/SessionListHeader.tsx +71 -0
  130. package/components/SessionList/hooks/useSessionListMutations.ts +269 -0
  131. package/components/SessionList/index.tsx +189 -0
  132. package/components/ShellDrawer/index.tsx +106 -0
  133. package/components/SidebarFooter.tsx +55 -0
  134. package/components/Terminal/KeybarToggleButton.tsx +45 -0
  135. package/components/Terminal/ScrollToBottomButton.tsx +32 -0
  136. package/components/Terminal/SearchBar.tsx +71 -0
  137. package/components/Terminal/TerminalToolbar.tsx +551 -0
  138. package/components/Terminal/VirtualKeyboard.tsx +711 -0
  139. package/components/Terminal/constants.ts +20 -0
  140. package/components/Terminal/hooks/index.ts +5 -0
  141. package/components/Terminal/hooks/resize-handlers.ts +140 -0
  142. package/components/Terminal/hooks/terminal-init.ts +151 -0
  143. package/components/Terminal/hooks/touch-scroll.ts +155 -0
  144. package/components/Terminal/hooks/useTerminalConnection.ts +282 -0
  145. package/components/Terminal/hooks/useTerminalConnection.types.ts +39 -0
  146. package/components/Terminal/hooks/useTerminalSearch.ts +103 -0
  147. package/components/Terminal/hooks/websocket-connection.ts +274 -0
  148. package/components/Terminal/index.tsx +320 -0
  149. package/components/ThemeToggle.tsx +168 -0
  150. package/components/TmuxSessions.tsx +132 -0
  151. package/components/ToolCallDisplay.tsx +71 -0
  152. package/components/WorkerCard.tsx +245 -0
  153. package/components/a/ABadge.tsx +115 -0
  154. package/components/a/AButton.tsx +163 -0
  155. package/components/a/ADialog.tsx +93 -0
  156. package/components/a/ADropdownMenu.tsx +279 -0
  157. package/components/a/AIconButton.tsx +190 -0
  158. package/components/a/ASheet.tsx +150 -0
  159. package/components/a/ATooltip.tsx +77 -0
  160. package/components/a/index.ts +64 -0
  161. package/components/mobile/SwipeSidebar.tsx +122 -0
  162. package/components/ui/badge.tsx +41 -0
  163. package/components/ui/button.tsx +60 -0
  164. package/components/ui/context-menu.tsx +197 -0
  165. package/components/ui/dialog.tsx +143 -0
  166. package/components/ui/dropdown-menu.tsx +257 -0
  167. package/components/ui/input.tsx +21 -0
  168. package/components/ui/scroll-area.tsx +52 -0
  169. package/components/ui/select.tsx +159 -0
  170. package/components/ui/skeleton.tsx +111 -0
  171. package/components/ui/switch.tsx +31 -0
  172. package/components/ui/textarea.tsx +21 -0
  173. package/components/ui/tooltip.tsx +32 -0
  174. package/components/views/DesktopView.tsx +244 -0
  175. package/components/views/MobileView.tsx +110 -0
  176. package/components/views/types.ts +75 -0
  177. package/contexts/PaneContext.tsx +336 -0
  178. package/data/claude/index.ts +9 -0
  179. package/data/claude/keys.ts +6 -0
  180. package/data/claude/queries.ts +120 -0
  181. package/data/claude/useClaudeUpdates.ts +37 -0
  182. package/data/code-search/index.ts +2 -0
  183. package/data/code-search/keys.ts +7 -0
  184. package/data/code-search/queries.ts +61 -0
  185. package/data/dev-servers/index.ts +8 -0
  186. package/data/dev-servers/keys.ts +4 -0
  187. package/data/dev-servers/queries.ts +104 -0
  188. package/data/files/index.ts +3 -0
  189. package/data/files/keys.ts +4 -0
  190. package/data/files/queries.ts +25 -0
  191. package/data/git/keys.ts +15 -0
  192. package/data/git/queries.ts +395 -0
  193. package/data/groups/index.ts +1 -0
  194. package/data/groups/mutations.ts +95 -0
  195. package/data/projects/index.ts +10 -0
  196. package/data/projects/keys.ts +4 -0
  197. package/data/projects/queries.ts +193 -0
  198. package/data/repositories/index.ts +7 -0
  199. package/data/repositories/keys.ts +5 -0
  200. package/data/repositories/queries.ts +122 -0
  201. package/data/sessions/index.ts +12 -0
  202. package/data/sessions/keys.ts +8 -0
  203. package/data/sessions/queries.ts +218 -0
  204. package/data/statuses/index.ts +1 -0
  205. package/data/statuses/queries.ts +69 -0
  206. package/hooks/useCopyToClipboard.ts +48 -0
  207. package/hooks/useDevServersManager.ts +73 -0
  208. package/hooks/useDirectoryBrowser.ts +90 -0
  209. package/hooks/useDrawerAnimation.ts +27 -0
  210. package/hooks/useFileDrop.ts +87 -0
  211. package/hooks/useFileEditor.ts +184 -0
  212. package/hooks/useGroups.ts +37 -0
  213. package/hooks/useHomePath.ts +34 -0
  214. package/hooks/useKeyRepeat.ts +55 -0
  215. package/hooks/useKeybarVisibility.ts +42 -0
  216. package/hooks/useNotifications.ts +257 -0
  217. package/hooks/useProjects.ts +53 -0
  218. package/hooks/useSessionStatuses.ts +30 -0
  219. package/hooks/useSessions.ts +86 -0
  220. package/hooks/useSpeechRecognition.ts +124 -0
  221. package/hooks/useViewport.ts +32 -0
  222. package/hooks/useViewportHeight.ts +50 -0
  223. package/lib/async-operations.ts +35 -0
  224. package/lib/banner.ts +81 -0
  225. package/lib/claude/jsonl-cache.ts +86 -0
  226. package/lib/claude/jsonl-reader.ts +271 -0
  227. package/lib/claude/process-manager.ts +278 -0
  228. package/lib/claude/stream-parser.ts +173 -0
  229. package/lib/claude/types.ts +154 -0
  230. package/lib/claude/watcher.ts +71 -0
  231. package/lib/client/session-registry.ts +111 -0
  232. package/lib/code-search.ts +121 -0
  233. package/lib/db/index.ts +48 -0
  234. package/lib/db/migrations.ts +45 -0
  235. package/lib/db/queries.ts +460 -0
  236. package/lib/db/schema.ts +114 -0
  237. package/lib/db/types.ts +92 -0
  238. package/lib/db.ts +2 -0
  239. package/lib/dev-servers.ts +509 -0
  240. package/lib/diff-parser.ts +221 -0
  241. package/lib/env-setup.ts +285 -0
  242. package/lib/file-upload.ts +34 -0
  243. package/lib/file-utils.ts +50 -0
  244. package/lib/files.ts +207 -0
  245. package/lib/git-history.ts +294 -0
  246. package/lib/git-status.ts +391 -0
  247. package/lib/git.ts +257 -0
  248. package/lib/mcp-config.ts +81 -0
  249. package/lib/multi-repo-git.ts +179 -0
  250. package/lib/notifications.ts +219 -0
  251. package/lib/orchestration.ts +448 -0
  252. package/lib/panes.ts +232 -0
  253. package/lib/ports.ts +97 -0
  254. package/lib/pr-generation.ts +307 -0
  255. package/lib/pr.ts +234 -0
  256. package/lib/projects.ts +578 -0
  257. package/lib/providers/registry.ts +70 -0
  258. package/lib/providers.ts +121 -0
  259. package/lib/query-client.ts +14 -0
  260. package/lib/rangeSelectionUtils.ts +65 -0
  261. package/lib/status-detector.ts +375 -0
  262. package/lib/terminal-themes.ts +265 -0
  263. package/lib/theme-config.ts +327 -0
  264. package/lib/utils.ts +6 -0
  265. package/lib/worktrees.ts +262 -0
  266. package/mcp/orchestration-server.ts +438 -0
  267. package/package.json +139 -0
  268. package/postcss.config.mjs +7 -0
  269. package/public/icon.svg +10 -0
  270. package/public/icons/icon-128x128.png +0 -0
  271. package/public/icons/icon-144x144.png +0 -0
  272. package/public/icons/icon-152x152.png +0 -0
  273. package/public/icons/icon-192x192.png +0 -0
  274. package/public/icons/icon-384x384.png +0 -0
  275. package/public/icons/icon-512x512.png +0 -0
  276. package/public/icons/icon-72x72.png +0 -0
  277. package/public/icons/icon-96x96.png +0 -0
  278. package/public/manifest.json +61 -0
  279. package/public/sw.js +64 -0
  280. package/scripts/agent-os +91 -0
  281. package/scripts/install.sh +48 -0
  282. package/scripts/lib/ai-clis.sh +132 -0
  283. package/scripts/lib/commands.sh +487 -0
  284. package/scripts/lib/common.sh +89 -0
  285. package/scripts/lib/prerequisites.sh +462 -0
  286. package/scripts/setup.sh +134 -0
  287. package/server.ts +155 -0
  288. package/stores/fileOpen.ts +26 -0
  289. package/stores/index.ts +1 -0
  290. package/stores/initialPrompt.ts +24 -0
  291. package/stores/sessionSelection.ts +48 -0
  292. package/styles/themes.css +603 -0
  293. package/tsconfig.json +33 -0
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
+ }