code-squad-cli 1.0.7 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,8 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // dist/index.js
4
- import * as path2 from "path";
5
- import * as fs2 from "fs";
4
+ import * as path7 from "path";
5
+ import * as fs7 from "fs";
6
+ import * as os3 from "os";
7
+ import * as crypto from "crypto";
6
8
  import chalk2 from "chalk";
7
9
 
8
10
  // dist/adapters/GitAdapter.js
@@ -49,11 +51,11 @@ var GitAdapter = class {
49
51
  const headMatch = headLine.match(/^HEAD (.+)$/);
50
52
  const branchMatch = branchLine?.match(/^branch refs\/heads\/(.+)$/);
51
53
  if (pathMatch && headMatch) {
52
- const path3 = pathMatch[1];
54
+ const path8 = pathMatch[1];
53
55
  const head = headMatch[1];
54
56
  const branch = branchMatch ? branchMatch[1] : "HEAD";
55
- if (path3 !== workspaceRoot) {
56
- worktrees.push({ path: path3, branch, head });
57
+ if (path8 !== workspaceRoot) {
58
+ worktrees.push({ path: path8, branch, head });
57
59
  }
58
60
  }
59
61
  i += 3;
@@ -99,14 +101,14 @@ var GitAdapter = class {
99
101
  });
100
102
  });
101
103
  }
