hub-launch 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (331) hide show
  1. package/CHANGELOG.md +144 -0
  2. package/README.md +195 -0
  3. package/bin/hublaunch +20 -0
  4. package/dist/commands/checkout.d.ts +7 -0
  5. package/dist/commands/checkout.d.ts.map +1 -0
  6. package/dist/commands/checkout.js +164 -0
  7. package/dist/commands/checkout.js.map +1 -0
  8. package/dist/commands/clean.d.ts +7 -0
  9. package/dist/commands/clean.d.ts.map +1 -0
  10. package/dist/commands/clean.js +206 -0
  11. package/dist/commands/clean.js.map +1 -0
  12. package/dist/commands/close.d.ts +7 -0
  13. package/dist/commands/close.d.ts.map +1 -0
  14. package/dist/commands/close.js +109 -0
  15. package/dist/commands/close.js.map +1 -0
  16. package/dist/commands/create.d.ts +7 -0
  17. package/dist/commands/create.d.ts.map +1 -0
  18. package/dist/commands/create.js +367 -0
  19. package/dist/commands/create.js.map +1 -0
  20. package/dist/commands/diff.d.ts +7 -0
  21. package/dist/commands/diff.d.ts.map +1 -0
  22. package/dist/commands/diff.js +98 -0
  23. package/dist/commands/diff.js.map +1 -0
  24. package/dist/commands/execute.d.ts +7 -0
  25. package/dist/commands/execute.d.ts.map +1 -0
  26. package/dist/commands/execute.js +376 -0
  27. package/dist/commands/execute.js.map +1 -0
  28. package/dist/commands/init.d.ts +8 -0
  29. package/dist/commands/init.d.ts.map +1 -0
  30. package/dist/commands/init.js +1243 -0
  31. package/dist/commands/init.js.map +1 -0
  32. package/dist/commands/interactive.d.ts +12 -0
  33. package/dist/commands/interactive.d.ts.map +1 -0
  34. package/dist/commands/interactive.js +1071 -0
  35. package/dist/commands/interactive.js.map +1 -0
  36. package/dist/commands/launch.d.ts +16 -0
  37. package/dist/commands/launch.d.ts.map +1 -0
  38. package/dist/commands/launch.js +684 -0
  39. package/dist/commands/launch.js.map +1 -0
  40. package/dist/commands/login.d.ts +40 -0
  41. package/dist/commands/login.d.ts.map +1 -0
  42. package/dist/commands/login.js +469 -0
  43. package/dist/commands/login.js.map +1 -0
  44. package/dist/commands/logs.d.ts +22 -0
  45. package/dist/commands/logs.d.ts.map +1 -0
  46. package/dist/commands/logs.js +105 -0
  47. package/dist/commands/logs.js.map +1 -0
  48. package/dist/commands/merge.d.ts +7 -0
  49. package/dist/commands/merge.d.ts.map +1 -0
  50. package/dist/commands/merge.js +240 -0
  51. package/dist/commands/merge.js.map +1 -0
  52. package/dist/commands/preview.d.ts +7 -0
  53. package/dist/commands/preview.d.ts.map +1 -0
  54. package/dist/commands/preview.js +160 -0
  55. package/dist/commands/preview.js.map +1 -0
  56. package/dist/commands/project.d.ts +18 -0
  57. package/dist/commands/project.d.ts.map +1 -0
  58. package/dist/commands/project.js +352 -0
  59. package/dist/commands/project.js.map +1 -0
  60. package/dist/commands/respond.d.ts +7 -0
  61. package/dist/commands/respond.d.ts.map +1 -0
  62. package/dist/commands/respond.js +112 -0
  63. package/dist/commands/respond.js.map +1 -0
  64. package/dist/commands/setPriority.d.ts +7 -0
  65. package/dist/commands/setPriority.d.ts.map +1 -0
  66. package/dist/commands/setPriority.js +112 -0
  67. package/dist/commands/setPriority.js.map +1 -0
  68. package/dist/commands/track.d.ts +7 -0
  69. package/dist/commands/track.d.ts.map +1 -0
  70. package/dist/commands/track.js +140 -0
  71. package/dist/commands/track.js.map +1 -0
  72. package/dist/commands/upload.d.ts +21 -0
  73. package/dist/commands/upload.d.ts.map +1 -0
  74. package/dist/commands/upload.js +145 -0
  75. package/dist/commands/upload.js.map +1 -0
  76. package/dist/commands/view.d.ts +7 -0
  77. package/dist/commands/view.d.ts.map +1 -0
  78. package/dist/commands/view.js +168 -0
  79. package/dist/commands/view.js.map +1 -0
  80. package/dist/commands/watch.d.ts +7 -0
  81. package/dist/commands/watch.d.ts.map +1 -0
  82. package/dist/commands/watch.js +438 -0
  83. package/dist/commands/watch.js.map +1 -0
  84. package/dist/commands/worktree.d.ts +7 -0
  85. package/dist/commands/worktree.d.ts.map +1 -0
  86. package/dist/commands/worktree.js +122 -0
  87. package/dist/commands/worktree.js.map +1 -0
  88. package/dist/config/command-names.d.ts +16 -0
  89. package/dist/config/command-names.d.ts.map +1 -0
  90. package/dist/config/command-names.js +17 -0
  91. package/dist/config/command-names.js.map +1 -0
  92. package/dist/config/constants.d.ts +12 -0
  93. package/dist/config/constants.d.ts.map +1 -0
  94. package/dist/config/constants.js +12 -0
  95. package/dist/config/constants.js.map +1 -0
  96. package/dist/config/index.d.ts +47 -0
  97. package/dist/config/index.d.ts.map +1 -0
  98. package/dist/config/index.js +175 -0
  99. package/dist/config/index.js.map +1 -0
  100. package/dist/index.d.ts +3 -0
  101. package/dist/index.d.ts.map +1 -0
  102. package/dist/index.js +148 -0
  103. package/dist/index.js.map +1 -0
  104. package/dist/services/api/HulaApiClient.d.ts +170 -0
  105. package/dist/services/api/HulaApiClient.d.ts.map +1 -0
  106. package/dist/services/api/HulaApiClient.js +233 -0
  107. package/dist/services/api/HulaApiClient.js.map +1 -0
  108. package/dist/services/diff/DiffService.d.ts +44 -0
  109. package/dist/services/diff/DiffService.d.ts.map +1 -0
  110. package/dist/services/diff/DiffService.js +126 -0
  111. package/dist/services/diff/DiffService.js.map +1 -0
  112. package/dist/services/editor/EditorService.d.ts +53 -0
  113. package/dist/services/editor/EditorService.d.ts.map +1 -0
  114. package/dist/services/editor/EditorService.js +161 -0
  115. package/dist/services/editor/EditorService.js.map +1 -0
  116. package/dist/services/git/GitService.d.ts +124 -0
  117. package/dist/services/git/GitService.d.ts.map +1 -0
  118. package/dist/services/git/GitService.js +423 -0
  119. package/dist/services/git/GitService.js.map +1 -0
  120. package/dist/services/git/WorktreeService.d.ts +144 -0
  121. package/dist/services/git/WorktreeService.d.ts.map +1 -0
  122. package/dist/services/git/WorktreeService.js +505 -0
  123. package/dist/services/git/WorktreeService.js.map +1 -0
  124. package/dist/services/github/ActionsService.d.ts +66 -0
  125. package/dist/services/github/ActionsService.d.ts.map +1 -0
  126. package/dist/services/github/ActionsService.js +114 -0
  127. package/dist/services/github/ActionsService.js.map +1 -0
  128. package/dist/services/github/AssetUploadService.d.ts +103 -0
  129. package/dist/services/github/AssetUploadService.d.ts.map +1 -0
  130. package/dist/services/github/AssetUploadService.js +299 -0
  131. package/dist/services/github/AssetUploadService.js.map +1 -0
  132. package/dist/services/github/BatchCopilotDetectionService.d.ts +58 -0
  133. package/dist/services/github/BatchCopilotDetectionService.d.ts.map +1 -0
  134. package/dist/services/github/BatchCopilotDetectionService.js +321 -0
  135. package/dist/services/github/BatchCopilotDetectionService.js.map +1 -0
  136. package/dist/services/github/CopilotDetectionService.d.ts +40 -0
  137. package/dist/services/github/CopilotDetectionService.d.ts.map +1 -0
  138. package/dist/services/github/CopilotDetectionService.js +280 -0
  139. package/dist/services/github/CopilotDetectionService.js.map +1 -0
  140. package/dist/services/github/CopilotService.d.ts +27 -0
  141. package/dist/services/github/CopilotService.d.ts.map +1 -0
  142. package/dist/services/github/CopilotService.js +249 -0
  143. package/dist/services/github/CopilotService.js.map +1 -0
  144. package/dist/services/github/DeploymentDetectionService.d.ts +38 -0
  145. package/dist/services/github/DeploymentDetectionService.d.ts.map +1 -0
  146. package/dist/services/github/DeploymentDetectionService.js +104 -0
  147. package/dist/services/github/DeploymentDetectionService.js.map +1 -0
  148. package/dist/services/github/GithubAppService.d.ts +40 -0
  149. package/dist/services/github/GithubAppService.d.ts.map +1 -0
  150. package/dist/services/github/GithubAppService.js +187 -0
  151. package/dist/services/github/GithubAppService.js.map +1 -0
  152. package/dist/services/github/IssueService.d.ts +65 -0
  153. package/dist/services/github/IssueService.d.ts.map +1 -0
  154. package/dist/services/github/IssueService.js +231 -0
  155. package/dist/services/github/IssueService.js.map +1 -0
  156. package/dist/services/github/LabelService.d.ts +36 -0
  157. package/dist/services/github/LabelService.d.ts.map +1 -0
  158. package/dist/services/github/LabelService.js +122 -0
  159. package/dist/services/github/LabelService.js.map +1 -0
  160. package/dist/services/github/PRService.d.ts +77 -0
  161. package/dist/services/github/PRService.d.ts.map +1 -0
  162. package/dist/services/github/PRService.js +287 -0
  163. package/dist/services/github/PRService.js.map +1 -0
  164. package/dist/services/github/ProjectService.d.ts +144 -0
  165. package/dist/services/github/ProjectService.d.ts.map +1 -0
  166. package/dist/services/github/ProjectService.js +804 -0
  167. package/dist/services/github/ProjectService.js.map +1 -0
  168. package/dist/services/hooks/HookExecutor.d.ts +66 -0
  169. package/dist/services/hooks/HookExecutor.d.ts.map +1 -0
  170. package/dist/services/hooks/HookExecutor.js +227 -0
  171. package/dist/services/hooks/HookExecutor.js.map +1 -0
  172. package/dist/services/hooks/index.d.ts +2 -0
  173. package/dist/services/hooks/index.d.ts.map +1 -0
  174. package/dist/services/hooks/index.js +2 -0
  175. package/dist/services/hooks/index.js.map +1 -0
  176. package/dist/services/logs/LogFormatterService.d.ts +17 -0
  177. package/dist/services/logs/LogFormatterService.d.ts.map +1 -0
  178. package/dist/services/logs/LogFormatterService.js +35 -0
  179. package/dist/services/logs/LogFormatterService.js.map +1 -0
  180. package/dist/services/logs/TempFileService.d.ts +27 -0
  181. package/dist/services/logs/TempFileService.d.ts.map +1 -0
  182. package/dist/services/logs/TempFileService.js +90 -0
  183. package/dist/services/logs/TempFileService.js.map +1 -0
  184. package/dist/services/logs/index.d.ts +3 -0
  185. package/dist/services/logs/index.d.ts.map +1 -0
  186. package/dist/services/logs/index.js +4 -0
  187. package/dist/services/logs/index.js.map +1 -0
  188. package/dist/services/plan/PlanService.d.ts +70 -0
  189. package/dist/services/plan/PlanService.d.ts.map +1 -0
  190. package/dist/services/plan/PlanService.js +211 -0
  191. package/dist/services/plan/PlanService.js.map +1 -0
  192. package/dist/services/polling/GitHubDirectPollingAdapter.d.ts +16 -0
  193. package/dist/services/polling/GitHubDirectPollingAdapter.d.ts.map +1 -0
  194. package/dist/services/polling/GitHubDirectPollingAdapter.js +87 -0
  195. package/dist/services/polling/GitHubDirectPollingAdapter.js.map +1 -0
  196. package/dist/services/polling/HulaServerPollingAdapter.d.ts +23 -0
  197. package/dist/services/polling/HulaServerPollingAdapter.d.ts.map +1 -0
  198. package/dist/services/polling/HulaServerPollingAdapter.js +78 -0
  199. package/dist/services/polling/HulaServerPollingAdapter.js.map +1 -0
  200. package/dist/services/polling/IStatusPollingAdapter.d.ts +29 -0
  201. package/dist/services/polling/IStatusPollingAdapter.d.ts.map +1 -0
  202. package/dist/services/polling/IStatusPollingAdapter.js +2 -0
  203. package/dist/services/polling/IStatusPollingAdapter.js.map +1 -0
  204. package/dist/services/polling/StatusPollingAdapterFactory.d.ts +14 -0
  205. package/dist/services/polling/StatusPollingAdapterFactory.d.ts.map +1 -0
  206. package/dist/services/polling/StatusPollingAdapterFactory.js +41 -0
  207. package/dist/services/polling/StatusPollingAdapterFactory.js.map +1 -0
  208. package/dist/services/polling/index.d.ts +5 -0
  209. package/dist/services/polling/index.d.ts.map +1 -0
  210. package/dist/services/polling/index.js +5 -0
  211. package/dist/services/polling/index.js.map +1 -0
  212. package/dist/services/preview/PreviewService.d.ts +86 -0
  213. package/dist/services/preview/PreviewService.d.ts.map +1 -0
  214. package/dist/services/preview/PreviewService.js +439 -0
  215. package/dist/services/preview/PreviewService.js.map +1 -0
  216. package/dist/services/tracking/ProjectTrackingAdapter.d.ts +41 -0
  217. package/dist/services/tracking/ProjectTrackingAdapter.d.ts.map +1 -0
  218. package/dist/services/tracking/ProjectTrackingAdapter.js +230 -0
  219. package/dist/services/tracking/ProjectTrackingAdapter.js.map +1 -0
  220. package/dist/services/tracking/RemoteTrackingAdapter.d.ts +62 -0
  221. package/dist/services/tracking/RemoteTrackingAdapter.d.ts.map +1 -0
  222. package/dist/services/tracking/RemoteTrackingAdapter.js +242 -0
  223. package/dist/services/tracking/RemoteTrackingAdapter.js.map +1 -0
  224. package/dist/services/tracking/TrackingAdapterFactory.d.ts +25 -0
  225. package/dist/services/tracking/TrackingAdapterFactory.d.ts.map +1 -0
  226. package/dist/services/tracking/TrackingAdapterFactory.js +55 -0
  227. package/dist/services/tracking/TrackingAdapterFactory.js.map +1 -0
  228. package/dist/services/tracking/TrackingService.d.ts +104 -0
  229. package/dist/services/tracking/TrackingService.d.ts.map +1 -0
  230. package/dist/services/tracking/TrackingService.js +256 -0
  231. package/dist/services/tracking/TrackingService.js.map +1 -0
  232. package/dist/services/tracking/UntrackedIssuesService.d.ts +20 -0
  233. package/dist/services/tracking/UntrackedIssuesService.d.ts.map +1 -0
  234. package/dist/services/tracking/UntrackedIssuesService.js +148 -0
  235. package/dist/services/tracking/UntrackedIssuesService.js.map +1 -0
  236. package/dist/services/tracking/local-file-migration.d.ts +16 -0
  237. package/dist/services/tracking/local-file-migration.d.ts.map +1 -0
  238. package/dist/services/tracking/local-file-migration.js +86 -0
  239. package/dist/services/tracking/local-file-migration.js.map +1 -0
  240. package/dist/templates/planning-instructions.md +752 -0
  241. package/dist/templates/proceed-instructions.md +453 -0
  242. package/dist/templates/scripts/hula-fix-commit.sh +110 -0
  243. package/dist/templates/scripts/hula-fix-setup.sh +145 -0
  244. package/dist/templates/scripts/hula-launch-run.sh +89 -0
  245. package/dist/templates/scripts/hula-merge-local.sh +138 -0
  246. package/dist/templates/scripts/hula-merge-remote.sh +74 -0
  247. package/dist/templates/scripts/hula-read-config.sh +38 -0
  248. package/dist/templates/scripts/hula-verify-gather.sh +135 -0
  249. package/dist/templates/scripts/hula-verify-post.sh +84 -0
  250. package/dist/templates/skills/hula-confirm/SKILL.md +361 -0
  251. package/dist/templates/skills/hula-create/SKILL.md +211 -0
  252. package/dist/templates/skills/hula-fix/SKILL.md +224 -0
  253. package/dist/templates/skills/hula-launch/SKILL.md +159 -0
  254. package/dist/templates/skills/hula-log/SKILL.md +105 -0
  255. package/dist/templates/skills/hula-merge/SKILL.md +387 -0
  256. package/dist/templates/skills/hula-plan/SKILL.md +148 -0
  257. package/dist/templates/skills/hula-upload/SKILL.md +115 -0
  258. package/dist/templates/skills/hula-verify/SKILL.md +754 -0
  259. package/dist/types/common.schema.d.ts +46 -0
  260. package/dist/types/common.schema.d.ts.map +1 -0
  261. package/dist/types/common.schema.js +60 -0
  262. package/dist/types/common.schema.js.map +1 -0
  263. package/dist/types/config.schema.d.ts +443 -0
  264. package/dist/types/config.schema.d.ts.map +1 -0
  265. package/dist/types/config.schema.js +126 -0
  266. package/dist/types/config.schema.js.map +1 -0
  267. package/dist/types/index.d.ts +6 -0
  268. package/dist/types/index.d.ts.map +1 -0
  269. package/dist/types/index.js +7 -0
  270. package/dist/types/index.js.map +1 -0
  271. package/dist/types/issue.schema.d.ts +136 -0
  272. package/dist/types/issue.schema.d.ts.map +1 -0
  273. package/dist/types/issue.schema.js +49 -0
  274. package/dist/types/issue.schema.js.map +1 -0
  275. package/dist/types/pr.schema.d.ts +118 -0
  276. package/dist/types/pr.schema.d.ts.map +1 -0
  277. package/dist/types/pr.schema.js +40 -0
  278. package/dist/types/pr.schema.js.map +1 -0
  279. package/dist/types/tracking.schema.d.ts +340 -0
  280. package/dist/types/tracking.schema.d.ts.map +1 -0
  281. package/dist/types/tracking.schema.js +56 -0
  282. package/dist/types/tracking.schema.js.map +1 -0
  283. package/dist/utils/browser-launcher.d.ts +60 -0
  284. package/dist/utils/browser-launcher.d.ts.map +1 -0
  285. package/dist/utils/browser-launcher.js +263 -0
  286. package/dist/utils/browser-launcher.js.map +1 -0
  287. package/dist/utils/cache.d.ts +44 -0
  288. package/dist/utils/cache.d.ts.map +1 -0
  289. package/dist/utils/cache.js +94 -0
  290. package/dist/utils/cache.js.map +1 -0
  291. package/dist/utils/config-parser.d.ts +16 -0
  292. package/dist/utils/config-parser.d.ts.map +1 -0
  293. package/dist/utils/config-parser.js +210 -0
  294. package/dist/utils/config-parser.js.map +1 -0
  295. package/dist/utils/editor.d.ts +21 -0
  296. package/dist/utils/editor.d.ts.map +1 -0
  297. package/dist/utils/editor.js +213 -0
  298. package/dist/utils/editor.js.map +1 -0
  299. package/dist/utils/git-utils.d.ts +5 -0
  300. package/dist/utils/git-utils.d.ts.map +1 -0
  301. package/dist/utils/git-utils.js +18 -0
  302. package/dist/utils/git-utils.js.map +1 -0
  303. package/dist/utils/github-cli.d.ts +62 -0
  304. package/dist/utils/github-cli.d.ts.map +1 -0
  305. package/dist/utils/github-cli.js +492 -0
  306. package/dist/utils/github-cli.js.map +1 -0
  307. package/dist/utils/logger.d.ts +45 -0
  308. package/dist/utils/logger.d.ts.map +1 -0
  309. package/dist/utils/logger.js +118 -0
  310. package/dist/utils/logger.js.map +1 -0
  311. package/dist/utils/open-url.d.ts +19 -0
  312. package/dist/utils/open-url.d.ts.map +1 -0
  313. package/dist/utils/open-url.js +37 -0
  314. package/dist/utils/open-url.js.map +1 -0
  315. package/dist/utils/project-resolver.d.ts +13 -0
  316. package/dist/utils/project-resolver.d.ts.map +1 -0
  317. package/dist/utils/project-resolver.js +35 -0
  318. package/dist/utils/project-resolver.js.map +1 -0
  319. package/dist/utils/prompts.d.ts +80 -0
  320. package/dist/utils/prompts.d.ts.map +1 -0
  321. package/dist/utils/prompts.js +326 -0
  322. package/dist/utils/prompts.js.map +1 -0
  323. package/dist/utils/shell.d.ts +54 -0
  324. package/dist/utils/shell.d.ts.map +1 -0
  325. package/dist/utils/shell.js +152 -0
  326. package/dist/utils/shell.js.map +1 -0
  327. package/dist/utils/validators.d.ts +32 -0
  328. package/dist/utils/validators.d.ts.map +1 -0
  329. package/dist/utils/validators.js +64 -0
  330. package/dist/utils/validators.js.map +1 -0
  331. package/package.json +73 -0
