@iloom/cli 0.3.3 → 0.4.0

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 (178) hide show
  1. package/README.md +13 -3
  2. package/dist/{BranchNamingService-A77VI6AI.js → BranchNamingService-TOM2KAUT.js} +4 -3
  3. package/dist/ClaudeContextManager-VEGJTS5E.js +16 -0
  4. package/dist/ClaudeService-ICSHJMQ5.js +15 -0
  5. package/dist/GitHubService-RPM27GWD.js +12 -0
  6. package/dist/{LoomLauncher-ZV3ZZIBA.js → LoomLauncher-SJBZFZXE.js} +25 -22
  7. package/dist/LoomLauncher-SJBZFZXE.js.map +1 -0
  8. package/dist/PromptTemplateManager-2TDZAUC6.js +9 -0
  9. package/dist/README.md +13 -3
  10. package/dist/{SettingsManager-I2LRCW2A.js → SettingsManager-FJFU6JJD.js} +7 -3
  11. package/dist/SettingsMigrationManager-EH3J2TCN.js +10 -0
  12. package/dist/{chunk-UJL4HI2R.js → chunk-3NFBZRPR.js} +2 -2
  13. package/dist/chunk-6UIGZD2N.js +20 -0
  14. package/dist/chunk-6UIGZD2N.js.map +1 -0
  15. package/dist/{chunk-RIEO2WML.js → chunk-74VMN2KC.js} +26 -2
  16. package/dist/chunk-74VMN2KC.js.map +1 -0
  17. package/dist/{chunk-OYF4VIFI.js → chunk-75B2HZZ5.js} +147 -22
  18. package/dist/chunk-75B2HZZ5.js.map +1 -0
  19. package/dist/{chunk-PGPI5LR4.js → chunk-ADDNFQJ4.js} +7 -21
  20. package/dist/chunk-ADDNFQJ4.js.map +1 -0
  21. package/dist/{chunk-AKUJXDNW.js → chunk-F4J6KEL6.js} +3 -3
  22. package/dist/{chunk-DLHA5VQ3.js → chunk-HD5SUKI2.js} +36 -179
  23. package/dist/chunk-HD5SUKI2.js.map +1 -0
  24. package/dist/chunk-HHDSIE72.js +667 -0
  25. package/dist/chunk-HHDSIE72.js.map +1 -0
  26. package/dist/{chunk-OXAM2WVC.js → chunk-HVGQP44L.js} +21 -1
  27. package/dist/chunk-HVGQP44L.js.map +1 -0
  28. package/dist/{chunk-RW54ZMBM.js → chunk-JJUPY5MM.js} +2 -2
  29. package/dist/{chunk-UAN4A3YU.js → chunk-KM3W7YQX.js} +11 -11
  30. package/dist/{chunk-3RUPPQRG.js → chunk-KO2FOMHL.js} +43 -2
  31. package/dist/{chunk-3RUPPQRG.js.map → chunk-KO2FOMHL.js.map} +1 -1
  32. package/dist/{chunk-2MAIX45J.js → chunk-LTNDJMTH.js} +104 -43
  33. package/dist/chunk-LTNDJMTH.js.map +1 -0
  34. package/dist/{chunk-2CXREBLZ.js → chunk-M5XUCTTJ.js} +8 -6
  35. package/dist/chunk-M5XUCTTJ.js.map +1 -0
  36. package/dist/{chunk-4XIDC3NF.js → chunk-MD6HA5IK.js} +2 -2
  37. package/dist/chunk-MLS5FAV7.js +189 -0
  38. package/dist/chunk-MLS5FAV7.js.map +1 -0
  39. package/dist/{chunk-2IJEMXOB.js → chunk-NFVFVYAP.js} +419 -427
  40. package/dist/chunk-NFVFVYAP.js.map +1 -0
  41. package/dist/{chunk-OC4H6HJD.js → chunk-O7WHXLCB.js} +2 -2
  42. package/dist/{chunk-M7JJCX53.js → chunk-OEGECBFS.js} +20 -20
  43. package/dist/chunk-OEGECBFS.js.map +1 -0
  44. package/dist/{chunk-MKWYLDFK.js → chunk-OF7BNW4D.js} +43 -3
  45. package/dist/chunk-OF7BNW4D.js.map +1 -0
  46. package/dist/{chunk-SUOXY5WJ.js → chunk-P2WZIDF3.js} +5 -5
  47. package/dist/chunk-P2WZIDF3.js.map +1 -0
  48. package/dist/{chunk-PA6Q6AWM.js → chunk-PSFVTBM7.js} +2 -2
  49. package/dist/chunk-QHA67Q7A.js +281 -0
  50. package/dist/chunk-QHA67Q7A.js.map +1 -0
  51. package/dist/{chunk-ZM3CFL5L.js → chunk-QRBOPFAA.js} +3 -3
  52. package/dist/{chunk-IFB4Z76W.js → chunk-S44CHE3G.js} +13 -12
  53. package/dist/chunk-S44CHE3G.js.map +1 -0
  54. package/dist/{chunk-CE26YH2U.js → chunk-SJ2GZ6RF.js} +48 -50
  55. package/dist/chunk-SJ2GZ6RF.js.map +1 -0
  56. package/dist/{chunk-SSCQCCJ7.js → chunk-THF25ICZ.js} +2 -2
  57. package/dist/{chunk-5Q3NDNNV.js → chunk-TR5MC2U6.js} +153 -6
  58. package/dist/chunk-TR5MC2U6.js.map +1 -0
  59. package/dist/{chunk-5VK4NRSF.js → chunk-UNXRACJ7.js} +35 -36
  60. package/dist/chunk-UNXRACJ7.js.map +1 -0
  61. package/dist/{chunk-GEHQXLEI.js → chunk-UYVWLISQ.js} +18 -35
  62. package/dist/chunk-UYVWLISQ.js.map +1 -0
  63. package/dist/{chunk-OSCLCMDG.js → chunk-UYWAESOT.js} +3 -3
  64. package/dist/{chunk-ZT3YZB4K.js → chunk-VBFDVGAE.js} +12 -12
  65. package/dist/chunk-VBFDVGAE.js.map +1 -0
  66. package/dist/{chunk-CDZERT7Z.js → chunk-VWNS6DH5.js} +48 -4
  67. package/dist/chunk-VWNS6DH5.js.map +1 -0
  68. package/dist/{chunk-CFFQ2Z7A.js → chunk-WUQQNE63.js} +2 -2
  69. package/dist/{claude-W52VKI6L.js → claude-X7EBJRB2.js} +8 -5
  70. package/dist/{cleanup-H4VXU3C3.js → cleanup-7QVPYBJJ.js} +133 -122
  71. package/dist/cleanup-7QVPYBJJ.js.map +1 -0
  72. package/dist/cli.js +901 -425
  73. package/dist/cli.js.map +1 -1
  74. package/dist/{color-F7RU6B6Z.js → color-ZPIIUADB.js} +3 -3
  75. package/dist/{contribute-Y7IQV5QY.js → contribute-RZYCYUDX.js} +8 -6
  76. package/dist/{contribute-Y7IQV5QY.js.map → contribute-RZYCYUDX.js.map} +1 -1
  77. package/dist/dev-server-LOY7YWCP.js +298 -0
  78. package/dist/dev-server-LOY7YWCP.js.map +1 -0
  79. package/dist/{feedback-XTUCKJNT.js → feedback-562KPG5U.js} +13 -12
  80. package/dist/{feedback-XTUCKJNT.js.map → feedback-562KPG5U.js.map} +1 -1
  81. package/dist/{git-IYA53VIC.js → git-OXJACVAU.js} +16 -4
  82. package/dist/hooks/iloom-hook.js +258 -0
  83. package/dist/{ignite-T74RYXCA.js → ignite-VSIPGKKG.js} +245 -39
  84. package/dist/ignite-VSIPGKKG.js.map +1 -0
  85. package/dist/index.d.ts +459 -124
  86. package/dist/index.js +740 -210
  87. package/dist/index.js.map +1 -1
  88. package/dist/init-SCR2LQ4A.js +21 -0
  89. package/dist/{installation-detector-VARGFFRZ.js → installation-detector-6R6YOFVZ.js} +3 -3
  90. package/dist/mcp/issue-management-server.js +2 -1
  91. package/dist/mcp/issue-management-server.js.map +1 -1
  92. package/dist/neon-helpers-L5CXQ5CT.js +11 -0
  93. package/dist/{open-UMXANW5S.js → open-CX7HUE26.js} +12 -10
  94. package/dist/{open-UMXANW5S.js.map → open-CX7HUE26.js.map} +1 -1
  95. package/dist/projects-6DTNDVLH.js +73 -0
  96. package/dist/projects-6DTNDVLH.js.map +1 -0
  97. package/dist/{prompt-QALMYTVC.js → prompt-A7GGRHSY.js} +3 -3
  98. package/dist/prompts/init-prompt.txt +49 -0
  99. package/dist/prompts/issue-prompt.txt +110 -8
  100. package/dist/prompts/regular-prompt.txt +463 -14
  101. package/dist/prompts/session-summary-prompt.txt +82 -0
  102. package/dist/{rebase-VJ2VKR6R.js → rebase-55URTXZC.js} +11 -9
  103. package/dist/{rebase-VJ2VKR6R.js.map → rebase-55URTXZC.js.map} +1 -1
  104. package/dist/{remote-VUNCQZ6J.js → remote-73TZ2ADI.js} +3 -3
  105. package/dist/{run-MJYY4PUT.js → run-DP2U2CA2.js} +12 -10
  106. package/dist/{run-MJYY4PUT.js.map → run-DP2U2CA2.js.map} +1 -1
  107. package/dist/schema/settings.schema.json +49 -0
  108. package/dist/summary-J3CJSM7L.js +244 -0
  109. package/dist/summary-J3CJSM7L.js.map +1 -0
  110. package/dist/{test-git-IT5EWQ5C.js → test-git-QLAIBJLX.js} +6 -4
  111. package/dist/{test-git-IT5EWQ5C.js.map → test-git-QLAIBJLX.js.map} +1 -1
  112. package/dist/{test-prefix-NPWDPUUH.js → test-prefix-6YM2ZOON.js} +6 -4
  113. package/dist/{test-prefix-NPWDPUUH.js.map → test-prefix-6YM2ZOON.js.map} +1 -1
  114. package/dist/{test-tabs-PRMRSHKI.js → test-tabs-JGO3VOXJ.js} +4 -4
  115. package/dist/{test-webserver-DAHONWCS.js → test-webserver-VPNLAFZ3.js} +2 -2
  116. package/dist/{update-4TDDUR5K.js → update-LETF5ASC.js} +4 -4
  117. package/dist/{update-notifier-QEX3CJHA.js → update-notifier-H55ZK7NU.js} +3 -3
  118. package/package.json +6 -6
  119. package/dist/ClaudeContextManager-BN7RE5ZQ.js +0 -15
  120. package/dist/ClaudeService-DLYLJUPA.js +0 -14
  121. package/dist/GitHubService-FZHHBOFG.js +0 -11
  122. package/dist/LoomLauncher-ZV3ZZIBA.js.map +0 -1
  123. package/dist/PromptTemplateManager-6HH3PVXV.js +0 -9
  124. package/dist/SettingsMigrationManager-TJ7UWZG5.js +0 -10
  125. package/dist/chunk-2CXREBLZ.js.map +0 -1
  126. package/dist/chunk-2IJEMXOB.js.map +0 -1
  127. package/dist/chunk-2MAIX45J.js.map +0 -1
  128. package/dist/chunk-5Q3NDNNV.js.map +0 -1
  129. package/dist/chunk-5VK4NRSF.js.map +0 -1
  130. package/dist/chunk-CDZERT7Z.js.map +0 -1
  131. package/dist/chunk-CE26YH2U.js.map +0 -1
  132. package/dist/chunk-DLHA5VQ3.js.map +0 -1
  133. package/dist/chunk-GEHQXLEI.js.map +0 -1
  134. package/dist/chunk-IFB4Z76W.js.map +0 -1
  135. package/dist/chunk-M7JJCX53.js.map +0 -1
  136. package/dist/chunk-MKWYLDFK.js.map +0 -1
  137. package/dist/chunk-OXAM2WVC.js.map +0 -1
  138. package/dist/chunk-OYF4VIFI.js.map +0 -1
  139. package/dist/chunk-PGPI5LR4.js.map +0 -1
  140. package/dist/chunk-RIEO2WML.js.map +0 -1
  141. package/dist/chunk-SUOXY5WJ.js.map +0 -1
  142. package/dist/chunk-ZT3YZB4K.js.map +0 -1
  143. package/dist/cleanup-H4VXU3C3.js.map +0 -1
  144. package/dist/ignite-T74RYXCA.js.map +0 -1
  145. package/dist/init-4FHTAM3F.js +0 -19
  146. package/dist/logger-MKYH4UDV.js +0 -12
  147. package/dist/neon-helpers-77PBPGJ5.js +0 -10
  148. package/dist/update-notifier-QEX3CJHA.js.map +0 -1
  149. /package/dist/{BranchNamingService-A77VI6AI.js.map → BranchNamingService-TOM2KAUT.js.map} +0 -0
  150. /package/dist/{ClaudeContextManager-BN7RE5ZQ.js.map → ClaudeContextManager-VEGJTS5E.js.map} +0 -0
  151. /package/dist/{ClaudeService-DLYLJUPA.js.map → ClaudeService-ICSHJMQ5.js.map} +0 -0
  152. /package/dist/{GitHubService-FZHHBOFG.js.map → GitHubService-RPM27GWD.js.map} +0 -0
  153. /package/dist/{PromptTemplateManager-6HH3PVXV.js.map → PromptTemplateManager-2TDZAUC6.js.map} +0 -0
  154. /package/dist/{SettingsManager-I2LRCW2A.js.map → SettingsManager-FJFU6JJD.js.map} +0 -0
  155. /package/dist/{SettingsMigrationManager-TJ7UWZG5.js.map → SettingsMigrationManager-EH3J2TCN.js.map} +0 -0
  156. /package/dist/{chunk-UJL4HI2R.js.map → chunk-3NFBZRPR.js.map} +0 -0
  157. /package/dist/{chunk-AKUJXDNW.js.map → chunk-F4J6KEL6.js.map} +0 -0
  158. /package/dist/{chunk-RW54ZMBM.js.map → chunk-JJUPY5MM.js.map} +0 -0
  159. /package/dist/{chunk-UAN4A3YU.js.map → chunk-KM3W7YQX.js.map} +0 -0
  160. /package/dist/{chunk-4XIDC3NF.js.map → chunk-MD6HA5IK.js.map} +0 -0
  161. /package/dist/{chunk-OC4H6HJD.js.map → chunk-O7WHXLCB.js.map} +0 -0
  162. /package/dist/{chunk-PA6Q6AWM.js.map → chunk-PSFVTBM7.js.map} +0 -0
  163. /package/dist/{chunk-ZM3CFL5L.js.map → chunk-QRBOPFAA.js.map} +0 -0
  164. /package/dist/{chunk-SSCQCCJ7.js.map → chunk-THF25ICZ.js.map} +0 -0
  165. /package/dist/{chunk-OSCLCMDG.js.map → chunk-UYWAESOT.js.map} +0 -0
  166. /package/dist/{chunk-CFFQ2Z7A.js.map → chunk-WUQQNE63.js.map} +0 -0
  167. /package/dist/{claude-W52VKI6L.js.map → claude-X7EBJRB2.js.map} +0 -0
  168. /package/dist/{color-F7RU6B6Z.js.map → color-ZPIIUADB.js.map} +0 -0
  169. /package/dist/{git-IYA53VIC.js.map → git-OXJACVAU.js.map} +0 -0
  170. /package/dist/{init-4FHTAM3F.js.map → init-SCR2LQ4A.js.map} +0 -0
  171. /package/dist/{installation-detector-VARGFFRZ.js.map → installation-detector-6R6YOFVZ.js.map} +0 -0
  172. /package/dist/{logger-MKYH4UDV.js.map → neon-helpers-L5CXQ5CT.js.map} +0 -0
  173. /package/dist/{neon-helpers-77PBPGJ5.js.map → prompt-A7GGRHSY.js.map} +0 -0
  174. /package/dist/{prompt-QALMYTVC.js.map → remote-73TZ2ADI.js.map} +0 -0
  175. /package/dist/{test-tabs-PRMRSHKI.js.map → test-tabs-JGO3VOXJ.js.map} +0 -0
  176. /package/dist/{test-webserver-DAHONWCS.js.map → test-webserver-VPNLAFZ3.js.map} +0 -0
  177. /package/dist/{update-4TDDUR5K.js.map → update-LETF5ASC.js.map} +0 -0
  178. /package/dist/{remote-VUNCQZ6J.js.map → update-notifier-H55ZK7NU.js.map} +0 -0
