code-squad-cli 1.2.9 → 1.2.11

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.
@@ -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,10 +8,14 @@ export function createStaticRouter() {
8
8
  // Get __dirname equivalent in ESM
9
9
  const __filename = fileURLToPath(import.meta.url);
10
10
  const __dirname = path.dirname(__filename);
11
- // Path to the built web-ui
12
- // In development (tsx): __dirname is src/flip/routes, go up 3 levels to packages/cli
13
- // In production (bundled): __dirname is dist/flip/routes, go up 3 levels to packages/cli
14
- const distPath = path.resolve(__dirname, '../../../flip-ui/dist');
11
+ // First, check for the bundled path. This is more robust than checking the filename.
12
+ // - Bundled: dist/flip-ui/dist
13
+ let distPath = path.resolve(__dirname, 'flip-ui/dist');
14
+ // If the bundled path doesn't exist, fall back to the unbundled (development) path.
15
+ // - Unbundled: packages/cli/flip-ui/dist
16
+ if (!fs.existsSync(distPath)) {
17
+ distPath = path.resolve(__dirname, '../../../flip-ui/dist');
18
+ }
15
19
  // Check if dist exists (for development vs production)
16
20
  if (fs.existsSync(distPath)) {
17
21
  // Serve static files
@@ -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
  }
package/dist/index.js CHANGED
@@ -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
  }
@@ -1106,7 +1107,10 @@ function createStaticRouter() {
1106
1107
  const router6 = Router6();
1107
1108
  const __filename = fileURLToPath(import.meta.url);
1108
1109
  const __dirname = path7.dirname(__filename);
1109
- const distPath = path7.resolve(__dirname, "../../../flip-ui/dist");
1110
+ let distPath = path7.resolve(__dirname, "flip-ui/dist");
1111
+ if (!fs7.existsSync(distPath)) {
1112
+ distPath = path7.resolve(__dirname, "../../../flip-ui/dist");
1113
+ }
1110
1114
  if (fs7.existsSync(distPath)) {
1111
1115
  router6.use(express.static(distPath));
1112
1116
  router6.get("*", (req, res) => {
@@ -1141,6 +1145,7 @@ function createEventsRouter(sseManager) {
1141
1145
  }, 3e4);
1142
1146
  req.on("close", () => {
1143
1147
  clearInterval(heartbeat);
1148
+ res.end();
1144
1149
  });
1145
1150
  });
1146
1151
  return router6;
@@ -1344,8 +1349,12 @@ var Server = class {
1344
1349
  });
1345
1350
  }
1346
1351
  };
1347
- async function findFreePort(preferred) {
1348
- return new Promise((resolve2) => {
1352
+ async function findFreePort(preferred, maxPort = 65535) {
1353
+ return new Promise((resolve2, reject) => {
1354
+ if (preferred > maxPort) {
1355
+ reject(new Error(`No available port found in range up to ${maxPort}`));
1356
+ return;
1357
+ }
1349
1358
  const server = net.createServer();
1350
1359
  server.listen(preferred, "127.0.0.1", () => {
1351
1360
  server.close(() => {
@@ -1353,7 +1362,9 @@ async function findFreePort(preferred) {
1353
1362
  });
1354
1363
  });
1355
1364
  server.on("error", () => {
1356
- findFreePort(preferred + 1).then(resolve2);
1365
+ server.close(() => {
1366
+ findFreePort(preferred + 1, maxPort).then(resolve2).catch(reject);
1367
+ });
1357
1368
  });
1358
1369
  });
1359
1370
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "code-squad-cli",
3
- "version": "1.2.9",
3
+ "version": "1.2.11",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "csq": "./dist/index.js"