102
- async isValidWorktree(path3, workspaceRoot) {
104
+ async isValidWorktree(path8, workspaceRoot) {
103
105
  try {
104
- await fs.promises.access(path3, fs.constants.R_OK);
106
+ await fs.promises.access(path8, fs.constants.R_OK);
105
107
  } catch {
106
108
  return false;
107
109
  }
108
110
  const isGitRepo = await new Promise((resolve) => {
109
- exec(`cd "${path3}" && git rev-parse --git-dir`, { maxBuffer: 1024 * 1024 }, (error) => {
111
+ exec(`cd "${path8}" && git rev-parse --git-dir`, { maxBuffer: 1024 * 1024 }, (error) => {
110
112
  resolve(!error);
111
113
  });
112
114
  });
@@ -114,7 +116,7 @@ var GitAdapter = class {
114
116
  return false;
115
117
  }
116
118
  const worktrees = await this.listWorktrees(workspaceRoot);
117
- return worktrees.some((wt) => wt.path === path3);
119
+ return worktrees.some((wt) => wt.path === path8);
118
120
  }
119
121
  async getWorktreeBranch(worktreePath) {
120
122
  return new Promise((resolve, reject) => {
@@ -399,7 +401,695 @@ async function confirmDeleteLocal(threadName) {
399
401
  });
400
402
  }
401
403
 
404
+ // dist/flip/server/Server.js
405
+ import express2 from "express";
406
+ import cors from "cors";
407
+ import http from "http";
408
+ import net from "net";
409
+
410
+ // dist/flip/routes/files.js
411
+ import { Router } from "express";
412
+ import fs2 from "fs";
413
+ import path2 from "path";
414
+ var router = Router();
415
+ var DEFAULT_IGNORES = [
416
+ "node_modules",
417
+ ".git",
418
+ ".svn",
419
+ ".hg",
420
+ "dist",
421
+ "build",
422
+ "out",
423
+ ".cache",
424
+ ".next",
425
+ ".nuxt",
426
+ "coverage",
427
+ "__pycache__",
428
+ ".pytest_cache",
429
+ "target",
430
+ "Cargo.lock",
431
+ "package-lock.json",
432
+ "pnpm-lock.yaml",
433
+ "yarn.lock",
434
+ ".DS_Store"
435
+ ];
436
+ function shouldIgnore(name) {
437
+ if (name.startsWith(".") && name !== ".gitignore" && name !== ".env.example") {
438
+ return true;
439
+ }
440
+ return DEFAULT_IGNORES.includes(name);
441
+ }
442
+ function buildFileTree(rootPath, currentPath, maxDepth, depth = 0) {
443
+ if (depth > maxDepth)
444
+ return [];
445
+ const entries = fs2.readdirSync(currentPath, { withFileTypes: true });
446
+ const nodes = [];
447
+ entries.sort((a, b) => {
448
+ if (a.isDirectory() && !b.isDirectory())
449
+ return -1;
450
+ if (!a.isDirectory() && b.isDirectory())
451
+ return 1;
452
+ return a.name.localeCompare(b.name);
453
+ });
454
+ for (const entry of entries) {
455
+ if (shouldIgnore(entry.name))
456
+ continue;
457
+ const fullPath = path2.join(currentPath, entry.name);
458
+ const relativePath = path2.relative(rootPath, fullPath);
459
+ const node = {
460
+ path: relativePath,
461
+ name: entry.name,
462
+ type: entry.isDirectory() ? "directory" : "file"
463
+ };
464
+ if (entry.isDirectory()) {
465
+ node.children = buildFileTree(rootPath, fullPath, maxDepth, depth + 1);
466
+ }
467
+ nodes.push(node);
468
+ }
469
+ return nodes;
470
+ }
471
+ function collectFlatFiles(rootPath, currentPath, maxDepth, depth = 0) {
472
+ if (depth > maxDepth)
473
+ return [];
474
+ const entries = fs2.readdirSync(currentPath, { withFileTypes: true });
475
+ const files = [];
476
+ for (const entry of entries) {
477
+ if (shouldIgnore(entry.name))
478
+ continue;
479
+ const fullPath = path2.join(currentPath, entry.name);
480
+ const relativePath = path2.relative(rootPath, fullPath);
481
+ if (entry.isFile()) {
482
+ files.push(relativePath);
483
+ } else if (entry.isDirectory()) {
484
+ files.push(...collectFlatFiles(rootPath, fullPath, maxDepth, depth + 1));
485
+ }
486
+ }
487
+ return files;
488
+ }
489
+ router.get("/", (req, res) => {
490
+ const state = req.app.locals.state;
491
+ const tree = buildFileTree(state.cwd, state.cwd, 10);
492
+ const response = {
493
+ root: state.cwd,
494
+ tree
495
+ };
496
+ res.json(response);
497
+ });
498
+ router.get("/flat", (req, res) => {
499
+ const state = req.app.locals.state;
500
+ const files = collectFlatFiles(state.cwd, state.cwd, 10);
501
+ files.sort();
502
+ const response = {
503
+ files
504
+ };
505
+ res.json(response);
506
+ });
507
+
508
+ // dist/flip/routes/file.js
509
+ import { Router as Router2 } from "express";
510
+ import fs3 from "fs";
511
+ import path3 from "path";
512
+ var router2 = Router2();
513
+ function detectLanguage(filePath) {
514
+ const ext = path3.extname(filePath).slice(1).toLowerCase();
515
+ const languageMap = {
516
+ rs: "rust",
517
+ js: "javascript",
518
+ ts: "typescript",
519
+ tsx: "tsx",
520
+ jsx: "jsx",
521
+ py: "python",
522
+ go: "go",
523
+ java: "java",
524
+ c: "c",
525
+ cpp: "cpp",
526
+ cc: "cpp",
527
+ cxx: "cpp",
528
+ h: "cpp",
529
+ hpp: "cpp",
530
+ md: "markdown",
531
+ json: "json",
532
+ yaml: "yaml",
533
+ yml: "yaml",
534
+ toml: "toml",
535
+ html: "html",
536
+ css: "css",
537
+ scss: "scss",
538
+ sh: "bash",
539
+ bash: "bash",
540
+ sql: "sql",
541
+ rb: "ruby",
542
+ swift: "swift",
543
+ kt: "kotlin",
544
+ kts: "kotlin",
545
+ xml: "xml",
546
+ vue: "vue"
547
+ };
548
+ return languageMap[ext] || "plaintext";
549
+ }
550
+ router2.get("/", (req, res) => {
551
+ const state = req.app.locals.state;
552
+ const relativePath = req.query.path;
553
+ if (!relativePath) {
554
+ res.status(400).json({ error: "Missing path parameter" });
555
+ return;
556
+ }
557
+ const filePath = path3.join(state.cwd, relativePath);
558
+ const resolvedPath = path3.resolve(filePath);
559
+ const resolvedCwd = path3.resolve(state.cwd);
560
+ if (!resolvedPath.startsWith(resolvedCwd)) {
561
+ res.status(403).json({ error: "Access denied" });
562
+ return;
563
+ }
564
+ try {
565
+ const content = fs3.readFileSync(resolvedPath, "utf-8");
566
+ const language = detectLanguage(relativePath);
567
+ const response = {
568
+ path: relativePath,
569
+ content,
570
+ language
571
+ };
572
+ res.json(response);
573
+ } catch (err) {
574
+ if (err.code === "ENOENT") {
575
+ res.status(404).json({ error: "File not found" });
576
+ } else {
577
+ res.status(500).json({ error: "Failed to read file" });
578
+ }
579
+ }
580
+ });
581
+
582
+ // dist/flip/routes/git.js
583
+ import { Router as Router3 } from "express";
584
+ import { execSync } from "child_process";
585
+ var router3 = Router3();
586
+ function parseGitStatus(output) {
587
+ const files = [];
588
+ for (const line of output.split("\n")) {
589
+ if (line.length < 3)
590
+ continue;
591
+ const statusCode = line.substring(0, 2);
592
+ let filePath = line.substring(3);
593
+ filePath = filePath.replace(/^"|"$/g, "");
594
+ let status;
595
+ if (statusCode === "??") {
596
+ status = "untracked";
597
+ } else if (statusCode.includes("M")) {
598
+ status = "modified";
599
+ } else if (statusCode.includes("D")) {
600
+ status = "deleted";
601
+ } else if (statusCode.includes("A")) {
602
+ status = "added";
603
+ } else if (statusCode.includes("R")) {
604
+ status = "renamed";
605
+ } else if (statusCode.includes("C")) {
606
+ status = "copied";
607
+ } else if (statusCode === "UU") {
608
+ status = "unmerged";
609
+ } else {
610
+ status = "modified";
611
+ }
612
+ files.push({ path: filePath, status });
613
+ }
614
+ return files;
615
+ }
616
+ router3.get("/status", (req, res) => {
617
+ const state = req.app.locals.state;
618
+ let isGitRepo = false;
619
+ try {
620
+ execSync("git rev-parse --git-dir", {
621
+ cwd: state.cwd,
622
+ stdio: "pipe"
623
+ });
624
+ isGitRepo = true;
625
+ } catch {
626
+ }
627
+ if (!isGitRepo) {
628
+ const response2 = {
629
+ isGitRepo: false,
630
+ unstaged: []
631
+ };
632
+ res.json(response2);
633
+ return;
634
+ }
635
+ let unstaged = [];
636
+ try {
637
+ const output = execSync("git status --porcelain", {
638
+ cwd: state.cwd,
639
+ encoding: "utf-8"
640
+ });
641
+ unstaged = parseGitStatus(output);
642
+ } catch {
643
+ }
644
+ const response = {
645
+ isGitRepo: true,
646
+ unstaged
647
+ };
648
+ res.json(response);
649
+ });
650
+
651
+ // dist/flip/routes/submit.js
652
+ import { Router as Router4 } from "express";
653
+
654
+ // dist/flip/output/formatter.js
655
+ function formatComments(comments) {
656
+ if (comments.length === 0) {
657
+ return "";
658
+ }
659
+ let output = "The user has annotated the following code locations:\n\n";
660
+ for (const comment of comments) {
661
+ const lineRange = comment.endLine && comment.endLine !== comment.line ? `${comment.line}-${comment.endLine}` : `${comment.line}`;
662
+ const location = `${comment.file}:${lineRange}`;
663
+ output += `- ${location} -> "${comment.text}"
664
+ `;
665
+ }
666
+ output += "\nPlease review these comments and address them.";
667
+ return output;
668
+ }
669
+
670
+ // dist/flip/output/clipboard.js
671
+ import clipboardy from "clipboardy";
672
+ async function copyToClipboard(text) {
673
+ await clipboardy.write(text);
674
+ }
675
+
676
+ // dist/flip/output/autopaste.js
677
+ import { execSync as execSync2 } from "child_process";
678
+ import fs4 from "fs";
679
+ import path4 from "path";
680
+ import os from "os";
681
+ async function schedulePaste(sessionId) {
682
+ if (process.platform !== "darwin") {
683
+ console.error("Auto-paste not supported on this platform. Please paste manually (Ctrl+V).");
684
+ return;
685
+ }
686
+ await pasteToOriginalSession(sessionId);
687
+ }
688
+ async function pasteToOriginalSession(sessionId) {
689
+ const sessionFile = path4.join(os.tmpdir(), `flip-view-session-${sessionId}`);
690
+ let itermSessionId;
691
+ try {
692
+ itermSessionId = fs4.readFileSync(sessionFile, "utf-8").trim();
693
+ } catch {
694
+ }
695
+ let script;
696
+ if (itermSessionId) {
697
+ script = `
698
+ tell application "iTerm2"
699
+ set found to false
700
+ repeat with w in windows
701
+ repeat with t in tabs of w
702
+ repeat with s in sessions of t
703
+ if id of s is "${itermSessionId}" then
704
+ set found to true
705
+ select s
706
+ tell s to write text (the clipboard)
707
+ tell s to write text ""
708
+ return
709
+ end if
710
+ end repeat
711
+ end repeat
712
+ end repeat
713
+
714
+ if not found then
715
+ display notification "Original session closed. Output copied to clipboard." with title "flip"
716
+ end if
717
+ end tell
718
+ `;
719
+ try {
720
+ fs4.unlinkSync(sessionFile);
721
+ } catch {
722
+ }
723
+ } else {
724
+ script = `
725
+ tell application "iTerm2"
726
+ activate
727
+ delay 0.1
728
+ tell current session of current window
729
+ write text (the clipboard)
730
+ write text ""
731
+ end tell
732
+ end tell
733
+ `;
734
+ }
735
+ try {
736
+ execSync2(`osascript -e '${script.replace(/'/g, `'"'"'`)}'`, {
737
+ stdio: "pipe"
738
+ });
739
+ } catch (e) {
740
+ console.error("Failed to paste to iTerm:", e);
741
+ }
742
+ }
743
+
744
+ // dist/flip/routes/submit.js
745
+ var router4 = Router4();
746
+ router4.post("/", async (req, res) => {
747
+ const state = req.app.locals.state;
748
+ const body = req.body;
749
+ if (!body.items || body.items.length === 0) {
750
+ res.status(400).json({ error: "No items to submit" });
751
+ return;
752
+ }
753
+ const comments = body.items.map((item) => ({
754
+ file: item.filePath,
755
+ line: item.startLine,
756
+ endLine: item.endLine !== item.startLine ? item.endLine : void 0,
757
+ text: item.comment
758
+ }));
759
+ const formatted = formatComments(comments);
760
+ try {
761
+ await copyToClipboard(formatted);
762
+ } catch (e) {
763
+ console.error("Failed to copy to clipboard:", e);
764
+ }
765
+ try {
766
+ await schedulePaste(body.session_id);
767
+ } catch (e) {
768
+ console.error("Failed to schedule paste:", e);
769
+ }
770
+ res.json({ status: "ok" });
771
+ if (state.resolve) {
772
+ state.resolve(formatted);
773
+ }
774
+ });
775
+
776
+ // dist/flip/routes/cancel.js
777
+ import { Router as Router5 } from "express";
778
+ var router5 = Router5();
779
+ router5.post("/", (req, res) => {
780
+ const state = req.app.locals.state;
781
+ res.json({ status: "ok" });
782
+ if (state.resolve) {
783
+ state.resolve(null);
784
+ }
785
+ });
786
+
787
+ // dist/flip/routes/static.js
788
+ import { Router as Router6 } from "express";
789
+ import express from "express";
790
+ import path5 from "path";
791
+ import { fileURLToPath } from "url";
792
+ import fs5 from "fs";
793
+ function createStaticRouter() {
794
+ const router6 = Router6();
795
+ const __filename = fileURLToPath(import.meta.url);
796
+ const __dirname = path5.dirname(__filename);
797
+ const distPath = path5.resolve(__dirname, "flip-ui/dist");
798
+ if (fs5.existsSync(distPath)) {
799
+ router6.use(express.static(distPath));
800
+ router6.get("*", (req, res) => {
801
+ const indexPath = path5.join(distPath, "index.html");
802
+ if (fs5.existsSync(indexPath)) {
803
+ res.sendFile(indexPath);
804
+ } else {
805
+ res.status(404).send("Not Found");
806
+ }
807
+ });
808
+ } else {
809
+ router6.get("*", (req, res) => {
810
+ res.redirect(`http://localhost:5173${req.url}`);
811
+ });
812
+ }
813
+ return router6;
814
+ }
815
+
816
+ // dist/flip/server/Server.js
817
+ var Server = class {
818
+ cwd;
819
+ port;
820
+ constructor(cwd, port) {
821
+ this.cwd = cwd;
822
+ this.port = port;
823
+ }
824
+ async run() {
825
+ return new Promise((resolve) => {
826
+ const state = {
827
+ cwd: this.cwd,
828
+ resolve: null
829
+ };
830
+ const app = express2();
831
+ app.use(cors());
832
+ app.use(express2.json());
833
+ app.locals.state = state;
834
+ app.use("/api/files", router);
835
+ app.use("/api/file", router2);
836
+ app.use("/api/git", router3);
837
+ app.use("/api/submit", router4);
838
+ app.use("/api/cancel", router5);
839
+ app.use(createStaticRouter());
840
+ const server = http.createServer(app);
841
+ state.resolve = (output) => {
842
+ server.close();
843
+ resolve(output);
844
+ };
845
+ server.listen(this.port, "127.0.0.1", () => {
846
+ console.log(`Server running at http://localhost:${this.port}`);
847
+ });
848
+ });
849
+ }
850
+ };
851
+ async function findFreePort(preferred) {
852
+ return new Promise((resolve) => {
853
+ const server = net.createServer();
854
+ server.listen(preferred, "127.0.0.1", () => {
855
+ server.close(() => {
856
+ resolve(preferred);
857
+ });
858
+ });
859
+ server.on("error", () => {
860
+ findFreePort(preferred + 1).then(resolve);
861
+ });
862
+ });
863
+ }
864
+
865
+ // dist/flip/index.js
866
+ import open from "open";
867
+ import path6 from "path";
868
+ import fs6 from "fs";
869
+ import os2 from "os";
870
+ import { execSync as execSync3 } from "child_process";
871
+ import { confirm as confirm2 } from "@inquirer/prompts";
872
+ import clipboardy2 from "clipboardy";
873
+ var DEFAULT_PORT = 51234;
874
+ function formatTime() {
875
+ const now = /* @__PURE__ */ new Date();
876
+ const hours = String(now.getHours()).padStart(2, "0");
877
+ const mins = String(now.getMinutes()).padStart(2, "0");
878
+ const secs = String(now.getSeconds()).padStart(2, "0");
879
+ return `${hours}:${mins}:${secs}`;
880
+ }
881
+ async function runFlip(args) {
882
+ let sessionId;
883
+ const sessionIdx = args.indexOf("--session");
884
+ if (sessionIdx !== -1 && args[sessionIdx + 1]) {
885
+ sessionId = args[sessionIdx + 1];
886
+ }
887
+ const filteredArgs = args.filter((arg, idx) => {
888
+ if (arg === "--session")
889
+ return false;
890
+ if (idx > 0 && args[idx - 1] === "--session")
891
+ return false;
892
+ return true;
893
+ });
894
+ let command;
895
+ let pathArg;
896
+ if (filteredArgs.length > 0) {
897
+ switch (filteredArgs[0]) {
898
+ case "serve":
899
+ command = "serve";
900
+ pathArg = filteredArgs[1];
901
+ break;
902
+ case "open":
903
+ command = "open";
904
+ break;
905
+ case "setup":
906
+ command = "setup";
907
+ break;
908
+ case "--help":
909
+ case "-h":
910
+ printUsage();
911
+ return;
912
+ default:
913
+ command = "oneshot";
914
+ pathArg = filteredArgs[0];
915
+ break;
916
+ }
917
+ } else {
918
+ command = "oneshot";
919
+ }
920
+ const cwd = pathArg ? path6.resolve(pathArg) : process.cwd();
921
+ switch (command) {
922
+ case "setup": {
923
+ await setupHotkey();
924
+ return;
925
+ }
926
+ case "serve": {
927
+ const port = await findFreePort(DEFAULT_PORT);
928
+ console.log(`Server running at http://localhost:${port}`);
929
+ console.log("Press Ctrl+C to stop");
930
+ console.log("");
931
+ console.log("To open browser, run: csq flip open");
932
+ console.log(`Or use hotkey to open: open http://localhost:${port}`);
933
+ while (true) {
934
+ const server = new Server(cwd, port);
935
+ const result = await server.run();
936
+ if (result) {
937
+ console.log(`[${formatTime()}] Submitted ${result.length} characters`);
938
+ } else {
939
+ console.log(`[${formatTime()}] Cancelled`);
940
+ }
941
+ console.log(`[${formatTime()}] Ready for next session...`);
942
+ }
943
+ }
944
+ case "open": {
945
+ const url = `http://localhost:${DEFAULT_PORT}`;
946
+ console.log(`Opening ${url} in browser...`);
947
+ try {
948
+ await open(url);
949
+ } catch (e) {
950
+ console.error("Failed to open browser:", e);
951
+ console.error("Is the server running? Start with: csq flip serve");
952
+ }
953
+ break;
954
+ }
955
+ case "oneshot":
956
+ default: {
957
+ const port = await findFreePort(DEFAULT_PORT);
958
+ const url = sessionId ? `http://localhost:${port}?session=${sessionId}` : `http://localhost:${port}`;
959
+ console.log(`Opening ${url} in browser...`);
960
+ try {
961
+ await open(url);
962
+ } catch (e) {
963
+ console.error("Failed to open browser:", e);
964
+ console.log(`Please open ${url} manually`);
965
+ }
966
+ const server = new Server(cwd, port);
967
+ const result = await server.run();
968
+ if (result) {
969
+ console.log(`
970
+ Submitted ${result.length} characters`);
971
+ } else {
972
+ console.log("\nCancelled");
973
+ }
974
+ break;
975
+ }
976
+ }
977
+ }
978
+ function printUsage() {
979
+ console.error("Usage: csq flip [command] [options]");
980
+ console.error("");
981
+ console.error("Commands:");
982
+ console.error(" serve [path] Start server in daemon mode (keeps running)");
983
+ console.error(" open Open browser to existing server");
984
+ console.error(" setup Setup iTerm2 hotkey");
985
+ console.error(" (no command) Start server + open browser (one-shot mode)");
986
+ console.error("");
987
+ console.error("Options:");
988
+ console.error(" path Directory to serve (default: current directory)");
989
+ console.error(" --session <uuid> Session ID for paste-back tracking");
990
+ }
991
+ async function setupHotkey() {
992
+ const configDir = path6.join(os2.homedir(), ".config", "flip");
993
+ const applescriptPath = path6.join(configDir, "flip.applescript");
994
+ const shPath = path6.join(configDir, "flip.sh");
995
+ let nodePath;
996
+ try {
997
+ nodePath = execSync3("which node", { encoding: "utf-8" }).trim();
998
+ } catch {
999
+ nodePath = "/usr/local/bin/node";
1000
+ }
1001
+ const csqPath = path6.resolve(path6.dirname(new URL(import.meta.url).pathname), "../../index.js");
1002
+ fs6.mkdirSync(configDir, { recursive: true });
1003
+ const applescriptContent = `#!/usr/bin/osascript
1004
+
1005
+ -- flip hotkey script
1006
+ -- Generated by: csq flip setup
1007
+
1008
+ tell application "iTerm2"
1009
+ tell current session of current window
1010
+ set originalSessionId to id
1011
+ set sessionUUID to do shell script "uuidgen"
1012
+ set currentPath to variable named "path"
1013
+ do shell script "echo '" & originalSessionId & "' > /tmp/flip-view-session-" & sessionUUID
1014
+ do shell script "nohup ${nodePath} ${csqPath} flip --session " & sessionUUID & " " & quoted form of currentPath & " > /tmp/flip.log 2>&1 &"
1015
+ end tell
1016
+ end tell
1017
+
1018
+ return ""`;
1019
+ fs6.writeFileSync(applescriptPath, applescriptContent);
1020
+ fs6.chmodSync(applescriptPath, "755");
1021
+ const shContent = `#!/bin/bash
1022
+ osascript ${applescriptPath} > /dev/null 2>&1
1023
+ `;
1024
+ fs6.writeFileSync(shPath, shContent);
1025
+ fs6.chmodSync(shPath, "755");
1026
+ console.log("");
1027
+ 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");
1028
+ console.log("\u2502 Flip Hotkey Setup Wizard \u2502");
1029
+ 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");
1030
+ console.log("");
1031
+ console.log("\u2713 Scripts generated:");
1032
+ console.log(` ${shPath}`);
1033
+ console.log("");
1034
+ const openSettings = await confirm2({
1035
+ message: "Open iTerm2 Settings? (Keys \u2192 Key Bindings)",
1036
+ default: true
1037
+ });
1038
+ if (openSettings) {
1039
+ try {
1040
+ execSync3(`osascript -e 'tell application "iTerm2" to activate' -e 'tell application "System Events" to keystroke "," using command down'`);
1041
+ console.log("");
1042
+ console.log(" \u2192 iTerm2 Settings opened. Navigate to: Keys \u2192 Key Bindings");
1043
+ } catch {
1044
+ console.log(" \u2192 Could not open settings automatically. Open manually: iTerm2 \u2192 Settings \u2192 Keys \u2192 Key Bindings");
1045
+ }
1046
+ }
1047
+ console.log("");
1048
+ const ready = await confirm2({
1049
+ message: "Ready to add Key Binding? (Click + button in Key Bindings tab)",
1050
+ default: true
1051
+ });
1052
+ if (!ready) {
1053
+ console.log("");
1054
+ console.log("Run `csq flip setup` again when ready.");
1055
+ return;
1056
+ }
1057
+ console.log("");
1058
+ 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");
1059
+ console.log("\u2502 Configure the Key Binding: \u2502");
1060
+ 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");
1061
+ console.log("\u2502 1. Keyboard Shortcut: Press your hotkey \u2502");
1062
+ console.log("\u2502 (e.g., \u2318\u21E7F) \u2502");
1063
+ console.log("\u2502 \u2502");
1064
+ console.log('\u2502 2. Action: Select "Run Coprocess" \u2502');
1065
+ console.log("\u2502 \u2502");
1066
+ console.log("\u2502 3. Command: Paste the path (copied below) \u2502");
1067
+ 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");
1068
+ console.log("");
1069
+ await clipboardy2.write(shPath);
1070
+ console.log(`\u2713 Copied to clipboard: ${shPath}`);
1071
+ console.log("");
1072
+ await confirm2({
1073
+ message: "Done configuring? (Paste the path and click OK)",
1074
+ default: true
1075
+ });
1076
+ console.log("");
1077
+ 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");
1078
+ console.log("\u2502 Setup Complete! \u2502");
1079
+ 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");
1080
+ console.log("\u2502 Usage: \u2502");
1081
+ console.log("\u2502 1. Focus on any terminal panel \u2502");
1082
+ console.log("\u2502 2. Press your hotkey \u2192 browser opens \u2502");
1083
+ console.log("\u2502 3. Select files, add comments \u2502");
1084
+ console.log("\u2502 4. Submit \u2192 text appears in terminal \u2502");
1085
+ 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");
1086
+ console.log("");
1087
+ }
1088
+
402
1089
  // dist/index.js
1090
+ process.on("SIGINT", () => {
1091
+ process.exit(130);
1092
+ });
403
1093
  var gitAdapter = new GitAdapter();
404
1094
  async function main() {
405
1095
  const args = process.argv.slice(2);
@@ -410,6 +1100,10 @@ async function main() {
410
1100
  printShellInit();
411
1101
  return;
412
1102
  }
1103
+ if (command === "flip") {
1104
+ await runFlip(filteredArgs.slice(1));
1105
+ return;
1106
+ }
413
1107
  const workspaceRoot = await findGitRoot(process.cwd());
414
1108
  if (!workspaceRoot) {
415
1109
  console.error(chalk2.red("Error: Not a git repository"));
@@ -436,13 +1130,18 @@ async function findGitRoot(cwd) {
436
1130
  }
437
1131
  return cwd;
438
1132
  }
1133
+ function getProjectHash(workspaceRoot) {
1134
+ return crypto.createHash("sha256").update(workspaceRoot).digest("hex").slice(0, 8);
1135
+ }
439
1136
  function getSessionsPath(workspaceRoot) {
440
- return path2.join(workspaceRoot, ".code-squad", "sessions.json");
1137
+ const projectHash = getProjectHash(workspaceRoot);
1138
+ const projectName = path7.basename(workspaceRoot);
1139
+ return path7.join(os3.homedir(), ".code-squad", "sessions", `${projectName}-${projectHash}.json`);
441
1140
  }
442
1141
  async function loadLocalThreads(workspaceRoot) {
443
1142
  const sessionsPath = getSessionsPath(workspaceRoot);
444
1143
  try {
445
- const content = await fs2.promises.readFile(sessionsPath, "utf-8");
1144
+ const content = await fs7.promises.readFile(sessionsPath, "utf-8");
446
1145
  const data = JSON.parse(content);
447
1146
  return data.localThreads || [];
448
1147
  } catch {
@@ -451,9 +1150,9 @@ async function loadLocalThreads(workspaceRoot) {
451
1150
  }
452
1151
  async function saveLocalThreads(workspaceRoot, threads) {
453
1152
  const sessionsPath = getSessionsPath(workspaceRoot);
454
- const dir = path2.dirname(sessionsPath);
455
- await fs2.promises.mkdir(dir, { recursive: true });
456
- await fs2.promises.writeFile(sessionsPath, JSON.stringify({ localThreads: threads }, null, 2));
1153
+ const dir = path7.dirname(sessionsPath);
1154
+ await fs7.promises.mkdir(dir, { recursive: true });
1155
+ await fs7.promises.writeFile(sessionsPath, JSON.stringify({ localThreads: threads }, null, 2));
457
1156
  }
458
1157
  async function addLocalThread(workspaceRoot, name) {
459
1158
  const threads = await loadLocalThreads(workspaceRoot);
@@ -533,13 +1232,13 @@ async function listThreads(workspaceRoot) {
533
1232
  }
534
1233
  }
535
1234
  async function createWorktree(workspaceRoot, name) {
536
- const repoName = path2.basename(workspaceRoot);
537
- const defaultBasePath = path2.join(path2.dirname(workspaceRoot), `${repoName}.worktree`);
1235
+ const repoName = path7.basename(workspaceRoot);
1236
+ const defaultBasePath = path7.join(path7.dirname(workspaceRoot), `${repoName}.worktree`);
538
1237
  let worktreeName;
539
1238
  let worktreePath;
540
1239
  if (name) {
541
1240
  worktreeName = name;
542
- worktreePath = path2.join(defaultBasePath, name);
1241
+ worktreePath = path7.join(defaultBasePath, name);
543
1242
  } else {
544
1243
  const form = await newWorktreeForm(defaultBasePath);
545
1244
  if (!form) {
@@ -561,7 +1260,7 @@ async function createWorktree(workspaceRoot, name) {
561
1260
  async function writeCdFile(targetPath) {
562
1261
  const cdFile = process.env.CSQ_CD_FILE;
563
1262
  if (cdFile) {
564
- await fs2.promises.writeFile(cdFile, targetPath);
1263
+ await fs7.promises.writeFile(cdFile, targetPath);
565
1264
  }
566
1265
  }
567
1266
  async function openNewTerminal(targetPath) {
@@ -612,7 +1311,7 @@ end tell`;
612
1311
  async function interactiveMode(workspaceRoot) {
613
1312
  const result = await runInteraction(workspaceRoot);
614
1313
  if (result?.cdPath) {
615
- await writeCdFile(result.cdPath);
1314
+ await openNewTerminal(result.cdPath);
616
1315
  }
617
1316
  }
618
1317
  async function persistentInteractiveMode(workspaceRoot) {
@@ -655,7 +1354,7 @@ async function executeDelete(thread, workspaceRoot) {
655
1354
  }
656
1355
  async function runInteraction(workspaceRoot, _persistent = false) {
657
1356
  const threads = await getAllThreads(workspaceRoot);
658
- const repoName = path2.basename(workspaceRoot);
1357
+ const repoName = path7.basename(workspaceRoot);
659
1358
  const choice = await selectThread(threads, repoName);
660
1359
  if (choice.type === "exit") {
661
1360
  return { exit: true };
@@ -686,7 +1385,7 @@ async function runInteraction(workspaceRoot, _persistent = false) {
686
1385
  return null;
687
1386
  }
688
1387
  if (newType === "worktree") {
689
- const defaultBasePath = path2.join(path2.dirname(workspaceRoot), `${repoName}.worktree`);
1388
+ const defaultBasePath = path7.join(path7.dirname(workspaceRoot), `${repoName}.worktree`);
690
1389
  const form = await newWorktreeForm(defaultBasePath);
691
1390
  if (!form) {
692
1391
  return null;
@@ -716,6 +1415,9 @@ async function runInteraction(workspaceRoot, _persistent = false) {
716
1415
  return null;
717
1416
  }
718
1417
  main().catch((error) => {
1418
+ if (error.message?.includes("SIGINT") || error.message?.includes("force closed")) {
1419
+ process.exit(130);
1420
+ }
719
1421
  console.error(chalk2.red(`Error: ${error.message}`));
720
1422
  process.exit(1);
721
1423
  });