@@ -1,13 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  buildDevServerCommand
4
- } from "./chunk-ZM3CFL5L.js";
4
+ } from "./chunk-QRBOPFAA.js";
5
5
  import {
6
6
  ProcessManager
7
7
  } from "./chunk-VU3QMIP2.js";
8
8
  import {
9
9
  logger
10
- } from "./chunk-GEHQXLEI.js";
10
+ } from "./chunk-UYVWLISQ.js";
11
11
 
12
12
  // src/lib/DevServerManager.ts
13
13
  import { execa } from "execa";
@@ -99,6 +99,46 @@ var DevServerManager = class {
99
99
  );
100
100
  return false;
101
101
  }
102
+ /**
103
+ * Check if a dev server is running on the specified port
104
+ *
105
+ * @param port - Port to check
106
+ * @returns true if server is running, false otherwise
107
+ */
108
+ async isServerRunning(port) {
109
+ const existingProcess = await this.processManager.detectDevServer(port);
110
+ return existingProcess !== null;
111
+ }
112
+ /**
113
+ * Run dev server in foreground mode (blocking)
114
+ * This method blocks until the server is stopped (e.g., via Ctrl+C)
115
+ *
116
+ * @param worktreePath - Path to the worktree
117
+ * @param port - Port the server should run on
118
+ * @param redirectToStderr - If true, redirect stdout/stderr to stderr (useful for JSON output)
119
+ * @param onProcessStarted - Callback called immediately after process starts with PID
120
+ * @returns Process information including PID
121
+ */
122
+ async runServerForeground(worktreePath, port, redirectToStderr = false, onProcessStarted) {
123
+ const devCommand = await buildDevServerCommand(worktreePath);
124
+ logger.debug(`Starting dev server in foreground with command: ${devCommand}`);
125
+ const stdio = redirectToStderr ? [process.stdin, process.stderr, process.stderr] : "inherit";
126
+ const serverProcess = execa("sh", ["-c", devCommand], {
127
+ cwd: worktreePath,
128
+ env: {
129
+ ...process.env,
130
+ PORT: port.toString()
131
+ },
132
+ // Configure stdio based on whether we want to redirect output
133
+ stdio
134
+ });
135
+ const processInfo = serverProcess.pid !== void 0 ? { pid: serverProcess.pid } : {};
136
+ if (onProcessStarted) {
137
+ onProcessStarted(processInfo.pid);
138
+ }
139
+ await serverProcess;
140
+ return processInfo;
141
+ }
102
142
  /**
103
143
  * Clean up all running server processes
104
144
  * This should be called when the manager is being disposed
@@ -121,4 +161,4 @@ var DevServerManager = class {
121
161
  export {
122
162
  DevServerManager
123
163
  };
124
- //# sourceMappingURL=chunk-MKWYLDFK.js.map
164
+ //# sourceMappingURL=chunk-OF7BNW4D.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/DevServerManager.ts"],"sourcesContent":["import { execa, type ExecaChildProcess } from 'execa'\nimport { setTimeout } from 'timers/promises'\nimport { ProcessManager } from './process/ProcessManager.js'\nimport { buildDevServerCommand } from '../utils/dev-server.js'\nimport { logger } from '../utils/logger.js'\n\nexport interface DevServerManagerOptions {\n\t/**\n\t * Maximum time to wait for server to start (in milliseconds)\n\t * Default: 30000 (30 seconds)\n\t */\n\tstartupTimeout?: number\n\n\t/**\n\t * Interval between port checks (in milliseconds)\n\t * Default: 1000 (1 second)\n\t */\n\tcheckInterval?: number\n}\n\n/**\n * DevServerManager handles auto-starting and monitoring dev servers\n * Used by open/run commands to ensure dev server is running before opening browser\n */\nexport class DevServerManager {\n\tprivate readonly processManager: ProcessManager\n\tprivate readonly options: Required<DevServerManagerOptions>\n\tprivate runningServers: Map<number, ExecaChildProcess> = new Map()\n\n\tconstructor(\n\t\tprocessManager?: ProcessManager,\n\t\toptions: DevServerManagerOptions = {}\n\t) {\n\t\tthis.processManager = processManager ?? new ProcessManager()\n\t\tthis.options = {\n\t\t\tstartupTimeout: options.startupTimeout ?? 30000,\n\t\t\tcheckInterval: options.checkInterval ?? 1000,\n\t\t}\n\t}\n\n\t/**\n\t * Ensure dev server is running on the specified port\n\t * If not running, start it and wait for it to be ready\n\t *\n\t * @param worktreePath - Path to the worktree\n\t * @param port - Port the server should run on\n\t * @returns true if server is ready, false if startup failed/timed out\n\t */\n\tasync ensureServerRunning(worktreePath: string, port: number): Promise<boolean> {\n\t\tlogger.debug(`Checking if dev server is running on port ${port}...`)\n\n\t\t// Check if already running\n\t\tconst existingProcess = await this.processManager.detectDevServer(port)\n\t\tif (existingProcess) {\n\t\t\tlogger.debug(\n\t\t\t\t`Dev server already running on port ${port} (PID: ${existingProcess.pid})`\n\t\t\t)\n\t\t\treturn true\n\t\t}\n\n\t\t// Not running - start it\n\t\tlogger.info(`Dev server not running on port ${port}, starting...`)\n\n\t\ttry {\n\t\t\tawait this.startDevServer(worktreePath, port)\n\t\t\treturn true\n\t\t} catch (error) {\n\t\t\tlogger.error(\n\t\t\t\t`Failed to start dev server: ${error instanceof Error ? error.message : 'Unknown error'}`\n\t\t\t)\n\t\t\treturn false\n\t\t}\n\t}\n\n\t/**\n\t * Start dev server in background and wait for it to be ready\n\t */\n\tprivate async startDevServer(worktreePath: string, port: number): Promise<void> {\n\t\t// Build dev server command\n\t\tconst devCommand = await buildDevServerCommand(worktreePath)\n\t\tlogger.debug(`Starting dev server with command: ${devCommand}`)\n\n\t\t// Start server in background\n\t\tconst serverProcess = execa('sh', ['-c', devCommand], {\n\t\t\tcwd: worktreePath,\n\t\t\tenv: {\n\t\t\t\t...process.env,\n\t\t\t\tPORT: port.toString(),\n\t\t\t},\n\t\t\t// Important: Don't inherit stdio - server runs in background\n\t\t\tstdio: 'ignore',\n\t\t\t// Detach from parent process so it continues running\n\t\t\tdetached: true,\n\t\t})\n\n\t\t// Store reference to prevent cleanup\n\t\tthis.runningServers.set(port, serverProcess)\n\n\t\t// Unref so parent can exit\n\t\tserverProcess.unref()\n\n\t\t// Wait for server to be ready\n\t\tlogger.info(`Waiting for dev server to start on port ${port}...`)\n\t\tconst ready = await this.waitForServerReady(port)\n\n\t\tif (!ready) {\n\t\t\tthrow new Error(\n\t\t\t\t`Dev server failed to start within ${this.options.startupTimeout}ms timeout`\n\t\t\t)\n\t\t}\n\n\t\tlogger.success(`Dev server started successfully on port ${port}`)\n\t}\n\n\t/**\n\t * Wait for server to be ready by polling the port\n\t */\n\tprivate async waitForServerReady(port: number): Promise<boolean> {\n\t\tconst startTime = Date.now()\n\t\tlet attempts = 0\n\n\t\twhile (Date.now() - startTime < this.options.startupTimeout) {\n\t\t\tattempts++\n\n\t\t\t// Check if server is listening\n\t\t\tconst processInfo = await this.processManager.detectDevServer(port)\n\n\t\t\tif (processInfo) {\n\t\t\t\tlogger.debug(\n\t\t\t\t\t`Server detected on port ${port} after ${attempts} attempts (${Date.now() - startTime}ms)`\n\t\t\t\t)\n\t\t\t\treturn true\n\t\t\t}\n\n\t\t\t// Wait before next check\n\t\t\tawait setTimeout(this.options.checkInterval)\n\t\t}\n\n\t\t// Timeout\n\t\tlogger.warn(\n\t\t\t`Server did not start on port ${port} after ${this.options.startupTimeout}ms (${attempts} attempts)`\n\t\t)\n\t\treturn false\n\t}\n\n\t/**\n\t * Check if a dev server is running on the specified port\n\t *\n\t * @param port - Port to check\n\t * @returns true if server is running, false otherwise\n\t */\n\tasync isServerRunning(port: number): Promise<boolean> {\n\t\tconst existingProcess = await this.processManager.detectDevServer(port)\n\t\treturn existingProcess !== null\n\t}\n\n\t/**\n\t * Run dev server in foreground mode (blocking)\n\t * This method blocks until the server is stopped (e.g., via Ctrl+C)\n\t *\n\t * @param worktreePath - Path to the worktree\n\t * @param port - Port the server should run on\n\t * @param redirectToStderr - If true, redirect stdout/stderr to stderr (useful for JSON output)\n\t * @param onProcessStarted - Callback called immediately after process starts with PID\n\t * @returns Process information including PID\n\t */\n\tasync runServerForeground(\n\t\tworktreePath: string,\n\t\tport: number,\n\t\tredirectToStderr = false,\n\t\tonProcessStarted?: (pid?: number) => void\n\t): Promise<{ pid?: number }> {\n\t\t// Build dev server command\n\t\tconst devCommand = await buildDevServerCommand(worktreePath)\n\t\tlogger.debug(`Starting dev server in foreground with command: ${devCommand}`)\n\n\t\t// Configure stdio based on redirect option\n\t\tconst stdio = redirectToStderr ? [process.stdin, process.stderr, process.stderr] : 'inherit'\n\n\t\t// Start server in foreground (blocking with configured stdio)\n\t\tconst serverProcess = execa('sh', ['-c', devCommand], {\n\t\t\tcwd: worktreePath,\n\t\t\tenv: {\n\t\t\t\t...process.env,\n\t\t\t\tPORT: port.toString(),\n\t\t\t},\n\t\t\t// Configure stdio based on whether we want to redirect output\n\t\t\tstdio,\n\t\t})\n\n\t\t// Process info is available immediately after spawn\n\t\t// Use conditional property to satisfy exactOptionalPropertyTypes\n\t\tconst processInfo: { pid?: number } = serverProcess.pid !== undefined ? { pid: serverProcess.pid } : {}\n\n\t\t// Call the callback immediately with the PID (for JSON output)\n\t\tif (onProcessStarted) {\n\t\t\tonProcessStarted(processInfo.pid)\n\t\t}\n\n\t\t// Now wait for the process to complete (this blocks)\n\t\tawait serverProcess\n\n\t\treturn processInfo\n\t}\n\n\t/**\n\t * Clean up all running server processes\n\t * This should be called when the manager is being disposed\n\t */\n\tasync cleanup(): Promise<void> {\n\t\tfor (const [port, serverProcess] of this.runningServers.entries()) {\n\t\t\ttry {\n\t\t\t\tlogger.debug(`Cleaning up server process on port ${port}`)\n\t\t\t\tserverProcess.kill()\n\t\t\t} catch (error) {\n\t\t\t\tlogger.warn(\n\t\t\t\t\t`Failed to kill server process on port ${port}: ${error instanceof Error ? error.message : 'Unknown error'}`\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t\tthis.runningServers.clear()\n\t}\n}\n"],"mappings":"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","names":[]}
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- logger
4
- } from "./chunk-GEHQXLEI.js";
3
+ getLogger
4
+ } from "./chunk-6UIGZD2N.js";
5
5
 
