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,104 @@
1
+ import { logger } from "../../utils/logger.js";
2
+ /**
3
+ * Service for detecting deployment status from PR comments
4
+ * Parses Vercel bot comments to determine deployment status
5
+ */
6
+ export class DeploymentDetectionService {
7
+ /**
8
+ * Detect deployment status from PR comments
9
+ * Looks for Vercel bot comments indicating deployment success or failure
10
+ */
11
+ detectDeploymentStatus(comments) {
12
+ // Filter for bot comments (Vercel, GitHub Actions, etc.)
13
+ const deploymentComments = comments.filter((comment) => comment.author.toLowerCase().includes("vercel") ||
14
+ comment.author.toLowerCase().includes("bot") ||
15
+ comment.author.toLowerCase().includes("github-actions"));
16
+ if (deploymentComments.length === 0) {
17
+ return { status: "pending" };
18
+ }
19
+ // Sort by timestamp descending to get most recent first
20
+ const sortedComments = [...deploymentComments].sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
21
+ // Check most recent deployment comment
22
+ for (const comment of sortedComments) {
23
+ const bodyLower = comment.body.toLowerCase();
24
+ // Check for failure patterns FIRST (highest priority)
25
+ // Vercel pattern: ![Failed](...) or explicit failure messages
26
+ if (bodyLower.includes("![failed]") ||
27
+ bodyLower.includes("[failed]") ||
28
+ bodyLower.includes("❌") ||
29
+ bodyLower.includes("deployment failed") ||
30
+ bodyLower.includes("build failed") ||
31
+ (bodyLower.includes("deployment") && bodyLower.includes("error"))) {
32
+ return {
33
+ status: "failed",
34
+ timestamp: comment.createdAt,
35
+ };
36
+ }
37
+ // Check for success patterns (second priority)
38
+ // Vercel pattern: ![Ready](...) [Ready](...) in Deployment column
39
+ if (bodyLower.includes("![ready]") ||
40
+ (bodyLower.includes("[ready]") && bodyLower.includes("deployment")) ||
41
+ bodyLower.includes("✅") ||
42
+ bodyLower.includes("successfully deployed") ||
43
+ bodyLower.includes("preview deployed")) {
44
+ return {
45
+ status: "success",
46
+ timestamp: comment.createdAt,
47
+ };
48
+ }
49
+ // Check for pending/in-progress patterns (lowest priority)
50
+ // Vercel pattern: ![Building](...) or ![Queued](...)
51
+ if (bodyLower.includes("![building]") ||
52
+ bodyLower.includes("[building]") ||
53
+ bodyLower.includes("![queued]") ||
54
+ bodyLower.includes("[queued]") ||
55
+ bodyLower.includes("deploying") ||
56
+ (bodyLower.includes("deployment") &&
57
+ bodyLower.includes("in progress"))) {
58
+ return {
59
+ status: "pending",
60
+ timestamp: comment.createdAt,
61
+ };
62
+ }
63
+ }
64
+ // If we found deployment comments but couldn't determine status, assume pending
65
+ return { status: "pending" };
66
+ }
67
+ /**
68
+ * Check if we should notify Copilot about a deployment failure
69
+ *
70
+ * Returns true if:
71
+ * - Deployment status is "failed" AND
72
+ * - Either no previous notification exists OR
73
+ * - The deployment failure timestamp is newer than the last notification timestamp
74
+ *
75
+ * This allows multiple notifications if deployment fails again after a fix attempt,
76
+ * while preventing duplicate notifications for the same failure.
77
+ *
78
+ * @param deploymentStatus The current deployment status with optional timestamp
79
+ * @param deploymentNotifiedAt ISO timestamp of when Copilot was last notified
80
+ * @returns true if Copilot should be notified about this failure
81
+ */
82
+ shouldNotifyCopilot(deploymentStatus, deploymentNotifiedAt) {
83
+ if (deploymentStatus.status !== "failed") {
84
+ return false;
85
+ }
86
+ // If never notified, should notify
87
+ if (!deploymentNotifiedAt) {
88
+ logger.debug("No previous notification found, should notify");
89
+ return true;
90
+ }
91
+ // If deployment failure has a timestamp, compare with last notification
92
+ if (deploymentStatus.timestamp) {
93
+ const failureTime = new Date(deploymentStatus.timestamp).getTime();
94
+ const notifiedTime = new Date(deploymentNotifiedAt).getTime();
95
+ const shouldNotify = failureTime > notifiedTime;
96
+ logger.debug(`Failure time: ${deploymentStatus.timestamp}, Last notified: ${deploymentNotifiedAt}, Should notify: ${shouldNotify}`);
97
+ return shouldNotify;
98
+ }
99
+ // If no timestamp on failure, be conservative and don't notify again
100
+ logger.debug("No timestamp on deployment failure, not notifying");
101
+ return false;
102
+ }
103
+ }
104
+ //# sourceMappingURL=DeploymentDetectionService.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DeploymentDetectionService.js","sourceRoot":"","sources":["../../../src/services/github/DeploymentDetectionService.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * GitHub App Installation Service
3
+ * Checks if user has GitHub App installed and prompts for installation
4
+ */
5
+ export declare class GithubAppService {
6
+ private static readonly APP_CHECK_FILE;
7
+ private static readonly CHECK_INTERVAL_DAYS;
8
+ /**
9
+ * Check if GitHub App is installed on user's repositories
10
+ * Returns install URL if not installed or if check is stale
11
+ */
12
+ checkAndPromptInstallation(config: any): Promise<void>;
13
+ /**
14
+ * Force check and prompt for installation (used after login)
15
+ */
16
+ checkAndPromptAfterLogin(installUrl: string | null | undefined): Promise<void>;
17
+ /**
18
+ * Check if we should skip the installation check
19
+ * Skip if recently checked (within CHECK_INTERVAL_DAYS)
20
+ */
21
+ private shouldSkipCheck;
22
+ /**
23
+ * Get installation status from API
24
+ */
25
+ private getInstallationStatus;
26
+ /**
27
+ * Prompt user to install GitHub App
28
+ */
29
+ private promptForInstallation;
30
+ /**
31
+ * Update check timestamp
32
+ */
33
+ private updateCheckTimestamp;
34
+ /**
35
+ * Clear check cache (force next check)
36
+ */
37
+ clearCheckCache(): Promise<void>;
38
+ }
39
+ export declare const githubAppService: GithubAppService;
40
+ //# sourceMappingURL=GithubAppService.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GithubAppService.d.ts","sourceRoot":"","sources":["../../../src/services/github/GithubAppService.ts"],"names":[],"mappings":"AAUA;;;GAGG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,cAAc,CAIpC;IAEF,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,mBAAmB,CAAK;IAEhD;;;OAGG;IACG,0BAA0B,CAAC,MAAM,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC;IA4B5D;;OAEG;IACG,wBAAwB,CAC5B,UAAU,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GACpC,OAAO,CAAC,IAAI,CAAC;IAWhB;;;OAGG;YACW,eAAe;IA6B7B;;OAEG;YACW,qBAAqB;IAqDnC;;OAEG;YACW,qBAAqB;IA4CnC;;OAEG;YACW,oBAAoB;IAoBlC;;OAEG;IACG,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC;CAOvC;AAED,eAAO,MAAM,gBAAgB,kBAAyB,CAAC"}
@@ -0,0 +1,187 @@
1
+ import { exec } from 'child_process';
2
+ import { promisify } from 'util';
3
+ import * as fs from 'fs/promises';
4
+ import * as path from 'path';
5
+ import * as os from 'os';
6
+ import { logger } from '../../utils/logger.js';
7
+ import { HULA_PROJECT_URL } from '../../config/constants.js';
8
+ const execAsync = promisify(exec);
9
+ /**
10
+ * GitHub App Installation Service
11
+ * Checks if user has GitHub App installed and prompts for installation
12
+ */
13
+ export class GithubAppService {
14
+ static APP_CHECK_FILE = path.join(os.homedir(), '.hublaunch', 'app-check.json');
15
+ static CHECK_INTERVAL_DAYS = 7; // Check every 7 days
16
+ /**
17
+ * Check if GitHub App is installed on user's repositories
18
+ * Returns install URL if not installed or if check is stale
19
+ */
20
+ async checkAndPromptInstallation(config) {
21
+ try {
22
+ // Check if we should skip this check (recently checked)
23
+ if (await this.shouldSkipCheck()) {
24
+ return;
25
+ }
26
+ // Get installation status from API
27
+ const installStatus = await this.getInstallationStatus(config);
28
+ if (!installStatus.isInstalled && installStatus.installUrl) {
29
+ await this.promptForInstallation(installStatus.installUrl);
30
+ }
31
+ else {
32
+ logger.debug('✓ GitHub App is installed');
33
+ }
34
+ // Update check timestamp
35
+ await this.updateCheckTimestamp(!installStatus.isInstalled);
36
+ }
37
+ catch (error) {
38
+ // Don't block execution if check fails
39
+ logger.debug(`Failed to check GitHub App installation: ${error instanceof Error ? error.message : 'Unknown error'}`);
40
+ }
41
+ }
42
+ /**
43
+ * Force check and prompt for installation (used after login)
44
+ */
45
+ async checkAndPromptAfterLogin(installUrl) {
46
+ if (!installUrl) {
47
+ logger.debug('No GitHub App install URL provided');
48
+ return;
49
+ }
50
+ logger.blank();
51
+ await this.promptForInstallation(installUrl, true);
52
+ await this.updateCheckTimestamp(false); // Reset check timer
53
+ }
54
+ /**
55
+ * Check if we should skip the installation check
56
+ * Skip if recently checked (within CHECK_INTERVAL_DAYS)
57
+ */
58
+ async shouldSkipCheck() {
59
+ try {
60
+ const data = await fs.readFile(GithubAppService.APP_CHECK_FILE, 'utf-8');
61
+ const checkData = JSON.parse(data);
62
+ const lastCheck = new Date(checkData.lastCheck);
63
+ const daysSinceCheck = (Date.now() - lastCheck.getTime()) / (1000 * 60 * 60 * 24);
64
+ // Skip if checked recently and user chose to skip
65
+ if (checkData.userSkipped &&
66
+ daysSinceCheck < GithubAppService.CHECK_INTERVAL_DAYS) {
67
+ logger.debug(`Skipping GitHub App check (last check: ${daysSinceCheck.toFixed(1)} days ago)`);
68
+ return true;
69
+ }
70
+ return false;
71
+ }
72
+ catch {
73
+ // File doesn't exist or is invalid, don't skip
74
+ return false;
75
+ }
76
+ }
77
+ /**
78
+ * Get installation status from API
79
+ */
80
+ async getInstallationStatus(config) {
81
+ const hulaProjectUrl = config.hulaProjectUrl || HULA_PROJECT_URL;
82
+ // If no API key configured, skip check
83
+ if (!config.hulaApiKey) {
84
+ logger.debug('No Hula API key configured, skipping GitHub App check');
85
+ return { isInstalled: true, installUrl: null }; // Don't prompt if not authenticated
86
+ }
87
+ try {
88
+ // Check if user has any repositories with GitHub App installed
89
+ const response = await fetch(`${hulaProjectUrl}/api/v1/user/github-app-status`, {
90
+ headers: {
91
+ Authorization: `Bearer ${config.hulaApiKey}`,
92
+ 'Content-Type': 'application/json',
93
+ },
94
+ });
95
+ if (!response.ok) {
96
+ logger.debug(`GitHub App status check failed: ${response.status} ${response.statusText}`);
97
+ // If endpoint doesn't exist or returns error, don't prompt
98
+ // This prevents false negatives
99
+ return {
100
+ isInstalled: true, // Assume installed to avoid false warnings
101
+ installUrl: null,
102
+ };
103
+ }
104
+ const data = await response.json();
105
+ logger.debug(`GitHub App status response: ${JSON.stringify(data)}`);
106
+ return {
107
+ isInstalled: data.hasAppInstalled || false,
108
+ installUrl: data.installUrl || null,
109
+ };
110
+ }
111
+ catch (error) {
112
+ logger.debug(`Failed to check installation status: ${error instanceof Error ? error.message : 'Unknown'}`);
113
+ // On error, assume installed to avoid false warnings
114
+ return { isInstalled: true, installUrl: null };
115
+ }
116
+ }
117
+ /**
118
+ * Prompt user to install GitHub App
119
+ */
120
+ async promptForInstallation(installUrl, forcePrompt = false) {
121
+ if (!forcePrompt) {
122
+ logger.blank();
123
+ logger.section('GitHub App Installation');
124
+ }
125
+ logger.warning('⚠️ GitHub App not installed on your repositories');
126
+ logger.blank();
127
+ logger.info('The Hula GitHub App enables:');
128
+ logger.info(' • Automatic issue status updates');
129
+ logger.info(' • Background polling every 5 minutes');
130
+ logger.info(' • Real-time sync when issues change');
131
+ logger.blank();
132
+ logger.info('Without the app:');
133
+ logger.info(' • You can still track issues manually');
134
+ logger.info(' • Use hub-launch commands to update status');
135
+ logger.blank();
136
+ logger.info(`Install URL: ${installUrl}`);
137
+ logger.blank();
138
+ // Try to open browser
139
+ try {
140
+ const openCommand = process.platform === 'darwin'
141
+ ? 'open'
142
+ : process.platform === 'win32'
143
+ ? 'start'
144
+ : 'xdg-open';
145
+ await execAsync(`${openCommand} "${installUrl}"`);
146
+ logger.success('✓ Opened installation page in browser');
147
+ logger.blank();
148
+ logger.info('After installing the app, run any hula command to verify.');
149
+ }
150
+ catch (error) {
151
+ logger.info('Copy the URL above and paste it in your browser to install.');
152
+ }
153
+ logger.blank();
154
+ }
155
+ /**
156
+ * Update check timestamp
157
+ */
158
+ async updateCheckTimestamp(userSkipped) {
159
+ try {
160
+ const checkData = {
161
+ lastCheck: new Date().toISOString(),
162
+ userSkipped,
163
+ };
164
+ await fs.mkdir(path.dirname(GithubAppService.APP_CHECK_FILE), {
165
+ recursive: true,
166
+ });
167
+ await fs.writeFile(GithubAppService.APP_CHECK_FILE, JSON.stringify(checkData, null, 2));
168
+ }
169
+ catch (error) {
170
+ // Non-critical, ignore errors
171
+ logger.debug(`Failed to update check timestamp: ${error}`);
172
+ }
173
+ }
174
+ /**
175
+ * Clear check cache (force next check)
176
+ */
177
+ async clearCheckCache() {
178
+ try {
179
+ await fs.unlink(GithubAppService.APP_CHECK_FILE);
180
+ }
181
+ catch {
182
+ // Ignore if file doesn't exist
183
+ }
184
+ }
185
+ }
186
+ export const githubAppService = new GithubAppService();
187
+ //# sourceMappingURL=GithubAppService.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GithubAppService.js","sourceRoot":"","sources":["../../../src/services/github/GithubAppService.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AACrC,OAAO,EAAE,SAAS,EAAE,MAAM,MAAM,CAAC;AACjC,OAAO,KAAK,EAAE,MAAM,aAAa,CAAC;AAClC,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAC/C,OAAO,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAE7D,MAAM,SAAS,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;AAElC;;;GAGG;AACH,MAAM,OAAO,gBAAgB;IACnB,MAAM,CAAU,cAAc,GAAG,IAAI,CAAC,IAAI,CAChD,EAAE,CAAC,OAAO,EAAE,EACZ,YAAY,EACZ,gBAAgB,CACjB,CAAC;IAEM,MAAM,CAAU,mBAAmB,GAAG,CAAC,CAAC,CAAC,qBAAqB;IAEtE;;;OAGG;IACH,KAAK,CAAC,0BAA0B,CAAC,MAAW;QAC1C,IAAI,CAAC;YACH,wDAAwD;YACxD,IAAI,MAAM,IAAI,CAAC,eAAe,EAAE,EAAE,CAAC;gBACjC,OAAO;YACT,CAAC;YAED,mCAAmC;YACnC,MAAM,aAAa,GAAG,MAAM,IAAI,CAAC,qBAAqB,CAAC,MAAM,CAAC,CAAC;YAE/D,IAAI,CAAC,aAAa,CAAC,WAAW,IAAI,aAAa,CAAC,UAAU,EAAE,CAAC;gBAC3D,MAAM,IAAI,CAAC,qBAAqB,CAAC,aAAa,CAAC,UAAU,CAAC,CAAC;YAC7D,CAAC;iBAAM,CAAC;gBACN,MAAM,CAAC,KAAK,CAAC,2BAA2B,CAAC,CAAC;YAC5C,CAAC;YAED,yBAAyB;YACzB,MAAM,IAAI,CAAC,oBAAoB,CAAC,CAAC,aAAa,CAAC,WAAW,CAAC,CAAC;QAC9D,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,uCAAuC;YACvC,MAAM,CAAC,KAAK,CACV,4CACE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,eAC3C,EAAE,CACH,CAAC;QACJ,CAAC;IACH,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,wBAAwB,CAC5B,UAAqC;QAErC,IAAI,CAAC,UAAU,EAAE,CAAC;YAChB,MAAM,CAAC,KAAK,CAAC,oCAAoC,CAAC,CAAC;YACnD,OAAO;QACT,CAAC;QAED,MAAM,CAAC,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,CAAC,qBAAqB,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;QACnD,MAAM,IAAI,CAAC,oBAAoB,CAAC,KAAK,CAAC,CAAC,CAAC,oBAAoB;IAC9D,CAAC;IAED;;;OAGG;IACK,KAAK,CAAC,eAAe;QAC3B,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,gBAAgB,CAAC,cAAc,EAAE,OAAO,CAAC,CAAC;YACzE,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YAEnC,MAAM,SAAS,GAAG,IAAI,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;YAChD,MAAM,cAAc,GAClB,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC,OAAO,EAAE,CAAC,GAAG,CAAC,IAAI,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC;YAE7D,kDAAkD;YAClD,IACE,SAAS,CAAC,WAAW;gBACrB,cAAc,GAAG,gBAAgB,CAAC,mBAAmB,EACrD,CAAC;gBACD,MAAM,CAAC,KAAK,CACV,0CAA0C,cAAc,CAAC,OAAO,CAC9D,CAAC,CACF,YAAY,CACd,CAAC;gBACF,OAAO,IAAI,CAAC;YACd,CAAC;YAED,OAAO,KAAK,CAAC;QACf,CAAC;QAAC,MAAM,CAAC;YACP,+CAA+C;YAC/C,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,qBAAqB,CAAC,MAAW;QAI7C,MAAM,cAAc,GAAG,MAAM,CAAC,cAAc,IAAI,gBAAgB,CAAC;QAEjE,uCAAuC;QACvC,IAAI,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC;YACvB,MAAM,CAAC,KAAK,CAAC,uDAAuD,CAAC,CAAC;YACtE,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC,oCAAoC;QACtF,CAAC;QAED,IAAI,CAAC;YACH,+DAA+D;YAC/D,MAAM,QAAQ,GAAG,MAAM,KAAK,CAC1B,GAAG,cAAc,gCAAgC,EACjD;gBACE,OAAO,EAAE;oBACP,aAAa,EAAE,UAAU,MAAM,CAAC,UAAU,EAAE;oBAC5C,cAAc,EAAE,kBAAkB;iBACnC;aACF,CACF,CAAC;YAEF,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;gBACjB,MAAM,CAAC,KAAK,CAAC,mCAAmC,QAAQ,CAAC,MAAM,IAAI,QAAQ,CAAC,UAAU,EAAE,CAAC,CAAC;gBAE1F,2DAA2D;gBAC3D,gCAAgC;gBAChC,OAAO;oBACL,WAAW,EAAE,IAAI,EAAE,2CAA2C;oBAC9D,UAAU,EAAE,IAAI;iBACjB,CAAC;YACJ,CAAC;YAED,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;YACnC,MAAM,CAAC,KAAK,CAAC,+BAA+B,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YAEpE,OAAO;gBACL,WAAW,EAAG,IAAY,CAAC,eAAe,IAAI,KAAK;gBACnD,UAAU,EAAG,IAAY,CAAC,UAAU,IAAI,IAAI;aAC7C,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,CAAC,KAAK,CACV,wCACE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,SAC3C,EAAE,CACH,CAAC;YACF,qDAAqD;YACrD,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC;QACjD,CAAC;IACH,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,qBAAqB,CACjC,UAAkB,EAClB,WAAW,GAAG,KAAK;QAEnB,IAAI,CAAC,WAAW,EAAE,CAAC;YACjB,MAAM,CAAC,KAAK,EAAE,CAAC;YACf,MAAM,CAAC,OAAO,CAAC,yBAAyB,CAAC,CAAC;QAC5C,CAAC;QAED,MAAM,CAAC,OAAO,CAAC,mDAAmD,CAAC,CAAC;QACpE,MAAM,CAAC,KAAK,EAAE,CAAC;QACf,MAAM,CAAC,IAAI,CAAC,8BAA8B,CAAC,CAAC;QAC5C,MAAM,CAAC,IAAI,CAAC,oCAAoC,CAAC,CAAC;QAClD,MAAM,CAAC,IAAI,CAAC,wCAAwC,CAAC,CAAC;QACtD,MAAM,CAAC,IAAI,CAAC,uCAAuC,CAAC,CAAC;QACrD,MAAM,CAAC,KAAK,EAAE,CAAC;QACf,MAAM,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;QAChC,MAAM,CAAC,IAAI,CAAC,yCAAyC,CAAC,CAAC;QACvD,MAAM,CAAC,IAAI,CAAC,8CAA8C,CAAC,CAAC;QAC5D,MAAM,CAAC,KAAK,EAAE,CAAC;QACf,MAAM,CAAC,IAAI,CAAC,gBAAgB,UAAU,EAAE,CAAC,CAAC;QAC1C,MAAM,CAAC,KAAK,EAAE,CAAC;QAEf,sBAAsB;QACtB,IAAI,CAAC;YACH,MAAM,WAAW,GACf,OAAO,CAAC,QAAQ,KAAK,QAAQ;gBAC3B,CAAC,CAAC,MAAM;gBACR,CAAC,CAAC,OAAO,CAAC,QAAQ,KAAK,OAAO;oBAC9B,CAAC,CAAC,OAAO;oBACT,CAAC,CAAC,UAAU,CAAC;YACjB,MAAM,SAAS,CAAC,GAAG,WAAW,KAAK,UAAU,GAAG,CAAC,CAAC;YAClD,MAAM,CAAC,OAAO,CAAC,uCAAuC,CAAC,CAAC;YACxD,MAAM,CAAC,KAAK,EAAE,CAAC;YACf,MAAM,CAAC,IAAI,CAAC,2DAA2D,CAAC,CAAC;QAC3E,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,CAAC,IAAI,CACT,6DAA6D,CAC9D,CAAC;QACJ,CAAC;QAED,MAAM,CAAC,KAAK,EAAE,CAAC;IACjB,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,oBAAoB,CAAC,WAAoB;QACrD,IAAI,CAAC;YACH,MAAM,SAAS,GAAG;gBAChB,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;gBACnC,WAAW;aACZ,CAAC;YAEF,MAAM,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,gBAAgB,CAAC,cAAc,CAAC,EAAE;gBAC5D,SAAS,EAAE,IAAI;aAChB,CAAC,CAAC;YACH,MAAM,EAAE,CAAC,SAAS,CAChB,gBAAgB,CAAC,cAAc,EAC/B,IAAI,CAAC,SAAS,CAAC,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC,CACnC,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,8BAA8B;YAC9B,MAAM,CAAC,KAAK,CAAC,qCAAqC,KAAK,EAAE,CAAC,CAAC;QAC7D,CAAC;IACH,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,eAAe;QACnB,IAAI,CAAC;YACH,MAAM,EAAE,CAAC,MAAM,CAAC,gBAAgB,CAAC,cAAc,CAAC,CAAC;QACnD,CAAC;QAAC,MAAM,CAAC;YACP,+BAA+B;QACjC,CAAC;IACH,CAAC;;AAGH,MAAM,CAAC,MAAM,gBAAgB,GAAG,IAAI,gBAAgB,EAAE,CAAC"}
@@ -0,0 +1,65 @@
1
+ import { type Issue, type CreateIssueParams, type IssueFilters } from "../../types/index.js";
2
+ /**
3
+ * Service for GitHub issue operations
4
+ * Single Responsibility: Issue CRUD and listing
5
+ */
6
+ export declare class IssueService {
7
+ /**
8
+ * Build a consistent cache key for an issue.
9
+ * Uses `issue:owner/repo#number` when owner/repo are known, otherwise `issue:number`.
10
+ */
11
+ static getCacheKey(issueNumber: number, owner?: string, repo?: string): string;
12
+ /**
13
+ * Create a new GitHub issue
14
+ */
15
+ create(params: CreateIssueParams): Promise<Issue>;
16
+ /**
17
+ * Get a specific issue by number.
18
+ * When owner/repo are provided, queries that repo directly instead of the CWD repo.
19
+ */
20
+ get(issueNumber: number, owner?: string, repo?: string): Promise<Issue>;
21
+ /**
22
+ * Update issue body
23
+ */
24
+ updateBody(issueNumber: number, body: string): Promise<void>;
25
+ /**
26
+ * Close an issue
27
+ */
28
+ close(issueNumber: number, owner?: string, repo?: string): Promise<void>;
29
+ /**
30
+ * List issues with filters
31
+ */
32
+ list(filters?: IssueFilters): Promise<Issue[]>;
33
+ /**
34
+ * Add a comment to an issue
35
+ */
36
+ addComment(issueNumber: number, body: string): Promise<void>;
37
+ /**
38
+ * Get comments for an issue/PR
39
+ */
40
+ getComments(issueNumber: number): Promise<Array<{
41
+ id: number;
42
+ body: string;
43
+ user: {
44
+ login: string;
45
+ };
46
+ created_at: string;
47
+ }>>;
48
+ /**
49
+ * Add labels to an issue
50
+ */
51
+ addLabels(issueNumber: number, labels: string[]): Promise<void>;
52
+ /**
53
+ * Remove a label from an issue
54
+ */
55
+ removeLabel(issueNumber: number, label: string): Promise<void>;
56
+ /**
57
+ * List user's open issues (authored or assigned)
58
+ */
59
+ listUserIssues(username: string): Promise<Issue[]>;
60
+ /**
61
+ * Transform GitHub API issue to internal format
62
+ */
63
+ private transformToInternalIssue;
64
+ }
65
+ //# sourceMappingURL=IssueService.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"IssueService.d.ts","sourceRoot":"","sources":["../../../src/services/github/IssueService.ts"],"names":[],"mappings":"AAIA,OAAO,EACL,KAAK,KAAK,EACV,KAAK,iBAAiB,EACtB,KAAK,YAAY,EAGlB,MAAM,sBAAsB,CAAC;AAE9B;;;GAGG;AACH,qBAAa,YAAY;IACvB;;;OAGG;IACH,MAAM,CAAC,WAAW,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM;IAK9E;;OAEG;IACG,MAAM,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,KAAK,CAAC;IA0BvD;;;OAGG;IACG,GAAG,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC;IAqB7E;;OAEG;IACG,UAAU,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAkBlE;;OAEG;IACG,KAAK,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAiB9E;;OAEG;IACG,IAAI,CAAC,OAAO,GAAE,YAAiB,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC;IAwBxD;;OAEG;IACG,UAAU,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAalE;;OAEG;IACG,WAAW,CACf,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE;YAAE,KAAK,EAAE,MAAM,CAAA;SAAE,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAY5F;;OAEG;IACG,SAAS,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAsBrE;;OAEG;IACG,WAAW,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAiBpE;;OAEG;IACG,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC;IAuFxD;;OAEG;IACH,OAAO,CAAC,wBAAwB;CAYjC"}
@@ -0,0 +1,231 @@
1
+ import { ghApi, ghGraphQL, getRepoInfo } from "../../utils/github-cli.js";
2
+ import { logger } from "../../utils/logger.js";
3
+ import { globalCache } from "../../utils/cache.js";
4
+ import { runCommandWithStdin } from "../../utils/shell.js";
5
+ import { GitHubIssueSchema, } from "../../types/index.js";
6
+ /**
7
+ * Service for GitHub issue operations
8
+ * Single Responsibility: Issue CRUD and listing
9
+ */
10
+ export class IssueService {
11
+ /**
12
+ * Build a consistent cache key for an issue.
13
+ * Uses `issue:owner/repo#number` when owner/repo are known, otherwise `issue:number`.
14
+ */
15
+ static getCacheKey(issueNumber, owner, repo) {
16
+ return (owner && repo)
17
+ ? `issue:${owner}/${repo}#${issueNumber}`
18
+ : `issue:${issueNumber}`;
19
+ }
20
+ /**
21
+ * Create a new GitHub issue
22
+ */
23
+ async create(params) {
24
+ const repo = await getRepoInfo();
25
+ logger.debug(`Creating issue in ${repo.owner}/${repo.name}`);
26
+ const response = await ghApi(`/repos/${repo.owner}/${repo.name}/issues`, {
27
+ method: "POST",
28
+ fields: {
29
+ title: params.title,
30
+ body: params.body ?? "",
31
+ ...(params.assignees && params.assignees.length > 0
32
+ ? { assignees: params.assignees }
33
+ : {}),
34
+ ...(params.labels && params.labels.length > 0
35
+ ? { labels: params.labels }
36
+ : {}),
37
+ },
38
+ });
39
+ const validated = GitHubIssueSchema.parse(response);
40
+ return this.transformToInternalIssue(validated);
41
+ }
42
+ /**
43
+ * Get a specific issue by number.
44
+ * When owner/repo are provided, queries that repo directly instead of the CWD repo.
45
+ */
46
+ async get(issueNumber, owner, repo) {
47
+ const cacheKey = IssueService.getCacheKey(issueNumber, owner, repo);
48
+ const cached = globalCache.get(cacheKey);
49
+ if (cached) {
50
+ return cached;
51
+ }
52
+ const repoInfo = (owner && repo) ? { owner, name: repo } : await getRepoInfo();
53
+ const response = await ghApi(`/repos/${repoInfo.owner}/${repoInfo.name}/issues/${issueNumber}`);
54
+ const validated = GitHubIssueSchema.parse(response);
55
+ const issue = this.transformToInternalIssue(validated);
56
+ // Cache for 5 minutes
57
+ globalCache.set(cacheKey, issue, 300);
58
+ return issue;
59
+ }
60
+ /**
61
+ * Update issue body
62
+ */
63
+ async updateBody(issueNumber, body) {
64
+ logger.debug(`Updating body for issue #${issueNumber}`);
65
+ const repo = await getRepoInfo();
66
+ // Use gh issue edit with --body-file - to safely handle special characters
67
+ // This pipes the body via stdin to avoid shell interpretation issues
68
+ await runCommandWithStdin(`gh issue edit ${issueNumber} --body-file -`, body, { silent: true });
69
+ // Invalidate both bare and repo-aware cache keys
70
+ globalCache.delete(IssueService.getCacheKey(issueNumber));
71
+ globalCache.delete(IssueService.getCacheKey(issueNumber, repo.owner, repo.name));
72
+ }
73
+ /**
74
+ * Close an issue
75
+ */
76
+ async close(issueNumber, owner, repo) {
77
+ const repoInfo = (owner && repo) ? { owner, name: repo } : await getRepoInfo();
78
+ logger.debug(`Closing issue #${issueNumber}`);
79
+ await ghApi(`/repos/${repoInfo.owner}/${repoInfo.name}/issues/${issueNumber}`, {
80
+ method: "PATCH",
81
+ fields: {
82
+ state: "closed",
83
+ },
84
+ });
85
+ // Invalidate both bare and repo-aware cache keys
86
+ globalCache.delete(IssueService.getCacheKey(issueNumber));
87
+ globalCache.delete(IssueService.getCacheKey(issueNumber, repoInfo.owner, repoInfo.name));
88
+ }
89
+ /**
90
+ * List issues with filters
91
+ */
92
+ async list(filters = {}) {
93
+ const repo = await getRepoInfo();
94
+ // Build query parameters
95
+ const params = [];
96
+ if (filters.state)
97
+ params.push(`state=${filters.state}`);
98
+ if (filters.assignee)
99
+ params.push(`assignee=${filters.assignee}`);
100
+ if (filters.labels && filters.labels.length > 0) {
101
+ params.push(`labels=${filters.labels.join(",")}`);
102
+ }
103
+ if (filters.since)
104
+ params.push(`since=${filters.since}`);
105
+ const queryString = params.length > 0 ? `?${params.join("&")}` : "";
106
+ const response = await ghApi(`/repos/${repo.owner}/${repo.name}/issues${queryString}`);
107
+ return response.map((issue) => {
108
+ const validated = GitHubIssueSchema.parse(issue);
109
+ return this.transformToInternalIssue(validated);
110
+ });
111
+ }
112
+ /**
113
+ * Add a comment to an issue
114
+ */
115
+ async addComment(issueNumber, body) {
116
+ const repo = await getRepoInfo();
117
+ logger.debug(`Adding comment to issue #${issueNumber}`);
118
+ // Use stdin to safely pass comment body with special characters
119
+ await runCommandWithStdin(`gh api /repos/${repo.owner}/${repo.name}/issues/${issueNumber}/comments -X POST -F body=@-`, body, { silent: true });
120
+ }
121
+ /**
122
+ * Get comments for an issue/PR
123
+ */
124
+ async getComments(issueNumber) {
125
+ const repo = await getRepoInfo();
126
+ logger.debug(`Getting comments for issue #${issueNumber}`);
127
+ const comments = await ghApi(`/repos/${repo.owner}/${repo.name}/issues/${issueNumber}/comments`);
128
+ return comments;
129
+ }
130
+ /**
131
+ * Add labels to an issue
132
+ */
133
+ async addLabels(issueNumber, labels) {
134
+ const repo = await getRepoInfo();
135
+ logger.debug(`Adding labels to issue #${issueNumber}: ${labels.join(", ")}`);
136
+ await ghApi(`/repos/${repo.owner}/${repo.name}/issues/${issueNumber}/labels`, {
137
+ method: "POST",
138
+ fields: {
139
+ labels: labels.join(","),
140
+ },
141
+ });
142
+ // Invalidate both bare and repo-aware cache keys
143
+ globalCache.delete(IssueService.getCacheKey(issueNumber));
144
+ globalCache.delete(IssueService.getCacheKey(issueNumber, repo.owner, repo.name));
145
+ }
146
+ /**
147
+ * Remove a label from an issue
148
+ */
149
+ async removeLabel(issueNumber, label) {
150
+ const repo = await getRepoInfo();
151
+ logger.debug(`Removing label from issue #${issueNumber}: ${label}`);
152
+ await ghApi(`/repos/${repo.owner}/${repo.name}/issues/${issueNumber}/labels/${label}`, {
153
+ method: "DELETE",
154
+ });
155
+ // Invalidate both bare and repo-aware cache keys
156
+ globalCache.delete(IssueService.getCacheKey(issueNumber));
157
+ globalCache.delete(IssueService.getCacheKey(issueNumber, repo.owner, repo.name));
158
+ }
159
+ /**
160
+ * List user's open issues (authored or assigned)
161
+ */
162
+ async listUserIssues(username) {
163
+ const repo = await getRepoInfo();
164
+ const query = `
165
+ query($owner: String!, $name: String!) {
166
+ repository(owner: $owner, name: $name) {
167
+ issues(first: 100, states: OPEN, orderBy: {field: CREATED_AT, direction: DESC}) {
168
+ nodes {
169
+ number
170
+ title
171
+ body
172
+ state
173
+ url
174
+ createdAt
175
+ updatedAt
176
+ author {
177
+ login
178
+ }
179
+ assignees(first: 10) {
180
+ nodes {
181
+ login
182
+ }
183
+ }
184
+ labels(first: 10) {
185
+ nodes {
186
+ name
187
+ color
188
+ }
189
+ }
190
+ }
191
+ }
192
+ }
193
+ }
194
+ `;
195
+ const response = await ghGraphQL(query, { owner: repo.owner, name: repo.name });
196
+ const issues = response.data.repository.issues.nodes;
197
+ logger.debug(`GraphQL returned ${issues.length} issues with states: OPEN filter`);
198
+ // Filter to only OPEN issues where user is author or assignee
199
+ const userIssues = issues.filter((issue) => issue.state === "OPEN" && // Ensure it's actually open
200
+ (issue.author.login === username ||
201
+ issue.assignees.nodes.some((a) => a.login === username)));
202
+ logger.debug(`Filtered to ${userIssues.length} open issues for user ${username}`);
203
+ // Transform to internal format
204
+ return userIssues.map((issue) => ({
205
+ number: issue.number,
206
+ title: issue.title,
207
+ body: issue.body ?? undefined,
208
+ state: issue.state.toLowerCase(),
209
+ url: issue.url,
210
+ createdAt: issue.createdAt,
211
+ updatedAt: issue.updatedAt,
212
+ assignees: issue.assignees.nodes.map((a) => a.login),
213
+ }));
214
+ }
215
+ /**
216
+ * Transform GitHub API issue to internal format
217
+ */
218
+ transformToInternalIssue(githubIssue) {
219
+ return {
220
+ number: githubIssue.number,
221
+ title: githubIssue.title,
222
+ body: githubIssue.body ?? undefined,
223
+ state: githubIssue.state,
224
+ url: githubIssue.html_url,
225
+ createdAt: githubIssue.created_at,
226
+ updatedAt: githubIssue.updated_at,
227
+ assignees: githubIssue.assignees.map((a) => a.login),
228
+ };
229
+ }
230
+ }
231
+ //# sourceMappingURL=IssueService.js.map