@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/env-setup.ts
ADDED
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Environment Setup for Worktrees
|
|
3
|
+
*
|
|
4
|
+
* Handles copying env files, installing dependencies, and running setup scripts
|
|
5
|
+
* when creating new worktrees.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { exec } from "child_process";
|
|
9
|
+
import { promisify } from "util";
|
|
10
|
+
import * as fs from "fs";
|
|
11
|
+
import * as path from "path";
|
|
12
|
+
|
|
13
|
+
const execAsync = promisify(exec);
|
|
14
|
+
|
|
15
|
+
export interface WorktreeConfig {
|
|
16
|
+
setup?: string[];
|
|
17
|
+
devServer?: {
|
|
18
|
+
command: string;
|
|
19
|
+
portEnvVar?: string;
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface SetupResult {
|
|
24
|
+
success: boolean;
|
|
25
|
+
steps: Array<{
|
|
26
|
+
name: string;
|
|
27
|
+
command: string;
|
|
28
|
+
success: boolean;
|
|
29
|
+
output?: string;
|
|
30
|
+
error?: string;
|
|
31
|
+
}>;
|
|
32
|
+
envFilesCopied: string[];
|
|
33
|
+
packageManager?: string;
|
|
34
|
+
port?: number;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Read worktree config from project
|
|
39
|
+
*/
|
|
40
|
+
export async function readWorktreeConfig(
|
|
41
|
+
projectPath: string
|
|
42
|
+
): Promise<WorktreeConfig | null> {
|
|
43
|
+
const configPaths = [
|
|
44
|
+
path.join(projectPath, ".claude-deck", "worktrees.json"),
|
|
45
|
+
path.join(projectPath, ".claude-deck.json"),
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
for (const configPath of configPaths) {
|
|
49
|
+
try {
|
|
50
|
+
if (fs.existsSync(configPath)) {
|
|
51
|
+
const content = await fs.promises.readFile(configPath, "utf-8");
|
|
52
|
+
return JSON.parse(content);
|
|
53
|
+
}
|
|
54
|
+
} catch {
|
|
55
|
+
// Continue to next path
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Detect package manager from lockfiles
|
|
64
|
+
*/
|
|
65
|
+
export function detectPackageManager(projectPath: string): {
|
|
66
|
+
name: string;
|
|
67
|
+
installCommand: string;
|
|
68
|
+
} | null {
|
|
69
|
+
const lockfiles = [
|
|
70
|
+
{ file: "bun.lockb", name: "bun", command: "bun install" },
|
|
71
|
+
{ file: "pnpm-lock.yaml", name: "pnpm", command: "pnpm install" },
|
|
72
|
+
{ file: "yarn.lock", name: "yarn", command: "yarn install" },
|
|
73
|
+
{ file: "package-lock.json", name: "npm", command: "npm install" },
|
|
74
|
+
];
|
|
75
|
+
|
|
76
|
+
for (const { file, name, command } of lockfiles) {
|
|
77
|
+
if (fs.existsSync(path.join(projectPath, file))) {
|
|
78
|
+
return { name, installCommand: command };
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Fallback: check if package.json exists
|
|
83
|
+
if (fs.existsSync(path.join(projectPath, "package.json"))) {
|
|
84
|
+
return { name: "npm", installCommand: "npm install" };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Find env files to copy (excludes .env.example)
|
|
92
|
+
*/
|
|
93
|
+
export function findEnvFiles(projectPath: string): string[] {
|
|
94
|
+
try {
|
|
95
|
+
const files = fs.readdirSync(projectPath);
|
|
96
|
+
return files.filter(
|
|
97
|
+
(f) =>
|
|
98
|
+
f.startsWith(".env") &&
|
|
99
|
+
!f.endsWith(".example") &&
|
|
100
|
+
fs.statSync(path.join(projectPath, f)).isFile()
|
|
101
|
+
);
|
|
102
|
+
} catch {
|
|
103
|
+
return [];
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Copy env files from source to worktree
|
|
109
|
+
*/
|
|
110
|
+
export async function copyEnvFiles(
|
|
111
|
+
sourcePath: string,
|
|
112
|
+
worktreePath: string
|
|
113
|
+
): Promise<string[]> {
|
|
114
|
+
const envFiles = findEnvFiles(sourcePath);
|
|
115
|
+
const copied: string[] = [];
|
|
116
|
+
|
|
117
|
+
for (const file of envFiles) {
|
|
118
|
+
try {
|
|
119
|
+
const src = path.join(sourcePath, file);
|
|
120
|
+
const dest = path.join(worktreePath, file);
|
|
121
|
+
await fs.promises.copyFile(src, dest);
|
|
122
|
+
copied.push(file);
|
|
123
|
+
} catch (error) {
|
|
124
|
+
console.error(`Failed to copy ${file}:`, error);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return copied;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Run a setup command in the worktree directory
|
|
133
|
+
*/
|
|
134
|
+
async function runCommand(
|
|
135
|
+
command: string,
|
|
136
|
+
cwd: string,
|
|
137
|
+
env: Record<string, string> = {}
|
|
138
|
+
): Promise<{ success: boolean; output: string; error?: string }> {
|
|
139
|
+
try {
|
|
140
|
+
const { stdout, stderr } = await execAsync(command, {
|
|
141
|
+
cwd,
|
|
142
|
+
timeout: 300000, // 5 minutes
|
|
143
|
+
env: { ...process.env, ...env },
|
|
144
|
+
});
|
|
145
|
+
return {
|
|
146
|
+
success: true,
|
|
147
|
+
output: stdout + (stderr ? `\n${stderr}` : ""),
|
|
148
|
+
};
|
|
149
|
+
} catch (error: unknown) {
|
|
150
|
+
const err = error as { stdout?: string; stderr?: string; message?: string };
|
|
151
|
+
return {
|
|
152
|
+
success: false,
|
|
153
|
+
output: err.stdout || "",
|
|
154
|
+
error: err.stderr || err.message || "Unknown error",
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Run setup for a new worktree
|
|
161
|
+
*/
|
|
162
|
+
export async function setupWorktree(options: {
|
|
163
|
+
worktreePath: string;
|
|
164
|
+
sourcePath: string;
|
|
165
|
+
port?: number;
|
|
166
|
+
skipInstall?: boolean;
|
|
167
|
+
}): Promise<SetupResult> {
|
|
168
|
+
const { worktreePath, sourcePath, port, skipInstall } = options;
|
|
169
|
+
|
|
170
|
+
const result: SetupResult = {
|
|
171
|
+
success: true,
|
|
172
|
+
steps: [],
|
|
173
|
+
envFilesCopied: [],
|
|
174
|
+
port,
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
// 1. Read config if exists
|
|
178
|
+
const config = await readWorktreeConfig(sourcePath);
|
|
179
|
+
|
|
180
|
+
// 2. Copy env files
|
|
181
|
+
result.envFilesCopied = await copyEnvFiles(sourcePath, worktreePath);
|
|
182
|
+
if (result.envFilesCopied.length > 0) {
|
|
183
|
+
result.steps.push({
|
|
184
|
+
name: "Copy env files",
|
|
185
|
+
command: `cp ${result.envFilesCopied.join(" ")}`,
|
|
186
|
+
success: true,
|
|
187
|
+
output: `Copied: ${result.envFilesCopied.join(", ")}`,
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Build env vars for commands
|
|
192
|
+
const envVars: Record<string, string> = {
|
|
193
|
+
ROOT_WORKTREE_PATH: sourcePath,
|
|
194
|
+
WORKTREE_PATH: worktreePath,
|
|
195
|
+
};
|
|
196
|
+
if (port) {
|
|
197
|
+
envVars.PORT = String(port);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// 3. Run config setup commands if present
|
|
201
|
+
if (config?.setup && config.setup.length > 0) {
|
|
202
|
+
for (const cmd of config.setup) {
|
|
203
|
+
// Expand variables in command
|
|
204
|
+
let expandedCmd = cmd;
|
|
205
|
+
for (const [key, value] of Object.entries(envVars)) {
|
|
206
|
+
expandedCmd = expandedCmd.replace(new RegExp(`\\$${key}`, "g"), value);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const cmdResult = await runCommand(expandedCmd, worktreePath, envVars);
|
|
210
|
+
result.steps.push({
|
|
211
|
+
name: `Config: ${cmd.slice(0, 50)}${cmd.length > 50 ? "..." : ""}`,
|
|
212
|
+
command: expandedCmd,
|
|
213
|
+
success: cmdResult.success,
|
|
214
|
+
output: cmdResult.output,
|
|
215
|
+
error: cmdResult.error,
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
if (!cmdResult.success) {
|
|
219
|
+
result.success = false;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
} else if (!skipInstall) {
|
|
223
|
+
// 4. Auto-detect and install dependencies
|
|
224
|
+
const pm = detectPackageManager(sourcePath);
|
|
225
|
+
if (pm) {
|
|
226
|
+
result.packageManager = pm.name;
|
|
227
|
+
const installResult = await runCommand(
|
|
228
|
+
pm.installCommand,
|
|
229
|
+
worktreePath,
|
|
230
|
+
envVars
|
|
231
|
+
);
|
|
232
|
+
result.steps.push({
|
|
233
|
+
name: `Install dependencies (${pm.name})`,
|
|
234
|
+
command: pm.installCommand,
|
|
235
|
+
success: installResult.success,
|
|
236
|
+
output: installResult.output,
|
|
237
|
+
error: installResult.error,
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
if (!installResult.success) {
|
|
241
|
+
result.success = false;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return result;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Get dev server command from config or package.json
|
|
251
|
+
*/
|
|
252
|
+
export async function getDevServerCommand(
|
|
253
|
+
projectPath: string,
|
|
254
|
+
port?: number
|
|
255
|
+
): Promise<{ command: string; port: number } | null> {
|
|
256
|
+
// Check config first
|
|
257
|
+
const config = await readWorktreeConfig(projectPath);
|
|
258
|
+
if (config?.devServer) {
|
|
259
|
+
const portEnvVar = config.devServer.portEnvVar || "PORT";
|
|
260
|
+
const finalPort = port || 3000;
|
|
261
|
+
return {
|
|
262
|
+
command: `${portEnvVar}=${finalPort} ${config.devServer.command}`,
|
|
263
|
+
port: finalPort,
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Check package.json for dev script
|
|
268
|
+
const pkgPath = path.join(projectPath, "package.json");
|
|
269
|
+
if (fs.existsSync(pkgPath)) {
|
|
270
|
+
try {
|
|
271
|
+
const pkg = JSON.parse(await fs.promises.readFile(pkgPath, "utf-8"));
|
|
272
|
+
if (pkg.scripts?.dev) {
|
|
273
|
+
const finalPort = port || 3000;
|
|
274
|
+
return {
|
|
275
|
+
command: `PORT=${finalPort} npm run dev`,
|
|
276
|
+
port: finalPort,
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
} catch {
|
|
280
|
+
// Ignore parse errors
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return null;
|
|
285
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Upload a file to temporary storage and return its path.
|
|
3
|
+
* Converts the file to base64 and POSTs to /api/files/upload-temp.
|
|
4
|
+
*
|
|
5
|
+
* @param file - The file to upload
|
|
6
|
+
* @returns The path to the uploaded file, or null if upload failed
|
|
7
|
+
*/
|
|
8
|
+
export async function uploadFileToTemp(file: File): Promise<string | null> {
|
|
9
|
+
const buffer = await file.arrayBuffer();
|
|
10
|
+
const base64 = btoa(
|
|
11
|
+
new Uint8Array(buffer).reduce(
|
|
12
|
+
(data, byte) => data + String.fromCharCode(byte),
|
|
13
|
+
""
|
|
14
|
+
)
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
const res = await fetch("/api/files/upload-temp", {
|
|
18
|
+
method: "POST",
|
|
19
|
+
headers: { "Content-Type": "application/json" },
|
|
20
|
+
body: JSON.stringify({
|
|
21
|
+
filename: file.name || `file-${Date.now()}`,
|
|
22
|
+
base64,
|
|
23
|
+
mimeType: file.type || "application/octet-stream",
|
|
24
|
+
}),
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const data = await res.json();
|
|
28
|
+
if (data.path) {
|
|
29
|
+
return data.path;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
console.error("Upload failed:", data.error);
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client-safe file utilities (no Node.js dependencies)
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export interface FileNode {
|
|
6
|
+
name: string;
|
|
7
|
+
path: string;
|
|
8
|
+
type: "file" | "directory";
|
|
9
|
+
size?: number;
|
|
10
|
+
extension?: string;
|
|
11
|
+
children?: FileNode[];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Get file extension for syntax highlighting
|
|
16
|
+
*/
|
|
17
|
+
export function getLanguageFromExtension(ext: string): string {
|
|
18
|
+
const languageMap: Record<string, string> = {
|
|
19
|
+
js: "javascript",
|
|
20
|
+
jsx: "javascript",
|
|
21
|
+
ts: "typescript",
|
|
22
|
+
tsx: "typescript",
|
|
23
|
+
py: "python",
|
|
24
|
+
rb: "ruby",
|
|
25
|
+
go: "go",
|
|
26
|
+
rs: "rust",
|
|
27
|
+
java: "java",
|
|
28
|
+
c: "c",
|
|
29
|
+
cpp: "cpp",
|
|
30
|
+
cs: "csharp",
|
|
31
|
+
php: "php",
|
|
32
|
+
html: "html",
|
|
33
|
+
css: "css",
|
|
34
|
+
scss: "scss",
|
|
35
|
+
json: "json",
|
|
36
|
+
xml: "xml",
|
|
37
|
+
yaml: "yaml",
|
|
38
|
+
yml: "yaml",
|
|
39
|
+
md: "markdown",
|
|
40
|
+
sh: "bash",
|
|
41
|
+
bash: "bash",
|
|
42
|
+
zsh: "bash",
|
|
43
|
+
sql: "sql",
|
|
44
|
+
graphql: "graphql",
|
|
45
|
+
vue: "vue",
|
|
46
|
+
svelte: "svelte",
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
return languageMap[ext.toLowerCase()] || "plaintext";
|
|
50
|
+
}
|
package/lib/files.ts
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File system utilities for file explorer (server-only)
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { readdirSync, statSync, readFileSync, writeFileSync } from "fs";
|
|
6
|
+
import { join, extname } from "path";
|
|
7
|
+
|
|
8
|
+
// Re-export client-safe types and utilities
|
|
9
|
+
export type { FileNode } from "./file-utils";
|
|
10
|
+
export { getLanguageFromExtension } from "./file-utils";
|
|
11
|
+
import type { FileNode } from "./file-utils";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Default exclude patterns (matches common ignore patterns)
|
|
15
|
+
*/
|
|
16
|
+
const DEFAULT_EXCLUDES = [
|
|
17
|
+
"node_modules",
|
|
18
|
+
".git",
|
|
19
|
+
".next",
|
|
20
|
+
"dist",
|
|
21
|
+
"build",
|
|
22
|
+
"out",
|
|
23
|
+
"coverage",
|
|
24
|
+
".cache",
|
|
25
|
+
".vercel",
|
|
26
|
+
".turbo",
|
|
27
|
+
"__pycache__",
|
|
28
|
+
".pytest_cache",
|
|
29
|
+
".mypy_cache",
|
|
30
|
+
".venv",
|
|
31
|
+
"venv",
|
|
32
|
+
".DS_Store",
|
|
33
|
+
"*.log",
|
|
34
|
+
".env",
|
|
35
|
+
".env.local",
|
|
36
|
+
".env.*.local",
|
|
37
|
+
"*.db",
|
|
38
|
+
"*.db-wal",
|
|
39
|
+
"*.db-shm",
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Check if a file/directory should be excluded
|
|
44
|
+
*/
|
|
45
|
+
function shouldExclude(name: string, excludePatterns: string[]): boolean {
|
|
46
|
+
return excludePatterns.some((pattern) => {
|
|
47
|
+
if (pattern.includes("*")) {
|
|
48
|
+
// Simple glob pattern matching
|
|
49
|
+
const regex = new RegExp("^" + pattern.replace(/\*/g, ".*") + "$");
|
|
50
|
+
return regex.test(name);
|
|
51
|
+
}
|
|
52
|
+
return name === pattern;
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* List directory contents with exclusions
|
|
58
|
+
*/
|
|
59
|
+
export function listDirectory(
|
|
60
|
+
dirPath: string,
|
|
61
|
+
options: {
|
|
62
|
+
excludePatterns?: string[];
|
|
63
|
+
recursive?: boolean;
|
|
64
|
+
maxDepth?: number;
|
|
65
|
+
currentDepth?: number;
|
|
66
|
+
} = {}
|
|
67
|
+
): FileNode[] {
|
|
68
|
+
const {
|
|
69
|
+
excludePatterns = DEFAULT_EXCLUDES,
|
|
70
|
+
recursive = false,
|
|
71
|
+
maxDepth = 3,
|
|
72
|
+
currentDepth = 0,
|
|
73
|
+
} = options;
|
|
74
|
+
|
|
75
|
+
try {
|
|
76
|
+
const entries = readdirSync(dirPath);
|
|
77
|
+
const nodes: FileNode[] = [];
|
|
78
|
+
|
|
79
|
+
for (const entry of entries) {
|
|
80
|
+
// Skip excluded files
|
|
81
|
+
if (shouldExclude(entry, excludePatterns)) {
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const fullPath = join(dirPath, entry);
|
|
86
|
+
let stat;
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
stat = statSync(fullPath);
|
|
90
|
+
} catch {
|
|
91
|
+
// Permission denied or other error, skip
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const isDir = stat.isDirectory();
|
|
96
|
+
const node: FileNode = {
|
|
97
|
+
name: entry,
|
|
98
|
+
path: fullPath,
|
|
99
|
+
type: isDir ? "directory" : "file",
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
if (!isDir) {
|
|
103
|
+
node.size = stat.size;
|
|
104
|
+
node.extension = extname(entry).slice(1); // Remove leading dot
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Recursively load children if requested and not at max depth
|
|
108
|
+
if (isDir && recursive && currentDepth < maxDepth) {
|
|
109
|
+
node.children = listDirectory(fullPath, {
|
|
110
|
+
excludePatterns,
|
|
111
|
+
recursive: true,
|
|
112
|
+
maxDepth,
|
|
113
|
+
currentDepth: currentDepth + 1,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
nodes.push(node);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Sort: directories first, then files, alphabetically within each group
|
|
121
|
+
return nodes.sort((a, b) => {
|
|
122
|
+
if (a.type !== b.type) {
|
|
123
|
+
return a.type === "directory" ? -1 : 1;
|
|
124
|
+
}
|
|
125
|
+
return a.name.localeCompare(b.name);
|
|
126
|
+
});
|
|
127
|
+
} catch (error) {
|
|
128
|
+
console.error(`Failed to list directory ${dirPath}:`, error);
|
|
129
|
+
return [];
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Read file contents with encoding detection
|
|
135
|
+
*/
|
|
136
|
+
export function readFileContent(
|
|
137
|
+
filePath: string,
|
|
138
|
+
options: { maxSize?: number } = {}
|
|
139
|
+
): { content: string; isBinary: boolean; size: number } {
|
|
140
|
+
const { maxSize = 1024 * 1024 } = options; // Default 1MB max
|
|
141
|
+
|
|
142
|
+
try {
|
|
143
|
+
const stat = statSync(filePath);
|
|
144
|
+
|
|
145
|
+
if (stat.size > maxSize) {
|
|
146
|
+
return {
|
|
147
|
+
content: `File too large (${(stat.size / 1024 / 1024).toFixed(2)}MB). Maximum size: ${(maxSize / 1024 / 1024).toFixed(2)}MB`,
|
|
148
|
+
isBinary: false,
|
|
149
|
+
size: stat.size,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const buffer = readFileSync(filePath);
|
|
154
|
+
|
|
155
|
+
// Simple binary detection: check for null bytes
|
|
156
|
+
const isBinary = buffer.includes(0);
|
|
157
|
+
|
|
158
|
+
if (isBinary) {
|
|
159
|
+
return {
|
|
160
|
+
content: `Binary file (${(stat.size / 1024).toFixed(2)}KB)`,
|
|
161
|
+
isBinary: true,
|
|
162
|
+
size: stat.size,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
content: buffer.toString("utf-8"),
|
|
168
|
+
isBinary: false,
|
|
169
|
+
size: stat.size,
|
|
170
|
+
};
|
|
171
|
+
} catch (error) {
|
|
172
|
+
throw new Error(
|
|
173
|
+
`Failed to read file: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Write content to a file
|
|
180
|
+
*/
|
|
181
|
+
export function writeFileContent(
|
|
182
|
+
filePath: string,
|
|
183
|
+
content: string,
|
|
184
|
+
options: { maxSize?: number } = {}
|
|
185
|
+
): { success: boolean; size: number } {
|
|
186
|
+
const { maxSize = 1024 * 1024 } = options; // Default 1MB max
|
|
187
|
+
|
|
188
|
+
const contentBuffer = Buffer.from(content, "utf-8");
|
|
189
|
+
|
|
190
|
+
if (contentBuffer.length > maxSize) {
|
|
191
|
+
throw new Error(
|
|
192
|
+
`Content too large (${(contentBuffer.length / 1024 / 1024).toFixed(2)}MB). Maximum size: ${(maxSize / 1024 / 1024).toFixed(2)}MB`
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
try {
|
|
197
|
+
writeFileSync(filePath, content, "utf-8");
|
|
198
|
+
return {
|
|
199
|
+
success: true,
|
|
200
|
+
size: contentBuffer.length,
|
|
201
|
+
};
|
|
202
|
+
} catch (error) {
|
|
203
|
+
throw new Error(
|
|
204
|
+
`Failed to write file: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
}
|