code-squad-cli 1.2.10 → 1.2.12
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/flip/index.js +29 -91
- package/dist/flip/output/autopaste.js +11 -7
- package/dist/flip/output/clipboard.js +30 -36
- package/dist/flip/routes/events.js +2 -1
- package/dist/flip/server/Server.d.ts +1 -1
- package/dist/flip/server/Server.js +9 -4
- package/dist/flip/watcher/FileWatcher.js +10 -1
- package/dist/index.js +98 -137
- package/package.json +1 -1
package/dist/flip/index.js
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
import { Server, findFreePort } from './server/Server.js';
|
|
2
2
|
import open from 'open';
|
|
3
3
|
import path from 'path';
|
|
4
|
-
import fs from 'fs';
|
|
5
|
-
import os from 'os';
|
|
6
4
|
import { execSync } from 'child_process';
|
|
7
|
-
import { confirm } from '@inquirer/prompts';
|
|
8
5
|
import { copyToClipboard } from './output/clipboard.js';
|
|
9
6
|
const DEFAULT_PORT = 51234;
|
|
10
7
|
function formatTime() {
|
|
@@ -130,7 +127,7 @@ function printUsage() {
|
|
|
130
127
|
console.error('Commands:');
|
|
131
128
|
console.error(' serve [path] Start server in daemon mode (keeps running)');
|
|
132
129
|
console.error(' open Open browser to existing server');
|
|
133
|
-
console.error(' setup Setup
|
|
130
|
+
console.error(' setup Setup Alt+; hotkey in shell config');
|
|
134
131
|
console.error(' (no command) Start server + open browser (one-shot mode)');
|
|
135
132
|
console.error('');
|
|
136
133
|
console.error('Options:');
|
|
@@ -138,10 +135,7 @@ function printUsage() {
|
|
|
138
135
|
console.error(' --session <uuid> Session ID for paste-back tracking');
|
|
139
136
|
}
|
|
140
137
|
async function setupHotkey() {
|
|
141
|
-
|
|
142
|
-
const applescriptPath = path.join(configDir, 'flip.applescript');
|
|
143
|
-
const shPath = path.join(configDir, 'flip.sh');
|
|
144
|
-
// Get node path
|
|
138
|
+
// Get node and csq paths for iTerm2 setup
|
|
145
139
|
let nodePath;
|
|
146
140
|
try {
|
|
147
141
|
nodePath = execSync('which node', { encoding: 'utf-8' }).trim();
|
|
@@ -149,99 +143,43 @@ async function setupHotkey() {
|
|
|
149
143
|
catch {
|
|
150
144
|
nodePath = '/usr/local/bin/node';
|
|
151
145
|
}
|
|
152
|
-
// Get csq path - after bundling, all code is in dist/index.js
|
|
153
|
-
// so import.meta.url points directly to the entry point
|
|
154
146
|
const csqPath = new URL(import.meta.url).pathname;
|
|
155
|
-
// Create config directory
|
|
156
|
-
fs.mkdirSync(configDir, { recursive: true });
|
|
157
|
-
// Write AppleScript
|
|
158
|
-
const applescriptContent = `#!/usr/bin/osascript
|
|
159
|
-
|
|
160
|
-
-- flip hotkey script
|
|
161
|
-
-- Generated by: csq flip setup
|
|
162
|
-
|
|
163
|
-
tell application "iTerm2"
|
|
164
|
-
tell current session of current window
|
|
165
|
-
set originalSessionId to id
|
|
166
|
-
set sessionUUID to do shell script "uuidgen"
|
|
167
|
-
set currentPath to variable named "path"
|
|
168
|
-
do shell script "echo '" & originalSessionId & "' > /tmp/flip-view-session-" & sessionUUID
|
|
169
|
-
do shell script "nohup ${nodePath} ${csqPath} flip --session " & sessionUUID & " " & quoted form of currentPath & " > /tmp/flip.log 2>&1 &"
|
|
170
|
-
end tell
|
|
171
|
-
end tell
|
|
172
|
-
|
|
173
|
-
return ""`;
|
|
174
|
-
fs.writeFileSync(applescriptPath, applescriptContent);
|
|
175
|
-
fs.chmodSync(applescriptPath, '755');
|
|
176
|
-
// Write shell wrapper
|
|
177
|
-
const shContent = `#!/bin/bash
|
|
178
|
-
osascript ${applescriptPath} > /dev/null 2>&1
|
|
179
|
-
`;
|
|
180
|
-
fs.writeFileSync(shPath, shContent);
|
|
181
|
-
fs.chmodSync(shPath, '755');
|
|
182
147
|
console.log('');
|
|
183
148
|
console.log('┌─────────────────────────────────────────────────┐');
|
|
184
|
-
console.log('│ Flip Hotkey Setup
|
|
149
|
+
console.log('│ Flip Hotkey Setup │');
|
|
185
150
|
console.log('└─────────────────────────────────────────────────┘');
|
|
186
151
|
console.log('');
|
|
187
|
-
console.log('
|
|
188
|
-
console.log(` ${shPath}`);
|
|
152
|
+
console.log('Choose one of the following options:');
|
|
189
153
|
console.log('');
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
});
|
|
195
|
-
if (openSettings) {
|
|
196
|
-
try {
|
|
197
|
-
execSync(`osascript -e 'tell application "iTerm2" to activate' -e 'tell application "System Events" to keystroke "," using command down'`);
|
|
198
|
-
console.log('');
|
|
199
|
-
console.log(' → iTerm2 Settings opened. Navigate to: Keys → Key Bindings');
|
|
200
|
-
}
|
|
201
|
-
catch {
|
|
202
|
-
console.log(' → Could not open settings automatically. Open manually: iTerm2 → Settings → Keys → Key Bindings');
|
|
203
|
-
}
|
|
204
|
-
}
|
|
154
|
+
console.log('─────────────────────────────────────────────────');
|
|
155
|
+
console.log('Option 1: Zsh keybinding (Alt+;)');
|
|
156
|
+
console.log('─────────────────────────────────────────────────');
|
|
157
|
+
console.log('Add to ~/.zshrc:');
|
|
205
158
|
console.log('');
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
});
|
|
211
|
-
if (!ready) {
|
|
212
|
-
console.log('');
|
|
213
|
-
console.log('Run `csq flip setup` again when ready.');
|
|
214
|
-
return;
|
|
215
|
-
}
|
|
159
|
+
console.log(' # Flip hotkey (Alt+;)');
|
|
160
|
+
console.log(' csq-flip-widget() { (csq flip &); zle reset-prompt; }');
|
|
161
|
+
console.log(' zle -N csq-flip-widget');
|
|
162
|
+
console.log(" bindkey '\\e;' csq-flip-widget");
|
|
216
163
|
console.log('');
|
|
217
|
-
console.log('
|
|
218
|
-
console.log('│ Configure the Key Binding: │');
|
|
219
|
-
console.log('├─────────────────────────────────────────────────┤');
|
|
220
|
-
console.log('│ 1. Keyboard Shortcut: Press your hotkey │');
|
|
221
|
-
console.log('│ (e.g., ⌘⇧F) │');
|
|
222
|
-
console.log('│ │');
|
|
223
|
-
console.log('│ 2. Action: Select "Run Coprocess" │');
|
|
224
|
-
console.log('│ │');
|
|
225
|
-
console.log('│ 3. Command: Paste the path (copied below) │');
|
|
226
|
-
console.log('└─────────────────────────────────────────────────┘');
|
|
164
|
+
console.log('Then run: source ~/.zshrc');
|
|
227
165
|
console.log('');
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
console.log(
|
|
166
|
+
console.log('─────────────────────────────────────────────────');
|
|
167
|
+
console.log('Option 2: iTerm2 hotkey (any key combo)');
|
|
168
|
+
console.log('─────────────────────────────────────────────────');
|
|
169
|
+
console.log('1. iTerm2 → Settings → Keys → Key Bindings');
|
|
170
|
+
console.log('2. Click + to add new binding');
|
|
171
|
+
console.log('3. Set your hotkey (e.g., ⌘⇧F)');
|
|
172
|
+
console.log('4. Action: "Run Coprocess"');
|
|
173
|
+
console.log('5. Command:');
|
|
231
174
|
console.log('');
|
|
232
|
-
|
|
233
|
-
message: 'Done configuring? (Paste the path and click OK)',
|
|
234
|
-
default: true,
|
|
235
|
-
});
|
|
175
|
+
console.log(` ${nodePath} ${csqPath} flip`);
|
|
236
176
|
console.log('');
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
console.log('│ 4. Submit → text appears in terminal │');
|
|
245
|
-
console.log('└─────────────────────────────────────────────────┘');
|
|
177
|
+
try {
|
|
178
|
+
await copyToClipboard(`${nodePath} ${csqPath} flip`);
|
|
179
|
+
console.log(' (Copied to clipboard!)');
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
// Ignore clipboard errors
|
|
183
|
+
}
|
|
246
184
|
console.log('');
|
|
247
185
|
}
|
|
@@ -73,18 +73,22 @@ async function pasteToOriginalSession(sessionId) {
|
|
|
73
73
|
// Write script to temp file and execute via detached process
|
|
74
74
|
const tmpScript = path.join(os.tmpdir(), `flip-paste-${Date.now()}.scpt`);
|
|
75
75
|
fs.writeFileSync(tmpScript, script);
|
|
76
|
+
const cleanupScript = () => {
|
|
77
|
+
try {
|
|
78
|
+
fs.unlinkSync(tmpScript);
|
|
79
|
+
}
|
|
80
|
+
catch { /* ignore */ }
|
|
81
|
+
};
|
|
76
82
|
const child = spawn('osascript', [tmpScript], {
|
|
77
83
|
detached: true,
|
|
78
84
|
stdio: 'ignore',
|
|
79
85
|
});
|
|
86
|
+
// Cleanup on process exit or error
|
|
87
|
+
child.on('exit', cleanupScript);
|
|
88
|
+
child.on('error', cleanupScript);
|
|
80
89
|
child.unref();
|
|
81
|
-
//
|
|
82
|
-
setTimeout(
|
|
83
|
-
try {
|
|
84
|
-
fs.unlinkSync(tmpScript);
|
|
85
|
-
}
|
|
86
|
-
catch { }
|
|
87
|
-
}, 5000);
|
|
90
|
+
// Fallback cleanup in case events don't fire (safety net)
|
|
91
|
+
setTimeout(cleanupScript, 10000);
|
|
88
92
|
}
|
|
89
93
|
catch (e) {
|
|
90
94
|
console.error('Failed to paste to iTerm:', e);
|
|
@@ -6,56 +6,50 @@ import os from 'os';
|
|
|
6
6
|
* Copy text to system clipboard
|
|
7
7
|
*/
|
|
8
8
|
export async function copyToClipboard(text) {
|
|
9
|
+
const tmpFile = path.join(os.tmpdir(), `flip-clipboard-${Date.now()}.txt`);
|
|
10
|
+
const cleanupTmpFile = () => {
|
|
11
|
+
try {
|
|
12
|
+
fs.unlinkSync(tmpFile);
|
|
13
|
+
}
|
|
14
|
+
catch { /* ignore */ }
|
|
15
|
+
};
|
|
16
|
+
const spawnWithCleanup = (command, args) => {
|
|
17
|
+
const child = spawn(command, args, {
|
|
18
|
+
detached: true,
|
|
19
|
+
stdio: 'ignore',
|
|
20
|
+
});
|
|
21
|
+
child.on('error', cleanupTmpFile);
|
|
22
|
+
child.on('exit', cleanupTmpFile);
|
|
23
|
+
child.unref();
|
|
24
|
+
};
|
|
9
25
|
if (process.platform === 'darwin') {
|
|
10
|
-
// Write to temp file first, then use pbcopy via shell
|
|
11
|
-
const tmpFile = path.join(os.tmpdir(), `flip-clipboard-${Date.now()}.txt`);
|
|
12
26
|
try {
|
|
13
27
|
fs.writeFileSync(tmpFile, text);
|
|
14
|
-
|
|
15
|
-
const child = spawn('sh', ['-c', `cat "${tmpFile}" | pbcopy && rm "${tmpFile}"`], {
|
|
16
|
-
detached: true,
|
|
17
|
-
stdio: 'ignore',
|
|
18
|
-
});
|
|
19
|
-
child.unref();
|
|
28
|
+
spawnWithCleanup('sh', ['-c', `cat "${tmpFile}" | pbcopy && rm "${tmpFile}"`]);
|
|
20
29
|
}
|
|
21
30
|
catch (e) {
|
|
22
|
-
// Fallback: just leave the file for manual copy
|
|
23
31
|
console.log(`Output saved to: ${tmpFile}`);
|
|
24
32
|
throw e;
|
|
25
33
|
}
|
|
26
34
|
}
|
|
27
35
|
else if (process.platform === 'linux') {
|
|
28
|
-
const tmpFile = path.join(os.tmpdir(), `flip-clipboard-${Date.now()}.txt`);
|
|
29
|
-
fs.writeFileSync(tmpFile, text);
|
|
30
36
|
try {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
stdio: 'ignore',
|
|
34
|
-
});
|
|
35
|
-
child.unref();
|
|
37
|
+
fs.writeFileSync(tmpFile, text);
|
|
38
|
+
spawnWithCleanup('sh', ['-c', `(xclip -selection clipboard < "${tmpFile}" || xsel --clipboard --input < "${tmpFile}") && rm "${tmpFile}"`]);
|
|
36
39
|
}
|
|
37
|
-
catch {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
detached: true,
|
|
41
|
-
stdio: 'ignore',
|
|
42
|
-
});
|
|
43
|
-
child.unref();
|
|
44
|
-
}
|
|
45
|
-
catch (e) {
|
|
46
|
-
// Final fallback if both xclip and xsel fail
|
|
47
|
-
console.log(`Output saved to: ${tmpFile}`);
|
|
48
|
-
throw e;
|
|
49
|
-
}
|
|
40
|
+
catch (e) {
|
|
41
|
+
console.log(`Output saved to: ${tmpFile}`);
|
|
42
|
+
throw e;
|
|
50
43
|
}
|
|
51
44
|
}
|
|
52
45
|
else if (process.platform === 'win32') {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
46
|
+
try {
|
|
47
|
+
fs.writeFileSync(tmpFile, text);
|
|
48
|
+
spawnWithCleanup('cmd', ['/c', `type "${tmpFile}" | clip && del "${tmpFile}"`]);
|
|
49
|
+
}
|
|
50
|
+
catch (e) {
|
|
51
|
+
console.log(`Output saved to: ${tmpFile}`);
|
|
52
|
+
throw e;
|
|
53
|
+
}
|
|
60
54
|
}
|
|
61
55
|
}
|
|
@@ -16,9 +16,10 @@ export function createEventsRouter(sseManager) {
|
|
|
16
16
|
const heartbeat = setInterval(() => {
|
|
17
17
|
res.write(':heartbeat\n\n');
|
|
18
18
|
}, 30000);
|
|
19
|
-
// Cleanup on close (removeClient is handled by SSEManager)
|
|
19
|
+
// Cleanup on close (removeClient is handled by SSEManager's close listener)
|
|
20
20
|
req.on('close', () => {
|
|
21
21
|
clearInterval(heartbeat);
|
|
22
|
+
res.end();
|
|
22
23
|
});
|
|
23
24
|
});
|
|
24
25
|
return router;
|
|
@@ -8,4 +8,4 @@ export declare class Server {
|
|
|
8
8
|
constructor(cwd: string, port: number);
|
|
9
9
|
run(): Promise<string | null>;
|
|
10
10
|
}
|
|
11
|
-
export declare function findFreePort(preferred: number): Promise<number>;
|
|
11
|
+
export declare function findFreePort(preferred: number, maxPort?: number): Promise<number>;
|
|
@@ -69,8 +69,12 @@ export class Server {
|
|
|
69
69
|
});
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
|
-
export async function findFreePort(preferred) {
|
|
73
|
-
return new Promise((resolve) => {
|
|
72
|
+
export async function findFreePort(preferred, maxPort = 65535) {
|
|
73
|
+
return new Promise((resolve, reject) => {
|
|
74
|
+
if (preferred > maxPort) {
|
|
75
|
+
reject(new Error(`No available port found in range up to ${maxPort}`));
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
74
78
|
const server = net.createServer();
|
|
75
79
|
server.listen(preferred, '127.0.0.1', () => {
|
|
76
80
|
server.close(() => {
|
|
@@ -78,8 +82,9 @@ export async function findFreePort(preferred) {
|
|
|
78
82
|
});
|
|
79
83
|
});
|
|
80
84
|
server.on('error', () => {
|
|
81
|
-
|
|
82
|
-
|
|
85
|
+
server.close(() => {
|
|
86
|
+
findFreePort(preferred + 1, maxPort).then(resolve).catch(reject);
|
|
87
|
+
});
|
|
83
88
|
});
|
|
84
89
|
});
|
|
85
90
|
}
|
|
@@ -38,7 +38,16 @@ export class FileWatcher {
|
|
|
38
38
|
.on('change', (filePath) => this.handleFileEvent(filePath, 'change'))
|
|
39
39
|
.on('addDir', (filePath) => this.handleDirEvent(filePath, 'addDir'))
|
|
40
40
|
.on('unlinkDir', (filePath) => this.handleDirEvent(filePath, 'unlinkDir'))
|
|
41
|
-
.on('error', (error) =>
|
|
41
|
+
.on('error', (error) => {
|
|
42
|
+
const code = error.code;
|
|
43
|
+
if (code === 'ENFILE' || code === 'EMFILE') {
|
|
44
|
+
console.error('File descriptor limit reached. Disabling file watcher.');
|
|
45
|
+
this.stop();
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
console.error('Watcher error:', error);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
42
51
|
}
|
|
43
52
|
async stop() {
|
|
44
53
|
if (this.filesDebounceTimer) {
|
package/dist/index.js
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
// dist/index.js
|
|
4
4
|
import * as path12 from "path";
|
|
5
|
-
import * as
|
|
6
|
-
import * as
|
|
5
|
+
import * as fs10 from "fs";
|
|
6
|
+
import * as os4 from "os";
|
|
7
7
|
import * as crypto from "crypto";
|
|
8
8
|
import chalk2 from "chalk";
|
|
9
9
|
|
|
@@ -411,7 +411,7 @@ async function confirmDeleteLocal(threadName) {
|
|
|
411
411
|
}
|
|
412
412
|
|
|
413
413
|
// dist/index.js
|
|
414
|
-
import { confirm as
|
|
414
|
+
import { confirm as confirm2 } from "@inquirer/prompts";
|
|
415
415
|
|
|
416
416
|
// dist/flip/server/Server.js
|
|
417
417
|
import express2 from "express";
|
|
@@ -923,48 +923,46 @@ import fs5 from "fs";
|
|
|
923
923
|
import path5 from "path";
|
|
924
924
|
import os from "os";
|
|
925
925
|
async function copyToClipboard(text) {
|
|
926
|
+
const tmpFile = path5.join(os.tmpdir(), `flip-clipboard-${Date.now()}.txt`);
|
|
927
|
+
const cleanupTmpFile = () => {
|
|
928
|
+
try {
|
|
929
|
+
fs5.unlinkSync(tmpFile);
|
|
930
|
+
} catch {
|
|
931
|
+
}
|
|
932
|
+
};
|
|
933
|
+
const spawnWithCleanup = (command, args) => {
|
|
934
|
+
const child = spawn(command, args, {
|
|
935
|
+
detached: true,
|
|
936
|
+
stdio: "ignore"
|
|
937
|
+
});
|
|
938
|
+
child.on("error", cleanupTmpFile);
|
|
939
|
+
child.on("exit", cleanupTmpFile);
|
|
940
|
+
child.unref();
|
|
941
|
+
};
|
|
926
942
|
if (process.platform === "darwin") {
|
|
927
|
-
const tmpFile = path5.join(os.tmpdir(), `flip-clipboard-${Date.now()}.txt`);
|
|
928
943
|
try {
|
|
929
944
|
fs5.writeFileSync(tmpFile, text);
|
|
930
|
-
|
|
931
|
-
detached: true,
|
|
932
|
-
stdio: "ignore"
|
|
933
|
-
});
|
|
934
|
-
child.unref();
|
|
945
|
+
spawnWithCleanup("sh", ["-c", `cat "${tmpFile}" | pbcopy && rm "${tmpFile}"`]);
|
|
935
946
|
} catch (e) {
|
|
936
947
|
console.log(`Output saved to: ${tmpFile}`);
|
|
937
948
|
throw e;
|
|
938
949
|
}
|
|
939
950
|
} else if (process.platform === "linux") {
|
|
940
|
-
const tmpFile = path5.join(os.tmpdir(), `flip-clipboard-${Date.now()}.txt`);
|
|
941
|
-
fs5.writeFileSync(tmpFile, text);
|
|
942
951
|
try {
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
});
|
|
947
|
-
|
|
948
|
-
} catch {
|
|
949
|
-
try {
|
|
950
|
-
const child = spawn("sh", ["-c", `cat "${tmpFile}" | xsel --clipboard --input && rm "${tmpFile}"`], {
|
|
951
|
-
detached: true,
|
|
952
|
-
stdio: "ignore"
|
|
953
|
-
});
|
|
954
|
-
child.unref();
|
|
955
|
-
} catch (e) {
|
|
956
|
-
console.log(`Output saved to: ${tmpFile}`);
|
|
957
|
-
throw e;
|
|
958
|
-
}
|
|
952
|
+
fs5.writeFileSync(tmpFile, text);
|
|
953
|
+
spawnWithCleanup("sh", ["-c", `(xclip -selection clipboard < "${tmpFile}" || xsel --clipboard --input < "${tmpFile}") && rm "${tmpFile}"`]);
|
|
954
|
+
} catch (e) {
|
|
955
|
+
console.log(`Output saved to: ${tmpFile}`);
|
|
956
|
+
throw e;
|
|
959
957
|
}
|
|
960
958
|
} else if (process.platform === "win32") {
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
959
|
+
try {
|
|
960
|
+
fs5.writeFileSync(tmpFile, text);
|
|
961
|
+
spawnWithCleanup("cmd", ["/c", `type "${tmpFile}" | clip && del "${tmpFile}"`]);
|
|
962
|
+
} catch (e) {
|
|
963
|
+
console.log(`Output saved to: ${tmpFile}`);
|
|
964
|
+
throw e;
|
|
965
|
+
}
|
|
968
966
|
}
|
|
969
967
|
}
|
|
970
968
|
|
|
@@ -1030,17 +1028,20 @@ async function pasteToOriginalSession(sessionId) {
|
|
|
1030
1028
|
try {
|
|
1031
1029
|
const tmpScript = path6.join(os2.tmpdir(), `flip-paste-${Date.now()}.scpt`);
|
|
1032
1030
|
fs6.writeFileSync(tmpScript, script);
|
|
1031
|
+
const cleanupScript = () => {
|
|
1032
|
+
try {
|
|
1033
|
+
fs6.unlinkSync(tmpScript);
|
|
1034
|
+
} catch {
|
|
1035
|
+
}
|
|
1036
|
+
};
|
|
1033
1037
|
const child = spawn2("osascript", [tmpScript], {
|
|
1034
1038
|
detached: true,
|
|
1035
1039
|
stdio: "ignore"
|
|
1036
1040
|
});
|
|
1041
|
+
child.on("exit", cleanupScript);
|
|
1042
|
+
child.on("error", cleanupScript);
|
|
1037
1043
|
child.unref();
|
|
1038
|
-
setTimeout(
|
|
1039
|
-
try {
|
|
1040
|
-
fs6.unlinkSync(tmpScript);
|
|
1041
|
-
} catch {
|
|
1042
|
-
}
|
|
1043
|
-
}, 5e3);
|
|
1044
|
+
setTimeout(cleanupScript, 1e4);
|
|
1044
1045
|
} catch (e) {
|
|
1045
1046
|
console.error("Failed to paste to iTerm:", e);
|
|
1046
1047
|
}
|
|
@@ -1144,6 +1145,7 @@ function createEventsRouter(sseManager) {
|
|
|
1144
1145
|
}, 3e4);
|
|
1145
1146
|
req.on("close", () => {
|
|
1146
1147
|
clearInterval(heartbeat);
|
|
1148
|
+
res.end();
|
|
1147
1149
|
});
|
|
1148
1150
|
});
|
|
1149
1151
|
return router6;
|
|
@@ -1182,7 +1184,15 @@ var FileWatcher = class {
|
|
|
1182
1184
|
pollInterval: 50
|
|
1183
1185
|
}
|
|
1184
1186
|
});
|
|
1185
|
-
this.watcher.on("add", (filePath) => this.handleFileEvent(filePath, "add")).on("unlink", (filePath) => this.handleFileEvent(filePath, "unlink")).on("change", (filePath) => this.handleFileEvent(filePath, "change")).on("addDir", (filePath) => this.handleDirEvent(filePath, "addDir")).on("unlinkDir", (filePath) => this.handleDirEvent(filePath, "unlinkDir")).on("error", (error) =>
|
|
1187
|
+
this.watcher.on("add", (filePath) => this.handleFileEvent(filePath, "add")).on("unlink", (filePath) => this.handleFileEvent(filePath, "unlink")).on("change", (filePath) => this.handleFileEvent(filePath, "change")).on("addDir", (filePath) => this.handleDirEvent(filePath, "addDir")).on("unlinkDir", (filePath) => this.handleDirEvent(filePath, "unlinkDir")).on("error", (error) => {
|
|
1188
|
+
const code = error.code;
|
|
1189
|
+
if (code === "ENFILE" || code === "EMFILE") {
|
|
1190
|
+
console.error("File descriptor limit reached. Disabling file watcher.");
|
|
1191
|
+
this.stop();
|
|
1192
|
+
} else {
|
|
1193
|
+
console.error("Watcher error:", error);
|
|
1194
|
+
}
|
|
1195
|
+
});
|
|
1186
1196
|
}
|
|
1187
1197
|
async stop() {
|
|
1188
1198
|
if (this.filesDebounceTimer) {
|
|
@@ -1347,8 +1357,12 @@ var Server = class {
|
|
|
1347
1357
|
});
|
|
1348
1358
|
}
|
|
1349
1359
|
};
|
|
1350
|
-
async function findFreePort(preferred) {
|
|
1351
|
-
return new Promise((resolve2) => {
|
|
1360
|
+
async function findFreePort(preferred, maxPort = 65535) {
|
|
1361
|
+
return new Promise((resolve2, reject) => {
|
|
1362
|
+
if (preferred > maxPort) {
|
|
1363
|
+
reject(new Error(`No available port found in range up to ${maxPort}`));
|
|
1364
|
+
return;
|
|
1365
|
+
}
|
|
1352
1366
|
const server = net.createServer();
|
|
1353
1367
|
server.listen(preferred, "127.0.0.1", () => {
|
|
1354
1368
|
server.close(() => {
|
|
@@ -1356,7 +1370,9 @@ async function findFreePort(preferred) {
|
|
|
1356
1370
|
});
|
|
1357
1371
|
});
|
|
1358
1372
|
server.on("error", () => {
|
|
1359
|
-
|
|
1373
|
+
server.close(() => {
|
|
1374
|
+
findFreePort(preferred + 1, maxPort).then(resolve2).catch(reject);
|
|
1375
|
+
});
|
|
1360
1376
|
});
|
|
1361
1377
|
});
|
|
1362
1378
|
}
|
|
@@ -1364,10 +1380,7 @@ async function findFreePort(preferred) {
|
|
|
1364
1380
|
// dist/flip/index.js
|
|
1365
1381
|
import open from "open";
|
|
1366
1382
|
import path9 from "path";
|
|
1367
|
-
import fs8 from "fs";
|
|
1368
|
-
import os3 from "os";
|
|
1369
1383
|
import { execSync as execSync2 } from "child_process";
|
|
1370
|
-
import { confirm as confirm2 } from "@inquirer/prompts";
|
|
1371
1384
|
var DEFAULT_PORT = 51234;
|
|
1372
1385
|
function formatTime() {
|
|
1373
1386
|
const now = /* @__PURE__ */ new Date();
|
|
@@ -1479,7 +1492,7 @@ function printUsage() {
|
|
|
1479
1492
|
console.error("Commands:");
|
|
1480
1493
|
console.error(" serve [path] Start server in daemon mode (keeps running)");
|
|
1481
1494
|
console.error(" open Open browser to existing server");
|
|
1482
|
-
console.error(" setup Setup
|
|
1495
|
+
console.error(" setup Setup Alt+; hotkey in shell config");
|
|
1483
1496
|
console.error(" (no command) Start server + open browser (one-shot mode)");
|
|
1484
1497
|
console.error("");
|
|
1485
1498
|
console.error("Options:");
|
|
@@ -1487,9 +1500,6 @@ function printUsage() {
|
|
|
1487
1500
|
console.error(" --session <uuid> Session ID for paste-back tracking");
|
|
1488
1501
|
}
|
|
1489
1502
|
async function setupHotkey() {
|
|
1490
|
-
const configDir = path9.join(os3.homedir(), ".config", "flip");
|
|
1491
|
-
const applescriptPath = path9.join(configDir, "flip.applescript");
|
|
1492
|
-
const shPath = path9.join(configDir, "flip.sh");
|
|
1493
1503
|
let nodePath;
|
|
1494
1504
|
try {
|
|
1495
1505
|
nodePath = execSync2("which node", { encoding: "utf-8" }).trim();
|
|
@@ -1497,101 +1507,52 @@ async function setupHotkey() {
|
|
|
1497
1507
|
nodePath = "/usr/local/bin/node";
|
|
1498
1508
|
}
|
|
1499
1509
|
const csqPath = new URL(import.meta.url).pathname;
|
|
1500
|
-
fs8.mkdirSync(configDir, { recursive: true });
|
|
1501
|
-
const applescriptContent = `#!/usr/bin/osascript
|
|
1502
|
-
|
|
1503
|
-
-- flip hotkey script
|
|
1504
|
-
-- Generated by: csq flip setup
|
|
1505
|
-
|
|
1506
|
-
tell application "iTerm2"
|
|
1507
|
-
tell current session of current window
|
|
1508
|
-
set originalSessionId to id
|
|
1509
|
-
set sessionUUID to do shell script "uuidgen"
|
|
1510
|
-
set currentPath to variable named "path"
|
|
1511
|
-
do shell script "echo '" & originalSessionId & "' > /tmp/flip-view-session-" & sessionUUID
|
|
1512
|
-
do shell script "nohup ${nodePath} ${csqPath} flip --session " & sessionUUID & " " & quoted form of currentPath & " > /tmp/flip.log 2>&1 &"
|
|
1513
|
-
end tell
|
|
1514
|
-
end tell
|
|
1515
|
-
|
|
1516
|
-
return ""`;
|
|
1517
|
-
fs8.writeFileSync(applescriptPath, applescriptContent);
|
|
1518
|
-
fs8.chmodSync(applescriptPath, "755");
|
|
1519
|
-
const shContent = `#!/bin/bash
|
|
1520
|
-
osascript ${applescriptPath} > /dev/null 2>&1
|
|
1521
|
-
`;
|
|
1522
|
-
fs8.writeFileSync(shPath, shContent);
|
|
1523
|
-
fs8.chmodSync(shPath, "755");
|
|
1524
1510
|
console.log("");
|
|
1525
1511
|
console.log("\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510");
|
|
1526
|
-
console.log("\u2502 Flip Hotkey Setup
|
|
1512
|
+
console.log("\u2502 Flip Hotkey Setup \u2502");
|
|
1527
1513
|
console.log("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518");
|
|
1528
1514
|
console.log("");
|
|
1529
|
-
console.log("
|
|
1530
|
-
console.log(` ${shPath}`);
|
|
1515
|
+
console.log("Choose one of the following options:");
|
|
1531
1516
|
console.log("");
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
if (openSettings) {
|
|
1537
|
-
try {
|
|
1538
|
-
execSync2(`osascript -e 'tell application "iTerm2" to activate' -e 'tell application "System Events" to keystroke "," using command down'`);
|
|
1539
|
-
console.log("");
|
|
1540
|
-
console.log(" \u2192 iTerm2 Settings opened. Navigate to: Keys \u2192 Key Bindings");
|
|
1541
|
-
} catch {
|
|
1542
|
-
console.log(" \u2192 Could not open settings automatically. Open manually: iTerm2 \u2192 Settings \u2192 Keys \u2192 Key Bindings");
|
|
1543
|
-
}
|
|
1544
|
-
}
|
|
1517
|
+
console.log("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
1518
|
+
console.log("Option 1: Zsh keybinding (Alt+;)");
|
|
1519
|
+
console.log("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
1520
|
+
console.log("Add to ~/.zshrc:");
|
|
1545
1521
|
console.log("");
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
if (!ready) {
|
|
1551
|
-
console.log("");
|
|
1552
|
-
console.log("Run `csq flip setup` again when ready.");
|
|
1553
|
-
return;
|
|
1554
|
-
}
|
|
1522
|
+
console.log(" # Flip hotkey (Alt+;)");
|
|
1523
|
+
console.log(" csq-flip-widget() { (csq flip &); zle reset-prompt; }");
|
|
1524
|
+
console.log(" zle -N csq-flip-widget");
|
|
1525
|
+
console.log(" bindkey '\\e;' csq-flip-widget");
|
|
1555
1526
|
console.log("");
|
|
1556
|
-
console.log("
|
|
1557
|
-
console.log("\u2502 Configure the Key Binding: \u2502");
|
|
1558
|
-
console.log("\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524");
|
|
1559
|
-
console.log("\u2502 1. Keyboard Shortcut: Press your hotkey \u2502");
|
|
1560
|
-
console.log("\u2502 (e.g., \u2318\u21E7F) \u2502");
|
|
1561
|
-
console.log("\u2502 \u2502");
|
|
1562
|
-
console.log('\u2502 2. Action: Select "Run Coprocess" \u2502');
|
|
1563
|
-
console.log("\u2502 \u2502");
|
|
1564
|
-
console.log("\u2502 3. Command: Paste the path (copied below) \u2502");
|
|
1565
|
-
console.log("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518");
|
|
1527
|
+
console.log("Then run: source ~/.zshrc");
|
|
1566
1528
|
console.log("");
|
|
1567
|
-
|
|
1568
|
-
console.log(
|
|
1529
|
+
console.log("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
1530
|
+
console.log("Option 2: iTerm2 hotkey (any key combo)");
|
|
1531
|
+
console.log("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
1532
|
+
console.log("1. iTerm2 \u2192 Settings \u2192 Keys \u2192 Key Bindings");
|
|
1533
|
+
console.log("2. Click + to add new binding");
|
|
1534
|
+
console.log("3. Set your hotkey (e.g., \u2318\u21E7F)");
|
|
1535
|
+
console.log('4. Action: "Run Coprocess"');
|
|
1536
|
+
console.log("5. Command:");
|
|
1569
1537
|
console.log("");
|
|
1570
|
-
|
|
1571
|
-
message: "Done configuring? (Paste the path and click OK)",
|
|
1572
|
-
default: true
|
|
1573
|
-
});
|
|
1538
|
+
console.log(` ${nodePath} ${csqPath} flip`);
|
|
1574
1539
|
console.log("");
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
console.log("\u2502 2. Press your hotkey \u2192 browser opens \u2502");
|
|
1581
|
-
console.log("\u2502 3. Select files, add comments \u2502");
|
|
1582
|
-
console.log("\u2502 4. Submit \u2192 text appears in terminal \u2502");
|
|
1583
|
-
console.log("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518");
|
|
1540
|
+
try {
|
|
1541
|
+
await copyToClipboard(`${nodePath} ${csqPath} flip`);
|
|
1542
|
+
console.log(" (Copied to clipboard!)");
|
|
1543
|
+
} catch {
|
|
1544
|
+
}
|
|
1584
1545
|
console.log("");
|
|
1585
1546
|
}
|
|
1586
1547
|
|
|
1587
1548
|
// dist/config.js
|
|
1588
|
-
import * as
|
|
1589
|
-
import * as
|
|
1549
|
+
import * as fs8 from "fs";
|
|
1550
|
+
import * as os3 from "os";
|
|
1590
1551
|
import * as path10 from "path";
|
|
1591
|
-
var GLOBAL_CONFIG_PATH = path10.join(
|
|
1552
|
+
var GLOBAL_CONFIG_PATH = path10.join(os3.homedir(), ".code-squad", "config.json");
|
|
1592
1553
|
async function loadGlobalConfig() {
|
|
1593
1554
|
try {
|
|
1594
|
-
const content = await
|
|
1555
|
+
const content = await fs8.promises.readFile(GLOBAL_CONFIG_PATH, "utf-8");
|
|
1595
1556
|
return JSON.parse(content);
|
|
1596
1557
|
} catch (error) {
|
|
1597
1558
|
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
@@ -1622,7 +1583,7 @@ function getWorktreeCopyPatterns(config) {
|
|
|
1622
1583
|
}
|
|
1623
1584
|
|
|
1624
1585
|
// dist/fileUtils.js
|
|
1625
|
-
import * as
|
|
1586
|
+
import * as fs9 from "fs";
|
|
1626
1587
|
import * as path11 from "path";
|
|
1627
1588
|
import fg from "fast-glob";
|
|
1628
1589
|
async function copyFilesWithPatterns(sourceRoot, destRoot, patterns) {
|
|
@@ -1659,8 +1620,8 @@ async function copySingleFile(absolutePath, sourceRoot, destRoot) {
|
|
|
1659
1620
|
const relativePath = path11.relative(sourceRoot, absolutePath);
|
|
1660
1621
|
const destPath = path11.join(destRoot, relativePath);
|
|
1661
1622
|
const destDir = path11.dirname(destPath);
|
|
1662
|
-
await
|
|
1663
|
-
await
|
|
1623
|
+
await fs9.promises.mkdir(destDir, { recursive: true });
|
|
1624
|
+
await fs9.promises.copyFile(absolutePath, destPath);
|
|
1664
1625
|
}
|
|
1665
1626
|
|
|
1666
1627
|
// dist/index.js
|
|
@@ -1716,12 +1677,12 @@ function getProjectHash(workspaceRoot) {
|
|
|
1716
1677
|
function getSessionsPath(workspaceRoot) {
|
|
1717
1678
|
const projectHash = getProjectHash(workspaceRoot);
|
|
1718
1679
|
const projectName = path12.basename(workspaceRoot);
|
|
1719
|
-
return path12.join(
|
|
1680
|
+
return path12.join(os4.homedir(), ".code-squad", "sessions", `${projectName}-${projectHash}.json`);
|
|
1720
1681
|
}
|
|
1721
1682
|
async function loadLocalThreads(workspaceRoot) {
|
|
1722
1683
|
const sessionsPath = getSessionsPath(workspaceRoot);
|
|
1723
1684
|
try {
|
|
1724
|
-
const content = await
|
|
1685
|
+
const content = await fs10.promises.readFile(sessionsPath, "utf-8");
|
|
1725
1686
|
const data = JSON.parse(content);
|
|
1726
1687
|
return data.localThreads || [];
|
|
1727
1688
|
} catch {
|
|
@@ -1731,8 +1692,8 @@ async function loadLocalThreads(workspaceRoot) {
|
|
|
1731
1692
|
async function saveLocalThreads(workspaceRoot, threads) {
|
|
1732
1693
|
const sessionsPath = getSessionsPath(workspaceRoot);
|
|
1733
1694
|
const dir = path12.dirname(sessionsPath);
|
|
1734
|
-
await
|
|
1735
|
-
await
|
|
1695
|
+
await fs10.promises.mkdir(dir, { recursive: true });
|
|
1696
|
+
await fs10.promises.writeFile(sessionsPath, JSON.stringify({ localThreads: threads }, null, 2));
|
|
1736
1697
|
}
|
|
1737
1698
|
async function addLocalThread(workspaceRoot, name) {
|
|
1738
1699
|
const threads = await loadLocalThreads(workspaceRoot);
|
|
@@ -1862,7 +1823,7 @@ async function quitWorktreeCommand() {
|
|
|
1862
1823
|
}
|
|
1863
1824
|
const isDirty = await gitAdapter.hasDirtyState(cwd);
|
|
1864
1825
|
if (isDirty) {
|
|
1865
|
-
const confirmed = await
|
|
1826
|
+
const confirmed = await confirm2({
|
|
1866
1827
|
message: "Uncommitted changes detected. Delete anyway?",
|
|
1867
1828
|
default: false
|
|
1868
1829
|
});
|