@iloom/cli 0.3.1 → 0.3.3

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.
Files changed (128) hide show
  1. package/README.md +8 -6
  2. package/dist/{BranchNamingService-OMWKUYMM.js → BranchNamingService-A77VI6AI.js} +2 -2
  3. package/dist/ClaudeContextManager-BN7RE5ZQ.js +15 -0
  4. package/dist/ClaudeService-DLYLJUPA.js +14 -0
  5. package/dist/{GitHubService-EBOETDIW.js → GitHubService-FZHHBOFG.js} +3 -3
  6. package/dist/{LoomLauncher-JF7JZMTZ.js → LoomLauncher-ZV3ZZIBA.js} +40 -26
  7. package/dist/LoomLauncher-ZV3ZZIBA.js.map +1 -0
  8. package/dist/{PromptTemplateManager-A52RUAMS.js → PromptTemplateManager-6HH3PVXV.js} +2 -2
  9. package/dist/README.md +8 -6
  10. package/dist/{SettingsManager-ZCWJ56WP.js → SettingsManager-I2LRCW2A.js} +2 -2
  11. package/dist/{SettingsMigrationManager-AGIIIPDQ.js → SettingsMigrationManager-TJ7UWZG5.js} +3 -3
  12. package/dist/agents/iloom-issue-complexity-evaluator.md +18 -3
  13. package/dist/agents/iloom-issue-enhancer.md +1 -1
  14. package/dist/{chunk-TSKY3JI7.js → chunk-2CXREBLZ.js} +2 -2
  15. package/dist/{chunk-HBYZH6GD.js → chunk-2IJEMXOB.js} +431 -128
  16. package/dist/chunk-2IJEMXOB.js.map +1 -0
  17. package/dist/{chunk-IXKLYTWO.js → chunk-2MAIX45J.js} +8 -8
  18. package/dist/{chunk-4BGK7T6X.js → chunk-5Q3NDNNV.js} +48 -8
  19. package/dist/chunk-5Q3NDNNV.js.map +1 -0
  20. package/dist/{chunk-JQFO7QQN.js → chunk-5VK4NRSF.js} +3 -3
  21. package/dist/{chunk-JQFO7QQN.js.map → chunk-5VK4NRSF.js.map} +1 -1
  22. package/dist/{chunk-XPKDPZ5D.js → chunk-AKUJXDNW.js} +2 -2
  23. package/dist/{chunk-O5OH5MRX.js → chunk-CDZERT7Z.js} +23 -11
  24. package/dist/chunk-CDZERT7Z.js.map +1 -0
  25. package/dist/{chunk-JKXJ7BGL.js → chunk-CE26YH2U.js} +42 -3
  26. package/dist/chunk-CE26YH2U.js.map +1 -0
  27. package/dist/{chunk-ZZZWQGTS.js → chunk-CFFQ2Z7A.js} +74 -75
  28. package/dist/chunk-CFFQ2Z7A.js.map +1 -0
  29. package/dist/{chunk-RO26VS3W.js → chunk-DLHA5VQ3.js} +174 -5
  30. package/dist/chunk-DLHA5VQ3.js.map +1 -0
  31. package/dist/{chunk-ZBQVSHVT.js → chunk-IFB4Z76W.js} +35 -10
  32. package/dist/chunk-IFB4Z76W.js.map +1 -0
  33. package/dist/{chunk-G2IEYOLQ.js → chunk-M7JJCX53.js} +17 -2
  34. package/dist/chunk-M7JJCX53.js.map +1 -0
  35. package/dist/{chunk-KLBYVHPK.js → chunk-OSCLCMDG.js} +2 -2
  36. package/dist/chunk-OXAM2WVC.js +68 -0
  37. package/dist/chunk-OXAM2WVC.js.map +1 -0
  38. package/dist/{chunk-ZWFBBPJI.js → chunk-OYF4VIFI.js} +5 -3
  39. package/dist/chunk-OYF4VIFI.js.map +1 -0
  40. package/dist/{chunk-U5QDY7ZD.js → chunk-PGPI5LR4.js} +8 -8
  41. package/dist/{chunk-WEN5C5DM.js → chunk-RIEO2WML.js} +4 -1
  42. package/dist/chunk-RIEO2WML.js.map +1 -0
  43. package/dist/{chunk-ZE74H5BR.js → chunk-RW54ZMBM.js} +26 -20
  44. package/dist/chunk-RW54ZMBM.js.map +1 -0
  45. package/dist/{chunk-INW24J2W.js → chunk-SUOXY5WJ.js} +2 -2
  46. package/dist/{init-L55Q73H4.js → chunk-UAN4A3YU.js} +345 -45
  47. package/dist/chunk-UAN4A3YU.js.map +1 -0
  48. package/dist/{chunk-IP7SMKIF.js → chunk-UJL4HI2R.js} +59 -60
  49. package/dist/chunk-UJL4HI2R.js.map +1 -0
  50. package/dist/{claude-LUZ35IMK.js → claude-W52VKI6L.js} +4 -2
  51. package/dist/{cleanup-3MONU4PU.js → cleanup-H4VXU3C3.js} +19 -17
  52. package/dist/{cleanup-3MONU4PU.js.map → cleanup-H4VXU3C3.js.map} +1 -1
  53. package/dist/cli.js +347 -114
  54. package/dist/cli.js.map +1 -1
  55. package/dist/{color-ZVALX37U.js → color-F7RU6B6Z.js} +10 -4
  56. package/dist/{contribute-UWJAGIG7.js → contribute-Y7IQV5QY.js} +4 -3
  57. package/dist/{contribute-UWJAGIG7.js.map → contribute-Y7IQV5QY.js.map} +1 -1
  58. package/dist/{feedback-W3BXTGIM.js → feedback-XTUCKJNT.js} +16 -12
  59. package/dist/{feedback-W3BXTGIM.js.map → feedback-XTUCKJNT.js.map} +1 -1
  60. package/dist/{git-34Z6QVDS.js → git-IYA53VIC.js} +9 -2
  61. package/dist/{ignite-KVJEFXNO.js → ignite-T74RYXCA.js} +25 -75
  62. package/dist/ignite-T74RYXCA.js.map +1 -0
  63. package/dist/index.d.ts +71 -14
  64. package/dist/index.js +407 -377
  65. package/dist/index.js.map +1 -1
  66. package/dist/init-4FHTAM3F.js +19 -0
  67. package/dist/mcp/issue-management-server.js +8 -1
  68. package/dist/mcp/issue-management-server.js.map +1 -1
  69. package/dist/{neon-helpers-WPUACUVC.js → neon-helpers-77PBPGJ5.js} +3 -3
  70. package/dist/{open-LNRZL3UU.js → open-UMXANW5S.js} +27 -14
  71. package/dist/open-UMXANW5S.js.map +1 -0
  72. package/dist/{prompt-7INJ7YRU.js → prompt-QALMYTVC.js} +4 -2
  73. package/dist/prompts/init-prompt.txt +89 -9
  74. package/dist/prompts/issue-prompt.txt +18 -11
  75. package/dist/{rebase-C4WNCVGM.js → rebase-VJ2VKR6R.js} +15 -13
  76. package/dist/rebase-VJ2VKR6R.js.map +1 -0
  77. package/dist/{run-IOGNIOYN.js → run-MJYY4PUT.js} +27 -14
  78. package/dist/run-MJYY4PUT.js.map +1 -0
  79. package/dist/schema/settings.schema.json +22 -4
  80. package/dist/{test-git-J7I5MFYH.js → test-git-IT5EWQ5C.js} +5 -5
  81. package/dist/{test-prefix-ZCONBCBX.js → test-prefix-NPWDPUUH.js} +5 -5
  82. package/dist/{test-tabs-RXDBZ6J7.js → test-tabs-PRMRSHKI.js} +3 -2
  83. package/dist/{test-tabs-RXDBZ6J7.js.map → test-tabs-PRMRSHKI.js.map} +1 -1
  84. package/package.json +2 -1
  85. package/dist/ClaudeContextManager-3VXA6UPR.js +0 -13
  86. package/dist/ClaudeService-6CPK43N4.js +0 -12
  87. package/dist/LoomLauncher-JF7JZMTZ.js.map +0 -1
  88. package/dist/chunk-4BGK7T6X.js.map +0 -1
  89. package/dist/chunk-4E4LD3QR.js +0 -302
  90. package/dist/chunk-4E4LD3QR.js.map +0 -1
  91. package/dist/chunk-G2IEYOLQ.js.map +0 -1
  92. package/dist/chunk-HBYZH6GD.js.map +0 -1
  93. package/dist/chunk-IP7SMKIF.js.map +0 -1
  94. package/dist/chunk-JKXJ7BGL.js.map +0 -1
  95. package/dist/chunk-O5OH5MRX.js.map +0 -1
  96. package/dist/chunk-RO26VS3W.js.map +0 -1
  97. package/dist/chunk-WEN5C5DM.js.map +0 -1
  98. package/dist/chunk-ZBQVSHVT.js.map +0 -1
  99. package/dist/chunk-ZE74H5BR.js.map +0 -1
  100. package/dist/chunk-ZWFBBPJI.js.map +0 -1
  101. package/dist/chunk-ZZZWQGTS.js.map +0 -1
  102. package/dist/ignite-KVJEFXNO.js.map +0 -1
  103. package/dist/init-L55Q73H4.js.map +0 -1
  104. package/dist/open-LNRZL3UU.js.map +0 -1
  105. package/dist/rebase-C4WNCVGM.js.map +0 -1
  106. package/dist/run-IOGNIOYN.js.map +0 -1
  107. package/dist/terminal-BIRBZ4AZ.js +0 -16
  108. /package/dist/{BranchNamingService-OMWKUYMM.js.map → BranchNamingService-A77VI6AI.js.map} +0 -0
  109. /package/dist/{ClaudeContextManager-3VXA6UPR.js.map → ClaudeContextManager-BN7RE5ZQ.js.map} +0 -0
  110. /package/dist/{ClaudeService-6CPK43N4.js.map → ClaudeService-DLYLJUPA.js.map} +0 -0
  111. /package/dist/{GitHubService-EBOETDIW.js.map → GitHubService-FZHHBOFG.js.map} +0 -0
  112. /package/dist/{PromptTemplateManager-A52RUAMS.js.map → PromptTemplateManager-6HH3PVXV.js.map} +0 -0
  113. /package/dist/{SettingsManager-ZCWJ56WP.js.map → SettingsManager-I2LRCW2A.js.map} +0 -0
  114. /package/dist/{SettingsMigrationManager-AGIIIPDQ.js.map → SettingsMigrationManager-TJ7UWZG5.js.map} +0 -0
  115. /package/dist/{chunk-TSKY3JI7.js.map → chunk-2CXREBLZ.js.map} +0 -0
  116. /package/dist/{chunk-IXKLYTWO.js.map → chunk-2MAIX45J.js.map} +0 -0
  117. /package/dist/{chunk-XPKDPZ5D.js.map → chunk-AKUJXDNW.js.map} +0 -0
  118. /package/dist/{chunk-KLBYVHPK.js.map → chunk-OSCLCMDG.js.map} +0 -0
  119. /package/dist/{chunk-U5QDY7ZD.js.map → chunk-PGPI5LR4.js.map} +0 -0
  120. /package/dist/{chunk-INW24J2W.js.map → chunk-SUOXY5WJ.js.map} +0 -0
  121. /package/dist/{claude-LUZ35IMK.js.map → claude-W52VKI6L.js.map} +0 -0
  122. /package/dist/{color-ZVALX37U.js.map → color-F7RU6B6Z.js.map} +0 -0
  123. /package/dist/{git-34Z6QVDS.js.map → git-IYA53VIC.js.map} +0 -0
  124. /package/dist/{neon-helpers-WPUACUVC.js.map → init-4FHTAM3F.js.map} +0 -0
  125. /package/dist/{prompt-7INJ7YRU.js.map → neon-helpers-77PBPGJ5.js.map} +0 -0
  126. /package/dist/{terminal-BIRBZ4AZ.js.map → prompt-QALMYTVC.js.map} +0 -0
  127. /package/dist/{test-git-J7I5MFYH.js.map → test-git-IT5EWQ5C.js.map} +0 -0
  128. /package/dist/{test-prefix-ZCONBCBX.js.map → test-prefix-NPWDPUUH.js.map} +0 -0