6
6
  // src/lib/BranchNamingService.ts
7
7
  var SimpleBranchNameStrategy = class {
@@ -15,7 +15,7 @@ var ClaudeBranchNameStrategy = class {
15
15
  this.claudeModel = claudeModel;
16
16
  }
17
17
  async generate(issueNumber, title) {
18
- const { generateBranchName } = await import("./claude-W52VKI6L.js");
18
+ const { generateBranchName } = await import("./claude-X7EBJRB2.js");
19
19
  return generateBranchName(title, issueNumber, this.claudeModel);
20
20
  }
21
21
  };
@@ -32,7 +32,7 @@ var DefaultBranchNamingService = class {
32
32
  async generateBranchName(options) {
33
33
  const { issueNumber, title, strategy } = options;
34
34
  const nameStrategy = strategy ?? this.defaultStrategy;
35
- logger.debug("Generating branch name", {
35
+ getLogger().debug("Generating branch name", {
36
36
  issueNumber,
37
37
  title,
38
38
  strategy: nameStrategy.constructor.name
@@ -52,4 +52,4 @@ export {
52
52
  ClaudeBranchNameStrategy,
53
53
  DefaultBranchNamingService
54
54
  };
55
- //# sourceMappingURL=chunk-SUOXY5WJ.js.map
55
+ //# sourceMappingURL=chunk-P2WZIDF3.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/BranchNamingService.ts"],"sourcesContent":["import type { BranchNameStrategy, BranchGenerationOptions } from '../types/branch-naming.js'\nimport { getLogger } from '../utils/logger-context.js'\n\n// ============================================\n// Strategy Classes\n// ============================================\n\n/**\n * Simple branch naming strategy\n * Format: feat/issue-{number}__{slug}\n */\nexport class SimpleBranchNameStrategy implements BranchNameStrategy {\n\tasync generate(issueNumber: string | number, title: string): Promise<string> {\n\t\t// Create a simple slug from the title\n\t\tconst slug = title\n\t\t\t.toLowerCase()\n\t\t\t.replace(/[^a-z0-9]+/g, '-')\n\t\t\t.replace(/^-|-$/g, '')\n\t\t\t.substring(0, 20) // Keep it short for the simple strategy\n\n\t\treturn `feat/issue-${issueNumber}__${slug}`\n\t}\n}\n\n/**\n * Claude Code-powered branch naming strategy\n * Uses Claude CLI to generate semantic branch names\n */\nexport class ClaudeBranchNameStrategy implements BranchNameStrategy {\n\tconstructor(private claudeModel = 'haiku') {}\n\n\tasync generate(issueNumber: string | number, title: string): Promise<string> {\n\t\t// Dynamic import to allow mocking in tests\n\t\tconst { generateBranchName } = await import('../utils/claude.js')\n\t\treturn generateBranchName(title, issueNumber, this.claudeModel)\n\t}\n}\n\n// ============================================\n// Service Interface and Implementation\n// ============================================\n\n/**\n * Service interface for branch name generation\n * Provides strategy management and generation capabilities\n */\nexport interface BranchNamingService {\n\tgenerateBranchName(options: BranchGenerationOptions): Promise<string>\n\tsetDefaultStrategy(strategy: BranchNameStrategy): void\n\tgetDefaultStrategy(): BranchNameStrategy\n}\n\n/**\n * Default implementation of BranchNamingService\n * Supports multiple naming strategies with configurable defaults\n */\nexport class DefaultBranchNamingService implements BranchNamingService {\n\tprivate defaultStrategy: BranchNameStrategy\n\n\tconstructor(options?: {\n\t\tstrategy?: BranchNameStrategy\n\t\tuseClaude?: boolean\n\t\tclaudeModel?: string\n\t}) {\n\t\t// Set up default strategy based on options\n\t\tif (options?.strategy) {\n\t\t\tthis.defaultStrategy = options.strategy\n\t\t} else if (options?.useClaude !== false) {\n\t\t\tthis.defaultStrategy = new ClaudeBranchNameStrategy(options?.claudeModel)\n\t\t} else {\n\t\t\tthis.defaultStrategy = new SimpleBranchNameStrategy()\n\t\t}\n\t}\n\n\tasync generateBranchName(options: BranchGenerationOptions): Promise<string> {\n\t\tconst { issueNumber, title, strategy } = options\n\n\t\t// Use provided strategy or fall back to default\n\t\tconst nameStrategy = strategy ?? this.defaultStrategy\n\n\t\tgetLogger().debug('Generating branch name', {\n\t\t\tissueNumber,\n\t\t\ttitle,\n\t\t\tstrategy: nameStrategy.constructor.name,\n\t\t})\n\n\t\treturn nameStrategy.generate(issueNumber, title)\n\t}\n\n\tsetDefaultStrategy(strategy: BranchNameStrategy): void {\n\t\tthis.defaultStrategy = strategy\n\t}\n\n\tgetDefaultStrategy(): BranchNameStrategy {\n\t\treturn this.defaultStrategy\n\t}\n}\n"],"mappings":";;;;;;AAWO,IAAM,2BAAN,MAA6D;AAAA,EACnE,MAAM,SAAS,aAA8B,OAAgC;AAE5E,UAAM,OAAO,MACX,YAAY,EACZ,QAAQ,eAAe,GAAG,EAC1B,QAAQ,UAAU,EAAE,EACpB,UAAU,GAAG,EAAE;AAEjB,WAAO,cAAc,WAAW,KAAK,IAAI;AAAA,EAC1C;AACD;AAMO,IAAM,2BAAN,MAA6D;AAAA,EACnE,YAAoB,cAAc,SAAS;AAAvB;AAAA,EAAwB;AAAA,EAE5C,MAAM,SAAS,aAA8B,OAAgC;AAE5E,UAAM,EAAE,mBAAmB,IAAI,MAAM,OAAO,sBAAoB;AAChE,WAAO,mBAAmB,OAAO,aAAa,KAAK,WAAW;AAAA,EAC/D;AACD;AAoBO,IAAM,6BAAN,MAAgE;AAAA,EAGtE,YAAY,SAIT;AAEF,QAAI,mCAAS,UAAU;AACtB,WAAK,kBAAkB,QAAQ;AAAA,IAChC,YAAW,mCAAS,eAAc,OAAO;AACxC,WAAK,kBAAkB,IAAI,yBAAyB,mCAAS,WAAW;AAAA,IACzE,OAAO;AACN,WAAK,kBAAkB,IAAI,yBAAyB;AAAA,IACrD;AAAA,EACD;AAAA,EAEA,MAAM,mBAAmB,SAAmD;AAC3E,UAAM,EAAE,aAAa,OAAO,SAAS,IAAI;AAGzC,UAAM,eAAe,YAAY,KAAK;AAEtC,cAAU,EAAE,MAAM,0BAA0B;AAAA,MAC3C;AAAA,MACA;AAAA,MACA,UAAU,aAAa,YAAY;AAAA,IACpC,CAAC;AAED,WAAO,aAAa,SAAS,aAAa,KAAK;AAAA,EAChD;AAAA,EAEA,mBAAmB,UAAoC;AACtD,SAAK,kBAAkB;AAAA,EACxB;AAAA,EAEA,qBAAyC;AACxC,WAAO,KAAK;AAAA,EACb;AACD;","names":[]}
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  logger_default
4
- } from "./chunk-GEHQXLEI.js";
4
+ } from "./chunk-UYVWLISQ.js";
5
5
 
6
6
  // src/utils/remote.ts
7
7
  import { execa } from "execa";
@@ -100,4 +100,4 @@ export {
100
100
  validateConfiguredRemote,
101
101
  getEffectivePRTargetRemote
102
102
  };
103
- //# sourceMappingURL=chunk-PA6Q6AWM.js.map
103
+ //# sourceMappingURL=chunk-PSFVTBM7.js.map
@@ -0,0 +1,281 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ logger
4
+ } from "./chunk-UYVWLISQ.js";
5
+
6
+ // src/types/linear.ts
7
+ var LinearServiceError = class _LinearServiceError extends Error {
8
+ constructor(code, message, details) {
9
+ super(message);
10
+ this.code = code;
11
+ this.details = details;
12
+ this.name = "LinearServiceError";
13
+ if (Error.captureStackTrace) {
14
+ Error.captureStackTrace(this, _LinearServiceError);
15
+ }
16
+ }
17
+ };
18
+
19
+ // src/utils/linear.ts
20
+ import { LinearClient } from "@linear/sdk";
21
+ function slugifyTitle(title, maxLength = 50) {
22
+ const slug = title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
23
+ if (slug.length <= maxLength) {
24
+ return slug;
25
+ }
26
+ const parts = slug.split("-");
27
+ let result = "";
28
+ for (const part of parts) {
29
+ const candidate = result ? `${result}-${part}` : part;
30
+ if (candidate.length > maxLength) {
31
+ break;
32
+ }
33
+ result = candidate;
34
+ }
35
+ return result || slug.slice(0, maxLength);
36
+ }
37
+ function buildLinearIssueUrl(identifier, title) {
38
+ const base = `https://linear.app/issue/${identifier}`;
39
+ if (title) {
40
+ const slug = slugifyTitle(title);
41
+ return slug ? `${base}/${slug}` : base;
42
+ }
43
+ return base;
44
+ }
45
+ function getLinearApiToken() {
46
+ const token = process.env.LINEAR_API_TOKEN;
47
+ if (!token) {
48
+ throw new LinearServiceError(
49
+ "UNAUTHORIZED",
50
+ "LINEAR_API_TOKEN not set. Configure in settings.local.json or set environment variable."
51
+ );
52
+ }
53
+ return token;
54
+ }
55
+ function createLinearClient() {
56
+ return new LinearClient({ apiKey: getLinearApiToken() });
57
+ }
58
+ function handleLinearError(error, context) {
59
+ logger.debug(`${context}: Handling error`, { error });
60
+ const errorMessage = error instanceof Error ? error.message : String(error);
61
+ if (errorMessage.includes("not found") || errorMessage.includes("Not found")) {
62
+ throw new LinearServiceError("NOT_FOUND", "Linear issue or resource not found", { error });
63
+ }
64
+ if (errorMessage.includes("unauthorized") || errorMessage.includes("Unauthorized") || errorMessage.includes("Invalid API key")) {
65
+ throw new LinearServiceError(
66
+ "UNAUTHORIZED",
67
+ "Linear authentication failed. Check LINEAR_API_TOKEN.",
68
+ { error }
69
+ );
70
+ }
71
+ if (errorMessage.includes("rate limit")) {
72
+ throw new LinearServiceError("RATE_LIMITED", "Linear API rate limit exceeded", { error });
73
+ }
74
+ throw new LinearServiceError("CLI_ERROR", `Linear SDK error: ${errorMessage}`, { error });
75
+ }
76
+ async function fetchLinearIssue(identifier) {
77
+ try {
78
+ logger.debug(`Fetching Linear issue: ${identifier}`);
79
+ const client = createLinearClient();
80
+ const issue = await client.issue(identifier);
81
+ if (!issue) {
82
+ throw new LinearServiceError("NOT_FOUND", `Linear issue ${identifier} not found`);
83
+ }
84
+ const result = {
85
+ id: issue.id,
86
+ identifier: issue.identifier,
87
+ title: issue.title,
88
+ url: issue.url,
89
+ createdAt: issue.createdAt.toISOString(),
90
+ updatedAt: issue.updatedAt.toISOString()
91
+ };
92
+ if (issue.description) {
93
+ result.description = issue.description;
94
+ }
95
+ if (issue.state) {
96
+ const state = await issue.state;
97
+ if (state == null ? void 0 : state.name) {
98
+ result.state = state.name;
99
+ }
100
+ }
101
+ return result;
102
+ } catch (error) {
103
+ if (error instanceof LinearServiceError) {
104
+ throw error;
105
+ }
106
+ handleLinearError(error, "fetchLinearIssue");
107
+ }
108
+ }
109
+ async function createLinearIssue(title, body, teamKey, _labels) {
110
+ try {
111
+ logger.debug(`Creating Linear issue in team ${teamKey}: ${title}`);
112
+ const client = createLinearClient();
113
+ const teams = await client.teams();
114
+ const team = teams.nodes.find((t) => t.key === teamKey);
115
+ if (!team) {
116
+ throw new LinearServiceError("NOT_FOUND", `Linear team ${teamKey} not found`);
117
+ }
118
+ const issueInput = {
119
+ teamId: team.id,
120
+ title
121
+ };
122
+ if (body) {
123
+ issueInput.description = body;
124
+ }
125
+ const payload = await client.createIssue(issueInput);
126
+ const issue = await payload.issue;
127
+ if (!issue) {
128
+ throw new LinearServiceError("CLI_ERROR", "Failed to create Linear issue");
129
+ }
130
+ const url = issue.url ?? buildLinearIssueUrl(issue.identifier, title);
131
+ return {
132
+ identifier: issue.identifier,
133
+ url
134
+ };
135
+ } catch (error) {
136
+ if (error instanceof LinearServiceError) {
137
+ throw error;
138
+ }
139
+ handleLinearError(error, "createLinearIssue");
140
+ }
141
+ }
142
+ async function createLinearComment(identifier, body) {
143
+ try {
144
+ logger.debug(`Creating comment on Linear issue ${identifier}`);
145
+ const client = createLinearClient();
146
+ const issue = await client.issue(identifier);
147
+ if (!issue) {
148
+ throw new LinearServiceError("NOT_FOUND", `Linear issue ${identifier} not found`);
149
+ }
150
+ const payload = await client.createComment({
151
+ issueId: issue.id,
152
+ body
153
+ });
154
+ const comment = await payload.comment;
155
+ if (!comment) {
156
+ throw new LinearServiceError("CLI_ERROR", "Failed to create Linear comment");
157
+ }
158
+ return {
159
+ id: comment.id,
160
+ body: comment.body,
161
+ createdAt: comment.createdAt.toISOString(),
162
+ updatedAt: comment.updatedAt.toISOString(),
163
+ url: comment.url
164
+ };
165
+ } catch (error) {
166
+ if (error instanceof LinearServiceError) {
167
+ throw error;
168
+ }
169
+ handleLinearError(error, "createLinearComment");
170
+ }
171
+ }
172
+ async function updateLinearIssueState(identifier, stateName) {
173
+ try {
174
+ logger.debug(`Updating Linear issue ${identifier} state to: ${stateName}`);
175
+ const client = createLinearClient();
176
+ const issue = await client.issue(identifier);
177
+ if (!issue) {
178
+ throw new LinearServiceError("NOT_FOUND", `Linear issue ${identifier} not found`);
179
+ }
180
+ const team = await issue.team;
181
+ if (!team) {
182
+ throw new LinearServiceError("CLI_ERROR", "Issue has no team");
183
+ }
184
+ const states = await team.states();
185
+ const state = states.nodes.find((s) => s.name === stateName);
186
+ if (!state) {
187
+ throw new LinearServiceError(
188
+ "NOT_FOUND",
189
+ `State "${stateName}" not found in team ${team.key}`
190
+ );
191
+ }
192
+ await client.updateIssue(issue.id, {
193
+ stateId: state.id
194
+ });
195
+ } catch (error) {
196
+ if (error instanceof LinearServiceError) {
197
+ throw error;
198
+ }
199
+ handleLinearError(error, "updateLinearIssueState");
200
+ }
201
+ }
202
+ async function getLinearComment(commentId) {
203
+ try {
204
+ logger.debug(`Fetching Linear comment: ${commentId}`);
205
+ const client = createLinearClient();
206
+ const comment = await client.comment({ id: commentId });
207
+ if (!comment) {
208
+ throw new LinearServiceError("NOT_FOUND", `Linear comment ${commentId} not found`);
209
+ }
210
+ return {
211
+ id: comment.id,
212
+ body: comment.body,
213
+ createdAt: comment.createdAt.toISOString(),
214
+ updatedAt: comment.updatedAt.toISOString(),
215
+ url: comment.url
216
+ };
217
+ } catch (error) {
218
+ if (error instanceof LinearServiceError) {
219
+ throw error;
220
+ }
221
+ handleLinearError(error, "getLinearComment");
222
+ }
223
+ }
224
+ async function updateLinearComment(commentId, body) {
225
+ try {
226
+ logger.debug(`Updating Linear comment: ${commentId}`);
227
+ const client = createLinearClient();
228
+ const payload = await client.updateComment(commentId, { body });
229
+ const comment = await payload.comment;
230
+ if (!comment) {
231
+ throw new LinearServiceError("CLI_ERROR", "Failed to update Linear comment");
232
+ }
233
+ return {
234
+ id: comment.id,
235
+ body: comment.body,
236
+ createdAt: comment.createdAt.toISOString(),
237
+ updatedAt: comment.updatedAt.toISOString(),
238
+ url: comment.url
239
+ };
240
+ } catch (error) {
241
+ if (error instanceof LinearServiceError) {
242
+ throw error;
243
+ }
244
+ handleLinearError(error, "updateLinearComment");
245
+ }
246
+ }
247
+ async function fetchLinearIssueComments(identifier) {
248
+ try {
249
+ logger.debug(`Fetching comments for Linear issue: ${identifier}`);
250
+ const client = createLinearClient();
251
+ const issue = await client.issue(identifier);
252
+ if (!issue) {
253
+ throw new LinearServiceError("NOT_FOUND", `Linear issue ${identifier} not found`);
254
+ }
255
+ const comments = await issue.comments({ first: 100 });
256
+ return comments.nodes.map((comment) => ({
257
+ id: comment.id,
258
+ body: comment.body,
259
+ createdAt: comment.createdAt.toISOString(),
260
+ updatedAt: comment.updatedAt.toISOString(),
261
+ url: comment.url
262
+ }));
263
+ } catch (error) {
264
+ if (error instanceof LinearServiceError) {
265
+ throw error;
266
+ }
267
+ handleLinearError(error, "fetchLinearIssueComments");
268
+ }
269
+ }
270
+
271
+ export {
272
+ LinearServiceError,
273
+ fetchLinearIssue,
274
+ createLinearIssue,
275
+ createLinearComment,
276
+ updateLinearIssueState,
277
+ getLinearComment,
278
+ updateLinearComment,
279
+ fetchLinearIssueComments
280
+ };
281
+ //# sourceMappingURL=chunk-QHA67Q7A.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/types/linear.ts","../src/utils/linear.ts"],"sourcesContent":["/**\n * Linear API response types (from @linear/sdk)\n */\n\n/**\n * Linear issue response from SDK\n */\nexport interface LinearIssue {\n /** Linear internal UUID */\n id: string\n /** Issue identifier in TEAM-NUMBER format (e.g., ENG-123) */\n identifier: string\n /** Issue title */\n title: string\n /** Issue description (markdown) */\n description?: string\n /** Current workflow state name */\n state?: string\n /** Linear web URL */\n url: string\n /** Creation timestamp (ISO string) */\n createdAt: string\n /** Last update timestamp (ISO string) */\n updatedAt: string\n}\n\n/**\n * Linear comment response from SDK\n */\nexport interface LinearComment {\n /** Comment UUID */\n id: string\n /** Comment body (markdown) */\n body: string\n /** Creation timestamp (ISO string) */\n createdAt: string\n /** Last update timestamp (ISO string) */\n updatedAt: string\n /** Comment URL */\n url: string\n}\n\n/**\n * Linear error codes\n */\nexport type LinearErrorCode =\n | 'NOT_FOUND'\n | 'UNAUTHORIZED'\n | 'INVALID_STATE'\n | 'RATE_LIMITED'\n | 'CLI_NOT_FOUND'\n | 'CLI_ERROR'\n\n/**\n * Linear error details\n */\nexport interface LinearError {\n code: LinearErrorCode\n message: string\n details?: unknown\n}\n\n/**\n * Custom error class for Linear operations\n */\nexport class LinearServiceError extends Error {\n constructor(\n public code: LinearErrorCode,\n message: string,\n public details?: unknown,\n ) {\n super(message)\n this.name = 'LinearServiceError'\n // Maintain proper stack trace for where error was thrown (V8 only)\n if (Error.captureStackTrace) {\n Error.captureStackTrace(this, LinearServiceError)\n }\n }\n}\n","/**\n * Linear SDK utilities\n * Wrapper functions for the @linear/sdk\n */\n\nimport { LinearClient } from '@linear/sdk'\nimport type { LinearIssue, LinearComment } from '../types/linear.js'\nimport { LinearServiceError } from '../types/linear.js'\nimport { logger } from './logger.js'\n\n/**\n * Slugify a title for use in Linear URLs\n * Converts to lowercase, replaces non-alphanumeric with hyphens, truncates to reasonable length\n * @param title - Issue title\n * @param maxLength - Maximum slug length (default: 50)\n * @returns Slugified title\n */\nexport function slugifyTitle(title: string, maxLength: number = 50): string {\n // Convert to lowercase, replace non-alphanumeric chars with hyphens\n const slug = title\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '') // trim leading/trailing hyphens\n\n // If already short enough, return as-is\n if (slug.length <= maxLength) {\n return slug\n }\n\n // Split by hyphens and rebuild until we hit the limit\n const parts = slug.split('-')\n let result = ''\n for (const part of parts) {\n const candidate = result ? `${result}-${part}` : part\n if (candidate.length > maxLength) {\n break\n }\n result = candidate\n }\n\n return result || slug.slice(0, maxLength) // fallback if first part is too long\n}\n\n/**\n * Build a Linear issue URL with optional title slug\n * @param identifier - Issue identifier (e.g., \"ENG-123\")\n * @param title - Optional issue title for slug\n * @returns Linear URL\n */\nexport function buildLinearIssueUrl(identifier: string, title?: string): string {\n const base = `https://linear.app/issue/${identifier}`\n if (title) {\n const slug = slugifyTitle(title)\n return slug ? `${base}/${slug}` : base\n }\n return base\n}\n\n/**\n * Get Linear API token from environment\n * @returns API token\n * @throws LinearServiceError if token not found\n */\nfunction getLinearApiToken(): string {\n const token = process.env.LINEAR_API_TOKEN\n if (!token) {\n throw new LinearServiceError(\n 'UNAUTHORIZED',\n 'LINEAR_API_TOKEN not set. Configure in settings.local.json or set environment variable.',\n )\n }\n return token\n}\n\n/**\n * Create a Linear SDK client instance\n * @returns Configured LinearClient\n */\nfunction createLinearClient(): LinearClient {\n return new LinearClient({ apiKey: getLinearApiToken() })\n}\n\n/**\n * Handle SDK errors and convert to LinearServiceError\n * @param error - Error from SDK\n * @param context - Context string for debugging\n * @throws LinearServiceError\n */\nfunction handleLinearError(error: unknown, context: string): never {\n logger.debug(`${context}: Handling error`, { error })\n\n // SDK errors typically have a message property\n const errorMessage = error instanceof Error ? error.message : String(error)\n\n // Map common error patterns\n if (errorMessage.includes('not found') || errorMessage.includes('Not found')) {\n throw new LinearServiceError('NOT_FOUND', 'Linear issue or resource not found', { error })\n }\n\n if (\n errorMessage.includes('unauthorized') ||\n errorMessage.includes('Unauthorized') ||\n errorMessage.includes('Invalid API key')\n ) {\n throw new LinearServiceError(\n 'UNAUTHORIZED',\n 'Linear authentication failed. Check LINEAR_API_TOKEN.',\n { error },\n )\n }\n\n if (errorMessage.includes('rate limit')) {\n throw new LinearServiceError('RATE_LIMITED', 'Linear API rate limit exceeded', { error })\n }\n\n // Generic SDK error\n throw new LinearServiceError('CLI_ERROR', `Linear SDK error: ${errorMessage}`, { error })\n}\n\n/**\n * Fetch a Linear issue by identifier\n * @param identifier - Linear issue identifier (e.g., \"ENG-123\")\n * @returns Linear issue details\n * @throws LinearServiceError if issue not found or SDK error\n */\nexport async function fetchLinearIssue(identifier: string): Promise<LinearIssue> {\n try {\n logger.debug(`Fetching Linear issue: ${identifier}`)\n const client = createLinearClient()\n const issue = await client.issue(identifier)\n\n if (!issue) {\n throw new LinearServiceError('NOT_FOUND', `Linear issue ${identifier} not found`)\n }\n\n // Convert SDK issue to our LinearIssue type\n const result: LinearIssue = {\n id: issue.id,\n identifier: issue.identifier,\n title: issue.title,\n url: issue.url,\n createdAt: issue.createdAt.toISOString(),\n updatedAt: issue.updatedAt.toISOString(),\n }\n\n // Add optional fields if present\n if (issue.description) {\n result.description = issue.description\n }\n\n if (issue.state) {\n const state = await issue.state\n if (state?.name) {\n result.state = state.name\n }\n }\n\n return result\n } catch (error) {\n if (error instanceof LinearServiceError) {\n throw error\n }\n handleLinearError(error, 'fetchLinearIssue')\n }\n}\n\n/**\n * Create a new Linear issue\n * @param title - Issue title\n * @param body - Issue description (markdown)\n * @param teamKey - Team key (e.g., \"ENG\", \"PLAT\")\n * @param labels - Optional label names to apply\n * @returns Created issue identifier and URL\n * @throws LinearServiceError on creation failure\n */\nexport async function createLinearIssue(\n title: string,\n body: string,\n teamKey: string,\n _labels?: string[],\n): Promise<{ identifier: string; url: string }> {\n try {\n logger.debug(`Creating Linear issue in team ${teamKey}: ${title}`)\n const client = createLinearClient()\n\n // Get team by key\n const teams = await client.teams()\n const team = teams.nodes.find((t) => t.key === teamKey)\n\n if (!team) {\n throw new LinearServiceError('NOT_FOUND', `Linear team ${teamKey} not found`)\n }\n\n // Create issue\n const issueInput: { teamId: string; title: string; description?: string } = {\n teamId: team.id,\n title,\n }\n\n if (body) {\n issueInput.description = body\n }\n\n const payload = await client.createIssue(issueInput)\n\n const issue = await payload.issue\n\n if (!issue) {\n throw new LinearServiceError('CLI_ERROR', 'Failed to create Linear issue')\n }\n\n // Construct URL\n const url = issue.url ?? buildLinearIssueUrl(issue.identifier, title)\n\n return {\n identifier: issue.identifier,\n url,\n }\n } catch (error) {\n if (error instanceof LinearServiceError) {\n throw error\n }\n handleLinearError(error, 'createLinearIssue')\n }\n}\n\n/**\n * Create a comment on a Linear issue\n * @param identifier - Linear issue identifier (e.g., \"ENG-123\")\n * @param body - Comment body (markdown)\n * @returns Created comment details\n * @throws LinearServiceError on creation failure\n */\nexport async function createLinearComment(\n identifier: string,\n body: string,\n): Promise<LinearComment> {\n try {\n logger.debug(`Creating comment on Linear issue ${identifier}`)\n const client = createLinearClient()\n\n // Get issue by identifier to get its ID\n const issue = await client.issue(identifier)\n if (!issue) {\n throw new LinearServiceError('NOT_FOUND', `Linear issue ${identifier} not found`)\n }\n\n // Create comment using issue ID\n const payload = await client.createComment({\n issueId: issue.id,\n body,\n })\n\n const comment = await payload.comment\n\n if (!comment) {\n throw new LinearServiceError('CLI_ERROR', 'Failed to create Linear comment')\n }\n\n return {\n id: comment.id,\n body: comment.body,\n createdAt: comment.createdAt.toISOString(),\n updatedAt: comment.updatedAt.toISOString(),\n url: comment.url,\n }\n } catch (error) {\n if (error instanceof LinearServiceError) {\n throw error\n }\n handleLinearError(error, 'createLinearComment')\n }\n}\n\n/**\n * Update a Linear issue's workflow state\n * @param identifier - Linear issue identifier (e.g., \"ENG-123\")\n * @param stateName - Target state name (e.g., \"In Progress\", \"Done\")\n * @throws LinearServiceError on update failure\n */\nexport async function updateLinearIssueState(\n identifier: string,\n stateName: string,\n): Promise<void> {\n try {\n logger.debug(`Updating Linear issue ${identifier} state to: ${stateName}`)\n const client = createLinearClient()\n\n // Get issue by identifier\n const issue = await client.issue(identifier)\n if (!issue) {\n throw new LinearServiceError('NOT_FOUND', `Linear issue ${identifier} not found`)\n }\n\n // Get team to find state\n const team = await issue.team\n if (!team) {\n throw new LinearServiceError('CLI_ERROR', 'Issue has no team')\n }\n\n // Find state by name\n const states = await team.states()\n const state = states.nodes.find((s) => s.name === stateName)\n\n if (!state) {\n throw new LinearServiceError(\n 'NOT_FOUND',\n `State \"${stateName}\" not found in team ${team.key}`,\n )\n }\n\n // Update issue state\n await client.updateIssue(issue.id, {\n stateId: state.id,\n })\n } catch (error) {\n if (error instanceof LinearServiceError) {\n throw error\n }\n handleLinearError(error, 'updateLinearIssueState')\n }\n}\n\n/**\n * Get a specific comment by ID\n * @param commentId - Linear comment UUID\n * @returns Comment details\n * @throws LinearServiceError if comment not found\n */\nexport async function getLinearComment(commentId: string): Promise<LinearComment> {\n try {\n logger.debug(`Fetching Linear comment: ${commentId}`)\n const client = createLinearClient()\n const comment = await client.comment({ id: commentId })\n\n if (!comment) {\n throw new LinearServiceError('NOT_FOUND', `Linear comment ${commentId} not found`)\n }\n\n return {\n id: comment.id,\n body: comment.body,\n createdAt: comment.createdAt.toISOString(),\n updatedAt: comment.updatedAt.toISOString(),\n url: comment.url,\n }\n } catch (error) {\n if (error instanceof LinearServiceError) {\n throw error\n }\n handleLinearError(error, 'getLinearComment')\n }\n}\n\n/**\n * Update an existing comment\n * @param commentId - Linear comment UUID\n * @param body - New comment body (markdown)\n * @returns Updated comment details\n * @throws LinearServiceError on update failure\n */\nexport async function updateLinearComment(\n commentId: string,\n body: string,\n): Promise<LinearComment> {\n try {\n logger.debug(`Updating Linear comment: ${commentId}`)\n const client = createLinearClient()\n\n const payload = await client.updateComment(commentId, { body })\n const comment = await payload.comment\n\n if (!comment) {\n throw new LinearServiceError('CLI_ERROR', 'Failed to update Linear comment')\n }\n\n return {\n id: comment.id,\n body: comment.body,\n createdAt: comment.createdAt.toISOString(),\n updatedAt: comment.updatedAt.toISOString(),\n url: comment.url,\n }\n } catch (error) {\n if (error instanceof LinearServiceError) {\n throw error\n }\n handleLinearError(error, 'updateLinearComment')\n }\n}\n\n/**\n * Fetch all comments for a Linear issue\n * @param identifier - Linear issue identifier (e.g., \"ENG-123\")\n * @returns Array of comments\n * @throws LinearServiceError on fetch failure\n */\nexport async function fetchLinearIssueComments(identifier: string): Promise<LinearComment[]> {\n try {\n logger.debug(`Fetching comments for Linear issue: ${identifier}`)\n const client = createLinearClient()\n\n // Get issue by identifier\n const issue = await client.issue(identifier)\n if (!issue) {\n throw new LinearServiceError('NOT_FOUND', `Linear issue ${identifier} not found`)\n }\n\n // Fetch comments\n const comments = await issue.comments({ first: 100 })\n\n return comments.nodes.map((comment) => ({\n id: comment.id,\n body: comment.body,\n createdAt: comment.createdAt.toISOString(),\n updatedAt: comment.updatedAt.toISOString(),\n url: comment.url,\n }))\n } catch (error) {\n if (error instanceof LinearServiceError) {\n throw error\n }\n handleLinearError(error, 'fetchLinearIssueComments')\n }\n}\n"],"mappings":"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","names":[]}
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  detectPackageManager
4
- } from "./chunk-ZT3YZB4K.js";
4
+ } from "./chunk-VBFDVGAE.js";
5
5
  import {
6
6
  logger
7
- } from "./chunk-GEHQXLEI.js";
7
+ } from "./chunk-UYVWLISQ.js";
8
8
 
