code-squad-cli 2.0.0 → 2.1.0
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/dist/index.js +372 -213
- package/dist/tui/App.d.ts +1 -0
- package/dist/tui/App.js +124 -0
- package/package.json +7 -6
- package/dist/flip-ui/dist/assets/index-DYY1gRRa.css +0 -1
- package/dist/flip-ui/dist/assets/index-KAtdqB2p.js +0 -217
- package/dist/flip-ui/dist/index.html +0 -13
package/dist/index.js
CHANGED
|
@@ -1,155 +1,384 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __esm = (fn, res) => function __init() {
|
|
5
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
6
|
+
};
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
6
11
|
|
|
7
12
|
// dist/adapters/GitAdapter.js
|
|
8
13
|
import { exec as execCallback } from "child_process";
|
|
9
14
|
import { promisify } from "util";
|
|
10
15
|
import * as fs from "fs";
|
|
11
|
-
var exec
|
|
12
|
-
var
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
return stdout.trim();
|
|
26
|
-
} catch {
|
|
27
|
-
try {
|
|
28
|
-
const { stdout } = await exec(`cd "${workspaceRoot}" && git symbolic-ref --short HEAD`, execOptions);
|
|
29
|
-
return stdout.trim();
|
|
30
|
-
} catch {
|
|
31
|
-
return "";
|
|
16
|
+
var exec, execOptions, GitAdapter;
|
|
17
|
+
var init_GitAdapter = __esm({
|
|
18
|
+
"dist/adapters/GitAdapter.js"() {
|
|
19
|
+
"use strict";
|
|
20
|
+
exec = promisify(execCallback);
|
|
21
|
+
execOptions = { maxBuffer: 1024 * 1024 };
|
|
22
|
+
GitAdapter = class {
|
|
23
|
+
async isGitRepository(workspaceRoot) {
|
|
24
|
+
try {
|
|
25
|
+
await exec(`cd "${workspaceRoot}" && git rev-parse --git-dir`, execOptions);
|
|
26
|
+
return true;
|
|
27
|
+
} catch {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
32
30
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const branchLine = lines[i + 2];
|
|
45
|
-
if (!worktreeLine || !headLine) {
|
|
46
|
-
i++;
|
|
47
|
-
continue;
|
|
31
|
+
async getCurrentBranch(workspaceRoot) {
|
|
32
|
+
try {
|
|
33
|
+
const { stdout } = await exec(`cd "${workspaceRoot}" && git rev-parse --abbrev-ref HEAD`, execOptions);
|
|
34
|
+
return stdout.trim();
|
|
35
|
+
} catch {
|
|
36
|
+
try {
|
|
37
|
+
const { stdout } = await exec(`cd "${workspaceRoot}" && git symbolic-ref --short HEAD`, execOptions);
|
|
38
|
+
return stdout.trim();
|
|
39
|
+
} catch {
|
|
40
|
+
return "";
|
|
41
|
+
}
|
|
48
42
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
const
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
43
|
+
}
|
|
44
|
+
async listWorktrees(workspaceRoot) {
|
|
45
|
+
try {
|
|
46
|
+
const { stdout } = await exec(`cd "${workspaceRoot}" && git worktree list --porcelain`, execOptions);
|
|
47
|
+
const worktrees = [];
|
|
48
|
+
const lines = stdout.split("\n").filter((line) => line.trim());
|
|
49
|
+
let i = 0;
|
|
50
|
+
while (i < lines.length) {
|
|
51
|
+
const worktreeLine = lines[i];
|
|
52
|
+
const headLine = lines[i + 1];
|
|
53
|
+
const branchLine = lines[i + 2];
|
|
54
|
+
if (!worktreeLine || !headLine) {
|
|
55
|
+
i++;
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
const pathMatch = worktreeLine.match(/^worktree (.+)$/);
|
|
59
|
+
const headMatch = headLine.match(/^HEAD (.+)$/);
|
|
60
|
+
const branchMatch = branchLine?.match(/^branch refs\/heads\/(.+)$/);
|
|
61
|
+
if (pathMatch && headMatch) {
|
|
62
|
+
const path13 = pathMatch[1];
|
|
63
|
+
const head = headMatch[1];
|
|
64
|
+
const branch = branchMatch ? branchMatch[1] : "HEAD";
|
|
65
|
+
if (path13 !== workspaceRoot) {
|
|
66
|
+
worktrees.push({ path: path13, branch, head });
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
i += 3;
|
|
58
70
|
}
|
|
71
|
+
return worktrees;
|
|
72
|
+
} catch {
|
|
73
|
+
return [];
|
|
59
74
|
}
|
|
60
|
-
i += 3;
|
|
61
75
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
76
|
+
async createWorktree(worktreePath, branch, workspaceRoot) {
|
|
77
|
+
await exec(`cd "${workspaceRoot}" && git worktree prune`, execOptions).catch(() => {
|
|
78
|
+
});
|
|
79
|
+
const parentDir = worktreePath.substring(0, worktreePath.lastIndexOf("/"));
|
|
80
|
+
const mkdirCmd = parentDir ? `mkdir -p "${parentDir}" && ` : "";
|
|
81
|
+
await exec(`cd "${workspaceRoot}" && ${mkdirCmd}git worktree add -f "${worktreePath}" -b "${branch}"`, execOptions);
|
|
82
|
+
}
|
|
83
|
+
async removeWorktree(worktreePath, workspaceRoot, force = false) {
|
|
84
|
+
const forceFlag = force ? " --force" : "";
|
|
85
|
+
try {
|
|
86
|
+
await exec(`cd "${workspaceRoot}" && git worktree remove "${worktreePath}"${forceFlag}`, execOptions);
|
|
87
|
+
} catch (error) {
|
|
88
|
+
throw new Error(`Failed to remove worktree: ${error.message}`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
async deleteBranch(branchName, workspaceRoot, force = false) {
|
|
92
|
+
const deleteFlag = force ? "-D" : "-d";
|
|
93
|
+
try {
|
|
94
|
+
await exec(`cd "${workspaceRoot}" && git branch ${deleteFlag} "${branchName}"`, execOptions);
|
|
95
|
+
} catch (error) {
|
|
96
|
+
throw new Error(`Failed to delete branch: ${error.message}`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
async isValidWorktree(path13, workspaceRoot) {
|
|
100
|
+
try {
|
|
101
|
+
await fs.promises.access(path13, fs.constants.R_OK);
|
|
102
|
+
} catch {
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
try {
|
|
106
|
+
await exec(`cd "${path13}" && git rev-parse --git-dir`, execOptions);
|
|
107
|
+
} catch {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
const worktrees = await this.listWorktrees(workspaceRoot);
|
|
111
|
+
return worktrees.some((wt) => wt.path === path13);
|
|
112
|
+
}
|
|
113
|
+
async getWorktreeBranch(worktreePath) {
|
|
114
|
+
try {
|
|
115
|
+
const { stdout } = await exec(`cd "${worktreePath}" && git rev-parse --abbrev-ref HEAD`, execOptions);
|
|
116
|
+
return stdout.trim();
|
|
117
|
+
} catch (error) {
|
|
118
|
+
throw new Error(`Failed to get branch name: ${error.message}`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* 현재 디렉토리가 워크트리인지 확인하고 컨텍스트 반환
|
|
123
|
+
*/
|
|
124
|
+
async getWorktreeContext(cwd) {
|
|
125
|
+
let commonDir = null;
|
|
126
|
+
try {
|
|
127
|
+
const { stdout } = await exec(`cd "${cwd}" && git rev-parse --git-common-dir`, execOptions);
|
|
128
|
+
commonDir = stdout.trim();
|
|
129
|
+
} catch {
|
|
130
|
+
return { isWorktree: false, mainRoot: null, currentPath: cwd, branch: null };
|
|
131
|
+
}
|
|
132
|
+
const isWorktree = commonDir !== ".git";
|
|
133
|
+
if (!isWorktree) {
|
|
134
|
+
return { isWorktree: false, mainRoot: cwd, currentPath: cwd, branch: null };
|
|
135
|
+
}
|
|
136
|
+
let mainRoot = null;
|
|
137
|
+
try {
|
|
138
|
+
const { stdout } = await exec(`cd "${cwd}" && git worktree list --porcelain`, execOptions);
|
|
139
|
+
const match = stdout.match(/^worktree (.+)$/m);
|
|
140
|
+
mainRoot = match ? match[1] : null;
|
|
141
|
+
} catch {
|
|
142
|
+
}
|
|
143
|
+
const branch = await this.getWorktreeBranch(cwd).catch(() => null);
|
|
144
|
+
return { isWorktree, mainRoot, currentPath: cwd, branch };
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* staged 또는 unstaged 변경사항이 있는지 확인 (untracked 제외)
|
|
148
|
+
*/
|
|
149
|
+
async hasDirtyState(workspacePath) {
|
|
150
|
+
try {
|
|
151
|
+
const { stdout } = await exec(`cd "${workspacePath}" && git status --porcelain`, execOptions);
|
|
152
|
+
const lines = stdout.split("\n").filter((line) => line.trim());
|
|
153
|
+
const dirtyLines = lines.filter((line) => !line.startsWith("??"));
|
|
154
|
+
return dirtyLines.length > 0;
|
|
155
|
+
} catch {
|
|
156
|
+
return true;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
};
|
|
73
160
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
// dist/config.js
|
|
164
|
+
import * as fs9 from "fs";
|
|
165
|
+
import * as os4 from "os";
|
|
166
|
+
import * as path9 from "path";
|
|
167
|
+
async function loadGlobalConfig() {
|
|
168
|
+
try {
|
|
169
|
+
const content = await fs9.promises.readFile(GLOBAL_CONFIG_PATH, "utf-8");
|
|
170
|
+
return JSON.parse(content);
|
|
171
|
+
} catch (error) {
|
|
172
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
173
|
+
return {};
|
|
80
174
|
}
|
|
175
|
+
console.warn(`[Code Squad] Warning: Could not load global config at ${GLOBAL_CONFIG_PATH}.`, error);
|
|
176
|
+
return {};
|
|
81
177
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
178
|
+
}
|
|
179
|
+
async function loadConfig(workspaceRoot) {
|
|
180
|
+
const globalConfig = await loadGlobalConfig();
|
|
181
|
+
const normalizedPath = path9.resolve(workspaceRoot);
|
|
182
|
+
const projectConfig = globalConfig.projects?.[normalizedPath] ?? {};
|
|
183
|
+
const defaults = globalConfig.defaults ?? {};
|
|
184
|
+
return {
|
|
185
|
+
...defaults,
|
|
186
|
+
...projectConfig,
|
|
187
|
+
worktreeCopyPatterns: [
|
|
188
|
+
.../* @__PURE__ */ new Set([
|
|
189
|
+
...defaults.worktreeCopyPatterns ?? [],
|
|
190
|
+
...projectConfig.worktreeCopyPatterns ?? []
|
|
191
|
+
])
|
|
192
|
+
]
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
function getWorktreeCopyPatterns(config) {
|
|
196
|
+
return config.worktreeCopyPatterns ?? [];
|
|
197
|
+
}
|
|
198
|
+
var GLOBAL_CONFIG_PATH;
|
|
199
|
+
var init_config = __esm({
|
|
200
|
+
"dist/config.js"() {
|
|
201
|
+
"use strict";
|
|
202
|
+
GLOBAL_CONFIG_PATH = path9.join(os4.homedir(), ".code-squad", "config.json");
|
|
89
203
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
// dist/fileUtils.js
|
|
207
|
+
import * as fs10 from "fs";
|
|
208
|
+
import * as path10 from "path";
|
|
209
|
+
import fg from "fast-glob";
|
|
210
|
+
async function copyFilesWithPatterns(sourceRoot, destRoot, patterns) {
|
|
211
|
+
const copied = [];
|
|
212
|
+
const failed = [];
|
|
213
|
+
if (patterns.length === 0) {
|
|
214
|
+
return { copied, failed };
|
|
215
|
+
}
|
|
216
|
+
for (const pattern of patterns) {
|
|
96
217
|
try {
|
|
97
|
-
await
|
|
218
|
+
const files = await fg(pattern, {
|
|
219
|
+
cwd: sourceRoot,
|
|
220
|
+
absolute: true,
|
|
221
|
+
onlyFiles: true,
|
|
222
|
+
dot: true
|
|
223
|
+
// .env 같은 dotfile도 매칭
|
|
224
|
+
});
|
|
225
|
+
for (const absolutePath of files) {
|
|
226
|
+
try {
|
|
227
|
+
await copySingleFile(absolutePath, sourceRoot, destRoot);
|
|
228
|
+
const relativePath = path10.relative(sourceRoot, absolutePath);
|
|
229
|
+
copied.push(relativePath);
|
|
230
|
+
} catch {
|
|
231
|
+
const relativePath = path10.relative(sourceRoot, absolutePath);
|
|
232
|
+
failed.push(relativePath);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
98
235
|
} catch {
|
|
99
|
-
return false;
|
|
100
236
|
}
|
|
101
|
-
const worktrees = await this.listWorktrees(workspaceRoot);
|
|
102
|
-
return worktrees.some((wt) => wt.path === path12);
|
|
103
237
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
238
|
+
return { copied, failed };
|
|
239
|
+
}
|
|
240
|
+
async function copySingleFile(absolutePath, sourceRoot, destRoot) {
|
|
241
|
+
const relativePath = path10.relative(sourceRoot, absolutePath);
|
|
242
|
+
const destPath = path10.join(destRoot, relativePath);
|
|
243
|
+
const destDir = path10.dirname(destPath);
|
|
244
|
+
await fs10.promises.mkdir(destDir, { recursive: true });
|
|
245
|
+
await fs10.promises.copyFile(absolutePath, destPath);
|
|
246
|
+
}
|
|
247
|
+
var init_fileUtils = __esm({
|
|
248
|
+
"dist/fileUtils.js"() {
|
|
249
|
+
"use strict";
|
|
111
250
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
// dist/tui/App.js
|
|
254
|
+
var App_exports = {};
|
|
255
|
+
__export(App_exports, {
|
|
256
|
+
runTui: () => runTui
|
|
257
|
+
});
|
|
258
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
259
|
+
import { useState, useEffect, useCallback } from "react";
|
|
260
|
+
import { render, Box, Text, useInput, useApp } from "ink";
|
|
261
|
+
import * as path11 from "path";
|
|
262
|
+
import * as os5 from "os";
|
|
263
|
+
function shorten(p) {
|
|
264
|
+
const home = os5.homedir();
|
|
265
|
+
return p.startsWith(home) ? "~" + p.slice(home.length) : p;
|
|
266
|
+
}
|
|
267
|
+
function App({ initialWorktrees, root }) {
|
|
268
|
+
const { exit } = useApp();
|
|
269
|
+
const [view, setView] = useState("list");
|
|
270
|
+
const [worktrees, setWorktrees] = useState(initialWorktrees);
|
|
271
|
+
const [cursor, setCursor] = useState(0);
|
|
272
|
+
const [input, setInput] = useState("");
|
|
273
|
+
const [msg, setMsg] = useState(null);
|
|
274
|
+
const [busy, setBusy] = useState(false);
|
|
275
|
+
useEffect(() => {
|
|
276
|
+
if (!msg)
|
|
277
|
+
return;
|
|
278
|
+
const t = setTimeout(() => setMsg(null), 2e3);
|
|
279
|
+
return () => clearTimeout(t);
|
|
280
|
+
}, [msg]);
|
|
281
|
+
const refresh = useCallback(async () => {
|
|
282
|
+
const wts = await git.listWorktrees(root);
|
|
283
|
+
setWorktrees(wts);
|
|
284
|
+
setCursor((c) => Math.min(c, Math.max(0, wts.length - 1)));
|
|
285
|
+
}, [root]);
|
|
286
|
+
useInput((ch, key) => {
|
|
287
|
+
if (busy)
|
|
288
|
+
return;
|
|
289
|
+
if (view === "list") {
|
|
290
|
+
if (key.upArrow) {
|
|
291
|
+
setCursor((c) => Math.max(0, c - 1));
|
|
292
|
+
} else if (key.downArrow) {
|
|
293
|
+
setCursor((c) => Math.min(worktrees.length - 1, c + 1));
|
|
294
|
+
} else if (key.return && worktrees.length > 0) {
|
|
295
|
+
process.stdout.write(worktrees[cursor].path + "\n");
|
|
296
|
+
exit();
|
|
297
|
+
} else if (ch === "n") {
|
|
298
|
+
setView("create");
|
|
299
|
+
setInput("");
|
|
300
|
+
} else if (ch === "d" && worktrees.length > 0) {
|
|
301
|
+
setView("delete");
|
|
302
|
+
} else if (ch === "q" || key.escape) {
|
|
303
|
+
exit();
|
|
304
|
+
}
|
|
305
|
+
return;
|
|
126
306
|
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
307
|
+
if (view === "create") {
|
|
308
|
+
if (key.escape) {
|
|
309
|
+
setView("list");
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
if (key.return && input.trim()) {
|
|
313
|
+
const name = input.trim();
|
|
314
|
+
setBusy(true);
|
|
315
|
+
const base = path11.join(path11.dirname(root), `${path11.basename(root)}.worktree`);
|
|
316
|
+
const wtPath = path11.join(base, name);
|
|
317
|
+
git.createWorktree(wtPath, name, root).then(async () => {
|
|
318
|
+
const config = await loadConfig(root);
|
|
319
|
+
const patterns = getWorktreeCopyPatterns(config);
|
|
320
|
+
if (patterns.length > 0) {
|
|
321
|
+
await copyFilesWithPatterns(root, wtPath, patterns);
|
|
322
|
+
}
|
|
323
|
+
process.stdout.write(wtPath + "\n");
|
|
324
|
+
exit();
|
|
325
|
+
}).catch((e) => {
|
|
326
|
+
setMsg({ text: e.message, color: "red" });
|
|
327
|
+
setView("list");
|
|
328
|
+
setBusy(false);
|
|
329
|
+
});
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
if (key.backspace || key.delete) {
|
|
333
|
+
setInput((v) => v.slice(0, -1));
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
if (ch && !key.ctrl && !key.meta) {
|
|
337
|
+
setInput((v) => v + ch);
|
|
338
|
+
}
|
|
339
|
+
return;
|
|
133
340
|
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
341
|
+
if (view === "delete") {
|
|
342
|
+
if (ch === "y" || key.return) {
|
|
343
|
+
const target = worktrees[cursor];
|
|
344
|
+
setBusy(true);
|
|
345
|
+
git.removeWorktree(target.path, root, true).then(() => git.deleteBranch(target.branch, root, true)).then(async () => {
|
|
346
|
+
setMsg({ text: `Deleted ${target.branch}`, color: "green" });
|
|
347
|
+
await refresh();
|
|
348
|
+
setBusy(false);
|
|
349
|
+
setView("list");
|
|
350
|
+
}).catch((e) => {
|
|
351
|
+
setMsg({ text: e.message, color: "red" });
|
|
352
|
+
setBusy(false);
|
|
353
|
+
setView("list");
|
|
354
|
+
});
|
|
355
|
+
} else if (ch === "n" || key.escape) {
|
|
356
|
+
setView("list");
|
|
357
|
+
}
|
|
148
358
|
}
|
|
359
|
+
});
|
|
360
|
+
return _jsxs(Box, { flexDirection: "column", paddingX: 1, paddingY: 1, children: [_jsxs(Box, { marginBottom: 1, children: [_jsx(Text, { bold: true, color: "cyan", children: "Code Squad" }), busy && _jsx(Text, { color: "yellow", children: " ..." })] }), view === "list" && (worktrees.length === 0 ? _jsxs(Box, { marginBottom: 1, children: [_jsx(Text, { dimColor: true, children: "No worktrees yet. Press " }), _jsx(Text, { color: "yellow", children: "n" }), _jsx(Text, { dimColor: true, children: " to create one." })] }) : _jsx(Box, { flexDirection: "column", marginBottom: 1, children: worktrees.map((wt, i) => _jsxs(Box, { children: [_jsx(Text, { color: i === cursor ? "cyan" : void 0, children: i === cursor ? "\u276F " : " " }), _jsx(Text, { bold: i === cursor, children: wt.branch.padEnd(20) }), _jsxs(Text, { dimColor: true, children: [" ", shorten(wt.path)] })] }, wt.path)) })), view === "create" && _jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { bold: true, children: "New Worktree" }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { dimColor: true, children: "> " }), _jsx(Text, { color: "cyan", children: input }), _jsx(Text, { dimColor: true, children: "\u2588" })] })] }), view === "delete" && worktrees[cursor] && _jsx(Box, { flexDirection: "column", marginBottom: 1, children: _jsxs(Text, { children: ["Delete ", _jsx(Text, { bold: true, color: "yellow", children: worktrees[cursor].branch }), "?"] }) }), msg && _jsx(Box, { marginBottom: 1, children: _jsx(Text, { color: msg.color, children: msg.text }) }), _jsxs(Box, { gap: 2, children: [view === "list" && _jsxs(_Fragment, { children: [worktrees.length > 0 && _jsxs(_Fragment, { children: [_jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: "\u2191\u2193" }), " ", _jsx(Text, { dimColor: true, children: "navigate" })] }), _jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: "\u21B5" }), " ", _jsx(Text, { dimColor: true, children: "switch" })] })] }), _jsxs(Text, { children: [_jsx(Text, { color: "yellow", children: "n" }), " ", _jsx(Text, { dimColor: true, children: "new" })] }), worktrees.length > 0 && _jsxs(Text, { children: [_jsx(Text, { color: "yellow", children: "d" }), " ", _jsx(Text, { dimColor: true, children: "delete" })] }), _jsxs(Text, { children: [_jsx(Text, { color: "yellow", children: "q" }), " ", _jsx(Text, { dimColor: true, children: "quit" })] })] }), view === "create" && _jsxs(_Fragment, { children: [_jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: "\u21B5" }), " ", _jsx(Text, { dimColor: true, children: "create" })] }), _jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: "esc" }), " ", _jsx(Text, { dimColor: true, children: "cancel" })] })] }), view === "delete" && _jsxs(_Fragment, { children: [_jsxs(Text, { children: [_jsx(Text, { color: "yellow", children: "y" }), " ", _jsx(Text, { dimColor: true, children: "confirm" })] }), _jsxs(Text, { children: [_jsx(Text, { color: "yellow", children: "n" }), " ", _jsx(Text, { dimColor: true, children: "cancel" })] })] })] })] });
|
|
361
|
+
}
|
|
362
|
+
async function runTui(workspaceRoot) {
|
|
363
|
+
const worktrees = await git.listWorktrees(workspaceRoot);
|
|
364
|
+
const { waitUntilExit } = render(_jsx(App, { initialWorktrees: worktrees, root: workspaceRoot }), { stdout: process.stderr });
|
|
365
|
+
await waitUntilExit();
|
|
366
|
+
}
|
|
367
|
+
var git;
|
|
368
|
+
var init_App = __esm({
|
|
369
|
+
"dist/tui/App.js"() {
|
|
370
|
+
"use strict";
|
|
371
|
+
init_GitAdapter();
|
|
372
|
+
init_config();
|
|
373
|
+
init_fileUtils();
|
|
374
|
+
git = new GitAdapter();
|
|
149
375
|
}
|
|
150
|
-
};
|
|
376
|
+
});
|
|
151
377
|
|
|
152
378
|
// dist/index.js
|
|
379
|
+
init_GitAdapter();
|
|
380
|
+
import * as path12 from "path";
|
|
381
|
+
import chalk from "chalk";
|
|
153
382
|
import { confirm } from "@inquirer/prompts";
|
|
154
383
|
|
|
155
384
|
// dist/flip/server/Server.js
|
|
@@ -433,9 +662,9 @@ var filenameMap = {
|
|
|
433
662
|
"CODEOWNERS": "gitignore"
|
|
434
663
|
};
|
|
435
664
|
function detectLanguage(filePath) {
|
|
436
|
-
const
|
|
437
|
-
if (filenameMap[
|
|
438
|
-
return filenameMap[
|
|
665
|
+
const basename3 = path2.basename(filePath);
|
|
666
|
+
if (filenameMap[basename3]) {
|
|
667
|
+
return filenameMap[basename3];
|
|
439
668
|
}
|
|
440
669
|
const ext = path2.extname(filePath).slice(1).toLowerCase();
|
|
441
670
|
if (extensionMap[ext]) {
|
|
@@ -1744,86 +1973,9 @@ fi
|
|
|
1744
1973
|
console.log("");
|
|
1745
1974
|
}
|
|
1746
1975
|
|
|
1747
|
-
// dist/config.js
|
|
1748
|
-
import * as fs9 from "fs";
|
|
1749
|
-
import * as os4 from "os";
|
|
1750
|
-
import * as path9 from "path";
|
|
1751
|
-
var GLOBAL_CONFIG_PATH = path9.join(os4.homedir(), ".code-squad", "config.json");
|
|
1752
|
-
async function loadGlobalConfig() {
|
|
1753
|
-
try {
|
|
1754
|
-
const content = await fs9.promises.readFile(GLOBAL_CONFIG_PATH, "utf-8");
|
|
1755
|
-
return JSON.parse(content);
|
|
1756
|
-
} catch (error) {
|
|
1757
|
-
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
1758
|
-
return {};
|
|
1759
|
-
}
|
|
1760
|
-
console.warn(`[Code Squad] Warning: Could not load global config at ${GLOBAL_CONFIG_PATH}.`, error);
|
|
1761
|
-
return {};
|
|
1762
|
-
}
|
|
1763
|
-
}
|
|
1764
|
-
async function loadConfig(workspaceRoot) {
|
|
1765
|
-
const globalConfig = await loadGlobalConfig();
|
|
1766
|
-
const normalizedPath = path9.resolve(workspaceRoot);
|
|
1767
|
-
const projectConfig = globalConfig.projects?.[normalizedPath] ?? {};
|
|
1768
|
-
const defaults = globalConfig.defaults ?? {};
|
|
1769
|
-
return {
|
|
1770
|
-
...defaults,
|
|
1771
|
-
...projectConfig,
|
|
1772
|
-
worktreeCopyPatterns: [
|
|
1773
|
-
.../* @__PURE__ */ new Set([
|
|
1774
|
-
...defaults.worktreeCopyPatterns ?? [],
|
|
1775
|
-
...projectConfig.worktreeCopyPatterns ?? []
|
|
1776
|
-
])
|
|
1777
|
-
]
|
|
1778
|
-
};
|
|
1779
|
-
}
|
|
1780
|
-
function getWorktreeCopyPatterns(config) {
|
|
1781
|
-
return config.worktreeCopyPatterns ?? [];
|
|
1782
|
-
}
|
|
1783
|
-
|
|
1784
|
-
// dist/fileUtils.js
|
|
1785
|
-
import * as fs10 from "fs";
|
|
1786
|
-
import * as path10 from "path";
|
|
1787
|
-
import fg from "fast-glob";
|
|
1788
|
-
async function copyFilesWithPatterns(sourceRoot, destRoot, patterns) {
|
|
1789
|
-
const copied = [];
|
|
1790
|
-
const failed = [];
|
|
1791
|
-
if (patterns.length === 0) {
|
|
1792
|
-
return { copied, failed };
|
|
1793
|
-
}
|
|
1794
|
-
for (const pattern of patterns) {
|
|
1795
|
-
try {
|
|
1796
|
-
const files = await fg(pattern, {
|
|
1797
|
-
cwd: sourceRoot,
|
|
1798
|
-
absolute: true,
|
|
1799
|
-
onlyFiles: true,
|
|
1800
|
-
dot: true
|
|
1801
|
-
// .env 같은 dotfile도 매칭
|
|
1802
|
-
});
|
|
1803
|
-
for (const absolutePath of files) {
|
|
1804
|
-
try {
|
|
1805
|
-
await copySingleFile(absolutePath, sourceRoot, destRoot);
|
|
1806
|
-
const relativePath = path10.relative(sourceRoot, absolutePath);
|
|
1807
|
-
copied.push(relativePath);
|
|
1808
|
-
} catch {
|
|
1809
|
-
const relativePath = path10.relative(sourceRoot, absolutePath);
|
|
1810
|
-
failed.push(relativePath);
|
|
1811
|
-
}
|
|
1812
|
-
}
|
|
1813
|
-
} catch {
|
|
1814
|
-
}
|
|
1815
|
-
}
|
|
1816
|
-
return { copied, failed };
|
|
1817
|
-
}
|
|
1818
|
-
async function copySingleFile(absolutePath, sourceRoot, destRoot) {
|
|
1819
|
-
const relativePath = path10.relative(sourceRoot, absolutePath);
|
|
1820
|
-
const destPath = path10.join(destRoot, relativePath);
|
|
1821
|
-
const destDir = path10.dirname(destPath);
|
|
1822
|
-
await fs10.promises.mkdir(destDir, { recursive: true });
|
|
1823
|
-
await fs10.promises.copyFile(absolutePath, destPath);
|
|
1824
|
-
}
|
|
1825
|
-
|
|
1826
1976
|
// dist/index.js
|
|
1977
|
+
init_config();
|
|
1978
|
+
init_fileUtils();
|
|
1827
1979
|
process.on("SIGINT", () => {
|
|
1828
1980
|
process.exit(130);
|
|
1829
1981
|
});
|
|
@@ -1852,8 +2004,15 @@ async function main() {
|
|
|
1852
2004
|
await quitWorktreeCommand();
|
|
1853
2005
|
break;
|
|
1854
2006
|
case "list":
|
|
1855
|
-
default:
|
|
1856
2007
|
await listWorktrees(workspaceRoot);
|
|
2008
|
+
break;
|
|
2009
|
+
default:
|
|
2010
|
+
if (process.stdin.isTTY) {
|
|
2011
|
+
const { runTui: runTui2 } = await Promise.resolve().then(() => (init_App(), App_exports));
|
|
2012
|
+
await runTui2(workspaceRoot);
|
|
2013
|
+
} else {
|
|
2014
|
+
await listWorktrees(workspaceRoot);
|
|
2015
|
+
}
|
|
1857
2016
|
}
|
|
1858
2017
|
}
|
|
1859
2018
|
async function findGitRoot(cwd) {
|
|
@@ -1871,7 +2030,7 @@ csq() {
|
|
|
1871
2030
|
fi
|
|
1872
2031
|
|
|
1873
2032
|
local output
|
|
1874
|
-
output=$(command csq "$@"
|
|
2033
|
+
output=$(command csq "$@")
|
|
1875
2034
|
local exit_code=$?
|
|
1876
2035
|
|
|
1877
2036
|
if [[ $exit_code -ne 0 ]]; then
|
|
@@ -1909,9 +2068,9 @@ async function createWorktreeCommand(workspaceRoot, args) {
|
|
|
1909
2068
|
console.error(chalk.dim("Usage: csq new <name>"));
|
|
1910
2069
|
process.exit(1);
|
|
1911
2070
|
}
|
|
1912
|
-
const repoName =
|
|
1913
|
-
const defaultBasePath =
|
|
1914
|
-
const worktreePath =
|
|
2071
|
+
const repoName = path12.basename(workspaceRoot);
|
|
2072
|
+
const defaultBasePath = path12.join(path12.dirname(workspaceRoot), `${repoName}.worktree`);
|
|
2073
|
+
const worktreePath = path12.join(defaultBasePath, name);
|
|
1915
2074
|
try {
|
|
1916
2075
|
await gitAdapter.createWorktree(worktreePath, name, workspaceRoot);
|
|
1917
2076
|
console.log(chalk.green(`\u2713 Created worktree: ${name}`));
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runTui(workspaceRoot: string): Promise<void>;
|