code-squad-cli 1.2.22 → 2.0.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/adapters/GitAdapter.js +18 -4
- package/dist/index.js +97 -661
- package/package.json +1 -1
- package/dist/ui/prompts.d.ts +0 -47
- package/dist/ui/prompts.js +0 -328
package/dist/index.js
CHANGED
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// dist/index.js
|
|
4
|
-
import * as
|
|
5
|
-
import
|
|
6
|
-
import * as os5 from "os";
|
|
7
|
-
import * as crypto from "crypto";
|
|
8
|
-
import chalk2 from "chalk";
|
|
4
|
+
import * as path11 from "path";
|
|
5
|
+
import chalk from "chalk";
|
|
9
6
|
|
|
10
7
|
// dist/adapters/GitAdapter.js
|
|
11
8
|
import { exec as execCallback } from "child_process";
|
|
@@ -23,8 +20,17 @@ var GitAdapter = class {
|
|
|
23
20
|
}
|
|
24
21
|
}
|
|
25
22
|
async getCurrentBranch(workspaceRoot) {
|
|
26
|
-
|
|
27
|
-
|
|
23
|
+
try {
|
|
24
|
+
const { stdout } = await exec(`cd "${workspaceRoot}" && git rev-parse --abbrev-ref HEAD`, execOptions);
|
|
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 "";
|
|
32
|
+
}
|
|
33
|
+
}
|
|
28
34
|
}
|
|
29
35
|
async listWorktrees(workspaceRoot) {
|
|
30
36
|
try {
|
|
@@ -44,11 +50,11 @@ var GitAdapter = class {
|
|
|
44
50
|
const headMatch = headLine.match(/^HEAD (.+)$/);
|
|
45
51
|
const branchMatch = branchLine?.match(/^branch refs\/heads\/(.+)$/);
|
|
46
52
|
if (pathMatch && headMatch) {
|
|
47
|
-
const
|
|
53
|
+
const path12 = pathMatch[1];
|
|
48
54
|
const head = headMatch[1];
|
|
49
55
|
const branch = branchMatch ? branchMatch[1] : "HEAD";
|
|
50
|
-
if (
|
|
51
|
-
worktrees.push({ path:
|
|
56
|
+
if (path12 !== workspaceRoot) {
|
|
57
|
+
worktrees.push({ path: path12, branch, head });
|
|
52
58
|
}
|
|
53
59
|
}
|
|
54
60
|
i += 3;
|
|
@@ -59,9 +65,11 @@ var GitAdapter = class {
|
|
|
59
65
|
}
|
|
60
66
|
}
|
|
61
67
|
async createWorktree(worktreePath, branch, workspaceRoot) {
|
|
68
|
+
await exec(`cd "${workspaceRoot}" && git worktree prune`, execOptions).catch(() => {
|
|
69
|
+
});
|
|
62
70
|
const parentDir = worktreePath.substring(0, worktreePath.lastIndexOf("/"));
|
|
63
71
|
const mkdirCmd = parentDir ? `mkdir -p "${parentDir}" && ` : "";
|
|
64
|
-
await exec(`cd "${workspaceRoot}" && ${mkdirCmd}git worktree add "${worktreePath}" -b "${branch}"`, execOptions);
|
|
72
|
+
await exec(`cd "${workspaceRoot}" && ${mkdirCmd}git worktree add -f "${worktreePath}" -b "${branch}"`, execOptions);
|
|
65
73
|
}
|
|
66
74
|
async removeWorktree(worktreePath, workspaceRoot, force = false) {
|
|
67
75
|
const forceFlag = force ? " --force" : "";
|
|
@@ -79,19 +87,19 @@ var GitAdapter = class {
|
|
|
79
87
|
throw new Error(`Failed to delete branch: ${error.message}`);
|
|
80
88
|
}
|
|
81
89
|
}
|
|
82
|
-
async isValidWorktree(
|
|
90
|
+
async isValidWorktree(path12, workspaceRoot) {
|
|
83
91
|
try {
|
|
84
|
-
await fs.promises.access(
|
|
92
|
+
await fs.promises.access(path12, fs.constants.R_OK);
|
|
85
93
|
} catch {
|
|
86
94
|
return false;
|
|
87
95
|
}
|
|
88
96
|
try {
|
|
89
|
-
await exec(`cd "${
|
|
97
|
+
await exec(`cd "${path12}" && git rev-parse --git-dir`, execOptions);
|
|
90
98
|
} catch {
|
|
91
99
|
return false;
|
|
92
100
|
}
|
|
93
101
|
const worktrees = await this.listWorktrees(workspaceRoot);
|
|
94
|
-
return worktrees.some((wt) => wt.path ===
|
|
102
|
+
return worktrees.some((wt) => wt.path === path12);
|
|
95
103
|
}
|
|
96
104
|
async getWorktreeBranch(worktreePath) {
|
|
97
105
|
try {
|
|
@@ -141,277 +149,8 @@ var GitAdapter = class {
|
|
|
141
149
|
}
|
|
142
150
|
};
|
|
143
151
|
|
|
144
|
-
// dist/ui/prompts.js
|
|
145
|
-
import { createPrompt, useState, useKeypress, usePrefix, isEnterKey, isBackspaceKey } from "@inquirer/core";
|
|
146
|
-
import { confirm } from "@inquirer/prompts";
|
|
147
|
-
import chalk from "chalk";
|
|
148
|
-
import * as path from "path";
|
|
149
|
-
var cancelableInput = createPrompt((config, done) => {
|
|
150
|
-
const [value, setValue] = useState(config.default || "");
|
|
151
|
-
const [error, setError] = useState(null);
|
|
152
|
-
const prefix = usePrefix({ status: "idle" });
|
|
153
|
-
useKeypress((key, rl) => {
|
|
154
|
-
if (key.name === "escape") {
|
|
155
|
-
done(null);
|
|
156
|
-
} else if (isEnterKey(key)) {
|
|
157
|
-
if (config.validate) {
|
|
158
|
-
const result = config.validate(value);
|
|
159
|
-
if (result !== true) {
|
|
160
|
-
setError(typeof result === "string" ? result : "Invalid input");
|
|
161
|
-
return;
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
done(value);
|
|
165
|
-
} else if (isBackspaceKey(key)) {
|
|
166
|
-
setValue(value.slice(0, -1));
|
|
167
|
-
setError(null);
|
|
168
|
-
} else if (key.ctrl || key.name === "tab" || key.name === "up" || key.name === "down") {
|
|
169
|
-
} else {
|
|
170
|
-
const seq = key.sequence;
|
|
171
|
-
if (seq && seq.length === 1 && seq >= " ") {
|
|
172
|
-
setValue(value + seq);
|
|
173
|
-
setError(null);
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
});
|
|
177
|
-
const errorMsg = error ? chalk.red(`
|
|
178
|
-
${error}`) : "";
|
|
179
|
-
return `${prefix} ${config.message} ${chalk.cyan(value)}${errorMsg}
|
|
180
|
-
${chalk.dim("ESC:cancel Enter:confirm")}`;
|
|
181
|
-
});
|
|
182
|
-
function truncatePath(fullPath, maxLen) {
|
|
183
|
-
if (fullPath.length <= maxLen)
|
|
184
|
-
return fullPath;
|
|
185
|
-
const home = process.env.HOME || "";
|
|
186
|
-
let display = fullPath.startsWith(home) ? "~" + fullPath.slice(home.length) : fullPath;
|
|
187
|
-
if (display.length <= maxLen)
|
|
188
|
-
return display;
|
|
189
|
-
const parts = display.split(path.sep);
|
|
190
|
-
if (parts.length > 2) {
|
|
191
|
-
return "\u2026/" + parts.slice(-2).join("/");
|
|
192
|
-
}
|
|
193
|
-
return "\u2026" + display.slice(-maxLen + 1);
|
|
194
|
-
}
|
|
195
|
-
var vimSelect = createPrompt((config, done) => {
|
|
196
|
-
const { choices, pageSize = 15, shortcuts = [] } = config;
|
|
197
|
-
const enabledChoices = choices.filter((c) => !c.disabled);
|
|
198
|
-
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
199
|
-
const prefix = usePrefix({ status: "idle" });
|
|
200
|
-
useKeypress((key) => {
|
|
201
|
-
if (key.name === "j" || key.name === "down") {
|
|
202
|
-
const nextIndex = (selectedIndex + 1) % enabledChoices.length;
|
|
203
|
-
setSelectedIndex(nextIndex);
|
|
204
|
-
} else if (key.name === "k" || key.name === "up") {
|
|
205
|
-
const prevIndex = (selectedIndex - 1 + enabledChoices.length) % enabledChoices.length;
|
|
206
|
-
setSelectedIndex(prevIndex);
|
|
207
|
-
} else if (isEnterKey(key)) {
|
|
208
|
-
done(enabledChoices[selectedIndex].value);
|
|
209
|
-
} else if (key.name === "escape" || key.name === "b") {
|
|
210
|
-
const backShortcut = shortcuts.find((s) => s.key === "back");
|
|
211
|
-
if (backShortcut) {
|
|
212
|
-
done(backShortcut.value);
|
|
213
|
-
}
|
|
214
|
-
} else {
|
|
215
|
-
const shortcut = shortcuts.find((s) => s.key === key.name);
|
|
216
|
-
if (shortcut) {
|
|
217
|
-
done(shortcut.value);
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
});
|
|
221
|
-
const totalItems = enabledChoices.length;
|
|
222
|
-
const halfPage = Math.floor(pageSize / 2);
|
|
223
|
-
let startIndex = 0;
|
|
224
|
-
if (totalItems > pageSize) {
|
|
225
|
-
if (selectedIndex <= halfPage) {
|
|
226
|
-
startIndex = 0;
|
|
227
|
-
} else if (selectedIndex >= totalItems - halfPage) {
|
|
228
|
-
startIndex = totalItems - pageSize;
|
|
229
|
-
} else {
|
|
230
|
-
startIndex = selectedIndex - halfPage;
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
const visibleChoices = enabledChoices.slice(startIndex, startIndex + pageSize);
|
|
234
|
-
const lines = visibleChoices.map((choice, i) => {
|
|
235
|
-
const actualIndex = startIndex + i;
|
|
236
|
-
const isSelected = actualIndex === selectedIndex;
|
|
237
|
-
const cursor = isSelected ? chalk.cyan("\u276F") : " ";
|
|
238
|
-
const name = isSelected ? chalk.cyan(choice.name) : choice.name;
|
|
239
|
-
return `${cursor} ${name}`;
|
|
240
|
-
});
|
|
241
|
-
if (startIndex > 0) {
|
|
242
|
-
lines.unshift(chalk.dim(" \u2191 more"));
|
|
243
|
-
}
|
|
244
|
-
if (startIndex + pageSize < totalItems) {
|
|
245
|
-
lines.push(chalk.dim(" \u2193 more"));
|
|
246
|
-
}
|
|
247
|
-
const shortcutHints = shortcuts.filter((s) => s.description).map((s) => chalk.dim(`${s.key}:${s.description}`)).join(" ");
|
|
248
|
-
const hint = chalk.dim("j/k:navigate enter:select") + (shortcutHints ? " " + shortcutHints : "");
|
|
249
|
-
return `${prefix} ${chalk.bold(config.message)}
|
|
250
|
-
${lines.join("\n")}
|
|
251
|
-
${hint}`;
|
|
252
|
-
});
|
|
253
|
-
async function selectThread(threads, repoName) {
|
|
254
|
-
const cols = process.stdout.columns || 80;
|
|
255
|
-
const nameWidth = 18;
|
|
256
|
-
const prefixWidth = 6;
|
|
257
|
-
const pathMaxLen = Math.max(20, cols - nameWidth - prefixWidth - 5);
|
|
258
|
-
const threadChoices = threads.map((t) => {
|
|
259
|
-
const typeIcon = t.type === "worktree" ? chalk.cyan("[W]") : chalk.yellow("[L]");
|
|
260
|
-
const displayPath = truncatePath(t.path, pathMaxLen);
|
|
261
|
-
return {
|
|
262
|
-
name: `${typeIcon} ${t.name.padEnd(nameWidth)} ${chalk.dim(displayPath)}`,
|
|
263
|
-
value: { type: "existing", thread: t },
|
|
264
|
-
thread: t
|
|
265
|
-
};
|
|
266
|
-
});
|
|
267
|
-
const newChoice = {
|
|
268
|
-
name: chalk.green("+ \uC0C8 \uC791\uC5C5"),
|
|
269
|
-
value: { type: "new" },
|
|
270
|
-
thread: null
|
|
271
|
-
};
|
|
272
|
-
const allChoices = [...threadChoices, newChoice];
|
|
273
|
-
const threadSelect = createPrompt((config, done) => {
|
|
274
|
-
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
275
|
-
const prefix = usePrefix({ status: "idle" });
|
|
276
|
-
const pageSize = 15;
|
|
277
|
-
useKeypress((key) => {
|
|
278
|
-
if (key.name === "j" || key.name === "down") {
|
|
279
|
-
setSelectedIndex((selectedIndex + 1) % allChoices.length);
|
|
280
|
-
} else if (key.name === "k" || key.name === "up") {
|
|
281
|
-
setSelectedIndex((selectedIndex - 1 + allChoices.length) % allChoices.length);
|
|
282
|
-
} else if (isEnterKey(key)) {
|
|
283
|
-
done(allChoices[selectedIndex].value);
|
|
284
|
-
} else if (key.name === "n") {
|
|
285
|
-
done({ type: "new" });
|
|
286
|
-
} else if (key.name === "q") {
|
|
287
|
-
done({ type: "exit" });
|
|
288
|
-
} else if (key.name === "d") {
|
|
289
|
-
const current = allChoices[selectedIndex];
|
|
290
|
-
if (current.thread) {
|
|
291
|
-
done({ type: "delete-selected", thread: current.thread });
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
});
|
|
295
|
-
const totalItems = allChoices.length;
|
|
296
|
-
const halfPage = Math.floor(pageSize / 2);
|
|
297
|
-
let startIndex = 0;
|
|
298
|
-
if (totalItems > pageSize) {
|
|
299
|
-
if (selectedIndex <= halfPage) {
|
|
300
|
-
startIndex = 0;
|
|
301
|
-
} else if (selectedIndex >= totalItems - halfPage) {
|
|
302
|
-
startIndex = totalItems - pageSize;
|
|
303
|
-
} else {
|
|
304
|
-
startIndex = selectedIndex - halfPage;
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
const visibleChoices = allChoices.slice(startIndex, startIndex + pageSize);
|
|
308
|
-
const lines = visibleChoices.map((choice, i) => {
|
|
309
|
-
const actualIndex = startIndex + i;
|
|
310
|
-
const isSelected = actualIndex === selectedIndex;
|
|
311
|
-
const cursor = isSelected ? chalk.cyan("\u276F") : " ";
|
|
312
|
-
const name = isSelected ? chalk.cyan(choice.name) : choice.name;
|
|
313
|
-
return `${cursor} ${name}`;
|
|
314
|
-
});
|
|
315
|
-
if (startIndex > 0) {
|
|
316
|
-
lines.unshift(chalk.dim(" \u2191 more"));
|
|
317
|
-
}
|
|
318
|
-
if (startIndex + pageSize < totalItems) {
|
|
319
|
-
lines.push(chalk.dim(" \u2193 more"));
|
|
320
|
-
}
|
|
321
|
-
const hint = chalk.dim("j/k:nav n:new d:delete q:quit");
|
|
322
|
-
return `${prefix} ${config.message}
|
|
323
|
-
${lines.join("\n")}
|
|
324
|
-
${hint}`;
|
|
325
|
-
});
|
|
326
|
-
return threadSelect({ message: chalk.bold(repoName) });
|
|
327
|
-
}
|
|
328
|
-
async function selectThreadAction(threadName) {
|
|
329
|
-
const choices = [
|
|
330
|
-
{ name: "\uD130\uBBF8\uB110 \uC5F4\uAE30", value: "open" },
|
|
331
|
-
{ name: chalk.red("\uC0AD\uC81C\uD558\uAE30"), value: "delete" },
|
|
332
|
-
{ name: chalk.dim("\u2190 \uB4A4\uB85C"), value: "back" }
|
|
333
|
-
];
|
|
334
|
-
return vimSelect({
|
|
335
|
-
message: `'${threadName}'`,
|
|
336
|
-
choices,
|
|
337
|
-
shortcuts: [{ key: "back", value: "back" }]
|
|
338
|
-
});
|
|
339
|
-
}
|
|
340
|
-
async function selectNewThreadType() {
|
|
341
|
-
const choices = [
|
|
342
|
-
{
|
|
343
|
-
name: chalk.cyan("\uC6CC\uD06C\uD2B8\uB9AC") + chalk.dim(" - \uC0C8 \uBE0C\uB79C\uCE58\uC640 \uB514\uB809\uD1A0\uB9AC \uC0DD\uC131"),
|
|
344
|
-
value: "worktree"
|
|
345
|
-
},
|
|
346
|
-
{
|
|
347
|
-
name: chalk.yellow("\uB85C\uCEEC") + chalk.dim(" - \uD604\uC7AC \uB514\uB809\uD1A0\uB9AC\uC5D0\uC11C \uC791\uC5C5"),
|
|
348
|
-
value: "local"
|
|
349
|
-
},
|
|
350
|
-
{ name: chalk.dim("\u2190 \uB4A4\uB85C"), value: "back" }
|
|
351
|
-
];
|
|
352
|
-
return vimSelect({
|
|
353
|
-
message: "\uC0C8 \uC791\uC5C5 \uD0C0\uC785",
|
|
354
|
-
choices,
|
|
355
|
-
shortcuts: [{ key: "back", value: "back" }]
|
|
356
|
-
});
|
|
357
|
-
}
|
|
358
|
-
async function newWorktreeForm(defaultBasePath) {
|
|
359
|
-
const name = await cancelableInput({
|
|
360
|
-
message: "\uC6CC\uD06C\uD2B8\uB9AC \uC774\uB984:",
|
|
361
|
-
validate: (value) => {
|
|
362
|
-
if (!value.trim())
|
|
363
|
-
return "\uC774\uB984\uC744 \uC785\uB825\uD574\uC8FC\uC138\uC694";
|
|
364
|
-
if (!/^[a-zA-Z0-9_-]+$/.test(value)) {
|
|
365
|
-
return "\uC601\uBB38, \uC22B\uC790, -, _ \uB9CC \uC0AC\uC6A9 \uAC00\uB2A5";
|
|
366
|
-
}
|
|
367
|
-
return true;
|
|
368
|
-
}
|
|
369
|
-
});
|
|
370
|
-
if (!name)
|
|
371
|
-
return null;
|
|
372
|
-
const defaultPath = `${defaultBasePath}/${name}`;
|
|
373
|
-
const pathInput = await cancelableInput({
|
|
374
|
-
message: "\uACBD\uB85C:",
|
|
375
|
-
default: defaultPath
|
|
376
|
-
});
|
|
377
|
-
if (!pathInput)
|
|
378
|
-
return null;
|
|
379
|
-
return { name, path: pathInput };
|
|
380
|
-
}
|
|
381
|
-
async function newLocalForm() {
|
|
382
|
-
const name = await cancelableInput({
|
|
383
|
-
message: "\uB85C\uCEEC \uC2A4\uB808\uB4DC \uC774\uB984:",
|
|
384
|
-
validate: (value) => {
|
|
385
|
-
if (!value.trim())
|
|
386
|
-
return "\uC774\uB984\uC744 \uC785\uB825\uD574\uC8FC\uC138\uC694";
|
|
387
|
-
return true;
|
|
388
|
-
}
|
|
389
|
-
});
|
|
390
|
-
return name || null;
|
|
391
|
-
}
|
|
392
|
-
async function confirmDeleteWorktree(threadName) {
|
|
393
|
-
const confirmed = await confirm({
|
|
394
|
-
message: `'${threadName}' \uC6CC\uD06C\uD2B8\uB9AC\uB97C \uC0AD\uC81C\uD560\uAE4C\uC694?`,
|
|
395
|
-
default: true
|
|
396
|
-
});
|
|
397
|
-
if (!confirmed) {
|
|
398
|
-
return { confirmed: false, removeGitWorktree: false };
|
|
399
|
-
}
|
|
400
|
-
const removeGitWorktree = await confirm({
|
|
401
|
-
message: "Git worktree\uC640 \uBE0C\uB79C\uCE58\uB3C4 \uD568\uAED8 \uC0AD\uC81C\uD560\uAE4C\uC694?",
|
|
402
|
-
default: true
|
|
403
|
-
});
|
|
404
|
-
return { confirmed, removeGitWorktree };
|
|
405
|
-
}
|
|
406
|
-
async function confirmDeleteLocal(threadName) {
|
|
407
|
-
return await confirm({
|
|
408
|
-
message: `'${threadName}' \uB85C\uCEEC \uC2A4\uB808\uB4DC\uB97C \uC0AD\uC81C\uD560\uAE4C\uC694?`,
|
|
409
|
-
default: true
|
|
410
|
-
});
|
|
411
|
-
}
|
|
412
|
-
|
|
413
152
|
// dist/index.js
|
|
414
|
-
import { confirm
|
|
153
|
+
import { confirm } from "@inquirer/prompts";
|
|
415
154
|
|
|
416
155
|
// dist/flip/server/Server.js
|
|
417
156
|
import express2 from "express";
|
|
@@ -422,7 +161,7 @@ import net from "net";
|
|
|
422
161
|
// dist/flip/routes/files.js
|
|
423
162
|
import { Router } from "express";
|
|
424
163
|
import fs2 from "fs";
|
|
425
|
-
import
|
|
164
|
+
import path from "path";
|
|
426
165
|
|
|
427
166
|
// dist/flip/constants/filters.js
|
|
428
167
|
var FILTERED_PATTERNS = /* @__PURE__ */ new Set([
|
|
@@ -489,8 +228,8 @@ function buildFileTree(rootPath, currentPath, maxDepth, depth = 0) {
|
|
|
489
228
|
return a.name.localeCompare(b.name);
|
|
490
229
|
});
|
|
491
230
|
for (const entry of entries) {
|
|
492
|
-
const fullPath =
|
|
493
|
-
const relativePath =
|
|
231
|
+
const fullPath = path.join(currentPath, entry.name);
|
|
232
|
+
const relativePath = path.relative(rootPath, fullPath);
|
|
494
233
|
const filtered = isFiltered(entry.name);
|
|
495
234
|
const node = {
|
|
496
235
|
path: relativePath,
|
|
@@ -510,8 +249,8 @@ function collectFlatFiles(rootPath, currentPath, maxDepth, depth = 0, result = {
|
|
|
510
249
|
return result;
|
|
511
250
|
const entries = fs2.readdirSync(currentPath, { withFileTypes: true });
|
|
512
251
|
for (const entry of entries) {
|
|
513
|
-
const fullPath =
|
|
514
|
-
const relativePath =
|
|
252
|
+
const fullPath = path.join(currentPath, entry.name);
|
|
253
|
+
const relativePath = path.relative(rootPath, fullPath);
|
|
515
254
|
if (isFiltered(entry.name)) {
|
|
516
255
|
if (entry.isDirectory()) {
|
|
517
256
|
result.filteredDirs.push(relativePath);
|
|
@@ -581,7 +320,7 @@ router.get("/flat", (req, res) => {
|
|
|
581
320
|
// dist/flip/routes/file.js
|
|
582
321
|
import { Router as Router2 } from "express";
|
|
583
322
|
import fs3 from "fs";
|
|
584
|
-
import
|
|
323
|
+
import path2 from "path";
|
|
585
324
|
var router2 = Router2();
|
|
586
325
|
var extensionMap = {
|
|
587
326
|
// JavaScript/TypeScript
|
|
@@ -694,11 +433,11 @@ var filenameMap = {
|
|
|
694
433
|
"CODEOWNERS": "gitignore"
|
|
695
434
|
};
|
|
696
435
|
function detectLanguage(filePath) {
|
|
697
|
-
const basename2 =
|
|
436
|
+
const basename2 = path2.basename(filePath);
|
|
698
437
|
if (filenameMap[basename2]) {
|
|
699
438
|
return filenameMap[basename2];
|
|
700
439
|
}
|
|
701
|
-
const ext =
|
|
440
|
+
const ext = path2.extname(filePath).slice(1).toLowerCase();
|
|
702
441
|
if (extensionMap[ext]) {
|
|
703
442
|
return extensionMap[ext];
|
|
704
443
|
}
|
|
@@ -727,9 +466,9 @@ router2.get("/", (req, res) => {
|
|
|
727
466
|
res.status(400).json({ error: "Missing path parameter" });
|
|
728
467
|
return;
|
|
729
468
|
}
|
|
730
|
-
const filePath =
|
|
731
|
-
const resolvedPath =
|
|
732
|
-
const resolvedCwd =
|
|
469
|
+
const filePath = path2.join(cwd, relativePath);
|
|
470
|
+
const resolvedPath = path2.resolve(filePath);
|
|
471
|
+
const resolvedCwd = path2.resolve(cwd);
|
|
733
472
|
if (!resolvedPath.startsWith(resolvedCwd)) {
|
|
734
473
|
res.status(403).json({ error: "Access denied" });
|
|
735
474
|
return;
|
|
@@ -756,7 +495,7 @@ router2.get("/", (req, res) => {
|
|
|
756
495
|
import { Router as Router3 } from "express";
|
|
757
496
|
import { execSync } from "child_process";
|
|
758
497
|
import * as fs4 from "fs";
|
|
759
|
-
import * as
|
|
498
|
+
import * as path3 from "path";
|
|
760
499
|
var router3 = Router3();
|
|
761
500
|
function parseGitStatus(output) {
|
|
762
501
|
const files = [];
|
|
@@ -916,7 +655,7 @@ router3.get("/diff", (req, res) => {
|
|
|
916
655
|
res.status(400).json({ error: "Missing path parameter" });
|
|
917
656
|
return;
|
|
918
657
|
}
|
|
919
|
-
const fullPath =
|
|
658
|
+
const fullPath = path3.join(cwd, relativePath);
|
|
920
659
|
let fileStatus = "modified";
|
|
921
660
|
try {
|
|
922
661
|
const statusOutput = execSync(`git status --porcelain -- "${relativePath}"`, {
|
|
@@ -992,10 +731,10 @@ function formatComments(comments) {
|
|
|
992
731
|
// dist/flip/output/clipboard.js
|
|
993
732
|
import { spawn } from "child_process";
|
|
994
733
|
import fs5 from "fs";
|
|
995
|
-
import
|
|
734
|
+
import path4 from "path";
|
|
996
735
|
import os from "os";
|
|
997
736
|
async function copyToClipboard(text) {
|
|
998
|
-
const tmpFile =
|
|
737
|
+
const tmpFile = path4.join(os.tmpdir(), `flip-clipboard-${Date.now()}.txt`);
|
|
999
738
|
const cleanupTmpFile = () => {
|
|
1000
739
|
try {
|
|
1001
740
|
fs5.unlinkSync(tmpFile);
|
|
@@ -1041,7 +780,7 @@ async function copyToClipboard(text) {
|
|
|
1041
780
|
// dist/flip/output/autopaste.js
|
|
1042
781
|
import { spawn as spawn2 } from "child_process";
|
|
1043
782
|
import fs6 from "fs";
|
|
1044
|
-
import
|
|
783
|
+
import path5 from "path";
|
|
1045
784
|
import os2 from "os";
|
|
1046
785
|
async function schedulePaste(sessionId) {
|
|
1047
786
|
if (process.platform !== "darwin") {
|
|
@@ -1051,7 +790,7 @@ async function schedulePaste(sessionId) {
|
|
|
1051
790
|
await pasteToOriginalSession(sessionId);
|
|
1052
791
|
}
|
|
1053
792
|
async function pasteToOriginalSession(sessionId) {
|
|
1054
|
-
const sessionFile =
|
|
793
|
+
const sessionFile = path5.join(os2.tmpdir(), `flip-view-session-${sessionId}`);
|
|
1055
794
|
let itermSessionId;
|
|
1056
795
|
try {
|
|
1057
796
|
itermSessionId = fs6.readFileSync(sessionFile, "utf-8").trim();
|
|
@@ -1098,7 +837,7 @@ async function pasteToOriginalSession(sessionId) {
|
|
|
1098
837
|
`;
|
|
1099
838
|
}
|
|
1100
839
|
try {
|
|
1101
|
-
const tmpScript =
|
|
840
|
+
const tmpScript = path5.join(os2.tmpdir(), `flip-paste-${Date.now()}.scpt`);
|
|
1102
841
|
fs6.writeFileSync(tmpScript, script);
|
|
1103
842
|
const cleanupScript = () => {
|
|
1104
843
|
try {
|
|
@@ -1177,21 +916,21 @@ router5.post("/", async (req, res) => {
|
|
|
1177
916
|
// dist/flip/routes/static.js
|
|
1178
917
|
import { Router as Router6 } from "express";
|
|
1179
918
|
import express from "express";
|
|
1180
|
-
import
|
|
919
|
+
import path6 from "path";
|
|
1181
920
|
import { fileURLToPath } from "url";
|
|
1182
921
|
import fs7 from "fs";
|
|
1183
922
|
function createStaticRouter() {
|
|
1184
923
|
const router6 = Router6();
|
|
1185
924
|
const __filename = fileURLToPath(import.meta.url);
|
|
1186
|
-
const __dirname =
|
|
1187
|
-
let distPath =
|
|
925
|
+
const __dirname = path6.dirname(__filename);
|
|
926
|
+
let distPath = path6.resolve(__dirname, "flip-ui/dist");
|
|
1188
927
|
if (!fs7.existsSync(distPath)) {
|
|
1189
|
-
distPath =
|
|
928
|
+
distPath = path6.resolve(__dirname, "../../../flip-ui/dist");
|
|
1190
929
|
}
|
|
1191
930
|
if (fs7.existsSync(distPath)) {
|
|
1192
931
|
router6.use(express.static(distPath));
|
|
1193
932
|
router6.get("*", (req, res) => {
|
|
1194
|
-
const indexPath =
|
|
933
|
+
const indexPath = path6.join(distPath, "index.html");
|
|
1195
934
|
if (fs7.existsSync(indexPath)) {
|
|
1196
935
|
res.sendFile(indexPath);
|
|
1197
936
|
} else {
|
|
@@ -1299,7 +1038,7 @@ function createChangesRouter(sessionManager) {
|
|
|
1299
1038
|
|
|
1300
1039
|
// dist/flip/watcher/FileWatcher.js
|
|
1301
1040
|
import chokidar from "chokidar";
|
|
1302
|
-
import * as
|
|
1041
|
+
import * as path7 from "path";
|
|
1303
1042
|
var FileWatcher = class {
|
|
1304
1043
|
watcher = null;
|
|
1305
1044
|
cwd;
|
|
@@ -1316,8 +1055,8 @@ var FileWatcher = class {
|
|
|
1316
1055
|
}
|
|
1317
1056
|
start() {
|
|
1318
1057
|
const shouldIgnore = (filePath) => {
|
|
1319
|
-
const relativePath =
|
|
1320
|
-
const segments = relativePath.split(
|
|
1058
|
+
const relativePath = path7.relative(this.cwd, filePath);
|
|
1059
|
+
const segments = relativePath.split(path7.sep);
|
|
1321
1060
|
return segments.some((segment) => segment !== ".git" && FILTERED_PATTERNS.has(segment));
|
|
1322
1061
|
};
|
|
1323
1062
|
this.watcher = chokidar.watch(this.cwd, {
|
|
@@ -1365,7 +1104,7 @@ var FileWatcher = class {
|
|
|
1365
1104
|
this.gitChangedCallbacks.push(callback);
|
|
1366
1105
|
}
|
|
1367
1106
|
handleFileEvent(filePath, event) {
|
|
1368
|
-
const relativePath =
|
|
1107
|
+
const relativePath = path7.relative(this.cwd, filePath);
|
|
1369
1108
|
if (relativePath.startsWith(".git")) {
|
|
1370
1109
|
this.emitGitChanged();
|
|
1371
1110
|
return;
|
|
@@ -1379,7 +1118,7 @@ var FileWatcher = class {
|
|
|
1379
1118
|
this.emitGitChanged();
|
|
1380
1119
|
}
|
|
1381
1120
|
handleDirEvent(dirPath, event) {
|
|
1382
|
-
const relativePath =
|
|
1121
|
+
const relativePath = path7.relative(this.cwd, dirPath);
|
|
1383
1122
|
if (relativePath.startsWith(".git")) {
|
|
1384
1123
|
return;
|
|
1385
1124
|
}
|
|
@@ -1739,7 +1478,7 @@ async function findExistingServer(startPort, endPort) {
|
|
|
1739
1478
|
|
|
1740
1479
|
// dist/flip/index.js
|
|
1741
1480
|
import open from "open";
|
|
1742
|
-
import
|
|
1481
|
+
import path8 from "path";
|
|
1743
1482
|
import fs8 from "fs";
|
|
1744
1483
|
import os3 from "os";
|
|
1745
1484
|
import http2 from "http";
|
|
@@ -1838,7 +1577,7 @@ async function runFlip(args) {
|
|
|
1838
1577
|
} else {
|
|
1839
1578
|
command = "oneshot";
|
|
1840
1579
|
}
|
|
1841
|
-
const cwd = pathArg ?
|
|
1580
|
+
const cwd = pathArg ? path8.resolve(pathArg) : process.cwd();
|
|
1842
1581
|
const finalSessionId = sessionId || getSessionId();
|
|
1843
1582
|
switch (command) {
|
|
1844
1583
|
case "setup": {
|
|
@@ -1952,7 +1691,7 @@ async function setupHotkey() {
|
|
|
1952
1691
|
} catch {
|
|
1953
1692
|
csqPath = new URL(import.meta.url).pathname;
|
|
1954
1693
|
}
|
|
1955
|
-
const nodeDir =
|
|
1694
|
+
const nodeDir = path8.dirname(nodePath);
|
|
1956
1695
|
const wrapperScript = `#!/bin/bash
|
|
1957
1696
|
# Add node to PATH (coprocess doesn't inherit shell PATH)
|
|
1958
1697
|
export PATH="${nodeDir}:$PATH"
|
|
@@ -1978,8 +1717,8 @@ else
|
|
|
1978
1717
|
exec ${csqPath} flip
|
|
1979
1718
|
fi
|
|
1980
1719
|
`;
|
|
1981
|
-
const scriptDir =
|
|
1982
|
-
const scriptPath =
|
|
1720
|
+
const scriptDir = path8.join(os3.homedir(), ".config", "csq");
|
|
1721
|
+
const scriptPath = path8.join(scriptDir, "flip-hotkey.sh");
|
|
1983
1722
|
if (!fs8.existsSync(scriptDir)) {
|
|
1984
1723
|
fs8.mkdirSync(scriptDir, { recursive: true });
|
|
1985
1724
|
}
|
|
@@ -2008,8 +1747,8 @@ fi
|
|
|
2008
1747
|
// dist/config.js
|
|
2009
1748
|
import * as fs9 from "fs";
|
|
2010
1749
|
import * as os4 from "os";
|
|
2011
|
-
import * as
|
|
2012
|
-
var GLOBAL_CONFIG_PATH =
|
|
1750
|
+
import * as path9 from "path";
|
|
1751
|
+
var GLOBAL_CONFIG_PATH = path9.join(os4.homedir(), ".code-squad", "config.json");
|
|
2013
1752
|
async function loadGlobalConfig() {
|
|
2014
1753
|
try {
|
|
2015
1754
|
const content = await fs9.promises.readFile(GLOBAL_CONFIG_PATH, "utf-8");
|
|
@@ -2024,7 +1763,7 @@ async function loadGlobalConfig() {
|
|
|
2024
1763
|
}
|
|
2025
1764
|
async function loadConfig(workspaceRoot) {
|
|
2026
1765
|
const globalConfig = await loadGlobalConfig();
|
|
2027
|
-
const normalizedPath =
|
|
1766
|
+
const normalizedPath = path9.resolve(workspaceRoot);
|
|
2028
1767
|
const projectConfig = globalConfig.projects?.[normalizedPath] ?? {};
|
|
2029
1768
|
const defaults = globalConfig.defaults ?? {};
|
|
2030
1769
|
return {
|
|
@@ -2044,7 +1783,7 @@ function getWorktreeCopyPatterns(config) {
|
|
|
2044
1783
|
|
|
2045
1784
|
// dist/fileUtils.js
|
|
2046
1785
|
import * as fs10 from "fs";
|
|
2047
|
-
import * as
|
|
1786
|
+
import * as path10 from "path";
|
|
2048
1787
|
import fg from "fast-glob";
|
|
2049
1788
|
async function copyFilesWithPatterns(sourceRoot, destRoot, patterns) {
|
|
2050
1789
|
const copied = [];
|
|
@@ -2064,10 +1803,10 @@ async function copyFilesWithPatterns(sourceRoot, destRoot, patterns) {
|
|
|
2064
1803
|
for (const absolutePath of files) {
|
|
2065
1804
|
try {
|
|
2066
1805
|
await copySingleFile(absolutePath, sourceRoot, destRoot);
|
|
2067
|
-
const relativePath =
|
|
1806
|
+
const relativePath = path10.relative(sourceRoot, absolutePath);
|
|
2068
1807
|
copied.push(relativePath);
|
|
2069
1808
|
} catch {
|
|
2070
|
-
const relativePath =
|
|
1809
|
+
const relativePath = path10.relative(sourceRoot, absolutePath);
|
|
2071
1810
|
failed.push(relativePath);
|
|
2072
1811
|
}
|
|
2073
1812
|
}
|
|
@@ -2077,9 +1816,9 @@ async function copyFilesWithPatterns(sourceRoot, destRoot, patterns) {
|
|
|
2077
1816
|
return { copied, failed };
|
|
2078
1817
|
}
|
|
2079
1818
|
async function copySingleFile(absolutePath, sourceRoot, destRoot) {
|
|
2080
|
-
const relativePath =
|
|
2081
|
-
const destPath =
|
|
2082
|
-
const destDir =
|
|
1819
|
+
const relativePath = path10.relative(sourceRoot, absolutePath);
|
|
1820
|
+
const destPath = path10.join(destRoot, relativePath);
|
|
1821
|
+
const destDir = path10.dirname(destPath);
|
|
2083
1822
|
await fs10.promises.mkdir(destDir, { recursive: true });
|
|
2084
1823
|
await fs10.promises.copyFile(absolutePath, destPath);
|
|
2085
1824
|
}
|
|
@@ -2091,38 +1830,30 @@ process.on("SIGINT", () => {
|
|
|
2091
1830
|
var gitAdapter = new GitAdapter();
|
|
2092
1831
|
async function main() {
|
|
2093
1832
|
const args = process.argv.slice(2);
|
|
2094
|
-
const
|
|
2095
|
-
const filteredArgs = args.filter((a) => a !== "-p" && a !== "--persistent");
|
|
2096
|
-
const command = filteredArgs[0];
|
|
1833
|
+
const command = args[0];
|
|
2097
1834
|
if (command === "--init" || command === "init") {
|
|
2098
1835
|
printShellInit();
|
|
2099
1836
|
return;
|
|
2100
1837
|
}
|
|
2101
1838
|
if (command === "flip") {
|
|
2102
|
-
await runFlip(
|
|
1839
|
+
await runFlip(args.slice(1));
|
|
2103
1840
|
return;
|
|
2104
1841
|
}
|
|
2105
1842
|
const workspaceRoot = await findGitRoot(process.cwd());
|
|
2106
1843
|
if (!workspaceRoot) {
|
|
2107
|
-
console.error(
|
|
1844
|
+
console.error(chalk.red("Error: Not a git repository"));
|
|
2108
1845
|
process.exit(1);
|
|
2109
1846
|
}
|
|
2110
1847
|
switch (command) {
|
|
2111
|
-
case "list":
|
|
2112
|
-
await listThreads(workspaceRoot);
|
|
2113
|
-
break;
|
|
2114
1848
|
case "new":
|
|
2115
|
-
await createWorktreeCommand(workspaceRoot,
|
|
1849
|
+
await createWorktreeCommand(workspaceRoot, args.slice(1));
|
|
2116
1850
|
break;
|
|
2117
1851
|
case "quit":
|
|
2118
1852
|
await quitWorktreeCommand();
|
|
2119
1853
|
break;
|
|
1854
|
+
case "list":
|
|
2120
1855
|
default:
|
|
2121
|
-
|
|
2122
|
-
await persistentInteractiveMode(workspaceRoot);
|
|
2123
|
-
} else {
|
|
2124
|
-
await interactiveMode(workspaceRoot);
|
|
2125
|
-
}
|
|
1856
|
+
await listWorktrees(workspaceRoot);
|
|
2126
1857
|
}
|
|
2127
1858
|
}
|
|
2128
1859
|
async function findGitRoot(cwd) {
|
|
@@ -2131,66 +1862,6 @@ async function findGitRoot(cwd) {
|
|
|
2131
1862
|
}
|
|
2132
1863
|
return cwd;
|
|
2133
1864
|
}
|
|
2134
|
-
function getProjectHash(workspaceRoot) {
|
|
2135
|
-
return crypto.createHash("sha256").update(workspaceRoot).digest("hex").slice(0, 8);
|
|
2136
|
-
}
|
|
2137
|
-
function getSessionsPath(workspaceRoot) {
|
|
2138
|
-
const projectHash = getProjectHash(workspaceRoot);
|
|
2139
|
-
const projectName = path12.basename(workspaceRoot);
|
|
2140
|
-
return path12.join(os5.homedir(), ".code-squad", "sessions", `${projectName}-${projectHash}.json`);
|
|
2141
|
-
}
|
|
2142
|
-
async function loadLocalThreads(workspaceRoot) {
|
|
2143
|
-
const sessionsPath = getSessionsPath(workspaceRoot);
|
|
2144
|
-
try {
|
|
2145
|
-
const content = await fs11.promises.readFile(sessionsPath, "utf-8");
|
|
2146
|
-
const data = JSON.parse(content);
|
|
2147
|
-
return data.localThreads || [];
|
|
2148
|
-
} catch {
|
|
2149
|
-
return [];
|
|
2150
|
-
}
|
|
2151
|
-
}
|
|
2152
|
-
async function saveLocalThreads(workspaceRoot, threads) {
|
|
2153
|
-
const sessionsPath = getSessionsPath(workspaceRoot);
|
|
2154
|
-
const dir = path12.dirname(sessionsPath);
|
|
2155
|
-
await fs11.promises.mkdir(dir, { recursive: true });
|
|
2156
|
-
await fs11.promises.writeFile(sessionsPath, JSON.stringify({ localThreads: threads }, null, 2));
|
|
2157
|
-
}
|
|
2158
|
-
async function addLocalThread(workspaceRoot, name) {
|
|
2159
|
-
const threads = await loadLocalThreads(workspaceRoot);
|
|
2160
|
-
const newThread = {
|
|
2161
|
-
id: Date.now().toString(),
|
|
2162
|
-
name,
|
|
2163
|
-
path: workspaceRoot,
|
|
2164
|
-
createdAt: Date.now()
|
|
2165
|
-
};
|
|
2166
|
-
threads.push(newThread);
|
|
2167
|
-
await saveLocalThreads(workspaceRoot, threads);
|
|
2168
|
-
return newThread;
|
|
2169
|
-
}
|
|
2170
|
-
async function removeLocalThread(workspaceRoot, id) {
|
|
2171
|
-
const threads = await loadLocalThreads(workspaceRoot);
|
|
2172
|
-
const filtered = threads.filter((t) => t.id !== id);
|
|
2173
|
-
await saveLocalThreads(workspaceRoot, filtered);
|
|
2174
|
-
}
|
|
2175
|
-
async function getAllThreads(workspaceRoot) {
|
|
2176
|
-
const worktrees = await gitAdapter.listWorktrees(workspaceRoot);
|
|
2177
|
-
const localThreads = await loadLocalThreads(workspaceRoot);
|
|
2178
|
-
const threads = [
|
|
2179
|
-
...worktrees.map((wt) => ({
|
|
2180
|
-
type: "worktree",
|
|
2181
|
-
name: wt.branch,
|
|
2182
|
-
path: wt.path,
|
|
2183
|
-
branch: wt.branch
|
|
2184
|
-
})),
|
|
2185
|
-
...localThreads.map((lt) => ({
|
|
2186
|
-
type: "local",
|
|
2187
|
-
name: lt.name,
|
|
2188
|
-
path: lt.path,
|
|
2189
|
-
id: lt.id
|
|
2190
|
-
}))
|
|
2191
|
-
];
|
|
2192
|
-
return threads;
|
|
2193
|
-
}
|
|
2194
1865
|
function printShellInit() {
|
|
2195
1866
|
const script = `
|
|
2196
1867
|
csq() {
|
|
@@ -2221,56 +1892,33 @@ csq() {
|
|
|
2221
1892
|
`.trim();
|
|
2222
1893
|
console.log(script);
|
|
2223
1894
|
}
|
|
2224
|
-
async function
|
|
2225
|
-
const
|
|
2226
|
-
if (
|
|
2227
|
-
console.log(
|
|
1895
|
+
async function listWorktrees(workspaceRoot) {
|
|
1896
|
+
const worktrees = await gitAdapter.listWorktrees(workspaceRoot);
|
|
1897
|
+
if (worktrees.length === 0) {
|
|
1898
|
+
console.log(chalk.dim("No worktrees found."));
|
|
2228
1899
|
return;
|
|
2229
1900
|
}
|
|
2230
|
-
for (const
|
|
2231
|
-
|
|
2232
|
-
console.log(`${typeLabel} ${t.name.padEnd(20)} ${chalk2.dim(t.path)}`);
|
|
2233
|
-
}
|
|
2234
|
-
}
|
|
2235
|
-
function parseNewArgs(args) {
|
|
2236
|
-
let name;
|
|
2237
|
-
let split = false;
|
|
2238
|
-
for (const arg of args) {
|
|
2239
|
-
if (arg === "-s" || arg === "--split") {
|
|
2240
|
-
split = true;
|
|
2241
|
-
} else if (!arg.startsWith("-") && !name) {
|
|
2242
|
-
name = arg;
|
|
2243
|
-
}
|
|
1901
|
+
for (const wt of worktrees) {
|
|
1902
|
+
console.log(`${chalk.cyan("[W]")} ${wt.branch.padEnd(20)} ${chalk.dim(wt.path)}`);
|
|
2244
1903
|
}
|
|
2245
|
-
return { name, split };
|
|
2246
1904
|
}
|
|
2247
1905
|
async function createWorktreeCommand(workspaceRoot, args) {
|
|
2248
|
-
const
|
|
1906
|
+
const name = args.find((a) => !a.startsWith("-"));
|
|
2249
1907
|
if (!name) {
|
|
2250
|
-
console.error(
|
|
2251
|
-
console.error(
|
|
1908
|
+
console.error(chalk.red("Error: Name is required"));
|
|
1909
|
+
console.error(chalk.dim("Usage: csq new <name>"));
|
|
2252
1910
|
process.exit(1);
|
|
2253
1911
|
}
|
|
2254
|
-
const repoName =
|
|
2255
|
-
const defaultBasePath =
|
|
2256
|
-
const worktreePath =
|
|
1912
|
+
const repoName = path11.basename(workspaceRoot);
|
|
1913
|
+
const defaultBasePath = path11.join(path11.dirname(workspaceRoot), `${repoName}.worktree`);
|
|
1914
|
+
const worktreePath = path11.join(defaultBasePath, name);
|
|
2257
1915
|
try {
|
|
2258
1916
|
await gitAdapter.createWorktree(worktreePath, name, workspaceRoot);
|
|
2259
|
-
console.log(
|
|
1917
|
+
console.log(chalk.green(`\u2713 Created worktree: ${name}`));
|
|
2260
1918
|
await copyWorktreeFiles(workspaceRoot, worktreePath);
|
|
2261
|
-
|
|
2262
|
-
await openNewTerminal(worktreePath);
|
|
2263
|
-
} else if (process.platform === "darwin") {
|
|
2264
|
-
const success = await cdInCurrentTerminal(worktreePath);
|
|
2265
|
-
if (!success) {
|
|
2266
|
-
console.log(chalk2.dim("\nNote: Auto-cd may require shell function setup."));
|
|
2267
|
-
console.log(chalk2.dim('Run: eval "$(csq --init)"'));
|
|
2268
|
-
}
|
|
2269
|
-
} else {
|
|
2270
|
-
console.log(worktreePath);
|
|
2271
|
-
}
|
|
1919
|
+
console.log(worktreePath);
|
|
2272
1920
|
} catch (error) {
|
|
2273
|
-
console.error(
|
|
1921
|
+
console.error(chalk.red(`Failed to create worktree: ${error.message}`));
|
|
2274
1922
|
process.exit(1);
|
|
2275
1923
|
}
|
|
2276
1924
|
}
|
|
@@ -2278,35 +1926,31 @@ async function quitWorktreeCommand() {
|
|
|
2278
1926
|
const cwd = process.cwd();
|
|
2279
1927
|
const context = await gitAdapter.getWorktreeContext(cwd);
|
|
2280
1928
|
if (!context.isWorktree) {
|
|
2281
|
-
console.error(
|
|
1929
|
+
console.error(chalk.red("Error: Not in a worktree"));
|
|
2282
1930
|
process.exit(1);
|
|
2283
1931
|
}
|
|
2284
1932
|
if (!context.mainRoot || !context.branch) {
|
|
2285
|
-
console.error(
|
|
1933
|
+
console.error(chalk.red("Error: Could not determine worktree context"));
|
|
2286
1934
|
process.exit(1);
|
|
2287
1935
|
}
|
|
2288
1936
|
const isDirty = await gitAdapter.hasDirtyState(cwd);
|
|
2289
1937
|
if (isDirty) {
|
|
2290
|
-
const confirmed = await
|
|
1938
|
+
const confirmed = await confirm({
|
|
2291
1939
|
message: "Uncommitted changes detected. Delete anyway?",
|
|
2292
1940
|
default: false
|
|
2293
1941
|
});
|
|
2294
1942
|
if (!confirmed) {
|
|
2295
|
-
console.log(
|
|
1943
|
+
console.log(chalk.dim("Cancelled."));
|
|
2296
1944
|
process.exit(0);
|
|
2297
1945
|
}
|
|
2298
1946
|
}
|
|
2299
1947
|
try {
|
|
2300
1948
|
await gitAdapter.removeWorktree(context.currentPath, context.mainRoot, true);
|
|
2301
1949
|
await gitAdapter.deleteBranch(context.branch, context.mainRoot, true);
|
|
2302
|
-
console.log(
|
|
2303
|
-
|
|
2304
|
-
await cdInCurrentTerminal(context.mainRoot);
|
|
2305
|
-
} else {
|
|
2306
|
-
console.log(context.mainRoot);
|
|
2307
|
-
}
|
|
1950
|
+
console.log(chalk.green(`\u2713 Deleted worktree and branch: ${context.branch}`));
|
|
1951
|
+
console.log(context.mainRoot);
|
|
2308
1952
|
} catch (error) {
|
|
2309
|
-
console.error(
|
|
1953
|
+
console.error(chalk.red(`Failed to quit: ${error.message}`));
|
|
2310
1954
|
process.exit(1);
|
|
2311
1955
|
}
|
|
2312
1956
|
}
|
|
@@ -2318,224 +1962,16 @@ async function copyWorktreeFiles(sourceRoot, destRoot) {
|
|
|
2318
1962
|
}
|
|
2319
1963
|
const { copied, failed } = await copyFilesWithPatterns(sourceRoot, destRoot, patterns);
|
|
2320
1964
|
if (copied.length > 0) {
|
|
2321
|
-
console.log(
|
|
1965
|
+
console.log(chalk.green(`\u2713 Copied ${copied.length} file(s) to worktree`));
|
|
2322
1966
|
}
|
|
2323
1967
|
if (failed.length > 0) {
|
|
2324
|
-
console.log(
|
|
2325
|
-
}
|
|
2326
|
-
}
|
|
2327
|
-
async function cdInCurrentTerminal(targetPath) {
|
|
2328
|
-
const { exec: exec2 } = await import("child_process");
|
|
2329
|
-
const escapedPath = targetPath.replace(/'/g, "'\\''");
|
|
2330
|
-
const termProgram = process.env.TERM_PROGRAM;
|
|
2331
|
-
if (termProgram === "vscode" || termProgram?.includes("cursor")) {
|
|
2332
|
-
console.log(targetPath);
|
|
2333
|
-
return true;
|
|
2334
|
-
}
|
|
2335
|
-
if (termProgram === "iTerm.app") {
|
|
2336
|
-
const script = `
|
|
2337
|
-
tell application "iTerm"
|
|
2338
|
-
tell current session of current window
|
|
2339
|
-
write text "cd '${escapedPath}'"
|
|
2340
|
-
end tell
|
|
2341
|
-
end tell`;
|
|
2342
|
-
return new Promise((resolve2) => {
|
|
2343
|
-
exec2(`osascript -e '${script}'`, (error, stdout, stderr) => {
|
|
2344
|
-
if (error) {
|
|
2345
|
-
console.log(targetPath);
|
|
2346
|
-
resolve2(true);
|
|
2347
|
-
return;
|
|
2348
|
-
}
|
|
2349
|
-
resolve2(true);
|
|
2350
|
-
});
|
|
2351
|
-
});
|
|
2352
|
-
}
|
|
2353
|
-
if (termProgram === "Apple_Terminal") {
|
|
2354
|
-
const terminalScript = `
|
|
2355
|
-
tell application "Terminal"
|
|
2356
|
-
do script "cd '${escapedPath}'" in front window
|
|
2357
|
-
end tell`;
|
|
2358
|
-
return new Promise((resolve2) => {
|
|
2359
|
-
exec2(`osascript -e '${terminalScript}'`, (error, stdout, stderr) => {
|
|
2360
|
-
if (error) {
|
|
2361
|
-
console.log(targetPath);
|
|
2362
|
-
resolve2(true);
|
|
2363
|
-
return;
|
|
2364
|
-
}
|
|
2365
|
-
resolve2(true);
|
|
2366
|
-
});
|
|
2367
|
-
});
|
|
2368
|
-
}
|
|
2369
|
-
const hasIterm = await new Promise((resolve2) => {
|
|
2370
|
-
exec2('mdfind "kMDItemCFBundleIdentifier == com.googlecode.iterm2"', (error, stdout) => {
|
|
2371
|
-
resolve2(!error && stdout.trim().length > 0);
|
|
2372
|
-
});
|
|
2373
|
-
});
|
|
2374
|
-
if (hasIterm) {
|
|
2375
|
-
const script = `
|
|
2376
|
-
tell application "iTerm2"
|
|
2377
|
-
tell current session of current window
|
|
2378
|
-
write text "cd '${escapedPath}'"
|
|
2379
|
-
end tell
|
|
2380
|
-
end tell`;
|
|
2381
|
-
return new Promise((resolve2) => {
|
|
2382
|
-
exec2(`osascript -e '${script}'`, (error) => {
|
|
2383
|
-
if (error) {
|
|
2384
|
-
console.log(targetPath);
|
|
2385
|
-
}
|
|
2386
|
-
resolve2(true);
|
|
2387
|
-
});
|
|
2388
|
-
});
|
|
2389
|
-
}
|
|
2390
|
-
console.log(targetPath);
|
|
2391
|
-
return true;
|
|
2392
|
-
}
|
|
2393
|
-
async function openNewTerminal(targetPath) {
|
|
2394
|
-
const { exec: exec2 } = await import("child_process");
|
|
2395
|
-
const escapedPath = targetPath.replace(/'/g, "'\\''");
|
|
2396
|
-
const hasIterm = await new Promise((resolve2) => {
|
|
2397
|
-
exec2('mdfind "kMDItemCFBundleIdentifier == com.googlecode.iterm2"', (error, stdout) => {
|
|
2398
|
-
resolve2(!error && stdout.trim().length > 0);
|
|
2399
|
-
});
|
|
2400
|
-
});
|
|
2401
|
-
if (hasIterm) {
|
|
2402
|
-
const script = `
|
|
2403
|
-
tell application "iTerm2"
|
|
2404
|
-
tell current session of current window
|
|
2405
|
-
set newSession to (split vertically with default profile)
|
|
2406
|
-
tell newSession
|
|
2407
|
-
write text "cd '${escapedPath}'"
|
|
2408
|
-
end tell
|
|
2409
|
-
end tell
|
|
2410
|
-
end tell`;
|
|
2411
|
-
return new Promise((resolve2) => {
|
|
2412
|
-
exec2(`osascript -e '${script}'`, (error) => {
|
|
2413
|
-
resolve2(!error);
|
|
2414
|
-
});
|
|
2415
|
-
});
|
|
2416
|
-
}
|
|
2417
|
-
const terminalScript = `
|
|
2418
|
-
tell application "Terminal"
|
|
2419
|
-
activate
|
|
2420
|
-
do script "cd '${escapedPath}'"
|
|
2421
|
-
end tell`;
|
|
2422
|
-
return new Promise((resolve2) => {
|
|
2423
|
-
exec2(`osascript -e '${terminalScript}'`, (error) => {
|
|
2424
|
-
resolve2(!error);
|
|
2425
|
-
});
|
|
2426
|
-
});
|
|
2427
|
-
}
|
|
2428
|
-
async function interactiveMode(workspaceRoot) {
|
|
2429
|
-
const result = await runInteraction(workspaceRoot);
|
|
2430
|
-
if (result?.cdPath) {
|
|
2431
|
-
await cdInCurrentTerminal(result.cdPath);
|
|
2432
|
-
}
|
|
2433
|
-
}
|
|
2434
|
-
async function persistentInteractiveMode(workspaceRoot) {
|
|
2435
|
-
while (true) {
|
|
2436
|
-
console.clear();
|
|
2437
|
-
const result = await runInteraction(workspaceRoot, true);
|
|
2438
|
-
if (result?.exit) {
|
|
2439
|
-
break;
|
|
2440
|
-
}
|
|
2441
|
-
if (result?.cdPath) {
|
|
2442
|
-
await openNewTerminal(result.cdPath);
|
|
2443
|
-
}
|
|
1968
|
+
console.log(chalk.yellow(`\u26A0 Failed to copy ${failed.length} file(s)`));
|
|
2444
1969
|
}
|
|
2445
1970
|
}
|
|
2446
|
-
async function executeDelete(thread, workspaceRoot) {
|
|
2447
|
-
if (thread.type === "local") {
|
|
2448
|
-
const confirmed = await confirmDeleteLocal(thread.name);
|
|
2449
|
-
if (confirmed) {
|
|
2450
|
-
await removeLocalThread(workspaceRoot, thread.id);
|
|
2451
|
-
console.log(chalk2.green(`\u2713 Deleted local thread: ${thread.name}`));
|
|
2452
|
-
} else {
|
|
2453
|
-
console.log(chalk2.dim("Cancelled."));
|
|
2454
|
-
}
|
|
2455
|
-
} else {
|
|
2456
|
-
const { confirmed, removeGitWorktree } = await confirmDeleteWorktree(thread.name);
|
|
2457
|
-
if (confirmed && removeGitWorktree) {
|
|
2458
|
-
try {
|
|
2459
|
-
await gitAdapter.removeWorktree(thread.path, workspaceRoot, true);
|
|
2460
|
-
await gitAdapter.deleteBranch(thread.branch, workspaceRoot, true);
|
|
2461
|
-
console.log(chalk2.green(`\u2713 Deleted worktree and branch: ${thread.name}`));
|
|
2462
|
-
} catch (error) {
|
|
2463
|
-
console.error(chalk2.red(`Failed to delete: ${error.message}`));
|
|
2464
|
-
}
|
|
2465
|
-
} else if (confirmed) {
|
|
2466
|
-
console.log(chalk2.yellow("Worktree kept."));
|
|
2467
|
-
} else {
|
|
2468
|
-
console.log(chalk2.dim("Cancelled."));
|
|
2469
|
-
}
|
|
2470
|
-
}
|
|
2471
|
-
}
|
|
2472
|
-
async function runInteraction(workspaceRoot, _persistent = false) {
|
|
2473
|
-
const threads = await getAllThreads(workspaceRoot);
|
|
2474
|
-
const repoName = path12.basename(workspaceRoot);
|
|
2475
|
-
const choice = await selectThread(threads, repoName);
|
|
2476
|
-
if (choice.type === "exit") {
|
|
2477
|
-
return { exit: true };
|
|
2478
|
-
}
|
|
2479
|
-
let targetPath;
|
|
2480
|
-
switch (choice.type) {
|
|
2481
|
-
case "existing": {
|
|
2482
|
-
const thread = choice.thread;
|
|
2483
|
-
const action = await selectThreadAction(thread.name);
|
|
2484
|
-
if (action === "open") {
|
|
2485
|
-
targetPath = thread.path;
|
|
2486
|
-
} else if (action === "delete") {
|
|
2487
|
-
await executeDelete(thread, workspaceRoot);
|
|
2488
|
-
return null;
|
|
2489
|
-
} else {
|
|
2490
|
-
return null;
|
|
2491
|
-
}
|
|
2492
|
-
break;
|
|
2493
|
-
}
|
|
2494
|
-
case "delete-selected": {
|
|
2495
|
-
const thread = choice.thread;
|
|
2496
|
-
await executeDelete(thread, workspaceRoot);
|
|
2497
|
-
return null;
|
|
2498
|
-
}
|
|
2499
|
-
case "new": {
|
|
2500
|
-
const newType = await selectNewThreadType();
|
|
2501
|
-
if (newType === "back") {
|
|
2502
|
-
return null;
|
|
2503
|
-
}
|
|
2504
|
-
if (newType === "worktree") {
|
|
2505
|
-
const defaultBasePath = path12.join(path12.dirname(workspaceRoot), `${repoName}.worktree`);
|
|
2506
|
-
const form = await newWorktreeForm(defaultBasePath);
|
|
2507
|
-
if (!form) {
|
|
2508
|
-
return null;
|
|
2509
|
-
}
|
|
2510
|
-
try {
|
|
2511
|
-
await gitAdapter.createWorktree(form.path, form.name, workspaceRoot);
|
|
2512
|
-
console.log(chalk2.green(`\u2713 Created worktree: ${form.name}`));
|
|
2513
|
-
await copyWorktreeFiles(workspaceRoot, form.path);
|
|
2514
|
-
targetPath = form.path;
|
|
2515
|
-
} catch (error) {
|
|
2516
|
-
console.error(chalk2.red(`Failed to create worktree: ${error.message}`));
|
|
2517
|
-
}
|
|
2518
|
-
} else {
|
|
2519
|
-
const name = await newLocalForm();
|
|
2520
|
-
if (!name) {
|
|
2521
|
-
return null;
|
|
2522
|
-
}
|
|
2523
|
-
await addLocalThread(workspaceRoot, name);
|
|
2524
|
-
console.log(chalk2.green(`\u2713 Created local thread: ${name}`));
|
|
2525
|
-
targetPath = workspaceRoot;
|
|
2526
|
-
}
|
|
2527
|
-
break;
|
|
2528
|
-
}
|
|
2529
|
-
}
|
|
2530
|
-
if (targetPath) {
|
|
2531
|
-
return { cdPath: targetPath };
|
|
2532
|
-
}
|
|
2533
|
-
return null;
|
|
2534
|
-
}
|
|
2535
1971
|
main().catch((error) => {
|
|
2536
1972
|
if (error.message?.includes("SIGINT") || error.message?.includes("force closed")) {
|
|
2537
1973
|
process.exit(130);
|
|
2538
1974
|
}
|
|
2539
|
-
console.error(
|
|
1975
|
+
console.error(chalk.red(`Error: ${error.message}`));
|
|
2540
1976
|
process.exit(1);
|
|
2541
1977
|
});
|