@iloom/cli 0.3.4 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -3
- package/dist/{BranchNamingService-A77VI6AI.js → BranchNamingService-GCCWB3LK.js} +4 -3
- package/dist/ClaudeContextManager-DK77227F.js +16 -0
- package/dist/ClaudeService-W3SA7HVG.js +15 -0
- package/dist/GitHubService-RPM27GWD.js +12 -0
- package/dist/{LoomLauncher-ZV3ZZIBA.js → LoomLauncher-S3YGJRJQ.js} +43 -27
- package/dist/LoomLauncher-S3YGJRJQ.js.map +1 -0
- package/dist/PromptTemplateManager-2TDZAUC6.js +9 -0
- package/dist/README.md +13 -3
- package/dist/{SettingsManager-I2LRCW2A.js → SettingsManager-FJFU6JJD.js} +7 -3
- package/dist/SettingsMigrationManager-EH3J2TCN.js +10 -0
- package/dist/{chunk-5Q3NDNNV.js → chunk-2W2FBL5G.js} +153 -6
- package/dist/chunk-2W2FBL5G.js.map +1 -0
- package/dist/{chunk-OXAM2WVC.js → chunk-55TB3FSG.js} +21 -1
- package/dist/chunk-55TB3FSG.js.map +1 -0
- package/dist/chunk-6UIGZD2N.js +20 -0
- package/dist/chunk-6UIGZD2N.js.map +1 -0
- package/dist/{chunk-RIEO2WML.js → chunk-74VMN2KC.js} +26 -2
- package/dist/chunk-74VMN2KC.js.map +1 -0
- package/dist/{chunk-2MAIX45J.js → chunk-BIIQHEXJ.js} +104 -43
- package/dist/chunk-BIIQHEXJ.js.map +1 -0
- package/dist/{chunk-UAN4A3YU.js → chunk-G6CIIJLT.js} +11 -11
- package/dist/{chunk-DLHA5VQ3.js → chunk-HD5SUKI2.js} +36 -179
- package/dist/chunk-HD5SUKI2.js.map +1 -0
- package/dist/{chunk-2IJEMXOB.js → chunk-IARWMDAX.js} +427 -428
- package/dist/chunk-IARWMDAX.js.map +1 -0
- package/dist/chunk-IJ7IGJT3.js +192 -0
- package/dist/chunk-IJ7IGJT3.js.map +1 -0
- package/dist/{chunk-2CXREBLZ.js → chunk-JC5HXN75.js} +8 -6
- package/dist/chunk-JC5HXN75.js.map +1 -0
- package/dist/{chunk-3RUPPQRG.js → chunk-KO2FOMHL.js} +43 -2
- package/dist/{chunk-3RUPPQRG.js.map → chunk-KO2FOMHL.js.map} +1 -1
- package/dist/{chunk-4XIDC3NF.js → chunk-MD6HA5IK.js} +2 -2
- package/dist/{chunk-OC4H6HJD.js → chunk-O7WHXLCB.js} +2 -2
- package/dist/{chunk-M7JJCX53.js → chunk-OEGECBFS.js} +20 -20
- package/dist/chunk-OEGECBFS.js.map +1 -0
- package/dist/{chunk-MKWYLDFK.js → chunk-OF7BNW4D.js} +43 -3
- package/dist/chunk-OF7BNW4D.js.map +1 -0
- package/dist/{chunk-PGPI5LR4.js → chunk-POI7KLBH.js} +7 -21
- package/dist/chunk-POI7KLBH.js.map +1 -0
- package/dist/{chunk-PA6Q6AWM.js → chunk-PSFVTBM7.js} +2 -2
- package/dist/chunk-QHA67Q7A.js +281 -0
- package/dist/chunk-QHA67Q7A.js.map +1 -0
- package/dist/{chunk-SUOXY5WJ.js → chunk-QIUJPPJQ.js} +5 -5
- package/dist/chunk-QIUJPPJQ.js.map +1 -0
- package/dist/{chunk-ZM3CFL5L.js → chunk-QRBOPFAA.js} +3 -3
- package/dist/{chunk-OYF4VIFI.js → chunk-RUC7OULH.js} +147 -22
- package/dist/chunk-RUC7OULH.js.map +1 -0
- package/dist/{chunk-CE26YH2U.js → chunk-SJ2GZ6RF.js} +48 -50
- package/dist/chunk-SJ2GZ6RF.js.map +1 -0
- package/dist/{chunk-SSCQCCJ7.js → chunk-THF25ICZ.js} +2 -2
- package/dist/chunk-TMZAVPGF.js +667 -0
- package/dist/chunk-TMZAVPGF.js.map +1 -0
- package/dist/{chunk-5VK4NRSF.js → chunk-UNXRACJ7.js} +35 -36
- package/dist/chunk-UNXRACJ7.js.map +1 -0
- package/dist/{chunk-AKUJXDNW.js → chunk-UPUAQYAW.js} +3 -3
- package/dist/{chunk-GEHQXLEI.js → chunk-UYVWLISQ.js} +18 -35
- package/dist/chunk-UYVWLISQ.js.map +1 -0
- package/dist/{chunk-OSCLCMDG.js → chunk-UYWAESOT.js} +3 -3
- package/dist/{chunk-RW54ZMBM.js → chunk-VAYGNQTE.js} +2 -2
- package/dist/{chunk-ZT3YZB4K.js → chunk-VBFDVGAE.js} +12 -12
- package/dist/chunk-VBFDVGAE.js.map +1 -0
- package/dist/{chunk-IFB4Z76W.js → chunk-VTXCGKV5.js} +13 -12
- package/dist/chunk-VTXCGKV5.js.map +1 -0
- package/dist/{chunk-CDZERT7Z.js → chunk-VWNS6DH5.js} +48 -4
- package/dist/chunk-VWNS6DH5.js.map +1 -0
- package/dist/{chunk-CFFQ2Z7A.js → chunk-WUQQNE63.js} +2 -2
- package/dist/{chunk-UJL4HI2R.js → chunk-Z5NXYJIG.js} +20 -2
- package/dist/chunk-Z5NXYJIG.js.map +1 -0
- package/dist/{claude-W52VKI6L.js → claude-ACVXNB6N.js} +8 -5
- package/dist/{cleanup-H4VXU3C3.js → cleanup-KDLVTT7M.js} +133 -122
- package/dist/cleanup-KDLVTT7M.js.map +1 -0
- package/dist/cli.js +953 -430
- package/dist/cli.js.map +1 -1
- package/dist/{color-F7RU6B6Z.js → color-ZPIIUADB.js} +3 -3
- package/dist/{contribute-Y7IQV5QY.js → contribute-HY372S6F.js} +8 -6
- package/dist/{contribute-Y7IQV5QY.js.map → contribute-HY372S6F.js.map} +1 -1
- package/dist/dev-server-JCJGQ3PV.js +298 -0
- package/dist/dev-server-JCJGQ3PV.js.map +1 -0
- package/dist/{feedback-XTUCKJNT.js → feedback-7PVBQNLJ.js} +13 -12
- package/dist/{feedback-XTUCKJNT.js.map → feedback-7PVBQNLJ.js.map} +1 -1
- package/dist/{git-IYA53VIC.js → git-4BVOOOOV.js} +16 -4
- package/dist/hooks/iloom-hook.js +258 -0
- package/dist/{ignite-T74RYXCA.js → ignite-3B264M7K.js} +245 -39
- package/dist/ignite-3B264M7K.js.map +1 -0
- package/dist/index.d.ts +461 -124
- package/dist/index.js +743 -210
- package/dist/index.js.map +1 -1
- package/dist/init-LBA6NUK2.js +21 -0
- package/dist/{installation-detector-VARGFFRZ.js → installation-detector-6R6YOFVZ.js} +3 -3
- package/dist/mcp/issue-management-server.js +2 -1
- package/dist/mcp/issue-management-server.js.map +1 -1
- package/dist/neon-helpers-L5CXQ5CT.js +11 -0
- package/dist/{open-UMXANW5S.js → open-OGCV32Z4.js} +15 -13
- package/dist/{open-UMXANW5S.js.map → open-OGCV32Z4.js.map} +1 -1
- package/dist/projects-P55273AB.js +73 -0
- package/dist/projects-P55273AB.js.map +1 -0
- package/dist/{prompt-QALMYTVC.js → prompt-A7GGRHSY.js} +3 -3
- package/dist/prompts/init-prompt.txt +49 -0
- package/dist/prompts/issue-prompt.txt +110 -8
- package/dist/prompts/regular-prompt.txt +90 -0
- package/dist/prompts/session-summary-prompt.txt +82 -0
- package/dist/{rebase-VJ2VKR6R.js → rebase-4T5FQHNH.js} +11 -9
- package/dist/{rebase-VJ2VKR6R.js.map → rebase-4T5FQHNH.js.map} +1 -1
- package/dist/{remote-VUNCQZ6J.js → remote-73TZ2ADI.js} +3 -3
- package/dist/{run-MJYY4PUT.js → run-HNOP6WE2.js} +15 -13
- package/dist/{run-MJYY4PUT.js.map → run-HNOP6WE2.js.map} +1 -1
- package/dist/schema/settings.schema.json +49 -0
- package/dist/shell-DE3HKJSM.js +240 -0
- package/dist/shell-DE3HKJSM.js.map +1 -0
- package/dist/summary-GDT7DTRI.js +244 -0
- package/dist/summary-GDT7DTRI.js.map +1 -0
- package/dist/{test-git-IT5EWQ5C.js → test-git-YMAE57UP.js} +6 -4
- package/dist/{test-git-IT5EWQ5C.js.map → test-git-YMAE57UP.js.map} +1 -1
- package/dist/{test-prefix-NPWDPUUH.js → test-prefix-YCKL6CMT.js} +6 -4
- package/dist/{test-prefix-NPWDPUUH.js.map → test-prefix-YCKL6CMT.js.map} +1 -1
- package/dist/{test-tabs-PRMRSHKI.js → test-tabs-3SCJWRKT.js} +4 -4
- package/dist/{test-webserver-DAHONWCS.js → test-webserver-VPNLAFZ3.js} +2 -2
- package/dist/{update-4TDDUR5K.js → update-LETF5ASC.js} +4 -4
- package/dist/{update-notifier-QEX3CJHA.js → update-notifier-H55ZK7NU.js} +3 -3
- package/package.json +6 -6
- package/dist/ClaudeContextManager-BN7RE5ZQ.js +0 -15
- package/dist/ClaudeService-DLYLJUPA.js +0 -14
- package/dist/GitHubService-FZHHBOFG.js +0 -11
- package/dist/LoomLauncher-ZV3ZZIBA.js.map +0 -1
- package/dist/PromptTemplateManager-6HH3PVXV.js +0 -9
- package/dist/SettingsMigrationManager-TJ7UWZG5.js +0 -10
- package/dist/chunk-2CXREBLZ.js.map +0 -1
- package/dist/chunk-2IJEMXOB.js.map +0 -1
- package/dist/chunk-2MAIX45J.js.map +0 -1
- package/dist/chunk-5Q3NDNNV.js.map +0 -1
- package/dist/chunk-5VK4NRSF.js.map +0 -1
- package/dist/chunk-CDZERT7Z.js.map +0 -1
- package/dist/chunk-CE26YH2U.js.map +0 -1
- package/dist/chunk-DLHA5VQ3.js.map +0 -1
- package/dist/chunk-GEHQXLEI.js.map +0 -1
- package/dist/chunk-IFB4Z76W.js.map +0 -1
- package/dist/chunk-M7JJCX53.js.map +0 -1
- package/dist/chunk-MKWYLDFK.js.map +0 -1
- package/dist/chunk-OXAM2WVC.js.map +0 -1
- package/dist/chunk-OYF4VIFI.js.map +0 -1
- package/dist/chunk-PGPI5LR4.js.map +0 -1
- package/dist/chunk-RIEO2WML.js.map +0 -1
- package/dist/chunk-SUOXY5WJ.js.map +0 -1
- package/dist/chunk-UJL4HI2R.js.map +0 -1
- package/dist/chunk-ZT3YZB4K.js.map +0 -1
- package/dist/cleanup-H4VXU3C3.js.map +0 -1
- package/dist/ignite-T74RYXCA.js.map +0 -1
- package/dist/init-4FHTAM3F.js +0 -19
- package/dist/logger-MKYH4UDV.js +0 -12
- package/dist/neon-helpers-77PBPGJ5.js +0 -10
- package/dist/update-notifier-QEX3CJHA.js.map +0 -1
- /package/dist/{BranchNamingService-A77VI6AI.js.map → BranchNamingService-GCCWB3LK.js.map} +0 -0
- /package/dist/{ClaudeContextManager-BN7RE5ZQ.js.map → ClaudeContextManager-DK77227F.js.map} +0 -0
- /package/dist/{ClaudeService-DLYLJUPA.js.map → ClaudeService-W3SA7HVG.js.map} +0 -0
- /package/dist/{GitHubService-FZHHBOFG.js.map → GitHubService-RPM27GWD.js.map} +0 -0
- /package/dist/{PromptTemplateManager-6HH3PVXV.js.map → PromptTemplateManager-2TDZAUC6.js.map} +0 -0
- /package/dist/{SettingsManager-I2LRCW2A.js.map → SettingsManager-FJFU6JJD.js.map} +0 -0
- /package/dist/{SettingsMigrationManager-TJ7UWZG5.js.map → SettingsMigrationManager-EH3J2TCN.js.map} +0 -0
- /package/dist/{chunk-UAN4A3YU.js.map → chunk-G6CIIJLT.js.map} +0 -0
- /package/dist/{chunk-4XIDC3NF.js.map → chunk-MD6HA5IK.js.map} +0 -0
- /package/dist/{chunk-OC4H6HJD.js.map → chunk-O7WHXLCB.js.map} +0 -0
- /package/dist/{chunk-PA6Q6AWM.js.map → chunk-PSFVTBM7.js.map} +0 -0
- /package/dist/{chunk-ZM3CFL5L.js.map → chunk-QRBOPFAA.js.map} +0 -0
- /package/dist/{chunk-SSCQCCJ7.js.map → chunk-THF25ICZ.js.map} +0 -0
- /package/dist/{chunk-AKUJXDNW.js.map → chunk-UPUAQYAW.js.map} +0 -0
- /package/dist/{chunk-OSCLCMDG.js.map → chunk-UYWAESOT.js.map} +0 -0
- /package/dist/{chunk-RW54ZMBM.js.map → chunk-VAYGNQTE.js.map} +0 -0
- /package/dist/{chunk-CFFQ2Z7A.js.map → chunk-WUQQNE63.js.map} +0 -0
- /package/dist/{claude-W52VKI6L.js.map → claude-ACVXNB6N.js.map} +0 -0
- /package/dist/{color-F7RU6B6Z.js.map → color-ZPIIUADB.js.map} +0 -0
- /package/dist/{git-IYA53VIC.js.map → git-4BVOOOOV.js.map} +0 -0
- /package/dist/{init-4FHTAM3F.js.map → init-LBA6NUK2.js.map} +0 -0
- /package/dist/{installation-detector-VARGFFRZ.js.map → installation-detector-6R6YOFVZ.js.map} +0 -0
- /package/dist/{logger-MKYH4UDV.js.map → neon-helpers-L5CXQ5CT.js.map} +0 -0
- /package/dist/{neon-helpers-77PBPGJ5.js.map → prompt-A7GGRHSY.js.map} +0 -0
- /package/dist/{prompt-QALMYTVC.js.map → remote-73TZ2ADI.js.map} +0 -0
- /package/dist/{test-tabs-PRMRSHKI.js.map → test-tabs-3SCJWRKT.js.map} +0 -0
- /package/dist/{test-webserver-DAHONWCS.js.map → test-webserver-VPNLAFZ3.js.map} +0 -0
- /package/dist/{update-4TDDUR5K.js.map → update-LETF5ASC.js.map} +0 -0
- /package/dist/{remote-VUNCQZ6J.js.map → update-notifier-H55ZK7NU.js.map} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/ClaudeService.ts"],"sourcesContent":["import { detectClaudeCli, launchClaude, launchClaudeInNewTerminalWindow, ClaudeCliOptions } from '../utils/claude.js'\nimport { PromptTemplateManager, TemplateVariables } from './PromptTemplateManager.js'\nimport { SettingsManager, IloomSettings } from './SettingsManager.js'\nimport { logger } from '../utils/logger.js'\n\nexport interface ClaudeWorkflowOptions {\n\ttype: 'issue' | 'pr' | 'regular'\n\tissueNumber?: string | number\n\tprNumber?: number\n\ttitle?: string\n\tworkspacePath: string\n\tport?: number\n\theadless?: boolean\n\tbranchName?: string\n\toneShot?: import('../types/index.js').OneShotMode\n\tsetArguments?: string[] // Raw --set arguments to forward\n\texecutablePath?: string // Executable path to use for spin command\n}\n\nexport class ClaudeService {\n\tprivate templateManager: PromptTemplateManager\n\tprivate settingsManager: SettingsManager\n\tprivate settings?: IloomSettings\n\n\tconstructor(templateManager?: PromptTemplateManager, settingsManager?: SettingsManager) {\n\t\tthis.templateManager = templateManager ?? new PromptTemplateManager()\n\t\tthis.settingsManager = settingsManager ?? new SettingsManager()\n\t}\n\n\t/**\n\t * Check if Claude CLI is available\n\t */\n\tasync isAvailable(): Promise<boolean> {\n\t\treturn detectClaudeCli()\n\t}\n\n\t/**\n\t * Get the appropriate permission mode for a workflow type\n\t */\n\tprivate getPermissionModeForWorkflow(\n\t\ttype: 'issue' | 'pr' | 'regular'\n\t): ClaudeCliOptions['permissionMode'] {\n\t\t// Check settings for configured permission mode\n\t\tif (this.settings?.workflows) {\n\t\t\tconst workflowConfig =\n\t\t\t\ttype === 'issue'\n\t\t\t\t\t? this.settings.workflows.issue\n\t\t\t\t\t: type === 'pr'\n\t\t\t\t\t\t? this.settings.workflows.pr\n\t\t\t\t\t\t: this.settings.workflows.regular\n\n\t\t\tif (workflowConfig?.permissionMode) {\n\t\t\t\treturn workflowConfig.permissionMode\n\t\t\t}\n\t\t}\n\n\t\t// Fall back to current defaults\n\t\tif (type === 'issue') {\n\t\t\treturn 'acceptEdits'\n\t\t}\n\t\t// For PR and regular workflows, use default permissions\n\t\treturn 'default'\n\t}\n\n\t/**\n\t * Launch Claude for a specific workflow\n\t */\n\tasync launchForWorkflow(options: ClaudeWorkflowOptions): Promise<string | void> {\n\t\tconst { type, issueNumber, prNumber, title, workspacePath, port, headless = false, branchName, oneShot = 'default', setArguments, executablePath } = options\n\n\t\ttry {\n\t\t\t// Load settings if not already cached\n\t\t\t// Settings are pre-validated at CLI startup, so no error handling needed here\n\t\t\tthis.settings ??= await this.settingsManager.loadSettings()\n\n\t\t\t// Build template variables\n\t\t\tconst variables: TemplateVariables = {\n\t\t\t\tWORKSPACE_PATH: workspacePath,\n\t\t\t}\n\n\t\t\tif (issueNumber !== undefined) {\n\t\t\t\tvariables.ISSUE_NUMBER = issueNumber\n\t\t\t}\n\n\t\t\tif (prNumber !== undefined) {\n\t\t\t\tvariables.PR_NUMBER = prNumber\n\t\t\t}\n\n\t\t\tif (title !== undefined) {\n\t\t\t\tif (type === 'issue') {\n\t\t\t\t\tvariables.ISSUE_TITLE = title\n\t\t\t\t} else if (type === 'pr') {\n\t\t\t\t\tvariables.PR_TITLE = title\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (port !== undefined) {\n\t\t\t\tvariables.PORT = port\n\t\t\t}\n\n\t\t\t// Get the prompt from template manager\n\t\t\tconst prompt = await this.templateManager.getPrompt(type, variables)\n\n\t\t\t// Determine permission mode (model uses Claude's default for start command)\n\t\t\tconst permissionMode = this.getPermissionModeForWorkflow(type)\n\n\t\t\t// Display warning if bypassPermissions mode is used\n\t\t\tif (permissionMode === 'bypassPermissions') {\n\t\t\t\tlogger.warn(\n\t\t\t\t\t'⚠️ WARNING: Using bypassPermissions mode - Claude will execute all tool calls without confirmation. ' +\n\t\t\t\t\t\t'This can be dangerous. Use with caution.'\n\t\t\t\t)\n\t\t\t}\n\n\t\t\t// Build Claude CLI options\n\t\t\tconst claudeOptions: ClaudeCliOptions = {\n\t\t\t\taddDir: workspacePath,\n\t\t\t\theadless,\n\t\t\t}\n\n\t\t\t// Add permission mode if not default\n\t\t\tif (permissionMode !== undefined && permissionMode !== 'default') {\n\t\t\t\tclaudeOptions.permissionMode = permissionMode\n\t\t\t}\n\n\t\t\t// Add optional branch name for terminal coloring\n\t\t\tif (branchName !== undefined) {\n\t\t\t\tclaudeOptions.branchName = branchName\n\t\t\t}\n\n\t\t\t// Add optional port for terminal window export\n\t\t\tif (port !== undefined) {\n\t\t\t\tclaudeOptions.port = port\n\t\t\t}\n\n\t\t\t// Add optional setArguments for forwarding\n\t\t\tif (setArguments !== undefined) {\n\t\t\t\tclaudeOptions.setArguments = setArguments\n\t\t\t}\n\n\t\t\t// Add optional executablePath for spin command\n\t\t\tif (executablePath !== undefined) {\n\t\t\t\tclaudeOptions.executablePath = executablePath\n\t\t\t}\n\n\t\t\tlogger.debug('Launching Claude for workflow', {\n\t\t\t\ttype,\n\t\t\t\tpermissionMode,\n\t\t\t\theadless,\n\t\t\t\tworkspacePath,\n\t\t\t})\n\n\t\t\t// Launch Claude\n\t\t\tif (headless) {\n\t\t\t\t// Headless mode: use simple launchClaude\n\t\t\t\treturn await launchClaude(prompt, claudeOptions)\n\t\t\t} else {\n\t\t\t\t// Interactive workflow mode: use terminal window launcher\n\t\t\t\t// This is the \"end of il start\" behavior\n\t\t\t\tif (!claudeOptions.addDir) {\n\t\t\t\t\tthrow new Error('workspacePath required for interactive workflow launch')\n\t\t\t\t}\n\n\t\t\t\treturn await launchClaudeInNewTerminalWindow(prompt, {\n\t\t\t\t\t...claudeOptions,\n\t\t\t\t\tworkspacePath: claudeOptions.addDir,\n\t\t\t\t\toneShot,\n\t\t\t\t})\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tlogger.error('Failed to launch Claude for workflow', { error, options })\n\t\t\tthrow error\n\t\t}\n\t}\n\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAmBO,IAAM,gBAAN,MAAoB;AAAA,EAK1B,YAAY,iBAAyC,iBAAmC;AACvF,SAAK,kBAAkB,mBAAmB,IAAI,sBAAsB;AACpE,SAAK,kBAAkB,mBAAmB,IAAI,gBAAgB;AAAA,EAC/D;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,cAAgC;AACrC,WAAO,gBAAgB;AAAA,EACxB;AAAA;AAAA;AAAA;AAAA,EAKQ,6BACP,MACqC;AAzCvC;AA2CE,SAAI,UAAK,aAAL,mBAAe,WAAW;AAC7B,YAAM,iBACL,SAAS,UACN,KAAK,SAAS,UAAU,QACxB,SAAS,OACR,KAAK,SAAS,UAAU,KACxB,KAAK,SAAS,UAAU;AAE7B,UAAI,iDAAgB,gBAAgB;AACnC,eAAO,eAAe;AAAA,MACvB;AAAA,IACD;AAGA,QAAI,SAAS,SAAS;AACrB,aAAO;AAAA,IACR;AAEA,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,kBAAkB,SAAwD;AAC/E,UAAM,EAAE,MAAM,aAAa,UAAU,OAAO,eAAe,MAAM,WAAW,OAAO,YAAY,UAAU,WAAW,cAAc,eAAe,IAAI;AAErJ,QAAI;AAGH,WAAK,aAAa,MAAM,KAAK,gBAAgB,aAAa;AAG1D,YAAM,YAA+B;AAAA,QACpC,gBAAgB;AAAA,MACjB;AAEA,UAAI,gBAAgB,QAAW;AAC9B,kBAAU,eAAe;AAAA,MAC1B;AAEA,UAAI,aAAa,QAAW;AAC3B,kBAAU,YAAY;AAAA,MACvB;AAEA,UAAI,UAAU,QAAW;AACxB,YAAI,SAAS,SAAS;AACrB,oBAAU,cAAc;AAAA,QACzB,WAAW,SAAS,MAAM;AACzB,oBAAU,WAAW;AAAA,QACtB;AAAA,MACD;AAEA,UAAI,SAAS,QAAW;AACvB,kBAAU,OAAO;AAAA,MAClB;AAGA,YAAM,SAAS,MAAM,KAAK,gBAAgB,UAAU,MAAM,SAAS;AAGnE,YAAM,iBAAiB,KAAK,6BAA6B,IAAI;AAG7D,UAAI,mBAAmB,qBAAqB;AAC3C,eAAO;AAAA,UACN;AAAA,QAED;AAAA,MACD;AAGA,YAAM,gBAAkC;AAAA,QACvC,QAAQ;AAAA,QACR;AAAA,MACD;AAGA,UAAI,mBAAmB,UAAa,mBAAmB,WAAW;AACjE,sBAAc,iBAAiB;AAAA,MAChC;AAGA,UAAI,eAAe,QAAW;AAC7B,sBAAc,aAAa;AAAA,MAC5B;AAGA,UAAI,SAAS,QAAW;AACvB,sBAAc,OAAO;AAAA,MACtB;AAGA,UAAI,iBAAiB,QAAW;AAC/B,sBAAc,eAAe;AAAA,MAC9B;AAGA,UAAI,mBAAmB,QAAW;AACjC,sBAAc,iBAAiB;AAAA,MAChC;AAEA,aAAO,MAAM,iCAAiC;AAAA,QAC7C;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACD,CAAC;AAGD,UAAI,UAAU;AAEb,eAAO,MAAM,aAAa,QAAQ,aAAa;AAAA,MAChD,OAAO;AAGN,YAAI,CAAC,cAAc,QAAQ;AAC1B,gBAAM,IAAI,MAAM,wDAAwD;AAAA,QACzE;AAEA,eAAO,MAAM,gCAAgC,QAAQ;AAAA,UACpD,GAAG;AAAA,UACH,eAAe,cAAc;AAAA,UAC7B;AAAA,QACD,CAAC;AAAA,MACF;AAAA,IACD,SAAS,OAAO;AACf,aAAO,MAAM,wCAAwC,EAAE,OAAO,QAAQ,CAAC;AACvE,YAAM;AAAA,IACP;AAAA,EACD;AAED;","names":[]}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
logger_default
|
|
4
|
-
} from "./chunk-
|
|
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-
|
|
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,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
} from "./chunk-
|
|
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-
|
|
18
|
+
const { generateBranchName } = await import("./claude-ACVXNB6N.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
|
-
|
|
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-
|
|
55
|
+
//# sourceMappingURL=chunk-QIUJPPJQ.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,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
detectPackageManager
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-VBFDVGAE.js";
|
|
5
5
|
import {
|
|
6
6
|
logger
|
|
7
|
-
} from "./chunk-
|
|
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-
|
|
48
|
+
//# sourceMappingURL=chunk-QRBOPFAA.js.map
|
|
@@ -1,15 +1,34 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
openTerminalWindow
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-VAYGNQTE.js";
|
|
5
|
+
import {
|
|
6
|
+
getLogger
|
|
7
|
+
} from "./chunk-6UIGZD2N.js";
|
|
5
8
|
import {
|
|
6
9
|
logger
|
|
7
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-UYVWLISQ.js";
|
|
8
11
|
|
|
9
12
|
// src/utils/claude.ts
|
|
10
13
|
import { execa } from "execa";
|
|
11
14
|
import { existsSync } from "fs";
|
|
12
15
|
import { join } from "path";
|
|
16
|
+
import { createHash } from "crypto";
|
|
17
|
+
function generateDeterministicSessionId(worktreePath) {
|
|
18
|
+
const URL_NAMESPACE = "6ba7b811-9dad-11d1-80b4-00c04fd430c8";
|
|
19
|
+
const hash = createHash("sha1");
|
|
20
|
+
const namespaceBytes = Buffer.from(URL_NAMESPACE.replace(/-/g, ""), "hex");
|
|
21
|
+
hash.update(namespaceBytes);
|
|
22
|
+
hash.update(worktreePath);
|
|
23
|
+
const digest = hash.digest();
|
|
24
|
+
const bytes = Array.from(digest.subarray(0, 16));
|
|
25
|
+
const byte6 = bytes[6] ?? 0;
|
|
26
|
+
bytes[6] = byte6 & 15 | 80;
|
|
27
|
+
const byte8 = bytes[8] ?? 0;
|
|
28
|
+
bytes[8] = byte8 & 63 | 128;
|
|
29
|
+
const hex = Buffer.from(bytes).toString("hex");
|
|
30
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`;
|
|
31
|
+
}
|
|
13
32
|
async function detectClaudeCli() {
|
|
14
33
|
try {
|
|
15
34
|
await execa("command", ["-v", "claude"], {
|
|
@@ -53,7 +72,8 @@ function parseJsonStreamOutput(output) {
|
|
|
53
72
|
}
|
|
54
73
|
}
|
|
55
74
|
async function launchClaude(prompt, options = {}) {
|
|
56
|
-
const { model, permissionMode, addDir, headless = false, appendSystemPrompt, mcpConfig, allowedTools, disallowedTools, agents } = options;
|
|
75
|
+
const { model, permissionMode, addDir, headless = false, appendSystemPrompt, mcpConfig, allowedTools, disallowedTools, agents, sessionId } = options;
|
|
76
|
+
const log = getLogger();
|
|
57
77
|
const args = [];
|
|
58
78
|
if (headless) {
|
|
59
79
|
args.push("-p");
|
|
@@ -87,6 +107,9 @@ async function launchClaude(prompt, options = {}) {
|
|
|
87
107
|
if (agents) {
|
|
88
108
|
args.push("--agents", JSON.stringify(agents));
|
|
89
109
|
}
|
|
110
|
+
if (sessionId) {
|
|
111
|
+
args.push("--session-id", sessionId);
|
|
112
|
+
}
|
|
90
113
|
try {
|
|
91
114
|
if (headless) {
|
|
92
115
|
const isDebugMode = logger.isDebugEnabled();
|
|
@@ -111,13 +134,13 @@ async function launchClaude(prompt, options = {}) {
|
|
|
111
134
|
const text = chunk.toString();
|
|
112
135
|
outputBuffer += text;
|
|
113
136
|
if (isDebugMode) {
|
|
114
|
-
|
|
137
|
+
log.stdout.write(text);
|
|
115
138
|
} else {
|
|
116
139
|
if (isFirstProgress) {
|
|
117
|
-
|
|
140
|
+
log.stdout.write("\u{1F916} .");
|
|
118
141
|
isFirstProgress = false;
|
|
119
142
|
} else {
|
|
120
|
-
|
|
143
|
+
log.stdout.write(".");
|
|
121
144
|
}
|
|
122
145
|
}
|
|
123
146
|
});
|
|
@@ -126,36 +149,137 @@ async function launchClaude(prompt, options = {}) {
|
|
|
126
149
|
if (isStreaming) {
|
|
127
150
|
const rawOutput = outputBuffer.trim();
|
|
128
151
|
if (!isDebugMode) {
|
|
129
|
-
|
|
152
|
+
log.stdout.write("\n");
|
|
130
153
|
}
|
|
131
154
|
return isJsonStreamFormat ? parseJsonStreamOutput(rawOutput) : rawOutput;
|
|
132
155
|
} else {
|
|
133
156
|
if (isDebugMode) {
|
|
134
|
-
|
|
157
|
+
log.stdout.write(result.stdout);
|
|
135
158
|
if (result.stdout && !result.stdout.endsWith("\n")) {
|
|
136
|
-
|
|
159
|
+
log.stdout.write("\n");
|
|
137
160
|
}
|
|
138
161
|
} else {
|
|
139
|
-
|
|
140
|
-
|
|
162
|
+
log.stdout.write("\u{1F916} .");
|
|
163
|
+
log.stdout.write("\n");
|
|
141
164
|
}
|
|
142
165
|
const rawOutput = result.stdout.trim();
|
|
143
166
|
return isJsonStreamFormat ? parseJsonStreamOutput(rawOutput) : rawOutput;
|
|
144
167
|
}
|
|
145
168
|
} else {
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
169
|
+
try {
|
|
170
|
+
await execa("claude", [...args, "--", prompt], {
|
|
171
|
+
...addDir && { cwd: addDir },
|
|
172
|
+
stdio: ["inherit", "inherit", "pipe"],
|
|
173
|
+
// Capture stderr to detect session conflicts
|
|
174
|
+
timeout: 0,
|
|
175
|
+
// Disable timeout
|
|
176
|
+
verbose: logger.isDebugEnabled()
|
|
177
|
+
});
|
|
178
|
+
return;
|
|
179
|
+
} catch (interactiveError) {
|
|
180
|
+
const interactiveExecaError = interactiveError;
|
|
181
|
+
const interactiveErrorMessage = interactiveExecaError.stderr ?? interactiveExecaError.message ?? "";
|
|
182
|
+
const sessionMatch = interactiveErrorMessage.match(/Session ID ([0-9a-f-]+) is already in use/i);
|
|
183
|
+
const conflictSessionId = sessionMatch == null ? void 0 : sessionMatch[1];
|
|
184
|
+
if (sessionMatch && sessionId && conflictSessionId) {
|
|
185
|
+
log.debug(`Session ID ${conflictSessionId} already in use, retrying with --resume`);
|
|
186
|
+
const resumeArgs = args.filter((arg, idx) => {
|
|
187
|
+
if (arg === "--session-id") return false;
|
|
188
|
+
if (idx > 0 && args[idx - 1] === "--session-id") return false;
|
|
189
|
+
return true;
|
|
190
|
+
});
|
|
191
|
+
resumeArgs.push("--resume", conflictSessionId);
|
|
192
|
+
await execa("claude", resumeArgs, {
|
|
193
|
+
...addDir && { cwd: addDir },
|
|
194
|
+
stdio: "inherit",
|
|
195
|
+
timeout: 0,
|
|
196
|
+
verbose: logger.isDebugEnabled()
|
|
197
|
+
});
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
throw interactiveError;
|
|
201
|
+
}
|
|
155
202
|
}
|
|
156
203
|
} catch (error) {
|
|
157
204
|
const execaError = error;
|
|
158
205
|
const errorMessage = execaError.stderr ?? execaError.message ?? "Unknown Claude CLI error";
|
|
206
|
+
const sessionInUseMatch = errorMessage.match(/Session ID ([0-9a-f-]+) is already in use/i);
|
|
207
|
+
const extractedSessionId = sessionInUseMatch == null ? void 0 : sessionInUseMatch[1];
|
|
208
|
+
if (sessionInUseMatch && sessionId && extractedSessionId) {
|
|
209
|
+
log.debug(`Session ID ${extractedSessionId} already in use, retrying with --resume`);
|
|
210
|
+
const resumeArgs = args.filter((arg, idx) => {
|
|
211
|
+
if (arg === "--session-id") return false;
|
|
212
|
+
if (idx > 0 && args[idx - 1] === "--session-id") return false;
|
|
213
|
+
return true;
|
|
214
|
+
});
|
|
215
|
+
resumeArgs.push("--resume", extractedSessionId);
|
|
216
|
+
try {
|
|
217
|
+
if (headless) {
|
|
218
|
+
const isDebugMode = logger.isDebugEnabled();
|
|
219
|
+
const execaOptions = {
|
|
220
|
+
input: prompt,
|
|
221
|
+
timeout: 0,
|
|
222
|
+
...addDir && { cwd: addDir },
|
|
223
|
+
verbose: isDebugMode,
|
|
224
|
+
...isDebugMode && { stdio: ["pipe", "pipe", "pipe"] }
|
|
225
|
+
};
|
|
226
|
+
const subprocess = execa("claude", resumeArgs, execaOptions);
|
|
227
|
+
const isJsonStreamFormat = resumeArgs.includes("--output-format") && resumeArgs.includes("stream-json");
|
|
228
|
+
let outputBuffer = "";
|
|
229
|
+
let isStreaming = false;
|
|
230
|
+
let isFirstProgress = true;
|
|
231
|
+
if (subprocess.stdout && typeof subprocess.stdout.on === "function") {
|
|
232
|
+
isStreaming = true;
|
|
233
|
+
subprocess.stdout.on("data", (chunk) => {
|
|
234
|
+
const text = chunk.toString();
|
|
235
|
+
outputBuffer += text;
|
|
236
|
+
if (isDebugMode) {
|
|
237
|
+
log.stdout.write(text);
|
|
238
|
+
} else {
|
|
239
|
+
if (isFirstProgress) {
|
|
240
|
+
log.stdout.write("\u{1F916} .");
|
|
241
|
+
isFirstProgress = false;
|
|
242
|
+
} else {
|
|
243
|
+
log.stdout.write(".");
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
const result = await subprocess;
|
|
249
|
+
if (isStreaming) {
|
|
250
|
+
const rawOutput = outputBuffer.trim();
|
|
251
|
+
if (!isDebugMode) {
|
|
252
|
+
log.stdout.write("\n");
|
|
253
|
+
}
|
|
254
|
+
return isJsonStreamFormat ? parseJsonStreamOutput(rawOutput) : rawOutput;
|
|
255
|
+
} else {
|
|
256
|
+
if (isDebugMode) {
|
|
257
|
+
log.stdout.write(result.stdout);
|
|
258
|
+
if (result.stdout && !result.stdout.endsWith("\n")) {
|
|
259
|
+
log.stdout.write("\n");
|
|
260
|
+
}
|
|
261
|
+
} else {
|
|
262
|
+
log.stdout.write("\u{1F916} .");
|
|
263
|
+
log.stdout.write("\n");
|
|
264
|
+
}
|
|
265
|
+
const rawOutput = result.stdout.trim();
|
|
266
|
+
return isJsonStreamFormat ? parseJsonStreamOutput(rawOutput) : rawOutput;
|
|
267
|
+
}
|
|
268
|
+
} else {
|
|
269
|
+
await execa("claude", resumeArgs, {
|
|
270
|
+
...addDir && { cwd: addDir },
|
|
271
|
+
stdio: "inherit",
|
|
272
|
+
timeout: 0,
|
|
273
|
+
verbose: logger.isDebugEnabled()
|
|
274
|
+
});
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
} catch (retryError) {
|
|
278
|
+
const retryExecaError = retryError;
|
|
279
|
+
const retryErrorMessage = retryExecaError.stderr ?? retryExecaError.message ?? "Unknown Claude CLI error";
|
|
280
|
+
throw new Error(`Claude CLI error: ${retryErrorMessage}`);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
159
283
|
throw new Error(`Claude CLI error: ${errorMessage}`);
|
|
160
284
|
}
|
|
161
285
|
}
|
|
@@ -177,7 +301,7 @@ async function launchClaudeInNewTerminalWindow(_prompt, options) {
|
|
|
177
301
|
let backgroundColor;
|
|
178
302
|
if (branchName) {
|
|
179
303
|
try {
|
|
180
|
-
const { generateColorFromBranchName } = await import("./color-
|
|
304
|
+
const { generateColorFromBranchName } = await import("./color-ZPIIUADB.js");
|
|
181
305
|
const colorData = generateColorFromBranchName(branchName);
|
|
182
306
|
backgroundColor = colorData.rgb;
|
|
183
307
|
} catch (error) {
|
|
@@ -243,10 +367,11 @@ function isValidBranchName(name, issueNumber) {
|
|
|
243
367
|
}
|
|
244
368
|
|
|
245
369
|
export {
|
|
370
|
+
generateDeterministicSessionId,
|
|
246
371
|
detectClaudeCli,
|
|
247
372
|
getClaudeVersion,
|
|
248
373
|
launchClaude,
|
|
249
374
|
launchClaudeInNewTerminalWindow,
|
|
250
375
|
generateBranchName
|
|
251
376
|
};
|
|
252
|
-
//# sourceMappingURL=chunk-
|
|
377
|
+
//# sourceMappingURL=chunk-RUC7OULH.js.map
|