9
9
  // src/utils/dev-server.ts
10
10
  async function buildDevServerCommand(workspacePath) {
@@ -45,4 +45,4 @@ export {
45
45
  buildDevServerCommand,
46
46
  getDevServerLaunchCommand
47
47
  };
48
- //# sourceMappingURL=chunk-ZM3CFL5L.js.map
48
+ //# sourceMappingURL=chunk-QRBOPFAA.js.map
@@ -4,13 +4,13 @@ import {
4
4
  } from "./chunk-YETJNRQM.js";
5
5
  import {
6
6
  waitForKeypress
7
- } from "./chunk-CE26YH2U.js";
7
+ } from "./chunk-SJ2GZ6RF.js";
8
8
  import {
9
9
  launchClaude
10
- } from "./chunk-OYF4VIFI.js";
10
+ } from "./chunk-75B2HZZ5.js";
11
11
  import {
12
- logger
13
- } from "./chunk-GEHQXLEI.js";
12
+ getLogger
13
+ } from "./chunk-6UIGZD2N.js";
14
14
 
15
15
  // src/lib/IssueEnhancementService.ts
16
16
  var IssueEnhancementService = class {
@@ -40,7 +40,7 @@ var IssueEnhancementService = class {
40
40
  */
41
41
  async enhanceDescription(description) {
42
42
  try {
43
- logger.info("Enhancing description with Claude Code. This may take a moment...");
43
+ getLogger().info("Enhancing description with Claude Code. This may take a moment...");
44
44
  const settings = await this.settingsManager.loadSettings();
45
45
  const loadedAgents = await this.agentManager.loadAgents(settings);
46
46
  const agents = this.agentManager.formatForCli(loadedAgents);
@@ -66,13 +66,13 @@ Your response should be the raw markdown that will become the GitHub issue body.
66
66
  agents
67
67
  });
68
68
  if (enhanced && typeof enhanced === "string") {
69
- logger.success("Description enhanced successfully");
69
+ getLogger().success("Description enhanced successfully");
70
70
  return enhanced;
71
71
  }
72
- logger.warn("Claude enhancement returned empty result, using original description");
72
+ getLogger().warn("Claude enhancement returned empty result, using original description");
73
73
  return description;
74
74
  } catch (error) {
75
- logger.warn(`Failed to enhance description: ${error instanceof Error ? error.message : "Unknown error"}`);
75
+ getLogger().warn(`Failed to enhance description: ${error instanceof Error ? error.message : "Unknown error"}`);
76
76
  return description;
77
77
  }
78
78
  }
