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.
@@ -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 iTerm2 hotkey');
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
- const configDir = path.join(os.homedir(), '.config', 'flip');
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 Wizard │');
149
+ console.log('│ Flip Hotkey Setup │');
185
150
  console.log('└─────────────────────────────────────────────────┘');
186
151
  console.log('');
187
- console.log(' Scripts generated:');
188
- console.log(` ${shPath}`);
152
+ console.log('Choose one of the following options:');
189
153
  console.log('');
190
- // Step 1: Open iTerm2 Settings
191
- const openSettings = await confirm({
192
- message: 'Open iTerm2 Settings? (Keys → Key Bindings)',
193
- default: true,
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
- // Step 2: Guide through adding key binding
207
- const ready = await confirm({
208
- message: 'Ready to add Key Binding? (Click + button in Key Bindings tab)',
209
- default: true,
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
- // Copy path to clipboard
229
- await copyToClipboard(shPath);
230
- console.log(`✓ Copied to clipboard: ${shPath}`);
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
- await confirm({
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
- console.log('┌─────────────────────────────────────────────────┐');
238
- console.log('│ Setup Complete! │');
239
- console.log('├─────────────────────────────────────────────────┤');
240
- console.log('│ Usage: │');
241
- console.log('│ 1. Focus on any terminal panel │');
242
- console.log('│ 2. Press your hotkey → browser opens │');
243
- console.log('│ 3. Select files, add comments │');
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
- // Schedule cleanup
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
- // Use a detached process to avoid EBADF
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
- const child = spawn('sh', ['-c', `cat "${tmpFile}" | xclip -selection clipboard && rm "${tmpFile}"`], {
32
- detached: true,
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
- try {
39
- const child = spawn('sh', ['-c', `cat "${tmpFile}" | xsel --clipboard --input && rm "${tmpFile}"`], {
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
- const tmpFile = path.join(os.tmpdir(), `flip-clipboard-${Date.now()}.txt`);
54
- fs.writeFileSync(tmpFile, text);
55
- const child = spawn('cmd', ['/c', `type "${tmpFile}" | clip && del "${tmpFile}"`], {
56
- detached: true,
57
- stdio: 'ignore',
58
- });
59
- child.unref();
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
- // Port in use, try next
82
- findFreePort(preferred + 1).then(resolve);
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) => console.error('Watcher 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 fs11 from "fs";
6
- import * as os5 from "os";
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 confirm3 } from "@inquirer/prompts";
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
- const child = spawn("sh", ["-c", `cat "${tmpFile}" | pbcopy && rm "${tmpFile}"`], {
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
- const child = spawn("sh", ["-c", `cat "${tmpFile}" | xclip -selection clipboard && rm "${tmpFile}"`], {
944
- detached: true,
945
- stdio: "ignore"
946
- });
947
- child.unref();
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
- const tmpFile = path5.join(os.tmpdir(), `flip-clipboard-${Date.now()}.txt`);
962
- fs5.writeFileSync(tmpFile, text);
963
- const child = spawn("cmd", ["/c", `type "${tmpFile}" | clip && del "${tmpFile}"`], {
964
- detached: true,
965
- stdio: "ignore"
966
- });
967
- child.unref();
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) => console.error("Watcher 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
- findFreePort(preferred + 1).then(resolve2);
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 iTerm2 hotkey");
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 Wizard \u2502");
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("\u2713 Scripts generated:");
1530
- console.log(` ${shPath}`);
1515
+ console.log("Choose one of the following options:");
1531
1516
  console.log("");
1532
- const openSettings = await confirm2({
1533
- message: "Open iTerm2 Settings? (Keys \u2192 Key Bindings)",
1534
- default: true
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
- const ready = await confirm2({
1547
- message: "Ready to add Key Binding? (Click + button in Key Bindings tab)",
1548
- default: true
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("\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");
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
- await copyToClipboard(shPath);
1568
- console.log(`\u2713 Copied to clipboard: ${shPath}`);
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
- await confirm2({
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
- 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");
1576
- console.log("\u2502 Setup Complete! \u2502");
1577
- 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");
1578
- console.log("\u2502 Usage: \u2502");
1579
- console.log("\u2502 1. Focus on any terminal panel \u2502");
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 fs9 from "fs";
1589
- import * as os4 from "os";
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(os4.homedir(), ".code-squad", "config.json");
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 fs9.promises.readFile(GLOBAL_CONFIG_PATH, "utf-8");
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 fs10 from "fs";
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 fs10.promises.mkdir(destDir, { recursive: true });
1663
- await fs10.promises.copyFile(absolutePath, destPath);
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(os5.homedir(), ".code-squad", "sessions", `${projectName}-${projectHash}.json`);
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 fs11.promises.readFile(sessionsPath, "utf-8");
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 fs11.promises.mkdir(dir, { recursive: true });
1735
- await fs11.promises.writeFile(sessionsPath, JSON.stringify({ localThreads: threads }, null, 2));
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 confirm3({
1826
+ const confirmed = await confirm2({
1866
1827
  message: "Uncommitted changes detected. Delete anyway?",
1867
1828
  default: false
1868
1829
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "code-squad-cli",
3
- "version": "1.2.10",
3
+ "version": "1.2.12",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "csq": "./dist/index.js"