package/dist/index.js CHANGED
@@ -226,7 +226,7 @@ var init_SettingsManager = __esm({
226
226
  IloomSettingsSchema = z.object({
227
227
  mainBranch: z.string().min(1, "Settings 'mainBranch' cannot be empty").optional().describe("Name of the main/primary branch for the repository"),
228
228
  sourceEnvOnStart: z.boolean().default(false).describe(
229
- "Source .env file when launching terminal processes (Claude, dev server, terminal). NOTE TO AI AGENTS: Before making changes to settings files, YOU MUST OFFER to check .env compatibility. WARNING: Unquoted values in .env files may break sourcing in bash/zsh shells. Before enabling, verify your .env file does not contain unquoted special characters (e.g., database URLs with ?, &, or other shell metacharacters). Shell compatibility issues may cause processes to fail or behave unexpectedly."
229
+ "Source all dotenv-flow files (.env, .env.local, .env.development, .env.development.local) when launching terminal processes (Claude, dev server, terminal). Files are sourced in precedence order so later files override earlier ones. NOTE TO AI AGENTS: Before making changes to settings files, YOU MUST OFFER to check .env file compatibility. WARNING: Unquoted values in .env files may break sourcing in bash/zsh shells. Before enabling, verify ALL your .env.* files do not contain unquoted special characters (e.g., database URLs with ?, &, or other shell metacharacters). Shell compatibility issues may cause processes to fail or behave unexpectedly."
230
230
  ),
231
231
  worktreePrefix: z.string().optional().refine(
232
232
  (val) => {
@@ -272,17 +272,23 @@ var init_SettingsManager = __esm({
272
272
  remote: z.string().optional()
273
273
  }).optional().describe("Merge behavior configuration: local (merge locally) or github-pr (create PR)"),
274
274
  ide: z.object({
275
- type: z.enum(["vscode", "cursor", "webstorm", "sublime", "intellij", "windsurf"]).default("vscode").describe(
276
- "IDE to launch when starting a loom. Options: vscode (Visual Studio Code), cursor (Cursor AI editor), webstorm (JetBrains WebStorm), sublime (Sublime Text), intellij (JetBrains IntelliJ IDEA), windsurf (Windsurf editor)."
275
+ type: z.enum(["vscode", "cursor", "webstorm", "sublime", "intellij", "windsurf", "antigravity"]).default("vscode").describe(
276
+ "IDE to launch when starting a loom. Options: vscode (Visual Studio Code), cursor (Cursor AI editor), webstorm (JetBrains WebStorm), sublime (Sublime Text), intellij (JetBrains IntelliJ IDEA), windsurf (Windsurf editor), antigravity (Antigravity IDE)."
277
277
  )
278
278
  }).optional().describe(
279
- "IDE configuration for workspace launches. Controls which editor opens when you start a loom. Supports VSCode, Cursor, WebStorm, Sublime Text, IntelliJ, and Windsurf. Note: Color synchronization (title bar colors) only works with VSCode-compatible editors (vscode, cursor, windsurf)."
280
- )
279
+ "IDE configuration for workspace launches. Controls which editor opens when you start a loom. Supports VSCode, Cursor, WebStorm, Sublime Text, IntelliJ, Windsurf, and Antigravity. Note: Color synchronization (title bar colors) only works with VSCode-compatible editors (vscode, cursor, windsurf, antigravity)."
280
+ ),
281
+ colors: z.object({
282
+ terminal: z.boolean().default(true).describe("Apply terminal background colors based on branch name (macOS only)"),
283
+ vscode: z.boolean().default(false).describe(
284
+ "Apply VSCode/Cursor title bar colors based on branch name. Note: This modifies .vscode/settings.json which may be in source control. Default is false for safety; enable via init or explicitly if .vscode is gitignored."
285
+ )
286
+ }).optional().describe("Color synchronization settings for workspace identification")
281
287
  });
282
288
  IloomSettingsSchemaNoDefaults = z.object({
283
289
  mainBranch: z.string().min(1, "Settings 'mainBranch' cannot be empty").optional().describe("Name of the main/primary branch for the repository"),
284
290
  sourceEnvOnStart: z.boolean().optional().describe(
285
- "Source .env file when launching terminal processes (Claude, dev server, terminal). NOTE TO AI AGENTS: Before making changes to settings files, YOU MUST OFFER to check .env compatibility. WARNING: Unquoted values in .env files may break sourcing in bash/zsh shells. Before enabling, verify your .env file does not contain unquoted special characters (e.g., database URLs with ?, &, or other shell metacharacters). Shell compatibility issues may cause processes to fail or behave unexpectedly."
291
+ "Source all dotenv-flow files (.env, .env.local, .env.development, .env.development.local) when launching terminal processes (Claude, dev server, terminal). Files are sourced in precedence order so later files override earlier ones. NOTE TO AI AGENTS: Before making changes to settings files, YOU MUST OFFER to check .env compatibility. WARNING: Unquoted values in .env files may break sourcing in bash/zsh shells. Before enabling, verify ALL your .env.* files do not contain unquoted special characters (e.g., database URLs with ?, &, or other shell metacharacters). Shell compatibility issues may cause processes to fail or behave unexpectedly."
286
292
  ),
287
293
  worktreePrefix: z.string().optional().refine(
288
294
  (val) => {
@@ -328,12 +334,18 @@ var init_SettingsManager = __esm({
328
334
  remote: z.string().optional()
329
335
  }).optional().describe("Merge behavior configuration: local (merge locally) or github-pr (create PR)"),
330
336
  ide: z.object({
331
- type: z.enum(["vscode", "cursor", "webstorm", "sublime", "intellij", "windsurf"]).optional().describe(
332
- "IDE to launch when starting a loom. Options: vscode (Visual Studio Code), cursor (Cursor AI editor), webstorm (JetBrains WebStorm), sublime (Sublime Text), intellij (JetBrains IntelliJ IDEA), windsurf (Windsurf editor)."
337
+ type: z.enum(["vscode", "cursor", "webstorm", "sublime", "intellij", "windsurf", "antigravity"]).optional().describe(
338
+ "IDE to launch when starting a loom. Options: vscode (Visual Studio Code), cursor (Cursor AI editor), webstorm (JetBrains WebStorm), sublime (Sublime Text), intellij (JetBrains IntelliJ IDEA), windsurf (Windsurf editor), antigravity (Antigravity IDE)."
333
339
  )
334
340
  }).optional().describe(
335
- "IDE configuration for workspace launches. Controls which editor opens when you start a loom. Supports VSCode, Cursor, WebStorm, Sublime Text, IntelliJ, and Windsurf. Note: Color synchronization (title bar colors) only works with VSCode-compatible editors (vscode, cursor, windsurf)."
336
- )
341
+ "IDE configuration for workspace launches. Controls which editor opens when you start a loom. Supports VSCode, Cursor, WebStorm, Sublime Text, IntelliJ, Windsurf, and Antigravity. Note: Color synchronization (title bar colors) only works with VSCode-compatible editors (vscode, cursor, windsurf, antigravity)."
342
+ ),
343
+ colors: z.object({
344
+ terminal: z.boolean().optional().describe("Apply terminal background colors based on branch name (macOS only)"),
345
+ vscode: z.boolean().optional().describe(
346
+ "Apply VSCode/Cursor title bar colors based on branch name. Note: This modifies .vscode/settings.json which may be in source control."
347
+ )
348
+ }).optional().describe("Color synchronization settings for workspace identification")
337
349
  });
338
350
  SettingsManager = class {
339
351
  /**
@@ -433,8 +445,8 @@ Note: CLI overrides were applied. Check your --set arguments.`);
433
445
  */
434
446
  formatAllZodErrors(error, settingsPath) {
435
447
  const errorMessages = error.issues.map((issue) => {
436
- const path5 = issue.path.length > 0 ? issue.path.join(".") : "root";
437
- return ` - ${path5}: ${issue.message}`;
448
+ const path6 = issue.path.length > 0 ? issue.path.join(".") : "root";
449
+ return ` - ${path6}: ${issue.message}`;
438
450
  });
439
451
  return new Error(
440
452
  `Settings validation failed at ${settingsPath}:
@@ -540,333 +552,55 @@ ${errorMessages.join("\n")}`
540
552
  }
541
553
  });
542
554
 
543
- // src/utils/terminal.ts
544
- var terminal_exports = {};
545
- __export(terminal_exports, {
546
- detectITerm2: () => detectITerm2,
547
- detectPlatform: () => detectPlatform,
548
- openDualTerminalWindow: () => openDualTerminalWindow,
549
- openMultipleTerminalWindows: () => openMultipleTerminalWindows,
550
- openTerminalWindow: () => openTerminalWindow
551
- });
552
- import { execa as execa3 } from "execa";
553
- import { existsSync } from "fs";
554
- function detectPlatform() {
555
- const platform = process.platform;
556
- if (platform === "darwin") return "darwin";
557
- if (platform === "linux") return "linux";
558
- if (platform === "win32") return "win32";
559
- return "unsupported";
560
- }
561
- async function detectITerm2() {
562
- const platform = detectPlatform();
563
- if (platform !== "darwin") return false;
564
- return existsSync("/Applications/iTerm.app");
565
- }
566
- async function openTerminalWindow(options) {
567
- const platform = detectPlatform();
568
- if (platform !== "darwin") {
569
- throw new Error(
570
- `Terminal window launching not yet supported on ${platform}. Currently only macOS is supported.`
571
- );
572
- }
573
- const hasITerm2 = await detectITerm2();
574
- const applescript = hasITerm2 ? buildITerm2SingleTabScript(options) : buildAppleScript(options);
575
- try {
576
- await execa3("osascript", ["-e", applescript]);
577
- if (!hasITerm2) {
578
- await execa3("osascript", ["-e", 'tell application "Terminal" to activate']);
579
- }
580
- } catch (error) {
581
- throw new Error(
582
- `Failed to open terminal window: ${error instanceof Error ? error.message : "Unknown error"}`
583
- );
584
- }
585
- }
586
- function buildAppleScript(options) {
587
- const {
588
- workspacePath,
589
- command,
590
- backgroundColor,
591
- port,
592
- includeEnvSetup,
593
- includePortExport
594
- } = options;
595
- const commands = [];
596
- if (workspacePath) {
597
- commands.push(`cd '${escapePathForAppleScript(workspacePath)}'`);
598
- }
599
- if (includeEnvSetup) {
600
- commands.push("source .env");
601
- }
602
- if (includePortExport && port !== void 0) {
603
- commands.push(`export PORT=${port}`);
604
- }
605
- if (command) {
606
- commands.push(command);
607
- }
608
- const fullCommand = commands.join(" && ");
609
- const historyFreeCommand = ` ${fullCommand}`;
610
- let script = `tell application "Terminal"
611
- `;
612
- script += ` set newTab to do script "${escapeForAppleScript(historyFreeCommand)}"
613
- `;
614
- if (backgroundColor) {
615
- const { r, g, b } = backgroundColor;
616
- script += ` set background color of newTab to {${Math.round(r * 257)}, ${Math.round(g * 257)}, ${Math.round(b * 257)}}
617
- `;
618
- }
619
- script += `end tell`;
620
- return script;
621
- }
622
- function escapePathForAppleScript(path5) {
623
- return path5.replace(/'/g, "'\\''");
624
- }
625
- function escapeForAppleScript(command) {
626
- return command.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
627
- }
628
- function buildITerm2SingleTabScript(options) {
629
- const command = buildCommandSequence(options);
630
- let script = 'tell application id "com.googlecode.iterm2"\n';
631
- script += " create window with default profile\n";
632
- script += " set s1 to current session of current window\n\n";
633
- if (options.backgroundColor) {
634
- const { r, g, b } = options.backgroundColor;
635
- script += ` set background color of s1 to {${Math.round(r * 257)}, ${Math.round(g * 257)}, ${Math.round(b * 257)}}
636
- `;
637
- }
638
- script += ` tell s1 to write text "${escapeForAppleScript(command)}"
639
-
640
- `;
641
- if (options.title) {
642
- script += ` set name of s1 to "${escapeForAppleScript(options.title)}"
643
-
644
- `;
645
- }
646
- script += " activate\n";
647
- script += "end tell";
648
- return script;
649
- }
650
- function buildCommandSequence(options) {
651
- const {
652
- workspacePath,
653
- command,
654
- port,
655
- includeEnvSetup,
656
- includePortExport
657
- } = options;
658
- const commands = [];
659
- if (workspacePath) {
660
- commands.push(`cd '${escapePathForAppleScript(workspacePath)}'`);
661
- }
662
- if (includeEnvSetup) {
663
- commands.push("source .env");
664
- }
665
- if (includePortExport && port !== void 0) {
666
- commands.push(`export PORT=${port}`);
667
- }
668
- if (command) {
669
- commands.push(command);
670
- }
671
- const fullCommand = commands.join(" && ");
672
- return ` ${fullCommand}`;
673
- }
674
- function buildITerm2MultiTabScript(optionsArray) {
675
- if (optionsArray.length < 2) {
676
- throw new Error("buildITerm2MultiTabScript requires at least 2 terminal options");
677
- }
678
- let script = 'tell application id "com.googlecode.iterm2"\n';
679
- script += " create window with default profile\n";
680
- script += " set newWindow to current window\n";
681
- const options1 = optionsArray[0];
682
- if (!options1) {
683
- throw new Error("First terminal option is undefined");
684
- }
685
- const command1 = buildCommandSequence(options1);
686
- script += " set s1 to current session of newWindow\n\n";
687
- if (options1.backgroundColor) {
688
- const { r, g, b } = options1.backgroundColor;
689
- script += ` set background color of s1 to {${Math.round(r * 257)}, ${Math.round(g * 257)}, ${Math.round(b * 257)}}
690
- `;
691
- }
692
- script += ` tell s1 to write text "${escapeForAppleScript(command1)}"
693
-
694
- `;
695
- if (options1.title) {
696
- script += ` set name of s1 to "${escapeForAppleScript(options1.title)}"
697
-
698
- `;
699
- }
700
- for (let i = 1; i < optionsArray.length; i++) {
701
- const options = optionsArray[i];
702
- if (!options) {
703
- throw new Error(`Terminal option at index ${i} is undefined`);
704
- }
705
- const command = buildCommandSequence(options);
706
- const sessionVar = `s${i + 1}`;
707
- script += " tell newWindow\n";
708
- script += ` set newTab${i} to (create tab with default profile)
709
- `;
710
- script += " end tell\n";
711
- script += ` set ${sessionVar} to current session of newTab${i}
712
-
713
- `;
714
- if (options.backgroundColor) {
715
- const { r, g, b } = options.backgroundColor;
716
- script += ` set background color of ${sessionVar} to {${Math.round(r * 257)}, ${Math.round(g * 257)}, ${Math.round(b * 257)}}
717
- `;
718
- }
719
- script += ` tell ${sessionVar} to write text "${escapeForAppleScript(command)}"
720
-
721
- `;
722
- if (options.title) {
723
- script += ` set name of ${sessionVar} to "${escapeForAppleScript(options.title)}"
724
-
725
- `;
726
- }
727
- }
728
- script += " activate\n";
729
- script += "end tell";
730
- return script;
731
- }
732
- async function openMultipleTerminalWindows(optionsArray) {
733
- if (optionsArray.length < 2) {
734
- throw new Error("openMultipleTerminalWindows requires at least 2 terminal options. Use openTerminalWindow for single terminal.");
735
- }
736
- const platform = detectPlatform();
737
- if (platform !== "darwin") {
738
- throw new Error(
739
- `Terminal window launching not yet supported on ${platform}. Currently only macOS is supported.`
740
- );
741
- }
742
- const hasITerm2 = await detectITerm2();
743
- if (hasITerm2) {
744
- const applescript = buildITerm2MultiTabScript(optionsArray);
745
- try {
746
- await execa3("osascript", ["-e", applescript]);
747
- } catch (error) {
748
- throw new Error(
749
- `Failed to open iTerm2 window: ${error instanceof Error ? error.message : "Unknown error"}`
750
- );
751
- }
752
- } else {
753
- for (let i = 0; i < optionsArray.length; i++) {
754
- const options = optionsArray[i];
755
- if (!options) {
756
- throw new Error(`Terminal option at index ${i} is undefined`);
757
- }
758
- await openTerminalWindow(options);
759
- if (i < optionsArray.length - 1) {
760
- await new Promise((resolve) => setTimeout(resolve, 1e3));
761
- }
762
- }
763
- }
764
- }
765
- async function openDualTerminalWindow(options1, options2) {
766
- await openMultipleTerminalWindows([options1, options2]);
767
- }
768
- var init_terminal = __esm({
769
- "src/utils/terminal.ts"() {
770
- "use strict";
771
- }
772
- });
773
-
774
555
  // src/utils/color.ts
775
556
  var color_exports = {};
776
557
  __export(color_exports, {
558
+ MIN_COLOR_DISTANCE: () => MIN_COLOR_DISTANCE,
777
559
  calculateForegroundColor: () => calculateForegroundColor,
560
+ colorDistance: () => colorDistance,
778
561
  generateColorFromBranchName: () => generateColorFromBranchName,
779
562
  getColorPalette: () => getColorPalette,
780
563
  hexToRgb: () => hexToRgb,
781
564
  lightenColor: () => lightenColor,
782
565
  rgbToHex: () => rgbToHex,
783
- saturateColor: () => saturateColor
566
+ saturateColor: () => saturateColor,
567
+ selectDistinctColor: () => selectDistinctColor
784
568
  });
785
569
  import { createHash as createHash2 } from "crypto";
786
570
  function getColorPalette() {
787
571
  return [
788
- // First 10 colors preserved for backward compatibility
789
- { r: 220, g: 235, b: 248 },
572
+ { r: 220, g: 235, b: 255 },
790
573
  // 0: Soft blue
791
- { r: 248, g: 220, b: 235 },
574
+ { r: 255, g: 220, b: 235 },
792
575
  // 1: Soft pink
793
- { r: 220, g: 248, b: 235 },
576
+ { r: 220, g: 255, b: 235 },
794
577
  // 2: Soft green
795
- { r: 248, g: 240, b: 220 },
578
+ { r: 255, g: 245, b: 220 },
796
579
  // 3: Soft cream
797
- { r: 240, g: 220, b: 248 },
580
+ { r: 245, g: 220, b: 255 },
798
581
  // 4: Soft lavender
799
- { r: 220, g: 240, b: 248 },
582
+ { r: 220, g: 245, b: 255 },
800
583
  // 5: Soft cyan
801
584
  { r: 235, g: 235, b: 235 },
802
585
  // 6: Soft grey
803
- { r: 228, g: 238, b: 248 },
804
- // 7: Soft ice blue
805
- { r: 248, g: 228, b: 238 },
806
- // 8: Soft rose
807
- { r: 228, g: 248, b: 238 },
808
- // 9: Soft mint
809
- // 30 new colors (indices 10-39)
810
- { r: 235, g: 245, b: 250 },
811
- // 10: Pale sky blue
812
- { r: 250, g: 235, b: 245 },
813
- // 11: Pale orchid
814
- { r: 235, g: 250, b: 245 },
815
- // 12: Pale seafoam
816
- { r: 250, g: 245, b: 235 },
817
- // 13: Pale peach
818
- { r: 245, g: 235, b: 250 },
819
- // 14: Pale periwinkle
820
- { r: 235, g: 245, b: 235 },
821
- // 15: Pale sage
822
- { r: 245, g: 250, b: 235 },
823
- // 16: Pale lemon
824
- { r: 245, g: 235, b: 235 },
825
- // 17: Pale blush
826
- { r: 235, g: 235, b: 250 },
827
- // 18: Pale lavender blue
828
- { r: 250, g: 235, b: 235 },
829
- // 19: Pale coral
830
- { r: 235, g: 250, b: 250 },
831
- // 20: Pale aqua
832
- { r: 240, g: 248, b: 255 },
833
- // 21: Alice blue
834
- { r: 255, g: 240, b: 248 },
835
- // 22: Lavender blush
836
- { r: 240, g: 255, b: 248 },
837
- // 23: Honeydew tint
838
- { r: 255, g: 248, b: 240 },
839
- // 24: Antique white
840
- { r: 248, g: 240, b: 255 },
841
- // 25: Magnolia
842
- { r: 240, g: 248, b: 240 },
843
- // 26: Mint cream tint
844
- { r: 248, g: 255, b: 240 },
845
- // 27: Ivory tint
846
- { r: 248, g: 240, b: 240 },
847
- // 28: Misty rose tint
848
- { r: 240, g: 240, b: 255 },
849
- // 29: Ghost white tint
850
- { r: 255, g: 245, b: 238 },
851
- // 30: Seashell
852
- { r: 245, g: 255, b: 250 },
853
- // 31: Azure mist
854
- { r: 250, g: 245, b: 255 },
855
- // 32: Lilac mist
856
- { r: 255, g: 250, b: 245 },
857
- // 33: Snow peach
858
- { r: 238, g: 245, b: 255 },
859
- // 34: Powder blue
860
- { r: 255, g: 238, b: 245 },
861
- // 35: Pink lace
862
- { r: 245, g: 255, b: 238 },
863
- // 36: Pale lime
864
- { r: 238, g: 255, b: 245 },
865
- // 37: Pale turquoise
866
- { r: 245, g: 238, b: 255 },
867
- // 38: Pale violet
868
- { r: 255, g: 245, b: 255 }
869
- // 39: Pale magenta
586
+ { r: 255, g: 230, b: 230 },
587
+ // 7: Soft coral
588
+ { r: 230, g: 255, b: 230 },
589
+ // 8: Soft mint
590
+ { r: 255, g: 245, b: 230 },
591
+ // 9: Soft peach
592
+ { r: 220, g: 255, b: 255 },
593
+ // 10: Soft aqua
594
+ { r: 255, g: 220, b: 255 },
595
+ // 11: Soft magenta
596
+ { r: 255, g: 255, b: 220 },
597
+ // 12: Soft yellow
598
+ { r: 235, g: 220, b: 255 },
599
+ // 13: Soft violet
600
+ { r: 220, g: 255, b: 245 },
601
+ // 14: Soft sea green
602
+ { r: 255, g: 235, b: 220 }
603
+ // 15: Soft salmon
870
604
  ];
871
605
  }
872
606
  function rgbToHex(r, g, b) {
@@ -906,6 +640,51 @@ function generateColorFromBranchName(branchName) {
906
640
  index
907
641
  };
908
642
  }
643
+ function colorDistance(a, b) {
644
+ return Math.sqrt(
645
+ Math.pow(a.r - b.r, 2) + Math.pow(a.g - b.g, 2) + Math.pow(a.b - b.b, 2)
646
+ );
647
+ }
648
+ function selectDistinctColor(branchName, usedHexColors) {
649
+ const palette = getColorPalette();
650
+ const hashBasedColor = generateColorFromBranchName(branchName);
651
+ if (usedHexColors.length === 0) {
652
+ return hashBasedColor;
653
+ }
654
+ const usedRgbColors = [];
655
+ for (const hex of usedHexColors) {
656
+ try {
657
+ usedRgbColors.push(hexToRgb(hex));
658
+ } catch {
659
+ logger_default.debug(`[selectDistinctColor] Skipping invalid hex color: ${hex}`);
660
+ }
661
+ }
662
+ if (usedRgbColors.length === 0) {
663
+ return hashBasedColor;
664
+ }
665
+ const isTooSimilar = usedRgbColors.some(
666
+ (usedRgb) => colorDistance(hashBasedColor.rgb, usedRgb) < MIN_COLOR_DISTANCE
667
+ );
668
+ if (!isTooSimilar) {
669
+ return hashBasedColor;
670
+ }
671
+ for (let i = 0; i < palette.length; i++) {
672
+ const candidateRgb = palette[i];
673
+ if (!candidateRgb) continue;
674
+ const isDistinct = usedRgbColors.every(
675
+ (usedRgb) => colorDistance(candidateRgb, usedRgb) >= MIN_COLOR_DISTANCE
676
+ );
677
+ if (isDistinct) {
678
+ return {
679
+ rgb: candidateRgb,
680
+ hex: rgbToHex(candidateRgb.r, candidateRgb.g, candidateRgb.b),
681
+ index: i
682
+ };
683
+ }
684
+ }
685
+ logger_default.debug(`[selectDistinctColor] No distinct color found, falling back to hash-based for ${branchName}`);
686
+ return hashBasedColor;
687
+ }
909
688
  function lightenColor(rgb, amount) {
910
689
  const clamp = (value) => Math.min(255, Math.max(0, Math.round(value)));
911
690
  return {
@@ -934,10 +713,12 @@ function calculateForegroundColor(rgb) {
934
713
  const luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b;
935
714
  return luminance > 0.5 ? "#000000" : "#ffffff";
936
715
  }
716
+ var MIN_COLOR_DISTANCE;
937
717
  var init_color = __esm({
938
718
  "src/utils/color.ts"() {
939
719
  "use strict";
940
720
  init_logger();
721
+ MIN_COLOR_DISTANCE = 20;
941
722
  }
942
723
  });
943
724
 
@@ -951,6 +732,7 @@ import path3 from "path";
951
732
  import fs from "fs-extra";
952
733
 
953
734
  // src/utils/git.ts
735
+ init_SettingsManager();
954
736
  init_logger();
955
737
  import path2 from "path";
956
738
  import { execa } from "execa";
@@ -1097,7 +879,7 @@ function extractIssueNumber(branchName) {
1097
879
  }
1098
880
  return null;
1099
881
  }
1100
- function isWorktreePath(path5) {
882
+ function isWorktreePath(path6) {
1101
883
  const worktreePatterns = [
1102
884
  /\/worktrees?\//i,
1103
885
  // Contains /worktree/ or /worktrees/
@@ -1112,7 +894,7 @@ function isWorktreePath(path5) {
1112
894
  /\.worktree$/i
1113
895
  // ends with .worktree
1114
896
  ];
1115
- return worktreePatterns.some((pattern) => pattern.test(path5));
897
+ return worktreePatterns.some((pattern) => pattern.test(path6));
1116
898
  }
1117
899
  function generateWorktreePath(branchName, rootDir = process.cwd(), options) {
1118
900
  let sanitized = branchName.replace(/\//g, "-");
@@ -1154,31 +936,31 @@ function generateWorktreePath(branchName, rootDir = process.cwd(), options) {
1154
936
  return path2.join(parentDir, `${prefix}${sanitized}`);
1155
937
  }
1156
938
  }
1157
- async function isValidGitRepo(path5) {
939
+ async function isValidGitRepo(path6) {
1158
940
  try {
1159
- await executeGitCommand(["rev-parse", "--git-dir"], { cwd: path5 });
941
+ await executeGitCommand(["rev-parse", "--git-dir"], { cwd: path6 });
1160
942
  return true;
1161
943
  } catch {
1162
944
  return false;
1163
945
  }
1164
946
  }
1165
- async function getCurrentBranch(path5 = process.cwd()) {
947
+ async function getCurrentBranch(path6 = process.cwd()) {
1166
948
  try {
1167
- const result = await executeGitCommand(["branch", "--show-current"], { cwd: path5 });
949
+ const result = await executeGitCommand(["branch", "--show-current"], { cwd: path6 });
1168
950
  return result.trim();
1169
951
  } catch {
1170
952
  return null;
1171
953
  }
1172
954
  }
1173
- async function branchExists(branchName, path5 = process.cwd(), includeRemote = true) {
955
+ async function branchExists(branchName, path6 = process.cwd(), includeRemote = true) {
1174
956
  try {
1175
- const localResult = await executeGitCommand(["branch", "--list", branchName], { cwd: path5 });
957
+ const localResult = await executeGitCommand(["branch", "--list", branchName], { cwd: path6 });
1176
958
  if (localResult.trim()) {
1177
959
  return true;
1178
960
  }
1179
961
  if (includeRemote) {
1180
962
  const remoteResult = await executeGitCommand(["branch", "-r", "--list", `*/${branchName}`], {
1181
- cwd: path5
963
+ cwd: path6
1182
964
  });
1183
965
  if (remoteResult.trim()) {
1184
966
  return true;
@@ -1189,17 +971,31 @@ async function branchExists(branchName, path5 = process.cwd(), includeRemote = t
1189
971
  return false;
1190
972
  }
1191
973
  }
1192
- async function getRepoRoot(path5 = process.cwd()) {
974
+ async function getWorktreeRoot(path6 = process.cwd()) {
1193
975
  try {
1194
- const result = await executeGitCommand(["rev-parse", "--show-toplevel"], { cwd: path5 });
976
+ const result = await executeGitCommand(["rev-parse", "--show-toplevel"], { cwd: path6 });
1195
977
  return result.trim();
1196
978
  } catch {
1197
979
  return null;
1198
980
  }
1199
981
  }
1200
- async function findMainWorktreePath(path5 = process.cwd(), options) {
982
+ async function getRepoRoot(path6 = process.cwd()) {
983
+ try {
984
+ const gitCommonDir = await executeGitCommand(
985
+ ["rev-parse", "--path-format=absolute", "--git-common-dir"],
986
+ { cwd: path6 }
987
+ );
988
+ const trimmedPath = gitCommonDir.trim();
989
+ const repoRoot = trimmedPath.replace(/\/\.git\/worktrees\/[^/]+$/, "").replace(/\/\.git$/, "");
990
+ return repoRoot;
991
+ } catch (error) {
992
+ logger.warn(`Failed to determine repo root from git-common-dir: ${path6}`, error instanceof Error ? error.message : String(error));
993
+ return null;
994
+ }
995
+ }
996
+ async function findMainWorktreePath(path6 = process.cwd(), options) {
1201
997
  try {
1202
- const output = await executeGitCommand(["worktree", "list", "--porcelain"], { cwd: path5 });
998
+ const output = await executeGitCommand(["worktree", "list", "--porcelain"], { cwd: path6 });
1203
999
  const worktrees = parseWorktreeList(output, options == null ? void 0 : options.mainBranch);
1204
1000
  if (worktrees.length === 0) {
1205
1001
  throw new Error("No worktrees found in repository");
@@ -1229,33 +1025,30 @@ async function findMainWorktreePath(path5 = process.cwd(), options) {
1229
1025
  throw new Error(`Failed to find main worktree: ${error instanceof Error ? error.message : String(error)}`);
1230
1026
  }
1231
1027
  }
1232
- async function findMainWorktreePathWithSettings(path5, settingsManager) {
1233
- if (!settingsManager) {
1234
- const { SettingsManager: SM } = await Promise.resolve().then(() => (init_SettingsManager(), SettingsManager_exports));
1235
- settingsManager = new SM();
1236
- }
1237
- const settings = await settingsManager.loadSettings(path5);
1028
+ async function findMainWorktreePathWithSettings(path6, settingsManager) {
1029
+ settingsManager ??= new SettingsManager();
1030
+ const settings = await settingsManager.loadSettings(path6);
1238
1031
  const findOptions = settings.mainBranch ? { mainBranch: settings.mainBranch } : void 0;
1239
- return findMainWorktreePath(path5, findOptions);
1032
+ return findMainWorktreePath(path6, findOptions);
1240
1033
  }
1241
- async function hasUncommittedChanges(path5 = process.cwd()) {
1034
+ async function hasUncommittedChanges(path6 = process.cwd()) {
1242
1035
  try {
1243
- const result = await executeGitCommand(["status", "--porcelain"], { cwd: path5 });
1036
+ const result = await executeGitCommand(["status", "--porcelain"], { cwd: path6 });
1244
1037
  return result.trim().length > 0;
1245
1038
  } catch {
1246
1039
  return false;
1247
1040
  }
1248
1041
  }
1249
- async function getDefaultBranch(path5 = process.cwd()) {
1042
+ async function getDefaultBranch(path6 = process.cwd()) {
1250
1043
  try {
1251
1044
  const remoteResult = await executeGitCommand(["symbolic-ref", "refs/remotes/origin/HEAD"], {
1252
- cwd: path5
1045
+ cwd: path6
1253
1046
  });
1254
1047
  const match = remoteResult.match(/refs\/remotes\/origin\/(.+)/);
1255
1048
  if (match) return match[1] ?? "main";
1256
1049
  const commonDefaults = ["main", "master", "develop"];
1257
1050
  for (const branch of commonDefaults) {
1258
- if (await branchExists(branch, path5)) {
1051
+ if (await branchExists(branch, path6)) {
1259
1052
  return branch;
1260
1053
  }
1261
1054
  }
@@ -1264,13 +1057,13 @@ async function getDefaultBranch(path5 = process.cwd()) {
1264
1057
  return "main";
1265
1058
  }
1266
1059
  }
1267
- async function findAllBranchesForIssue(issueNumber, path5 = process.cwd(), settingsManager) {
1060
+ async function findAllBranchesForIssue(issueNumber, path6 = process.cwd(), settingsManager) {
1268
1061
  if (!settingsManager) {
1269
1062
  const { SettingsManager: SM } = await Promise.resolve().then(() => (init_SettingsManager(), SettingsManager_exports));
1270
1063
  settingsManager = new SM();
1271
1064
  }
1272
- const protectedBranches = await settingsManager.getProtectedBranches(path5);
1273
- const output = await executeGitCommand(["branch", "-a"], { cwd: path5 });
1065
+ const protectedBranches = await settingsManager.getProtectedBranches(path6);
1066
+ const output = await executeGitCommand(["branch", "-a"], { cwd: path6 });
1274
1067
  const branches = [];
1275
1068
  const lines = output.split("\n").filter(Boolean);
1276
1069
  for (const line of lines) {
@@ -1327,18 +1120,18 @@ async function findAllBranchesForIssue(issueNumber, path5 = process.cwd(), setti
1327
1120
  }
1328
1121
  return branches;
1329
1122
  }
1330
- async function isEmptyRepository(path5 = process.cwd()) {
1123
+ async function isEmptyRepository(path6 = process.cwd()) {
1331
1124
  try {
1332
- await executeGitCommand(["rev-parse", "--verify", "HEAD"], { cwd: path5 });
1125
+ await executeGitCommand(["rev-parse", "--verify", "HEAD"], { cwd: path6 });
1333
1126
  return false;
1334
1127
  } catch {
1335
1128
  return true;
1336
1129
  }
1337
1130
  }
1338
- async function ensureRepositoryHasCommits(path5 = process.cwd()) {
1339
- const isEmpty = await isEmptyRepository(path5);
1131
+ async function ensureRepositoryHasCommits(path6 = process.cwd()) {
1132
+ const isEmpty = await isEmptyRepository(path6);
1340
1133
  if (isEmpty) {
1341
- await executeGitCommand(["commit", "--no-verify", "--allow-empty", "-m", "Initial commit"], { cwd: path5 });
1134
+ await executeGitCommand(["commit", "--no-verify", "--allow-empty", "-m", "Initial commit"], { cwd: path6 });
1342
1135
  }
1343
1136
  }
1344
1137
  async function pushBranchToRemote(branchName, worktreePath, options) {
@@ -1383,6 +1176,29 @@ async function pushBranchToRemote(branchName, worktreePath, options) {
1383
1176
  throw new Error(`Failed to push to remote: ${errorMessage}`);
1384
1177
  }
1385
1178
  }
1179
+ async function isFileTrackedByGit(filePath, cwd = process.cwd()) {
1180
+ try {
1181
+ const result = await executeGitCommand(
1182
+ ["ls-files", "--error-unmatch", filePath],
1183
+ { cwd }
1184
+ );
1185
+ return result.trim().length > 0;
1186
+ } catch (error) {
1187
+ const errorMessage = error instanceof Error ? error.message : String(error);
1188
+ if (errorMessage.includes("pathspec") && errorMessage.includes("did not match")) {
1189
+ return false;
1190
+ }
1191
+ throw error;
1192
+ }
1193
+ }
1194
+ async function isFileGitignored(filePath, cwd = process.cwd()) {
1195
+ try {
1196
+ await executeGitCommand(["check-ignore", "-q", filePath], { cwd });
1197
+ return true;
1198
+ } catch {
1199
+ return false;
1200
+ }
1201
+ }
1386
1202
 
1387
1203
  // src/lib/GitWorktreeManager.ts
1388
1204
  var GitWorktreeManager = class {
@@ -1752,6 +1568,9 @@ var GitWorktreeManager = class {
1752
1568
  }
1753
1569
  };
1754
1570
 
1571
+ // src/lib/GitHubService.ts
1572
+ import { execSync } from "child_process";
1573
+
1755
1574
  // src/types/github.ts
1756
1575
  var GitHubError = class extends Error {
1757
1576
  constructor(code, message, details) {
@@ -1991,6 +1810,18 @@ var GitHubService = class {
1991
1810
  this.supportsPullRequests = true;
1992
1811
  this.prompter = (options == null ? void 0 : options.prompter) ?? promptConfirmation;
1993
1812
  }
1813
+ /**
1814
+ * Check if GitHub CLI (gh) is available on the system
1815
+ * @returns true if gh CLI is installed and accessible, false otherwise
1816
+ */
1817
+ static isCliAvailable() {
1818
+ try {
1819
+ execSync("gh --version", { stdio: "ignore" });
1820
+ return true;
1821
+ } catch {
1822
+ return false;
1823
+ }
1824
+ }
1994
1825
  // Input detection - IssueTracker interface implementation
1995
1826
  async detectInputType(input, repo) {
1996
1827
  const numberMatch = input.match(/^#?(\d+)$/);
@@ -2611,6 +2442,7 @@ import fs2 from "fs-extra";
2611
2442
 
2612
2443
  // src/utils/env.ts
2613
2444
  init_logger();
2445
+ import path4 from "path";
2614
2446
  import dotenvFlow from "dotenv-flow";
2615
2447
  function parseEnvFile(content) {
2616
2448
  const envMap = /* @__PURE__ */ new Map();
@@ -2664,6 +2496,45 @@ function isValidEnvKey(key) {
2664
2496
  const validKeyRegex = /^[A-Za-z_][A-Za-z0-9_]*$/;
2665
2497
  return validKeyRegex.test(key);
2666
2498
  }
2499
+ var DOTENV_FLOW_NODE_ENV = process.env.DOTENV_FLOW_NODE_ENV ?? "development";
2500
+ function getDotenvFlowFiles() {
2501
+ return [
2502
+ ".env",
2503
+ ".env.local",
2504
+ `.env.${DOTENV_FLOW_NODE_ENV}`,
2505
+ `.env.${DOTENV_FLOW_NODE_ENV}.local`
2506
+ ];
2507
+ }
2508
+ async function buildEnvSourceCommands(workspacePath, fileExists) {
2509
+ const files = getDotenvFlowFiles();
2510
+ const commands = [];
2511
+ for (const file of files) {
2512
+ const fullPath = path4.join(workspacePath, file);
2513
+ const exists = await fileExists(fullPath);
2514
+ if (exists) {
2515
+ commands.push(`source ${file}`);
2516
+ }
2517
+ }
2518
+ return commands;
2519
+ }
2520
+ async function findEnvFileContainingVariable(workspacePath, variableName, fileExists, getEnvVariable) {
2521
+ const files = getDotenvFlowFiles().reverse();
2522
+ for (const file of files) {
2523
+ const fullPath = path4.join(workspacePath, file);
2524
+ if (!await fileExists(fullPath)) {
2525
+ continue;
2526
+ }
2527
+ const value = await getEnvVariable(fullPath, variableName);
2528
+ if (value !== null) {
2529
+ return file;
2530
+ }
2531
+ }
2532
+ return null;
2533
+ }
2534
+ async function hasVariableInAnyEnvFile(workspacePath, variableName, fileExists, getEnvVariable) {
2535
+ const file = await findEnvFileContainingVariable(workspacePath, variableName, fileExists, getEnvVariable);
2536
+ return file !== null;
2537
+ }
2667
2538
 
2668
2539
  // src/utils/port.ts
2669
2540
  import { createHash } from "crypto";
@@ -2882,6 +2753,7 @@ var EnvironmentManager = class {
2882
2753
 
2883
2754
  // src/lib/DatabaseManager.ts
2884
2755
  init_logger();
2756
+ import fs3 from "fs-extra";
2885
2757
  var logger3 = createLogger({ prefix: "\u{1F5C2}\uFE0F" });
2886
2758
  var DatabaseManager = class {
2887
2759
  constructor(provider, environment, databaseUrlEnvVarName = "DATABASE_URL") {
@@ -2904,17 +2776,17 @@ var DatabaseManager = class {
2904
2776
  * Check if database branching should be used
2905
2777
  * Requires BOTH conditions:
2906
2778
  * 1. Database provider is properly configured (checked via provider.isConfigured())
2907
- * 2. .env file contains the configured database URL variable
2779
+ * 2. Any dotenv-flow file contains the configured database URL variable
2908
2780
  */
2909
- async shouldUseDatabaseBranching(envFilePath) {
2781
+ async shouldUseDatabaseBranching(workspacePath) {
2910
2782
  if (!this.provider.isConfigured()) {
2911
2783
  logger3.debug("Skipping database branching: Database provider not configured");
2912
2784
  return false;
2913
2785
  }
2914
- const hasDatabaseUrl = await this.hasDatabaseUrlInEnv(envFilePath);
2786
+ const hasDatabaseUrl = await this.hasDatabaseUrlInEnv(workspacePath);
2915
2787
  if (!hasDatabaseUrl) {
2916
2788
  logger3.debug(
2917
- "Skipping database branching: configured database URL variable not found in .env file"
2789
+ "Skipping database branching: configured database URL variable not found in any env file"
2918
2790
  );
2919
2791
  return false;
2920
2792
  }
@@ -2925,12 +2797,12 @@ var DatabaseManager = class {
2925
2797
  * Returns connection string if branch was created, null if skipped
2926
2798
  *
2927
2799
  * @param branchName - Name of the branch to create
2928
- * @param envFilePath - Path to .env file for configuration checks
2800
+ * @param workspacePath - Path to workspace for configuration checks (checks all dotenv-flow files)
2929
2801
  * @param cwd - Optional working directory to run commands from
2930
2802
  * @param fromBranch - Optional parent branch to create from (for child looms)
2931
2803
  */
2932
- async createBranchIfConfigured(branchName, envFilePath, cwd, fromBranch) {
2933
- if (!await this.shouldUseDatabaseBranching(envFilePath)) {
2804
+ async createBranchIfConfigured(branchName, workspacePath, cwd, fromBranch) {
2805
+ if (!await this.shouldUseDatabaseBranching(workspacePath)) {
2934
2806
  return null;
2935
2807
  }
2936
2808
  if (!await this.provider.isCliAvailable()) {
@@ -3057,19 +2929,24 @@ var DatabaseManager = class {
3057
2929
  return null;
3058
2930
  }
3059
2931
  /**
3060
- * Check if .env has the configured database URL variable
2932
+ * Check if any dotenv-flow file has the configured database URL variable
3061
2933
  * CRITICAL: If user explicitly configured a custom variable name (not default),
3062
- * throw an error if it's missing from .env
2934
+ * throw an error if it's missing from all env files
3063
2935
  */
3064
- async hasDatabaseUrlInEnv(envFilePath) {
2936
+ async hasDatabaseUrlInEnv(workspacePath) {
3065
2937
  try {
3066
- const envMap = await this.environment.readEnvFile(envFilePath);
3067
2938
  if (this.databaseUrlEnvVarName !== "DATABASE_URL") {
3068
2939
  logger3.debug(`Looking for custom database URL variable: ${this.databaseUrlEnvVarName}`);
3069
2940
  } else {
3070
2941
  logger3.debug("Looking for default database URL variable: DATABASE_URL");
3071
2942
  }
3072
- if (envMap.has(this.databaseUrlEnvVarName)) {
2943
+ const hasConfiguredVar = await hasVariableInAnyEnvFile(
2944
+ workspacePath,
2945
+ this.databaseUrlEnvVarName,
2946
+ async (p) => fs3.pathExists(p),
2947
+ async (p, v) => this.environment.getEnvVariable(p, v)
2948
+ );
2949
+ if (hasConfiguredVar) {
3073
2950
  if (this.databaseUrlEnvVarName !== "DATABASE_URL") {
3074
2951
  logger3.debug(`\u2705 Found custom database URL variable: ${this.databaseUrlEnvVarName}`);
3075
2952
  } else {
@@ -3078,20 +2955,25 @@ var DatabaseManager = class {
3078
2955
  return true;
3079
2956
  }
3080
2957
  if (this.databaseUrlEnvVarName !== "DATABASE_URL") {
3081
- logger3.debug(`\u274C Custom database URL variable '${this.databaseUrlEnvVarName}' not found in .env file`);
2958
+ logger3.debug(`\u274C Custom database URL variable '${this.databaseUrlEnvVarName}' not found in any env file`);
3082
2959
  throw new Error(
3083
- `Configured database URL environment variable '${this.databaseUrlEnvVarName}' not found in .env file. Please add it to your .env file or update your iloom configuration.`
2960
+ `Configured database URL environment variable '${this.databaseUrlEnvVarName}' not found in any dotenv-flow file. Please add it to an .env file or update your iloom configuration.`
3084
2961
  );
3085
2962
  }
3086
- const hasDefaultVar = envMap.has("DATABASE_URL");
2963
+ const hasDefaultVar = await hasVariableInAnyEnvFile(
2964
+ workspacePath,
2965
+ "DATABASE_URL",
2966
+ async (p) => fs3.pathExists(p),
2967
+ async (p, v) => this.environment.getEnvVariable(p, v)
2968
+ );
3087
2969
  if (hasDefaultVar) {
3088
2970
  logger3.debug("\u2705 Found fallback DATABASE_URL variable");
3089
2971
  } else {
3090
- logger3.debug("\u274C No DATABASE_URL variable found in .env file");
2972
+ logger3.debug("\u274C No DATABASE_URL variable found in any env file");
3091
2973
  }
3092
2974
  return hasDefaultVar;
3093
2975
  } catch (error) {
3094
- if (error instanceof Error && error.message.includes("not found in .env")) {
2976
+ if (error instanceof Error && error.message.includes("not found in")) {
3095
2977
  throw error;
3096
2978
  }
3097
2979
  return false;
@@ -3104,6 +2986,140 @@ init_logger();
3104
2986
  import { execa as execa4 } from "execa";
3105
2987
  import { existsSync as existsSync2 } from "fs";
3106
2988
  import { join } from "path";
2989
+
2990
+ // src/utils/terminal.ts
2991
+ import { execa as execa3 } from "execa";
2992
+ import { existsSync } from "fs";
2993
+ function detectPlatform() {
2994
+ const platform = process.platform;
2995
+ if (platform === "darwin") return "darwin";
2996
+ if (platform === "linux") return "linux";
2997
+ if (platform === "win32") return "win32";
2998
+ return "unsupported";
2999
+ }
3000
+ async function detectITerm2() {
3001
+ const platform = detectPlatform();
3002
+ if (platform !== "darwin") return false;
3003
+ return existsSync("/Applications/iTerm.app");
3004
+ }
3005
+ async function openTerminalWindow(options) {
3006
+ const platform = detectPlatform();
3007
+ if (platform !== "darwin") {
3008
+ throw new Error(
3009
+ `Terminal window launching not yet supported on ${platform}. Currently only macOS is supported.`
3010
+ );
3011
+ }
3012
+ const hasITerm2 = await detectITerm2();
3013
+ const applescript = hasITerm2 ? await buildITerm2SingleTabScript(options) : await buildAppleScript(options);
3014
+ try {
3015
+ await execa3("osascript", ["-e", applescript]);
3016
+ if (!hasITerm2) {
3017
+ await execa3("osascript", ["-e", 'tell application "Terminal" to activate']);
3018
+ }
3019
+ } catch (error) {
3020
+ throw new Error(
3021
+ `Failed to open terminal window: ${error instanceof Error ? error.message : "Unknown error"}`
3022
+ );
3023
+ }
3024
+ }
3025
+ async function buildAppleScript(options) {
3026
+ const {
3027
+ workspacePath,
3028
+ command,
3029
+ backgroundColor,
3030
+ port,
3031
+ includeEnvSetup,
3032
+ includePortExport
3033
+ } = options;
3034
+ const commands = [];
3035
+ if (workspacePath) {
3036
+ commands.push(`cd '${escapePathForAppleScript(workspacePath)}'`);
3037
+ }
3038
+ if (includeEnvSetup && workspacePath) {
3039
+ const sourceCommands = await buildEnvSourceCommands(
3040
+ workspacePath,
3041
+ async (p) => existsSync(p)
3042
+ );
3043
+ commands.push(...sourceCommands);
3044
+ }
3045
+ if (includePortExport && port !== void 0) {
3046
+ commands.push(`export PORT=${port}`);
3047
+ }
3048
+ if (command) {
3049
+ commands.push(command);
3050
+ }
3051
+ const fullCommand = commands.join(" && ");
3052
+ const historyFreeCommand = ` ${fullCommand}`;
3053
+ let script = `tell application "Terminal"
3054
+ `;
3055
+ script += ` set newTab to do script "${escapeForAppleScript(historyFreeCommand)}"
3056
+ `;
3057
+ if (backgroundColor) {
3058
+ const { r, g, b } = backgroundColor;
3059
+ script += ` set background color of newTab to {${Math.round(r * 257)}, ${Math.round(g * 257)}, ${Math.round(b * 257)}}
3060
+ `;
3061
+ }
3062
+ script += `end tell`;
3063
+ return script;
3064
+ }
3065
+ function escapePathForAppleScript(path6) {
3066
+ return path6.replace(/'/g, "'\\''");
3067
+ }
3068
+ function escapeForAppleScript(command) {
3069
+ return command.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
3070
+ }
3071
+ async function buildITerm2SingleTabScript(options) {
3072
+ const command = await buildCommandSequence(options);
3073
+ let script = 'tell application id "com.googlecode.iterm2"\n';
3074
+ script += " create window with default profile\n";
3075
+ script += " set s1 to current session of current window\n\n";
3076
+ if (options.backgroundColor) {
3077
+ const { r, g, b } = options.backgroundColor;
3078
+ script += ` set background color of s1 to {${Math.round(r * 257)}, ${Math.round(g * 257)}, ${Math.round(b * 257)}}
3079
+ `;
3080
+ }
3081
+ script += ` tell s1 to write text "${escapeForAppleScript(command)}"
3082
+
3083
+ `;
3084
+ if (options.title) {
3085
+ script += ` set name of s1 to "${escapeForAppleScript(options.title)}"
3086
+
3087
+ `;
3088
+ }
3089
+ script += " activate\n";
3090
+ script += "end tell";
3091
+ return script;
3092
+ }
3093
+ async function buildCommandSequence(options) {
3094
+ const {
3095
+ workspacePath,
3096
+ command,
3097
+ port,
3098
+ includeEnvSetup,
3099
+ includePortExport
3100
+ } = options;
3101
+ const commands = [];
3102
+ if (workspacePath) {
3103
+ commands.push(`cd '${escapePathForAppleScript(workspacePath)}'`);
3104
+ }
3105
+ if (includeEnvSetup && workspacePath) {
3106
+ const sourceCommands = await buildEnvSourceCommands(
3107
+ workspacePath,
3108
+ async (p) => existsSync(p)
3109
+ );
3110
+ commands.push(...sourceCommands);
3111
+ }
3112
+ if (includePortExport && port !== void 0) {
3113
+ commands.push(`export PORT=${port}`);
3114
+ }
3115
+ if (command) {
3116
+ commands.push(command);
3117
+ }
3118
+ const fullCommand = commands.join(" && ");
3119
+ return ` ${fullCommand}`;
3120
+ }
3121
+
3122
+ // src/utils/claude.ts
3107
3123
  async function detectClaudeCli() {
3108
3124
  try {
3109
3125
  await execa4("command", ["-v", "claude"], {
@@ -3247,7 +3263,6 @@ async function launchClaudeInNewTerminalWindow(_prompt, options) {
3247
3263
  if (!workspacePath) {
3248
3264
  throw new Error("workspacePath is required for terminal window launch");
3249
3265
  }
3250
- const { openTerminalWindow: openTerminalWindow2 } = await Promise.resolve().then(() => (init_terminal(), terminal_exports));
3251
3266
  const executable = executablePath ?? "iloom";
3252
3267
  let launchCommand = `${executable} spin`;
3253
3268
  if (oneShot !== "default") {
@@ -3271,7 +3286,7 @@ async function launchClaudeInNewTerminalWindow(_prompt, options) {
3271
3286
  }
3272
3287
  }
3273
3288
  const hasEnvFile = existsSync2(join(workspacePath, ".env"));
3274
- await openTerminalWindow2({
3289
+ await openTerminalWindow({
3275
3290
  workspacePath,
3276
3291
  command: launchCommand,
3277
3292
  ...backgroundColor && { backgroundColor },
@@ -3285,7 +3300,7 @@ async function launchClaudeInNewTerminalWindow(_prompt, options) {
3285
3300
  init_logger();
3286
3301
  import { readFile as readFile2 } from "fs/promises";
3287
3302
  import { accessSync } from "fs";
3288
- import path4 from "path";
3303
+ import path5 from "path";
3289
3304
  import { fileURLToPath } from "url";
3290
3305
  var PromptTemplateManager = class {
3291
3306
  constructor(templateDir) {
@@ -3294,17 +3309,17 @@ var PromptTemplateManager = class {
3294
3309
  } else {
3295
3310
  const currentFileUrl = import.meta.url;
3296
3311
  const currentFilePath = fileURLToPath(currentFileUrl);
3297
- const distDir = path4.dirname(currentFilePath);
3298
- let templateDir2 = path4.join(distDir, "prompts");
3312
+ const distDir = path5.dirname(currentFilePath);
3313
+ let templateDir2 = path5.join(distDir, "prompts");
3299
3314
  let currentDir = distDir;
3300
- while (currentDir !== path4.dirname(currentDir)) {
3301
- const candidatePath = path4.join(currentDir, "prompts");
3315
+ while (currentDir !== path5.dirname(currentDir)) {
3316
+ const candidatePath = path5.join(currentDir, "prompts");
3302
3317
  try {
3303
3318
  accessSync(candidatePath);
3304
3319
  templateDir2 = candidatePath;
3305
3320
  break;
3306
3321
  } catch {
3307
- currentDir = path4.dirname(currentDir);
3322
+ currentDir = path5.dirname(currentDir);
3308
3323
  }
3309
3324
  }
3310
3325
  this.templateDir = templateDir2;
@@ -3319,7 +3334,7 @@ var PromptTemplateManager = class {
3319
3334
  * Load a template file by name
3320
3335
  */
3321
3336
  async loadTemplate(templateName) {
3322
- const templatePath = path4.join(this.templateDir, `${templateName}-prompt.txt`);
3337
+ const templatePath = path5.join(this.templateDir, `${templateName}-prompt.txt`);
3323
3338
  logger.debug("Loading template", {
3324
3339
  templateName,
3325
3340
  templateDir: this.templateDir,
@@ -3401,6 +3416,9 @@ var PromptTemplateManager = class {
3401
3416
  if (variables.SETTINGS_SCHEMA_CONTENT !== void 0) {
3402
3417
  result = result.replace(/SETTINGS_SCHEMA_CONTENT/g, variables.SETTINGS_SCHEMA_CONTENT);
3403
3418
  }
3419
+ if (variables.VSCODE_SETTINGS_GITIGNORED !== void 0) {
3420
+ result = result.replace(/VSCODE_SETTINGS_GITIGNORED/g, variables.VSCODE_SETTINGS_GITIGNORED);
3421
+ }
3404
3422
  return result;
3405
3423
  }
3406
3424
  /**
@@ -3646,6 +3664,14 @@ var ClaudeContextManager = class {
3646
3664
  }
3647
3665
  };
3648
3666
 
3667
+ // src/types/index.ts
3668
+ var UserAbortedCommitError = class extends Error {
3669
+ constructor(message = "User aborted the commit") {
3670
+ super(message);
3671
+ this.name = "UserAbortedCommitError";
3672
+ }
3673
+ };
3674
+
3649
3675
  // src/utils/index.ts
3650
3676
  init_logger();
3651
3677
 
@@ -3958,6 +3984,7 @@ export {
3958
3984
  IssueTrackerFactory,
3959
3985
  LinearMarkupConverter,
3960
3986
  TableFormatter,
3987
+ UserAbortedCommitError,
3961
3988
  WorkspaceManager,
3962
3989
  branchExists,
3963
3990
  createLogger,
@@ -3972,8 +3999,11 @@ export {
3972
3999
  getCurrentBranch,
3973
4000
  getDefaultBranch,
3974
4001
  getRepoRoot,
4002
+ getWorktreeRoot,
3975
4003
  hasUncommittedChanges,
3976
4004
  isEmptyRepository,
4005
+ isFileGitignored,
4006
+ isFileTrackedByGit,
3977
4007
  isPRBranch,
3978
4008
  isValidGitRepo,
3979
4009
  isWorktreePath,