@@ -0,0 +1,684 @@
1
+ import axios from 'axios';
2
+ import chalk from 'chalk';
3
+ import os from 'os';
4
+ import path from 'path';
5
+ import { logger } from '../utils/logger.js';
6
+ import { GitService } from '../services/git/GitService.js';
7
+ import { PlanService } from '../services/plan/PlanService.js';
8
+ import { HulaApiClient } from '../services/api/HulaApiClient.js';
9
+ import { TempFileService } from '../services/logs/TempFileService.js';
10
+ import { EditorService } from '../services/editor/EditorService.js';
11
+ import { executeUpload } from './upload.js';
12
+ import { loadGitHubToken } from './login.js';
13
+ import { resolveProject } from '../utils/project-resolver.js';
14
+ import { HULA_PROJECT_URL } from '../config/constants.js';
15
+ const DEFAULT_CONTAINER_RESOURCES = { cpu: 2, memory: 4, disk: 10 };
16
+ /**
17
+ * Launch command - Trigger a launch job on hula-project server
18
+ *
19
+ * Usage: hula launch <issueName> <planPath> [options]
20
+ * Usage: hula launch --show <name>
21
+ * Usage: hula launch --logs <name>
22
+ */
23
+ export function launchCommand(program, config) {
24
+ program
25
+ .command('launch [issueName] [planPath]')
26
+ .description('Trigger a launch job on hula-project server')
27
+ .option('--url <url>', 'Override hula-project server URL')
28
+ .option('--api-key <key>', 'Override API key')
29
+ .option('--duration <hours>', 'Hours to keep Mac awake', parseInt)
30
+ .option('--launch <path>', 'Path to launch.md file (relative to project)')
31
+ .option('--worktree <path>', 'Base directory for worktree')
32
+ .option('--no-worktree', 'Run in current directory (no isolation)')
33
+ .option('--resume <step>', 'Resume from step N (1-9)', parseInt)
34
+ .option('--fix <instructions>', 'Fix instructions (use with --resume)')
35
+ .option('--regression', 'Force regression tests to run')
36
+ .option('--verbose', 'Show full output')
37
+ .option('--handoff <username>', 'Assign the created issue to this GitHub user')
38
+ .option('--anthropic-key <key>', 'Anthropic API key for Claude Code (ephemeral)')
39
+ .option('--daytona-key <key>', 'Daytona API key (required by hula-server)')
40
+ .option('--daytona-tier <n>', 'Daytona org tier (1–4) for capacity calculation', parseInt)
41
+ .option('--container-cpu <n>', 'vCPU cores for the sandbox (1–32)', parseInt)
42
+ .option('--container-memory <n>', 'GiB RAM for the sandbox (1–128)', parseInt)
43
+ .option('--container-disk <n>', 'GiB disk for the sandbox (5–200)', parseInt)
44
+ .option('--update-notification-url <url>', 'Webhook URL to POST a notification to on every terminal task transition')
45
+ .option('--show <name>', 'Show job status for the specified issue')
46
+ .option('--logs <name>', 'Show job logs for the specified issue')
47
+ .option('--lines <n>', 'Number of log lines to show (default: 100)', parseInt)
48
+ .option('--type <type>', 'Log type to fetch: log or output (default: log)')
49
+ .option('--editor', 'Open logs in editor instead of printing to stdout')
50
+ .action(async (issueName, planPath, options) => {
51
+ try {
52
+ // Handle --show flag
53
+ if (options.show) {
54
+ await showJobStatus(config, options.show, options);
55
+ return;
56
+ }
57
+ // Handle --logs flag
58
+ if (options.logs) {
59
+ await showJobLogs(config, options.logs, options);
60
+ return;
61
+ }
62
+ // Regular launch requires issueName and planPath
63
+ if (!issueName || !planPath) {
64
+ logger.error('Usage: hula launch <issueName> <planPath>');
65
+ logger.info('Or use --show <name> or --logs <name> to view job details');
66
+ process.exit(1);
67
+ }
68
+ await executeLaunch(config, issueName, planPath, options);
69
+ }
70
+ catch (error) {
71
+ logger.error('Failed to trigger launch:');
72
+ logger.error(error instanceof Error ? error.message : String(error));
73
+ process.exit(1);
74
+ }
75
+ });
76
+ }
77
+ async function validateLaunchCredentials(anthropicApiKey, githubToken) {
78
+ console.log('Checking your credentials before launch...\n');
79
+ // --- Anthropic ---
80
+ const anthropicAuthHeaders = {
81
+ Authorization: `Bearer ${anthropicApiKey}`,
82
+ 'anthropic-version': '2023-06-01',
83
+ };
84
+ const anthropicPromise = axios
85
+ .get('https://api.anthropic.com/v1/models', {
86
+ headers: anthropicAuthHeaders,
87
+ timeout: 8000,
88
+ })
89
+ .then(() => ({ ok: true }))
90
+ .catch((err) => {
91
+ if (axios.isAxiosError(err) &&
92
+ err.response &&
93
+ err.response.status < 500) {
94
+ // Any 4xx → token is rejected (invalid, expired, or forbidden)
95
+ return {
96
+ ok: false,
97
+ hard: true,
98
+ message: 'Anthropic OAuth token is invalid or expired.',
99
+ hint: 'Generate a new OAuth token at https://claude.ai/settings (requires a paid Claude.ai plan — Pro or Max).',
100
+ };
101
+ }
102
+ // Network error / timeout / 5xx — non-blocking warning
103
+ return {
104
+ ok: false,
105
+ hard: false,
106
+ message: 'Could not reach the Anthropic API to verify your OAuth token (network issue).',
107
+ hint: 'Proceeding anyway — the server will validate the token on startup.',
108
+ };
109
+ });
110
+ // --- GitHub ---
111
+ const githubPromise = githubToken
112
+ ? axios
113
+ .get('https://api.github.com/user', {
114
+ headers: { Authorization: `Bearer ${githubToken}` },
115
+ timeout: 8000,
116
+ })
117
+ .then(() => ({ ok: true }))
118
+ .catch((err) => {
119
+ if (axios.isAxiosError(err) &&
120
+ err.response?.status === 401) {
121
+ return {
122
+ ok: false,
123
+ hard: true,
124
+ message: 'GitHub token is invalid or expired.',
125
+ hint: "Run 'hula login' to refresh your GitHub authentication.",
126
+ };
127
+ }
128
+ return {
129
+ ok: false,
130
+ hard: false,
131
+ message: 'Could not reach the GitHub API to verify your token (network issue).',
132
+ hint: 'Proceeding anyway — the server will validate the token.',
133
+ };
134
+ })
135
+ : Promise.resolve({
136
+ ok: false,
137
+ hard: true,
138
+ message: 'No GitHub token found.',
139
+ hint: "Run 'hula login' to authenticate with GitHub first.",
140
+ });
141
+ const [anthropicResult, githubResult] = await Promise.allSettled([
142
+ anthropicPromise,
143
+ githubPromise,
144
+ ]);
145
+ let hasHardFailure = false;
146
+ // Print Anthropic result
147
+ const result = anthropicResult.status === 'fulfilled'
148
+ ? anthropicResult.value
149
+ : {
150
+ ok: false,
151
+ hard: false,
152
+ message: 'Unexpected error during Anthropic validation.',
153
+ hint: 'Proceeding anyway.',
154
+ };
155
+ if (result.ok) {
156
+ console.log(` ${chalk.green('✓')} Anthropic OAuth token is valid — Claude Code is ready to go 🤖`);
157
+ }
158
+ else if (result.hard) {
159
+ console.error(` ${chalk.red('✗')} ${result.message}`);
160
+ console.error(` ${chalk.red('→')} ${result.hint}`);
161
+ hasHardFailure = true;
162
+ }
163
+ else {
164
+ console.warn(` ${chalk.yellow('⚠')} ${result.message}`);
165
+ console.warn(` ${chalk.yellow('→')} ${result.hint}`);
166
+ }
167
+ // Print GitHub result
168
+ const ghResult = githubResult.status === 'fulfilled'
169
+ ? githubResult.value
170
+ : {
171
+ ok: false,
172
+ hard: false,
173
+ message: 'Unexpected error during GitHub validation.',
174
+ hint: 'Proceeding anyway.',
175
+ };
176
+ if (ghResult.ok) {
177
+ console.log(` ${chalk.green('✓')} GitHub token is valid — you're all set 🐙`);
178
+ }
179
+ else if (ghResult.hard) {
180
+ console.error(` ${chalk.red('✗')} ${ghResult.message}`);
181
+ console.error(` ${chalk.red('→')} ${ghResult.hint}`);
182
+ hasHardFailure = true;
183
+ }
184
+ else {
185
+ console.warn(` ${chalk.yellow('⚠')} ${ghResult.message}`);
186
+ console.warn(` ${chalk.yellow('→')} ${ghResult.hint}`);
187
+ }
188
+ console.log();
189
+ if (hasHardFailure) {
190
+ process.exit(1);
191
+ }
192
+ }
193
+ async function executeLaunch(config, issueName, planPath, options) {
194
+ logger.section('Launch');
195
+ // Check if plan is synced to origin/main
196
+ const planSyncStatus = await checkPlanSyncStatus(config, planPath);
197
+ if (!planSyncStatus.synced) {
198
+ if (planSyncStatus.location === 'not-found') {
199
+ logger.error('Plan not found locally or on origin/main');
200
+ logger.info('Make sure you created the plan with /hula-plan');
201
+ process.exit(1);
202
+ }
203
+ // Plan exists locally but not on origin/main - auto-upload
204
+ logger.info('Plan found locally, pushing to origin/main...');
205
+ try {
206
+ await executeUpload(config, planPath);
207
+ logger.success('Plan synced to origin/main');
208
+ logger.blank();
209
+ }
210
+ catch (uploadError) {
211
+ logger.error('Failed to upload plan:');
212
+ logger.error(uploadError instanceof Error
213
+ ? uploadError.message
214
+ : String(uploadError));
215
+ process.exit(1);
216
+ }
217
+ }
218
+ // Get server URL and API key
219
+ const serverUrl = options.url || config.hulaProjectUrl || HULA_PROJECT_URL;
220
+ const apiKey = options.apiKey || config.hulaApiKey || process.env.HULA_API_KEY;
221
+ if (!apiKey) {
222
+ logger.error('No API key configured.');
223
+ logger.info('Set hulaApiKey in your config or use --api-key option.');
224
+ logger.info('You can also set HULA_API_KEY environment variable.');
225
+ logger.info("Run 'hula login' to authenticate with hula-project.");
226
+ process.exit(1);
227
+ }
228
+ // Get project ID - from config or detect from git
229
+ const repositoryId = await resolveProject(config);
230
+ // Validate fix requires resume
231
+ if (options.fix && options.resume === undefined) {
232
+ logger.error('--fix requires --resume to be set');
233
+ process.exit(1);
234
+ }
235
+ // Validate resume range
236
+ if (options.resume !== undefined &&
237
+ (options.resume < 1 || options.resume > 9)) {
238
+ logger.error('--resume must be between 1 and 9');
239
+ process.exit(1);
240
+ }
241
+ // Build the API endpoint
242
+ const endpoint = `${serverUrl.replace(/\/$/, '')}/api/v1/ralph-run`;
243
+ const updateNotificationUrl = options.updateNotificationUrl ||
244
+ config.updateNotificationUrl ||
245
+ process.env.HULA_UPDATE_NOTIFICATION_URL;
246
+ // Resolve Daytona tier — CLI flag > config > DAYTONA_TIER env var > default 1
247
+ const daytonaTierRaw = options.daytonaTier ??
248
+ config.daytonaTier ??
249
+ (process.env.DAYTONA_TIER ? parseInt(process.env.DAYTONA_TIER, 10) : 1);
250
+ if (!Number.isInteger(daytonaTierRaw) ||
251
+ ![1, 2, 3, 4].includes(daytonaTierRaw)) {
252
+ logger.error('daytonaTier must be 1, 2, 3, or 4 (set via --daytona-tier, config.daytonaTier, or DAYTONA_TIER env var)');
253
+ process.exit(1);
254
+ }
255
+ const daytonaTier = daytonaTierRaw;
256
+ logger.info(`Triggering launch job...`);
257
+ logger.log(` Issue Name: ${issueName}`);
258
+ logger.log(` Plan Path: ${planPath}`);
259
+ logger.log(` Server: ${serverUrl}`);
260
+ // Log optional parameters if set
261
+ if (options.duration)
262
+ logger.log(` Duration: ${options.duration} hours`);
263
+ if (options.launch)
264
+ logger.log(` Launch Path: ${options.launch}`);
265
+ if (options.worktree === false)
266
+ logger.log(` Worktree: disabled`);
267
+ else if (typeof options.worktree === 'string')
268
+ logger.log(` Worktree Base: ${options.worktree}`);
269
+ if (options.resume)
270
+ logger.log(` Resume From: Step ${options.resume}`);
271
+ if (options.fix)
272
+ logger.log(` Fix Instructions: ${options.fix}`);
273
+ if (options.regression)
274
+ logger.log(` Force Regression: yes`);
275
+ if (options.verbose)
276
+ logger.log(` Verbose: yes`);
277
+ if (options.handoff)
278
+ logger.log(` Handoff To: ${options.handoff}`);
279
+ if (updateNotificationUrl)
280
+ logger.log(` Notify on completion: enabled`);
281
+ logger.log(` Daytona Tier: ${daytonaTier}`);
282
+ logger.blank();
283
+ // Build request body
284
+ const requestBody = {
285
+ issueName,
286
+ planPath,
287
+ repositoryId,
288
+ };
289
+ if (options.duration !== undefined) {
290
+ requestBody.duration = options.duration;
291
+ }
292
+ // Keep 'ralphPath' property name for server API compatibility
293
+ // even though the CLI option is now '--launch'
294
+ if (options.launch) {
295
+ requestBody.ralphPath = options.launch;
296
+ }
297
+ // Handle worktree: commander sets it to false for --no-worktree
298
+ if (options.worktree === false) {
299
+ requestBody.worktree = false;
300
+ }
301
+ else if (typeof options.worktree === 'string') {
302
+ requestBody.worktree = options.worktree;
303
+ }
304
+ if (options.resume !== undefined) {
305
+ requestBody.resume = options.resume;
306
+ }
307
+ if (options.fix) {
308
+ requestBody.fix = options.fix;
309
+ }
310
+ if (options.regression) {
311
+ requestBody.regression = true;
312
+ }
313
+ if (options.verbose) {
314
+ requestBody.verbose = true;
315
+ }
316
+ if (options.handoff) {
317
+ requestBody.handoff = options.handoff;
318
+ }
319
+ // Resolve ephemeral credentials — collect ALL errors before exiting
320
+ const anthropicApiKey = options.anthropicKey ||
321
+ config.anthropicApiKey ||
322
+ process.env.ANTHROPIC_API_KEY;
323
+ const daytonaApiKey = options.daytonaKey ||
324
+ config.daytonaApiKey ||
325
+ process.env.DAYTONA_API_KEY;
326
+ const credentialErrors = [];
327
+ if (!anthropicApiKey) {
328
+ credentialErrors.push([
329
+ chalk.red('✗ No Anthropic OAuth token configured.'),
330
+ ' Set anthropicApiKey in your config at .hublaunch/hublaunch.config.js, use --anthropic-key, or set ANTHROPIC_API_KEY env var.',
331
+ ' Get your OAuth token at: https://claude.ai/settings (requires a paid Claude.ai plan — Pro or Max)',
332
+ ].join('\n'));
333
+ }
334
+ else if (!anthropicApiKey.startsWith('sk-ant-oat')) {
335
+ credentialErrors.push([
336
+ chalk.red('✗ Anthropic credential must be an OAuth token (starts with sk-ant-oat01-...).'),
337
+ ' Standard API keys (sk-ant-api03-...) are not supported — the container requires CLAUDE_CODE_OAUTH_TOKEN.',
338
+ ' Get your OAuth token at: https://claude.ai/settings (requires a paid Claude.ai plan — Pro or Max)',
339
+ ].join('\n'));
340
+ }
341
+ if (!daytonaApiKey) {
342
+ credentialErrors.push([
343
+ chalk.red('✗ No Daytona API key configured.'),
344
+ ' Set daytonaApiKey in your config at .hublaunch/hublaunch.config.js, use --daytona-key, or set DAYTONA_API_KEY env var.',
345
+ ' Get your Daytona API key at: https://app.daytona.io',
346
+ ].join('\n'));
347
+ }
348
+ if (credentialErrors.length > 0) {
349
+ credentialErrors.forEach((msg) => console.error(msg));
350
+ process.exit(1);
351
+ }
352
+ requestBody.anthropicApiKey = anthropicApiKey;
353
+ requestBody.daytonaApiKey = daytonaApiKey;
354
+ requestBody.daytonaTier = daytonaTier;
355
+ const cpuRaw = options.containerCpu ?? config.containerResources?.cpu;
356
+ const memRaw = options.containerMemory ?? config.containerResources?.memory;
357
+ const diskRaw = options.containerDisk ?? config.containerResources?.disk;
358
+ if (cpuRaw !== undefined || memRaw !== undefined || diskRaw !== undefined) {
359
+ const cpu = cpuRaw ?? DEFAULT_CONTAINER_RESOURCES.cpu;
360
+ const memory = memRaw ?? DEFAULT_CONTAINER_RESOURCES.memory;
361
+ const disk = diskRaw ?? DEFAULT_CONTAINER_RESOURCES.disk;
362
+ if (!Number.isInteger(cpu) || cpu < 1 || cpu > 32) {
363
+ logger.error('containerResources.cpu must be an integer between 1 and 32');
364
+ process.exit(1);
365
+ }
366
+ if (!Number.isInteger(memory) || memory < 1 || memory > 128) {
367
+ logger.error('containerResources.memory must be an integer between 1 and 128 GiB');
368
+ process.exit(1);
369
+ }
370
+ if (!Number.isInteger(disk) || disk < 5 || disk > 200) {
371
+ logger.error('containerResources.disk must be an integer between 5 and 200 GiB');
372
+ process.exit(1);
373
+ }
374
+ requestBody.containerResources = { cpu, memory, disk };
375
+ logger.log(` Container Resources: cpu=${cpu} memory=${memory}GiB disk=${disk}GiB`);
376
+ }
377
+ if (updateNotificationUrl) {
378
+ requestBody.updateNotificationUrl = updateNotificationUrl;
379
+ }
380
+ const tokenData = await loadGitHubToken();
381
+ const githubToken = tokenData?.access_token || process.env.GITHUB_TOKEN;
382
+ if (githubToken) {
383
+ requestBody.githubToken = githubToken;
384
+ }
385
+ // Pre-flight credential validation
386
+ // anthropicApiKey is guaranteed non-null here — missing key exits above
387
+ await validateLaunchCredentials(anthropicApiKey, githubToken);
388
+ try {
389
+ const response = await axios.post(endpoint, requestBody, {
390
+ headers: {
391
+ 'Content-Type': 'application/json',
392
+ Authorization: `Bearer ${apiKey}`,
393
+ },
394
+ timeout: 30000, // 30 second timeout
395
+ });
396
+ if (response.data.success) {
397
+ logger.success(response.data.message);
398
+ if (response.data.job) {
399
+ const job = response.data.job;
400
+ logger.blank();
401
+ logger.info('Job Details:');
402
+ logger.log(` ID: ${job.id}`);
403
+ logger.log(` Issue: ${job.issueName}`);
404
+ logger.log(` Plan: ${job.planPath}`);
405
+ if (job.pid) {
406
+ logger.log(` PID: ${job.pid}`);
407
+ }
408
+ logger.log(` Status: ${job.status}`);
409
+ logger.log(` Started At: ${job.startedAt}`);
410
+ }
411
+ logger.blank();
412
+ logger.info('You can go to https://www.hublaunch.site/dashboard to see a summary of your active plans.');
413
+ if (!updateNotificationUrl) {
414
+ logger.info('Set updateNotificationUrl in your local hublaunch.config.js file to be able to get update messages about the status.');
415
+ }
416
+ logger.blank();
417
+ // Clean up local plan file — it's now on origin/main and submitted to server
418
+ const planService = new PlanService(config.planPath);
419
+ planService.deletePlan(planPath);
420
+ }
421
+ else {
422
+ logger.error(response.data.error || 'Unknown error occurred');
423
+ process.exit(1);
424
+ }
425
+ }
426
+ catch (error) {
427
+ if (axios.isAxiosError(error)) {
428
+ if (error.response) {
429
+ const status = error.response.status;
430
+ const data = error.response.data;
431
+ switch (status) {
432
+ case 400:
433
+ logger.error(`Bad request: ${data.error || 'Invalid parameters'}`);
434
+ break;
435
+ case 401:
436
+ logger.error('Authentication failed. Your API key may be invalid.');
437
+ logger.info("Run 'hula login' to re-authenticate.");
438
+ break;
439
+ case 403:
440
+ if (data.error === 'need_pro_plan') {
441
+ logger.error('You cannot run launch because you are on the free tier.');
442
+ logger.blank();
443
+ logger.info('Your options:');
444
+ logger.info(' â€ĸ Run /hula-create, which will assign the issue to Copilot on GitHub.');
445
+ logger.info(' â€ĸ Go to https://www.hublaunch.site and upgrade your subscription to Pro.');
446
+ logger.blank();
447
+ logger.info('The Pro tier allows launching as many plans as you like, subject to the constraints of your own Anthropic and Daytona subscriptions.');
448
+ }
449
+ else {
450
+ logger.error('Access denied. You may not be authorized for launch.');
451
+ }
452
+ break;
453
+ case 404:
454
+ logger.error(data.error || `Not found: ${planPath}`);
455
+ break;
456
+ case 409:
457
+ logger.error(`Job already running: ${issueName}`);
458
+ break;
459
+ case 500:
460
+ logger.error(`Server error: ${data.error || 'Internal server error'}`);
461
+ break;
462
+ default:
463
+ logger.error(`Request failed with status ${status}: ${data.error || error.message}`);
464
+ }
465
+ }
466
+ else if (error.code === 'ECONNREFUSED') {
467
+ logger.error(`Cannot connect to server at ${serverUrl}`);
468
+ logger.info('Make sure the hula-project server is running.');
469
+ }
470
+ else if (error.code === 'ETIMEDOUT' || error.code === 'ECONNABORTED') {
471
+ logger.error('Request timed out');
472
+ logger.info('The server may be slow or unreachable.');
473
+ }
474
+ else {
475
+ logger.error(`Network error: ${error.message}`);
476
+ }
477
+ }
478
+ else {
479
+ throw error;
480
+ }
481
+ process.exit(1);
482
+ }
483
+ }
484
+ /**
485
+ * Show job status for a tracked issue
486
+ */
487
+ async function showJobStatus(config, issueName, options) {
488
+ const serverUrl = options.url || config.hulaProjectUrl || process.env.HULA_PROJECT_URL;
489
+ const apiKey = options.apiKey || config.hulaApiKey || process.env.HULA_API_KEY;
490
+ if (!serverUrl || !apiKey) {
491
+ logger.error('Server URL and API key are required.');
492
+ logger.info("Run 'hula login' to authenticate with hula-project.");
493
+ process.exit(1);
494
+ }
495
+ const endpoint = `${serverUrl.replace(/\/$/, '')}/api/v1/ralph-run/${encodeURIComponent(issueName)}/status`;
496
+ const project = await resolveProject(config);
497
+ try {
498
+ const response = await axios.get(endpoint, {
499
+ headers: { Authorization: `Bearer ${apiKey}` },
500
+ params: { project },
501
+ timeout: 15000,
502
+ });
503
+ const data = response.data;
504
+ if (data.jobStatus) {
505
+ logger.section(`Job Status: ${issueName}`);
506
+ logger.log(` Status: ${formatJobStatus(data.jobStatus)}`);
507
+ if (data.jobPid)
508
+ logger.log(` PID: ${data.jobPid}`);
509
+ if (data.jobStartedAt)
510
+ logger.log(` Started: ${data.jobStartedAt}`);
511
+ if (data.jobCompletedAt)
512
+ logger.log(` Completed: ${data.jobCompletedAt}`);
513
+ if (data.jobExitCode !== null && data.jobExitCode !== undefined) {
514
+ logger.log(` Exit Code: ${data.jobExitCode}`);
515
+ }
516
+ if (data.prSource)
517
+ logger.log(` Source: ${data.prSource}`);
518
+ if (data.planPath)
519
+ logger.log(` Plan: ${data.planPath}`);
520
+ if (data.jobWorkerHost)
521
+ logger.log(` Worker: ${data.jobWorkerHost}`);
522
+ logger.blank();
523
+ }
524
+ else {
525
+ logger.error(`No job found for issue: ${issueName}`);
526
+ process.exit(1);
527
+ }
528
+ }
529
+ catch (error) {
530
+ handleApiError(error, serverUrl);
531
+ process.exit(1);
532
+ }
533
+ }
534
+ /**
535
+ * Show job logs for a tracked issue
536
+ */
537
+ async function showJobLogs(config, issueName, options) {
538
+ const serverUrl = options.url || config.hulaProjectUrl || process.env.HULA_PROJECT_URL;
539
+ const apiKey = options.apiKey || config.hulaApiKey || process.env.HULA_API_KEY;
540
+ const lines = options.lines || 100;
541
+ const logType = options.type === 'output' ? 'output' : 'log';
542
+ if (!serverUrl || !apiKey) {
543
+ logger.error('Server URL and API key are required.');
544
+ logger.info("Run 'hula login' to authenticate with hula-project.");
545
+ process.exit(1);
546
+ }
547
+ const apiClient = new HulaApiClient({
548
+ baseUrl: serverUrl.replace(/\/$/, ''),
549
+ apiKey,
550
+ timeout: 30000,
551
+ });
552
+ const project = await resolveProject(config);
553
+ try {
554
+ const content = await apiClient.fetchTaskLogs(issueName, {
555
+ type: logType,
556
+ lines,
557
+ project,
558
+ });
559
+ if (options.editor) {
560
+ const tempFileService = new TempFileService(path.join(os.tmpdir(), 'hublaunch-logs'));
561
+ const filePath = await tempFileService.createTempFile(content, issueName);
562
+ logger.info(`Log file created: ${filePath}`);
563
+ const editorService = new EditorService(config.editorCommand);
564
+ await editorService.openFile(filePath);
565
+ logger.success('✓ Log file opened in editor');
566
+ }
567
+ else {
568
+ process.stdout.write(content);
569
+ }
570
+ }
571
+ catch (error) {
572
+ handleApiError(error, serverUrl);
573
+ process.exit(1);
574
+ }
575
+ }
576
+ /**
577
+ * Format job status with color/emoji indicator
578
+ */
579
+ function formatJobStatus(status) {
580
+ switch (status) {
581
+ case 'queued':
582
+ return 'âŗ queued';
583
+ case 'running':
584
+ return '🔄 running';
585
+ case 'completed':
586
+ return '✅ completed';
587
+ case 'failed':
588
+ return '❌ failed';
589
+ case 'cancelled':
590
+ return 'âšī¸ cancelled';
591
+ default:
592
+ return status;
593
+ }
594
+ }
595
+ /**
596
+ * Handle API errors consistently
597
+ */
598
+ function handleApiError(error, serverUrl) {
599
+ if (axios.isAxiosError(error)) {
600
+ if (error.response) {
601
+ const status = error.response.status;
602
+ const data = error.response.data;
603
+ switch (status) {
604
+ case 401:
605
+ logger.error('Authentication failed. Your API key may be invalid.');
606
+ logger.info("Run 'hula login' to re-authenticate.");
607
+ break;
608
+ case 404:
609
+ logger.error(data.error || 'Not found');
610
+ break;
611
+ default:
612
+ logger.error(data.error || `Request failed with status ${status}`);
613
+ }
614
+ }
615
+ else if (error.code === 'ECONNREFUSED') {
616
+ logger.error(`Cannot connect to server at ${serverUrl}`);
617
+ }
618
+ else {
619
+ logger.error(`Network error: ${error.message}`);
620
+ }
621
+ }
622
+ else {
623
+ throw error;
624
+ }
625
+ }
626
+ /**
627
+ * Check if plan is synced to origin/main
628
+ */
629
+ async function checkPlanSyncStatus(config, planPath) {
630
+ const gitService = new GitService();
631
+ const planService = new PlanService(config.planPath);
632
+ // Fetch latest from origin
633
+ await gitService.fetch('origin', 'main');
634
+ // Check if plan exists on origin/main
635
+ const relativePath = planService.getRelativePath(planPath);
636
+ const onOrigin = await gitService.fileExistsOnRef('origin/main', relativePath);
637
+ if (onOrigin) {
638
+ return { synced: true, location: 'origin-main' };
639
+ }
640
+ // Check local main
641
+ const onLocalMain = await gitService.fileExistsOnRef('main', relativePath);
642
+ if (onLocalMain) {
643
+ return { synced: false, location: 'local-main' };
644
+ }
645
+ return { synced: false, location: 'not-found' };
646
+ }
647
+ /**
648
+ * Deprecated ralph command - alias for launch
649
+ * @deprecated Use launchCommand instead
650
+ */
651
+ export function ralphCommand(program, config) {
652
+ program
653
+ .command('ralph <issueName> <planPath>')
654
+ .description('[DEPRECATED] Use "hula launch" instead')
655
+ .option('--url <url>', 'Override hula-project server URL')
656
+ .option('--api-key <key>', 'Override API key')
657
+ .option('--duration <hours>', 'Hours to keep Mac awake', parseInt)
658
+ .option('--ralph <path>', 'Path to ralph.md file (relative to project)')
659
+ .option('--launch <path>', 'Path to launch.md file (relative to project)')
660
+ .option('--worktree <path>', 'Base directory for worktree')
661
+ .option('--no-worktree', 'Run in current directory (no isolation)')
662
+ .option('--resume <step>', 'Resume from step N (1-9)', parseInt)
663
+ .option('--fix <instructions>', 'Fix instructions (use with --resume)')
664
+ .option('--regression', 'Force regression tests to run')
665
+ .option('--verbose', 'Show full output')
666
+ .action(async (issueName, planPath, options) => {
667
+ logger.warning('âš ī¸ "hula ralph" is deprecated. Please use "hula launch" instead.');
668
+ logger.blank();
669
+ // Support both --ralph and --launch options for backward compatibility
670
+ const launchOptions = {
671
+ ...options,
672
+ launch: options.launch || options.ralph,
673
+ };
674
+ try {
675
+ await executeLaunch(config, issueName, planPath, launchOptions);
676
+ }
677
+ catch (error) {
678
+ logger.error('Failed to trigger launch:');
679
+ logger.error(error instanceof Error ? error.message : String(error));
680
+ process.exit(1);
681
+ }
682
+ });
683
+ }
684
+ //# sourceMappingURL=launch.js.map