@@ -85,7 +85,7 @@ Your response should be the raw markdown that will become the GitHub issue body.
85
85
  * @returns Issue number and URL
86
86
  */
87
87
  async createEnhancedIssue(originalDescription, enhancedDescription, repository, labels) {
88
- logger.info("Creating GitHub issue from description...");
88
+ getLogger().info("Creating GitHub issue from description...");
89
89
  const result = await this.issueTrackerService.createIssue(
90
90
  originalDescription,
91
91
  // Use original description as title
@@ -104,8 +104,9 @@ Your response should be the raw markdown that will become the GitHub issue body.
104
104
  */
105
105
  async waitForReviewAndOpen(issueNumber, confirm = false, repository) {
106
106
  const isCI = process.env.CI === "true";
107
- if (isCI) {
108
- logger.info(`Running in CI environment - skipping interactive prompts for issue #${issueNumber}`);
107
+ const isNonInteractive = isCI || !process.stdin.isTTY;
108
+ if (isNonInteractive) {
109
+ getLogger().info(`Running in non-interactive environment - skipping interactive prompts for issue #${issueNumber}`);
109
110
  return;
110
111
  }
111
112
  const issueUrl = await this.issueTrackerService.getIssueUrl(issueNumber, repository);
@@ -142,4 +143,4 @@ export {
142
143
  IssueEnhancementService,
143
144
  capitalizeFirstLetter
144
145
  };
145
- //# sourceMappingURL=chunk-IFB4Z76W.js.map
146
+ //# sourceMappingURL=chunk-S44CHE3G.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/IssueEnhancementService.ts","../src/utils/text.ts"],"sourcesContent":["import type { IssueTracker } from './IssueTracker.js'\nimport type { AgentManager } from './AgentManager.js'\nimport type { SettingsManager } from './SettingsManager.js'\nimport { launchClaude } from '../utils/claude.js'\nimport { openBrowser } from '../utils/browser.js'\nimport { waitForKeypress } from '../utils/prompt.js'\nimport { getLogger } from '../utils/logger-context.js'\n\n/**\n * Service for enhancing and creating issues with AI assistance.\n * Extracts reusable issue enhancement logic from StartCommand.\n */\nexport class IssueEnhancementService {\n\tconstructor(\n\t\tprivate issueTrackerService: IssueTracker,\n\t\tprivate agentManager: AgentManager,\n\t\tprivate settingsManager: SettingsManager\n\t) {\n\t\t// No-op - logger now uses AsyncLocalStorage context\n\t}\n\n\t/**\n\t * Expose issue tracker for provider checks\n\t */\n\tpublic get issueTracker(): IssueTracker {\n\t\treturn this.issueTrackerService\n\t}\n\n\t/**\n\t * Validates that a description meets minimum requirements.\n\t * Requirements: >30 characters AND >2 spaces\n\t */\n\tpublic validateDescription(description: string): boolean {\n\t\tconst trimmedDescription = description.trim()\n\t\tconst spaceCount = (trimmedDescription.match(/ /g) ?? []).length\n\n\t\treturn trimmedDescription.length > 30 && spaceCount > 2\n\t}\n\n\t/**\n\t * Enhances a description using Claude Code in headless mode.\n\t * Falls back to original description if enhancement fails.\n\t */\n\tpublic async enhanceDescription(description: string): Promise<string> {\n\t\ttry {\n\t\t\tgetLogger().info('Enhancing description with Claude Code. This may take a moment...')\n\n\t\t\t// Load agent configurations\n\t\t\tconst settings = await this.settingsManager.loadSettings()\n\t\t\tconst loadedAgents = await this.agentManager.loadAgents(settings)\n\t\t\tconst agents = this.agentManager.formatForCli(loadedAgents)\n\n\t\t\t// Call Claude in headless mode with issue enhancer agent\n\t\t\tconst prompt = `@agent-iloom-issue-enhancer\n\nTASK: Enhance the following issue description for GitHub.\n\nINPUT:\n${description}\n\nOUTPUT REQUIREMENTS:\n- Return ONLY the enhanced description markdown text\n- NO meta-commentary (no \"Here is...\", \"The enhanced...\", \"I have...\", etc)\n- NO code block markers (\\`\\`\\`)\n- NO conversational framing or acknowledgments\n- NO explanations of your work\n- Start your response immediately with the enhanced content\n\nYour response should be the raw markdown that will become the GitHub issue body.`\n\n\t\t\tconst enhanced = await launchClaude(prompt, {\n\t\t\t\theadless: true,\n\t\t\t\tmodel: 'sonnet',\n\t\t\t\tagents,\n\t\t\t})\n\n\t\t\tif (enhanced && typeof enhanced === 'string') {\n\t\t\t\tgetLogger().success('Description enhanced successfully')\n\t\t\t\treturn enhanced\n\t\t\t}\n\n\t\t\t// Fallback to original description\n\t\t\tgetLogger().warn('Claude enhancement returned empty result, using original description')\n\t\t\treturn description\n\t\t} catch (error) {\n\t\t\tgetLogger().warn(`Failed to enhance description: ${error instanceof Error ? error.message : 'Unknown error'}`)\n\t\t\treturn description\n\t\t}\n\t}\n\n\t/**\n\t * Creates a GitHub issue with title and enhanced body.\n\t * @param originalDescription - Used as the issue title\n\t * @param enhancedDescription - Used as the issue body\n\t * @param repository - Optional repository override (format: \"owner/repo\")\n\t * @param labels - Optional array of label names to add to the issue\n\t * @returns Issue number and URL\n\t */\n\tpublic async createEnhancedIssue(\n\t\toriginalDescription: string,\n\t\tenhancedDescription: string,\n\t\trepository?: string,\n\t\tlabels?: string[]\n\t): Promise<{ number: string | number; url: string }> {\n\t\tgetLogger().info('Creating GitHub issue from description...')\n\n\t\tconst result = await this.issueTrackerService.createIssue(\n\t\t\toriginalDescription, // Use original description as title\n\t\t\tenhancedDescription, // Use enhanced description as body\n\t\t\trepository,\n\t\t\tlabels\n\t\t)\n\n\t\treturn result\n\t}\n\n\t/**\n\t * Waits for user keypress and opens issue in browser for review.\n\t * @param issueNumber - Issue number to open for review\n\t * @param confirm - If true, wait for additional keypress after opening browser before returning\n\t * @param repository - Optional repository to fetch issue from (format: \"owner/repo\")\n\t */\n\tpublic async waitForReviewAndOpen(issueNumber: string | number, confirm = false, repository?: string): Promise<void> {\n\t\t// Check if running in non-interactive environment (CI or no TTY)\n\t\tconst isCI = process.env.CI === 'true'\n\t\tconst isNonInteractive = isCI || !process.stdin.isTTY\n\n\t\tif (isNonInteractive) {\n\t\t\t// In non-interactive environment: Skip all interactive operations\n\t\t\tgetLogger().info(`Running in non-interactive environment - skipping interactive prompts for issue #${issueNumber}`)\n\t\t\treturn\n\t\t}\n\n\t\t// Get issue URL\n\t\tconst issueUrl = await this.issueTrackerService.getIssueUrl(issueNumber, repository)\n\n\t\t// Display message and wait for first keypress\n\t\tconst message = `Created issue #${issueNumber}.\nReview and edit the issue in your browser if needed.\nPress any key to open issue for editing...`\n\t\tawait waitForKeypress(message)\n\n\t\t// Open issue in browser\n\t\tawait openBrowser(issueUrl)\n\n\t\t// If confirmation required, wait for second keypress\n\t\tif (confirm) {\n\t\t\tawait waitForKeypress('Press any key to continue with loom creation...')\n\t\t}\n\t}\n}\n","/**\n * Capitalizes the first letter of a string.\n *\n * Override behavior: If the string starts with a space, it signals the user\n * wants to opt-out of auto-capitalization. In this case, the leading space\n * is stripped and the first letter is NOT capitalized.\n *\n * @param str - The string to process\n * @returns The processed string with first letter capitalized (or original if override)\n */\nexport function capitalizeFirstLetter(str: string): string {\n\t// Handle empty or whitespace-only strings\n\tif (!str || str.length === 0) {\n\t\treturn str\n\t}\n\n\t// Check for space-prefix override: strip leading space and return as-is\n\tif (str.startsWith(' ')) {\n\t\treturn str.slice(1)\n\t}\n\n\t// Find the first character that could be capitalized (a letter)\n\tconst firstChar = str.charAt(0)\n\n\t// If first character is a letter (including unicode), capitalize it\n\t// Check if toUpperCase() produces a different result (indicates it's a letter with case)\n\tconst upperChar = firstChar.toUpperCase()\n\tif (upperChar !== firstChar.toLowerCase() || /\\p{L}/u.test(firstChar)) {\n\t\t// Only capitalize if it actually changes (avoids issues with non-cased scripts)\n\t\tif (upperChar !== firstChar) {\n\t\t\treturn upperChar + str.slice(1)\n\t\t}\n\t}\n\n\t// Non-letter first character or no case transformation available: return unchanged\n\treturn str\n}\n"],"mappings":";;;;;;;;;;;;;;;AAYO,IAAM,0BAAN,MAA8B;AAAA,EACpC,YACS,qBACA,cACA,iBACP;AAHO;AACA;AACA;AAAA,EAGT;AAAA;AAAA;AAAA;AAAA,EAKA,IAAW,eAA6B;AACvC,WAAO,KAAK;AAAA,EACb;AAAA;AAAA;AAAA;AAAA;AAAA,EAMO,oBAAoB,aAA8B;AACxD,UAAM,qBAAqB,YAAY,KAAK;AAC5C,UAAM,cAAc,mBAAmB,MAAM,IAAI,KAAK,CAAC,GAAG;AAE1D,WAAO,mBAAmB,SAAS,MAAM,aAAa;AAAA,EACvD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAa,mBAAmB,aAAsC;AACrE,QAAI;AACH,gBAAU,EAAE,KAAK,mEAAmE;AAGpF,YAAM,WAAW,MAAM,KAAK,gBAAgB,aAAa;AACzD,YAAM,eAAe,MAAM,KAAK,aAAa,WAAW,QAAQ;AAChE,YAAM,SAAS,KAAK,aAAa,aAAa,YAAY;AAG1D,YAAM,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA,EAKhB,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAYV,YAAM,WAAW,MAAM,aAAa,QAAQ;AAAA,QAC3C,UAAU;AAAA,QACV,OAAO;AAAA,QACP;AAAA,MACD,CAAC;AAED,UAAI,YAAY,OAAO,aAAa,UAAU;AAC7C,kBAAU,EAAE,QAAQ,mCAAmC;AACvD,eAAO;AAAA,MACR;AAGA,gBAAU,EAAE,KAAK,sEAAsE;AACvF,aAAO;AAAA,IACR,SAAS,OAAO;AACf,gBAAU,EAAE,KAAK,kCAAkC,iBAAiB,QAAQ,MAAM,UAAU,eAAe,EAAE;AAC7G,aAAO;AAAA,IACR;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAa,oBACZ,qBACA,qBACA,YACA,QACoD;AACpD,cAAU,EAAE,KAAK,2CAA2C;AAE5D,UAAM,SAAS,MAAM,KAAK,oBAAoB;AAAA,MAC7C;AAAA;AAAA,MACA;AAAA;AAAA,MACA;AAAA,MACA;AAAA,IACD;AAEA,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAa,qBAAqB,aAA8B,UAAU,OAAO,YAAoC;AAEpH,UAAM,OAAO,QAAQ,IAAI,OAAO;AAChC,UAAM,mBAAmB,QAAQ,CAAC,QAAQ,MAAM;AAEhD,QAAI,kBAAkB;AAErB,gBAAU,EAAE,KAAK,oFAAoF,WAAW,EAAE;AAClH;AAAA,IACD;AAGA,UAAM,WAAW,MAAM,KAAK,oBAAoB,YAAY,aAAa,UAAU;AAGnF,UAAM,UAAU,kBAAkB,WAAW;AAAA;AAAA;AAG7C,UAAM,gBAAgB,OAAO;AAG7B,UAAM,YAAY,QAAQ;AAG1B,QAAI,SAAS;AACZ,YAAM,gBAAgB,iDAAiD;AAAA,IACxE;AAAA,EACD;AACD;;;AC5IO,SAAS,sBAAsB,KAAqB;AAE1D,MAAI,CAAC,OAAO,IAAI,WAAW,GAAG;AAC7B,WAAO;AAAA,EACR;AAGA,MAAI,IAAI,WAAW,GAAG,GAAG;AACxB,WAAO,IAAI,MAAM,CAAC;AAAA,EACnB;AAGA,QAAM,YAAY,IAAI,OAAO,CAAC;AAI9B,QAAM,YAAY,UAAU,YAAY;AACxC,MAAI,cAAc,UAAU,YAAY,KAAK,WAAC,UAAM,GAAC,EAAC,KAAK,SAAS,GAAG;AAEtE,QAAI,cAAc,WAAW;AAC5B,aAAO,YAAY,IAAI,MAAM,CAAC;AAAA,IAC/B;AAAA,EACD;AAGA,SAAO;AACR;","names":[]}