code-squad-cli 1.0.8 → 1.1.2

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,9 +1,9 @@
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";
6
- import * as os from "os";
4
+ import * as path7 from "path";
5
+ import * as fs7 from "fs";
6
+ import * as os3 from "os";
7
7
  import * as crypto from "crypto";
8
8
  import chalk2 from "chalk";
9
9
 
@@ -51,11 +51,11 @@ var GitAdapter = class {
51
51
  const headMatch = headLine.match(/^HEAD (.+)$/);
52
52
  const branchMatch = branchLine?.match(/^branch refs\/heads\/(.+)$/);
53
53
  if (pathMatch && headMatch) {
54
- const path3 = pathMatch[1];
54
+ const path8 = pathMatch[1];
55
55
  const head = headMatch[1];
56
56
  const branch = branchMatch ? branchMatch[1] : "HEAD";
57
- if (path3 !== workspaceRoot) {
58
- worktrees.push({ path: path3, branch, head });
57
+ if (path8 !== workspaceRoot) {
58
+ worktrees.push({ path: path8, branch, head });
59
59
  }
60
60
  }
61
61
  i += 3;
@@ -101,14 +101,14 @@ var GitAdapter = class {
101
101
  });
102
102
  });
103
103
  }
104
- async isValidWorktree(path3, workspaceRoot) {
104
+ async isValidWorktree(path8, workspaceRoot) {
105
105
  try {
106
- await fs.promises.access(path3, fs.constants.R_OK);
106
+ await fs.promises.access(path8, fs.constants.R_OK);
107
107
  } catch {
108
108
  return false;
109
109
  }
110
110
  const isGitRepo = await new Promise((resolve) => {
111
- 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) => {
112
112
  resolve(!error);
113
113
  });
114
114
  });
@@ -116,7 +116,7 @@ var GitAdapter = class {
116
116
  return false;
117
117
  }
118
118
  const worktrees = await this.listWorktrees(workspaceRoot);
119
- return worktrees.some((wt) => wt.path === path3);
119
+ return worktrees.some((wt) => wt.path === path8);
120
120
  }
121
121
  async getWorktreeBranch(worktreePath) {
122
122
  return new Promise((resolve, reject) => {
@@ -401,6 +401,691 @@ async function confirmDeleteLocal(threadName) {
401
401
  });
402
402
  }
403
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 = new URL(import.meta.url).pathname;
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
+
404
1089
  // dist/index.js
405
1090
  process.on("SIGINT", () => {
406
1091
  process.exit(130);
@@ -415,6 +1100,10 @@ async function main() {
415
1100
  printShellInit();
416
1101
  return;
417
1102
  }
1103
+ if (command === "flip") {
1104
+ await runFlip(filteredArgs.slice(1));
1105
+ return;
1106
+ }
418
1107
  const workspaceRoot = await findGitRoot(process.cwd());
419
1108
  if (!workspaceRoot) {
420
1109
  console.error(chalk2.red("Error: Not a git repository"));
@@ -446,13 +1135,13 @@ function getProjectHash(workspaceRoot) {
446
1135
  }
447
1136
  function getSessionsPath(workspaceRoot) {
448
1137
  const projectHash = getProjectHash(workspaceRoot);
449
- const projectName = path2.basename(workspaceRoot);
450
- return path2.join(os.homedir(), ".code-squad", "sessions", `${projectName}-${projectHash}.json`);
1138
+ const projectName = path7.basename(workspaceRoot);
1139
+ return path7.join(os3.homedir(), ".code-squad", "sessions", `${projectName}-${projectHash}.json`);
451
1140
  }
452
1141
  async function loadLocalThreads(workspaceRoot) {
453
1142
  const sessionsPath = getSessionsPath(workspaceRoot);
454
1143
  try {
455
- const content = await fs2.promises.readFile(sessionsPath, "utf-8");
1144
+ const content = await fs7.promises.readFile(sessionsPath, "utf-8");
456
1145
  const data = JSON.parse(content);
457
1146
  return data.localThreads || [];
458
1147
  } catch {
@@ -461,9 +1150,9 @@ async function loadLocalThreads(workspaceRoot) {
461
1150
  }
462
1151
  async function saveLocalThreads(workspaceRoot, threads) {
463
1152
  const sessionsPath = getSessionsPath(workspaceRoot);
464
- const dir = path2.dirname(sessionsPath);
465
- await fs2.promises.mkdir(dir, { recursive: true });
466
- 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));
467
1156
  }
468
1157
  async function addLocalThread(workspaceRoot, name) {
469
1158
  const threads = await loadLocalThreads(workspaceRoot);
@@ -543,13 +1232,13 @@ async function listThreads(workspaceRoot) {
543
1232
  }
544
1233
  }
545
1234
  async function createWorktree(workspaceRoot, name) {
546
- const repoName = path2.basename(workspaceRoot);
547
- 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`);
548
1237
  let worktreeName;
549
1238
  let worktreePath;
550
1239
  if (name) {
551
1240
  worktreeName = name;
552
- worktreePath = path2.join(defaultBasePath, name);
1241
+ worktreePath = path7.join(defaultBasePath, name);
553
1242
  } else {
554
1243
  const form = await newWorktreeForm(defaultBasePath);
555
1244
  if (!form) {
@@ -571,7 +1260,7 @@ async function createWorktree(workspaceRoot, name) {
571
1260
  async function writeCdFile(targetPath) {
572
1261
  const cdFile = process.env.CSQ_CD_FILE;
573
1262
  if (cdFile) {
574
- await fs2.promises.writeFile(cdFile, targetPath);
1263
+ await fs7.promises.writeFile(cdFile, targetPath);
575
1264
  }
576
1265
  }
577
1266
  async function openNewTerminal(targetPath) {
@@ -665,7 +1354,7 @@ async function executeDelete(thread, workspaceRoot) {
665
1354
  }
666
1355
  async function runInteraction(workspaceRoot, _persistent = false) {
667
1356
  const threads = await getAllThreads(workspaceRoot);
668
- const repoName = path2.basename(workspaceRoot);
1357
+ const repoName = path7.basename(workspaceRoot);
669
1358
  const choice = await selectThread(threads, repoName);
670
1359
  if (choice.type === "exit") {
671
1360
  return { exit: true };
@@ -696,7 +1385,7 @@ async function runInteraction(workspaceRoot, _persistent = false) {
696
1385
  return null;
697
1386
  }
698
1387
  if (newType === "worktree") {
699
- const defaultBasePath = path2.join(path2.dirname(workspaceRoot), `${repoName}.worktree`);
1388
+ const defaultBasePath = path7.join(path7.dirname(workspaceRoot), `${repoName}.worktree`);
700
1389
  const form = await newWorktreeForm(defaultBasePath);
701
1390
  if (!form) {
702
1391
  return null;