@jonit-dev/night-watch-cli 1.7.9 → 1.7.11

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 (350) hide show
  1. package/LICENSE +1 -1
  2. package/dist/cli.js +3 -0
  3. package/dist/cli.js.map +1 -1
  4. package/dist/commands/dashboard/tab-config.d.ts.map +1 -1
  5. package/dist/commands/dashboard/tab-config.js +9 -1
  6. package/dist/commands/dashboard/tab-config.js.map +1 -1
  7. package/dist/commands/doctor.d.ts.map +1 -1
  8. package/dist/commands/doctor.js +3 -0
  9. package/dist/commands/doctor.js.map +1 -1
  10. package/dist/commands/init.d.ts.map +1 -1
  11. package/dist/commands/init.js +131 -19
  12. package/dist/commands/init.js.map +1 -1
  13. package/dist/commands/install.d.ts +4 -0
  14. package/dist/commands/install.d.ts.map +1 -1
  15. package/dist/commands/install.js +24 -0
  16. package/dist/commands/install.js.map +1 -1
  17. package/dist/commands/qa.d.ts +30 -0
  18. package/dist/commands/qa.d.ts.map +1 -0
  19. package/dist/commands/qa.js +159 -0
  20. package/dist/commands/qa.js.map +1 -0
  21. package/dist/commands/review.d.ts +5 -0
  22. package/dist/commands/review.d.ts.map +1 -1
  23. package/dist/commands/review.js +40 -0
  24. package/dist/commands/review.js.map +1 -1
  25. package/dist/commands/run.d.ts +11 -0
  26. package/dist/commands/run.d.ts.map +1 -1
  27. package/dist/commands/run.js +56 -1
  28. package/dist/commands/run.js.map +1 -1
  29. package/dist/commands/status.d.ts.map +1 -1
  30. package/dist/commands/status.js +3 -0
  31. package/dist/commands/status.js.map +1 -1
  32. package/dist/config.d.ts.map +1 -1
  33. package/dist/config.js +155 -1
  34. package/dist/config.js.map +1 -1
  35. package/dist/constants.d.ts +20 -1
  36. package/dist/constants.d.ts.map +1 -1
  37. package/dist/constants.js +40 -0
  38. package/dist/constants.js.map +1 -1
  39. package/dist/server/index.d.ts.map +1 -1
  40. package/dist/server/index.js +47 -5
  41. package/dist/server/index.js.map +1 -1
  42. package/dist/shared/types.d.ts +223 -0
  43. package/dist/shared/types.d.ts.map +1 -0
  44. package/dist/shared/types.js +7 -0
  45. package/dist/shared/types.js.map +1 -0
  46. package/dist/src/agents/soul-compiler.d.ts +11 -0
  47. package/dist/src/agents/soul-compiler.d.ts.map +1 -0
  48. package/dist/src/agents/soul-compiler.js +103 -0
  49. package/dist/src/agents/soul-compiler.js.map +1 -0
  50. package/dist/src/board/factory.d.ts +3 -0
  51. package/dist/src/board/factory.d.ts.map +1 -0
  52. package/dist/src/board/factory.js +10 -0
  53. package/dist/src/board/factory.js.map +1 -0
  54. package/dist/src/board/providers/github-graphql.d.ts +16 -0
  55. package/dist/src/board/providers/github-graphql.d.ts.map +1 -0
  56. package/dist/src/board/providers/github-graphql.js +43 -0
  57. package/dist/src/board/providers/github-graphql.js.map +1 -0
  58. package/dist/src/board/providers/github-projects.d.ts +51 -0
  59. package/dist/src/board/providers/github-projects.d.ts.map +1 -0
  60. package/dist/src/board/providers/github-projects.js +672 -0
  61. package/dist/src/board/providers/github-projects.js.map +1 -0
  62. package/dist/src/board/types.d.ts +60 -0
  63. package/dist/src/board/types.d.ts.map +1 -0
  64. package/dist/src/board/types.js +4 -0
  65. package/dist/src/board/types.js.map +1 -0
  66. package/dist/src/cli.d.ts +3 -0
  67. package/dist/src/cli.d.ts.map +1 -0
  68. package/dist/src/cli.js +80 -0
  69. package/dist/src/cli.js.map +1 -0
  70. package/dist/src/commands/board.d.ts +9 -0
  71. package/dist/src/commands/board.d.ts.map +1 -0
  72. package/dist/src/commands/board.js +294 -0
  73. package/dist/src/commands/board.js.map +1 -0
  74. package/dist/src/commands/cancel.d.ts +46 -0
  75. package/dist/src/commands/cancel.d.ts.map +1 -0
  76. package/dist/src/commands/cancel.js +241 -0
  77. package/dist/src/commands/cancel.js.map +1 -0
  78. package/dist/src/commands/dashboard/tab-actions.d.ts +10 -0
  79. package/dist/src/commands/dashboard/tab-actions.d.ts.map +1 -0
  80. package/dist/src/commands/dashboard/tab-actions.js +245 -0
  81. package/dist/src/commands/dashboard/tab-actions.js.map +1 -0
  82. package/dist/src/commands/dashboard/tab-config.d.ts +21 -0
  83. package/dist/src/commands/dashboard/tab-config.d.ts.map +1 -0
  84. package/dist/src/commands/dashboard/tab-config.js +829 -0
  85. package/dist/src/commands/dashboard/tab-config.js.map +1 -0
  86. package/dist/src/commands/dashboard/tab-logs.d.ts +10 -0
  87. package/dist/src/commands/dashboard/tab-logs.d.ts.map +1 -0
  88. package/dist/src/commands/dashboard/tab-logs.js +178 -0
  89. package/dist/src/commands/dashboard/tab-logs.js.map +1 -0
  90. package/dist/src/commands/dashboard/tab-schedules.d.ts +21 -0
  91. package/dist/src/commands/dashboard/tab-schedules.d.ts.map +1 -0
  92. package/dist/src/commands/dashboard/tab-schedules.js +304 -0
  93. package/dist/src/commands/dashboard/tab-schedules.js.map +1 -0
  94. package/dist/src/commands/dashboard/tab-status.d.ts +32 -0
  95. package/dist/src/commands/dashboard/tab-status.d.ts.map +1 -0
  96. package/dist/src/commands/dashboard/tab-status.js +421 -0
  97. package/dist/src/commands/dashboard/tab-status.js.map +1 -0
  98. package/dist/src/commands/dashboard/types.d.ts +43 -0
  99. package/dist/src/commands/dashboard/types.d.ts.map +1 -0
  100. package/dist/src/commands/dashboard/types.js +5 -0
  101. package/dist/src/commands/dashboard/types.js.map +1 -0
  102. package/dist/src/commands/dashboard.d.ts +11 -0
  103. package/dist/src/commands/dashboard.d.ts.map +1 -0
  104. package/dist/src/commands/dashboard.js +239 -0
  105. package/dist/src/commands/dashboard.js.map +1 -0
  106. package/dist/src/commands/doctor.d.ts +16 -0
  107. package/dist/src/commands/doctor.d.ts.map +1 -0
  108. package/dist/src/commands/doctor.js +202 -0
  109. package/dist/src/commands/doctor.js.map +1 -0
  110. package/dist/src/commands/history.d.ts +7 -0
  111. package/dist/src/commands/history.d.ts.map +1 -0
  112. package/dist/src/commands/history.js +56 -0
  113. package/dist/src/commands/history.js.map +1 -0
  114. package/dist/src/commands/init.d.ts +25 -0
  115. package/dist/src/commands/init.d.ts.map +1 -0
  116. package/dist/src/commands/init.js +543 -0
  117. package/dist/src/commands/init.js.map +1 -0
  118. package/dist/src/commands/install.d.ts +48 -0
  119. package/dist/src/commands/install.d.ts.map +1 -0
  120. package/dist/src/commands/install.js +303 -0
  121. package/dist/src/commands/install.js.map +1 -0
  122. package/dist/src/commands/logs.d.ts +15 -0
  123. package/dist/src/commands/logs.d.ts.map +1 -0
  124. package/dist/src/commands/logs.js +104 -0
  125. package/dist/src/commands/logs.js.map +1 -0
  126. package/dist/src/commands/prd-state.d.ts +12 -0
  127. package/dist/src/commands/prd-state.d.ts.map +1 -0
  128. package/dist/src/commands/prd-state.js +47 -0
  129. package/dist/src/commands/prd-state.js.map +1 -0
  130. package/dist/src/commands/prd.d.ts +24 -0
  131. package/dist/src/commands/prd.d.ts.map +1 -0
  132. package/dist/src/commands/prd.js +283 -0
  133. package/dist/src/commands/prd.js.map +1 -0
  134. package/dist/src/commands/prds.d.ts +13 -0
  135. package/dist/src/commands/prds.d.ts.map +1 -0
  136. package/dist/src/commands/prds.js +196 -0
  137. package/dist/src/commands/prds.js.map +1 -0
  138. package/dist/src/commands/prs.d.ts +14 -0
  139. package/dist/src/commands/prs.d.ts.map +1 -0
  140. package/dist/src/commands/prs.js +106 -0
  141. package/dist/src/commands/prs.js.map +1 -0
  142. package/dist/src/commands/qa.d.ts +30 -0
  143. package/dist/src/commands/qa.d.ts.map +1 -0
  144. package/dist/src/commands/qa.js +159 -0
  145. package/dist/src/commands/qa.js.map +1 -0
  146. package/dist/src/commands/retry.d.ts +9 -0
  147. package/dist/src/commands/retry.d.ts.map +1 -0
  148. package/dist/src/commands/retry.js +72 -0
  149. package/dist/src/commands/retry.js.map +1 -0
  150. package/dist/src/commands/review.d.ts +35 -0
  151. package/dist/src/commands/review.d.ts.map +1 -0
  152. package/dist/src/commands/review.js +252 -0
  153. package/dist/src/commands/review.js.map +1 -0
  154. package/dist/src/commands/run.d.ts +61 -0
  155. package/dist/src/commands/run.d.ts.map +1 -0
  156. package/dist/src/commands/run.js +364 -0
  157. package/dist/src/commands/run.js.map +1 -0
  158. package/dist/src/commands/serve.d.ts +7 -0
  159. package/dist/src/commands/serve.d.ts.map +1 -0
  160. package/dist/src/commands/serve.js +27 -0
  161. package/dist/src/commands/serve.js.map +1 -0
  162. package/dist/src/commands/slice.d.ts +26 -0
  163. package/dist/src/commands/slice.d.ts.map +1 -0
  164. package/dist/src/commands/slice.js +175 -0
  165. package/dist/src/commands/slice.js.map +1 -0
  166. package/dist/src/commands/state.d.ts +8 -0
  167. package/dist/src/commands/state.d.ts.map +1 -0
  168. package/dist/src/commands/state.js +56 -0
  169. package/dist/src/commands/state.js.map +1 -0
  170. package/dist/src/commands/status.d.ts +14 -0
  171. package/dist/src/commands/status.d.ts.map +1 -0
  172. package/dist/src/commands/status.js +147 -0
  173. package/dist/src/commands/status.js.map +1 -0
  174. package/dist/src/commands/uninstall.d.ts +25 -0
  175. package/dist/src/commands/uninstall.d.ts.map +1 -0
  176. package/dist/src/commands/uninstall.js +141 -0
  177. package/dist/src/commands/uninstall.js.map +1 -0
  178. package/dist/src/commands/update.d.ts +21 -0
  179. package/dist/src/commands/update.d.ts.map +1 -0
  180. package/dist/src/commands/update.js +87 -0
  181. package/dist/src/commands/update.js.map +1 -0
  182. package/dist/src/config.d.ts +23 -0
  183. package/dist/src/config.d.ts.map +1 -0
  184. package/dist/src/config.js +629 -0
  185. package/dist/src/config.js.map +1 -0
  186. package/dist/src/constants.d.ts +60 -0
  187. package/dist/src/constants.d.ts.map +1 -0
  188. package/dist/src/constants.js +118 -0
  189. package/dist/src/constants.js.map +1 -0
  190. package/dist/src/server/index.d.ts +23 -0
  191. package/dist/src/server/index.d.ts.map +1 -0
  192. package/dist/src/server/index.js +1642 -0
  193. package/dist/src/server/index.js.map +1 -0
  194. package/dist/src/slack/channel-manager.d.ts +32 -0
  195. package/dist/src/slack/channel-manager.d.ts.map +1 -0
  196. package/dist/src/slack/channel-manager.js +128 -0
  197. package/dist/src/slack/channel-manager.js.map +1 -0
  198. package/dist/src/slack/client.d.ts +63 -0
  199. package/dist/src/slack/client.d.ts.map +1 -0
  200. package/dist/src/slack/client.js +151 -0
  201. package/dist/src/slack/client.js.map +1 -0
  202. package/dist/src/slack/deliberation.d.ts +45 -0
  203. package/dist/src/slack/deliberation.d.ts.map +1 -0
  204. package/dist/src/slack/deliberation.js +539 -0
  205. package/dist/src/slack/deliberation.js.map +1 -0
  206. package/dist/src/slack/index.d.ts +6 -0
  207. package/dist/src/slack/index.d.ts.map +1 -0
  208. package/dist/src/slack/index.js +5 -0
  209. package/dist/src/slack/index.js.map +1 -0
  210. package/dist/src/slack/interaction-listener.d.ts +47 -0
  211. package/dist/src/slack/interaction-listener.d.ts.map +1 -0
  212. package/dist/src/slack/interaction-listener.js +216 -0
  213. package/dist/src/slack/interaction-listener.js.map +1 -0
  214. package/dist/src/storage/json-state-migrator.d.ts +24 -0
  215. package/dist/src/storage/json-state-migrator.d.ts.map +1 -0
  216. package/dist/src/storage/json-state-migrator.js +197 -0
  217. package/dist/src/storage/json-state-migrator.js.map +1 -0
  218. package/dist/src/storage/repositories/index.d.ts +25 -0
  219. package/dist/src/storage/repositories/index.d.ts.map +1 -0
  220. package/dist/src/storage/repositories/index.js +43 -0
  221. package/dist/src/storage/repositories/index.js.map +1 -0
  222. package/dist/src/storage/repositories/interfaces.d.ts +59 -0
  223. package/dist/src/storage/repositories/interfaces.d.ts.map +1 -0
  224. package/dist/src/storage/repositories/interfaces.js +6 -0
  225. package/dist/src/storage/repositories/interfaces.js.map +1 -0
  226. package/dist/src/storage/repositories/sqlite/agent-persona-repository.d.ts +27 -0
  227. package/dist/src/storage/repositories/sqlite/agent-persona-repository.d.ts.map +1 -0
  228. package/dist/src/storage/repositories/sqlite/agent-persona-repository.js +569 -0
  229. package/dist/src/storage/repositories/sqlite/agent-persona-repository.js.map +1 -0
  230. package/dist/src/storage/repositories/sqlite/execution-history-repository.d.ts +21 -0
  231. package/dist/src/storage/repositories/sqlite/execution-history-repository.d.ts.map +1 -0
  232. package/dist/src/storage/repositories/sqlite/execution-history-repository.js +94 -0
  233. package/dist/src/storage/repositories/sqlite/execution-history-repository.js.map +1 -0
  234. package/dist/src/storage/repositories/sqlite/prd-state-repository.d.ts +17 -0
  235. package/dist/src/storage/repositories/sqlite/prd-state-repository.d.ts.map +1 -0
  236. package/dist/src/storage/repositories/sqlite/prd-state-repository.js +74 -0
  237. package/dist/src/storage/repositories/sqlite/prd-state-repository.js.map +1 -0
  238. package/dist/src/storage/repositories/sqlite/project-registry-repository.d.ts +17 -0
  239. package/dist/src/storage/repositories/sqlite/project-registry-repository.d.ts.map +1 -0
  240. package/dist/src/storage/repositories/sqlite/project-registry-repository.js +43 -0
  241. package/dist/src/storage/repositories/sqlite/project-registry-repository.js.map +1 -0
  242. package/dist/src/storage/repositories/sqlite/roadmap-state-repository.d.ts +14 -0
  243. package/dist/src/storage/repositories/sqlite/roadmap-state-repository.d.ts.map +1 -0
  244. package/dist/src/storage/repositories/sqlite/roadmap-state-repository.js +47 -0
  245. package/dist/src/storage/repositories/sqlite/roadmap-state-repository.js.map +1 -0
  246. package/dist/src/storage/repositories/sqlite/slack-discussion-repository.d.ts +20 -0
  247. package/dist/src/storage/repositories/sqlite/slack-discussion-repository.d.ts.map +1 -0
  248. package/dist/src/storage/repositories/sqlite/slack-discussion-repository.js +88 -0
  249. package/dist/src/storage/repositories/sqlite/slack-discussion-repository.js.map +1 -0
  250. package/dist/src/storage/sqlite/client.d.ts +23 -0
  251. package/dist/src/storage/sqlite/client.d.ts.map +1 -0
  252. package/dist/src/storage/sqlite/client.js +47 -0
  253. package/dist/src/storage/sqlite/client.js.map +1 -0
  254. package/dist/src/storage/sqlite/migrations.d.ts +11 -0
  255. package/dist/src/storage/sqlite/migrations.d.ts.map +1 -0
  256. package/dist/src/storage/sqlite/migrations.js +94 -0
  257. package/dist/src/storage/sqlite/migrations.js.map +1 -0
  258. package/dist/src/templates/prd-template.d.ts +11 -0
  259. package/dist/src/templates/prd-template.d.ts.map +1 -0
  260. package/dist/src/templates/prd-template.js +166 -0
  261. package/dist/src/templates/prd-template.js.map +1 -0
  262. package/dist/src/templates/slicer-prompt.d.ts +54 -0
  263. package/dist/src/templates/slicer-prompt.d.ts.map +1 -0
  264. package/dist/src/templates/slicer-prompt.js +163 -0
  265. package/dist/src/templates/slicer-prompt.js.map +1 -0
  266. package/dist/src/types.d.ts +127 -0
  267. package/dist/src/types.d.ts.map +1 -0
  268. package/dist/src/types.js +5 -0
  269. package/dist/src/types.js.map +1 -0
  270. package/dist/src/utils/checks.d.ts +55 -0
  271. package/dist/src/utils/checks.d.ts.map +1 -0
  272. package/dist/src/utils/checks.js +246 -0
  273. package/dist/src/utils/checks.js.map +1 -0
  274. package/dist/src/utils/config-writer.d.ts +16 -0
  275. package/dist/src/utils/config-writer.d.ts.map +1 -0
  276. package/dist/src/utils/config-writer.js +45 -0
  277. package/dist/src/utils/config-writer.js.map +1 -0
  278. package/dist/src/utils/crontab.d.ts +62 -0
  279. package/dist/src/utils/crontab.d.ts.map +1 -0
  280. package/dist/src/utils/crontab.js +168 -0
  281. package/dist/src/utils/crontab.js.map +1 -0
  282. package/dist/src/utils/execution-history.d.ts +54 -0
  283. package/dist/src/utils/execution-history.d.ts.map +1 -0
  284. package/dist/src/utils/execution-history.js +80 -0
  285. package/dist/src/utils/execution-history.js.map +1 -0
  286. package/dist/src/utils/github.d.ts +40 -0
  287. package/dist/src/utils/github.d.ts.map +1 -0
  288. package/dist/src/utils/github.js +126 -0
  289. package/dist/src/utils/github.js.map +1 -0
  290. package/dist/src/utils/notify.d.ts +63 -0
  291. package/dist/src/utils/notify.d.ts.map +1 -0
  292. package/dist/src/utils/notify.js +389 -0
  293. package/dist/src/utils/notify.js.map +1 -0
  294. package/dist/src/utils/prd-states.d.ts +16 -0
  295. package/dist/src/utils/prd-states.d.ts.map +1 -0
  296. package/dist/src/utils/prd-states.js +28 -0
  297. package/dist/src/utils/prd-states.js.map +1 -0
  298. package/dist/src/utils/registry.d.ts +45 -0
  299. package/dist/src/utils/registry.d.ts.map +1 -0
  300. package/dist/src/utils/registry.js +86 -0
  301. package/dist/src/utils/registry.js.map +1 -0
  302. package/dist/src/utils/roadmap-parser.d.ts +45 -0
  303. package/dist/src/utils/roadmap-parser.d.ts.map +1 -0
  304. package/dist/src/utils/roadmap-parser.js +136 -0
  305. package/dist/src/utils/roadmap-parser.js.map +1 -0
  306. package/dist/src/utils/roadmap-scanner.d.ts +92 -0
  307. package/dist/src/utils/roadmap-scanner.d.ts.map +1 -0
  308. package/dist/src/utils/roadmap-scanner.js +349 -0
  309. package/dist/src/utils/roadmap-scanner.js.map +1 -0
  310. package/dist/src/utils/roadmap-state.d.ts +90 -0
  311. package/dist/src/utils/roadmap-state.d.ts.map +1 -0
  312. package/dist/src/utils/roadmap-state.js +154 -0
  313. package/dist/src/utils/roadmap-state.js.map +1 -0
  314. package/dist/src/utils/script-result.d.ts +12 -0
  315. package/dist/src/utils/script-result.d.ts.map +1 -0
  316. package/dist/src/utils/script-result.js +46 -0
  317. package/dist/src/utils/script-result.js.map +1 -0
  318. package/dist/src/utils/shell.d.ts +27 -0
  319. package/dist/src/utils/shell.d.ts.map +1 -0
  320. package/dist/src/utils/shell.js +64 -0
  321. package/dist/src/utils/shell.js.map +1 -0
  322. package/dist/src/utils/status-data.d.ts +148 -0
  323. package/dist/src/utils/status-data.d.ts.map +1 -0
  324. package/dist/src/utils/status-data.js +593 -0
  325. package/dist/src/utils/status-data.js.map +1 -0
  326. package/dist/src/utils/ui.d.ts +55 -0
  327. package/dist/src/utils/ui.d.ts.map +1 -0
  328. package/dist/src/utils/ui.js +121 -0
  329. package/dist/src/utils/ui.js.map +1 -0
  330. package/dist/types.d.ts +43 -1
  331. package/dist/types.d.ts.map +1 -1
  332. package/dist/utils/notify.d.ts.map +1 -1
  333. package/dist/utils/notify.js +18 -0
  334. package/dist/utils/notify.js.map +1 -1
  335. package/dist/utils/status-data.d.ts +4 -0
  336. package/dist/utils/status-data.d.ts.map +1 -1
  337. package/dist/utils/status-data.js +13 -3
  338. package/dist/utils/status-data.js.map +1 -1
  339. package/package.json +3 -1
  340. package/scripts/night-watch-cron.sh +50 -2
  341. package/scripts/night-watch-helpers.sh +54 -2
  342. package/scripts/night-watch-pr-reviewer-cron.sh +79 -1
  343. package/scripts/night-watch-qa-cron.sh +269 -0
  344. package/templates/night-watch-qa.md +157 -0
  345. package/templates/night-watch.config.json +14 -1
  346. package/web/dist/assets/index-BtxQU4oX.css +1 -0
  347. package/web/dist/assets/index-CsNIryJz.js +473 -0
  348. package/web/dist/index.html +2 -2
  349. package/web/dist/assets/index-C64sy08d.js +0 -360
  350. package/web/dist/assets/index-DzoZeo_Y.css +0 -1
@@ -0,0 +1,829 @@
1
+ /**
2
+ * Config tab for the dashboard TUI
3
+ * Allows viewing and editing all configuration fields
4
+ */
5
+ import blessed from "blessed";
6
+ import { VALID_PROVIDERS } from "../../constants.js";
7
+ import { saveConfig } from "../../utils/config-writer.js";
8
+ import { performUninstall } from "../uninstall.js";
9
+ import { performInstall } from "../install.js";
10
+ const SENSITIVE_PATTERNS = /TOKEN|KEY|SECRET|PASSWORD/i;
11
+ const WEBHOOK_TYPES = ["slack", "discord", "telegram"];
12
+ const NOTIFICATION_EVENTS = [
13
+ "run_started",
14
+ "run_succeeded",
15
+ "run_failed",
16
+ "run_timeout",
17
+ "review_completed",
18
+ "pr_auto_merged",
19
+ "rate_limit_fallback",
20
+ "qa_completed",
21
+ ];
22
+ /**
23
+ * GLM-5 default provider environment configuration
24
+ */
25
+ const GLM5_DEFAULTS = {
26
+ ANTHROPIC_BASE_URL: "https://api.z.ai/api/anthropic",
27
+ API_TIMEOUT_MS: "3000000",
28
+ ANTHROPIC_DEFAULT_OPUS_MODEL: "glm-5",
29
+ ANTHROPIC_DEFAULT_SONNET_MODEL: "glm-5",
30
+ };
31
+ export const CONFIG_FIELDS = [
32
+ { key: "provider", label: "Provider", type: "enum", options: [...VALID_PROVIDERS] },
33
+ { key: "reviewerEnabled", label: "Reviewer Enabled", type: "boolean" },
34
+ { key: "defaultBranch", label: "Default Branch", type: "string" },
35
+ { key: "prdDir", label: "PRD Directory", type: "string" },
36
+ { key: "branchPrefix", label: "Branch Prefix", type: "string" },
37
+ { key: "branchPatterns", label: "Branch Patterns", type: "string[]" },
38
+ { key: "cronSchedule", label: "Executor Schedule", type: "string" },
39
+ { key: "reviewerSchedule", label: "Reviewer Schedule", type: "string" },
40
+ {
41
+ key: "maxRuntime", label: "Max Runtime (s)", type: "number",
42
+ validate: (v) => { const n = parseInt(v, 10); return isNaN(n) || n <= 0 ? "Must be a positive integer" : null; },
43
+ },
44
+ {
45
+ key: "reviewerMaxRuntime", label: "Reviewer Max Runtime (s)", type: "number",
46
+ validate: (v) => { const n = parseInt(v, 10); return isNaN(n) || n <= 0 ? "Must be a positive integer" : null; },
47
+ },
48
+ {
49
+ key: "minReviewScore", label: "Min Review Score", type: "number",
50
+ validate: (v) => { const n = parseInt(v, 10); return isNaN(n) || n < 0 || n > 100 ? "Must be 0-100" : null; },
51
+ },
52
+ {
53
+ key: "maxLogSize", label: "Max Log Size (bytes)", type: "number",
54
+ validate: (v) => { const n = parseInt(v, 10); return isNaN(n) || n <= 0 ? "Must be a positive integer" : null; },
55
+ },
56
+ { key: "providerEnv", label: "Provider Env Vars", type: "keyvalue" },
57
+ { key: "notifications", label: "Notifications", type: "webhooks" },
58
+ ];
59
+ function maskValue(key, value) {
60
+ if (SENSITIVE_PATTERNS.test(key) && value.length > 6) {
61
+ return value.slice(0, 3) + "***" + value.slice(-3);
62
+ }
63
+ return value;
64
+ }
65
+ function formatFieldValue(config, field) {
66
+ const value = config[field.key];
67
+ if (field.type === "string[]" && Array.isArray(value)) {
68
+ return value.join(", ");
69
+ }
70
+ if (field.type === "keyvalue") {
71
+ const env = value;
72
+ const count = Object.keys(env).length;
73
+ return count > 0 ? `${count} variable(s) set` : "(none)";
74
+ }
75
+ if (field.type === "webhooks") {
76
+ const notif = value;
77
+ return notif.webhooks.length > 0 ? `${notif.webhooks.length} webhook(s)` : "(none)";
78
+ }
79
+ return String(value);
80
+ }
81
+ /**
82
+ * Create the Config editor tab
83
+ */
84
+ export function createConfigTab() {
85
+ const container = blessed.box({
86
+ top: 0,
87
+ left: 0,
88
+ width: "100%",
89
+ height: "100%",
90
+ hidden: true,
91
+ });
92
+ const configList = blessed.list({
93
+ top: 0,
94
+ left: 0,
95
+ width: "100%",
96
+ height: "100%-3",
97
+ border: { type: "line" },
98
+ label: "[ Configuration ]",
99
+ tags: true,
100
+ scrollable: true,
101
+ alwaysScroll: true,
102
+ scrollbar: { style: { bg: "blue" } },
103
+ style: {
104
+ border: { fg: "cyan" },
105
+ selected: { bg: "blue", fg: "white" },
106
+ item: { fg: "white" },
107
+ },
108
+ keys: true,
109
+ vi: false,
110
+ mouse: false,
111
+ interactive: true,
112
+ });
113
+ const statusBar = blessed.box({
114
+ bottom: 0,
115
+ left: 0,
116
+ width: "100%",
117
+ height: 3,
118
+ border: { type: "line" },
119
+ tags: true,
120
+ style: { border: { fg: "white" } },
121
+ content: "",
122
+ });
123
+ container.append(configList);
124
+ container.append(statusBar);
125
+ const pendingChanges = {};
126
+ let currentConfig = null;
127
+ function buildListItems(config) {
128
+ return CONFIG_FIELDS.map((field) => {
129
+ const hasChange = field.key in pendingChanges;
130
+ const value = hasChange
131
+ ? formatFieldValue({ ...config, ...pendingChanges }, field)
132
+ : formatFieldValue(config, field);
133
+ const marker = hasChange ? " {yellow-fg}*{/yellow-fg}" : "";
134
+ const editHint = field.type === "keyvalue" || field.type === "webhooks" ? " {#888888-fg}(Enter to manage){/#888888-fg}" : "";
135
+ return ` ${field.label}: ${value}${marker}${editHint}`;
136
+ });
137
+ }
138
+ function updateStatusBar() {
139
+ const changeCount = Object.keys(pendingChanges).length;
140
+ if (changeCount > 0) {
141
+ statusBar.setContent(` {yellow-fg}${changeCount} unsaved change(s){/yellow-fg} | s:Save & Apply u:Undo All`);
142
+ }
143
+ else {
144
+ statusBar.setContent(" No pending changes");
145
+ }
146
+ }
147
+ function refreshList(config) {
148
+ configList.setItems(buildListItems(config));
149
+ updateStatusBar();
150
+ }
151
+ // ── Key-Value Editor (providerEnv) ──────────────────────────────────────
152
+ function showKeyValueEditor(ctx, config) {
153
+ const currentEnv = {
154
+ ...(config.providerEnv || {}),
155
+ ...(pendingChanges.providerEnv || {}),
156
+ };
157
+ // If pendingChanges has providerEnv, use it entirely; otherwise merge from config
158
+ const editableEnv = pendingChanges.providerEnv
159
+ ? { ...pendingChanges.providerEnv }
160
+ : { ...currentEnv };
161
+ function buildItems() {
162
+ const entries = Object.entries(editableEnv);
163
+ if (entries.length === 0)
164
+ return [" (no variables set)"];
165
+ return entries.map(([k, v]) => ` ${k} = ${maskValue(k, v)}`);
166
+ }
167
+ const kvList = blessed.list({
168
+ top: "center",
169
+ left: "center",
170
+ width: "70%",
171
+ height: Math.min(Object.keys(editableEnv).length + 4, 20),
172
+ border: { type: "line" },
173
+ label: "[ Provider Env Vars | a:Add Enter:Edit d:Delete Esc:Done ]",
174
+ tags: true,
175
+ style: {
176
+ border: { fg: "cyan" },
177
+ selected: { bg: "blue", fg: "white" },
178
+ item: { fg: "white" },
179
+ },
180
+ keys: true,
181
+ vi: false,
182
+ interactive: true,
183
+ });
184
+ kvList.setItems(buildItems());
185
+ ctx.setEditing(true);
186
+ ctx.screen.append(kvList);
187
+ kvList.focus();
188
+ ctx.screen.render();
189
+ function refreshKvList() {
190
+ kvList.setItems(buildItems());
191
+ kvList.height = Math.min(Object.keys(editableEnv).length + 4, 20);
192
+ ctx.screen.render();
193
+ }
194
+ function promptTextbox(label, initialValue, cb) {
195
+ const input = blessed.textbox({
196
+ top: "center",
197
+ left: "center",
198
+ width: "50%",
199
+ height: 3,
200
+ border: { type: "line" },
201
+ label: `[ ${label} ]`,
202
+ tags: true,
203
+ style: { border: { fg: "yellow" }, fg: "white" },
204
+ inputOnFocus: true,
205
+ });
206
+ ctx.screen.append(input);
207
+ input.setValue(initialValue);
208
+ input.focus();
209
+ ctx.screen.render();
210
+ input.on("submit", (value) => {
211
+ input.destroy();
212
+ cb(value.trim());
213
+ });
214
+ input.on("cancel", () => {
215
+ input.destroy();
216
+ cb(null);
217
+ });
218
+ }
219
+ kvList.key(["a"], () => {
220
+ promptTextbox("Variable Name", "", (key) => {
221
+ if (!key) {
222
+ kvList.focus();
223
+ ctx.screen.render();
224
+ return;
225
+ }
226
+ promptTextbox(`Value for ${key}`, "", (value) => {
227
+ if (value !== null) {
228
+ editableEnv[key] = value;
229
+ pendingChanges.providerEnv = { ...editableEnv };
230
+ refreshKvList();
231
+ }
232
+ kvList.focus();
233
+ ctx.screen.render();
234
+ });
235
+ });
236
+ });
237
+ kvList.key(["enter"], () => {
238
+ const keys = Object.keys(editableEnv);
239
+ if (keys.length === 0)
240
+ return;
241
+ const idx = kvList.selected;
242
+ if (idx < 0 || idx >= keys.length)
243
+ return;
244
+ const selectedKey = keys[idx];
245
+ promptTextbox(`Edit ${selectedKey}`, editableEnv[selectedKey], (value) => {
246
+ if (value !== null) {
247
+ editableEnv[selectedKey] = value;
248
+ pendingChanges.providerEnv = { ...editableEnv };
249
+ refreshKvList();
250
+ }
251
+ kvList.focus();
252
+ ctx.screen.render();
253
+ });
254
+ });
255
+ kvList.key(["d"], () => {
256
+ const keys = Object.keys(editableEnv);
257
+ if (keys.length === 0)
258
+ return;
259
+ const idx = kvList.selected;
260
+ if (idx < 0 || idx >= keys.length)
261
+ return;
262
+ const selectedKey = keys[idx];
263
+ delete editableEnv[selectedKey];
264
+ pendingChanges.providerEnv = { ...editableEnv };
265
+ refreshKvList();
266
+ });
267
+ kvList.key(["escape"], () => {
268
+ kvList.destroy();
269
+ ctx.setEditing(false);
270
+ if (currentConfig)
271
+ refreshList(currentConfig);
272
+ configList.focus();
273
+ ctx.screen.render();
274
+ });
275
+ }
276
+ // ── Webhook Editor (notifications) ──────────────────────────────────────
277
+ function showWebhookEditor(ctx, config) {
278
+ const currentNotif = pendingChanges.notifications
279
+ ? pendingChanges.notifications
280
+ : config.notifications;
281
+ const editableWebhooks = currentNotif.webhooks.map((w) => ({ ...w, events: [...w.events] }));
282
+ function buildItems() {
283
+ if (editableWebhooks.length === 0)
284
+ return [" (no webhooks configured)"];
285
+ return editableWebhooks.map((w) => {
286
+ const identifier = w.type === "telegram"
287
+ ? `token:${maskValue("TOKEN", w.botToken || "")}`
288
+ : (w.url ? maskValue("URL", w.url) : "no url");
289
+ return ` [${w.type}] ${identifier} events: ${w.events.length}`;
290
+ });
291
+ }
292
+ const whList = blessed.list({
293
+ top: "center",
294
+ left: "center",
295
+ width: "70%",
296
+ height: Math.min(editableWebhooks.length + 4, 20),
297
+ border: { type: "line" },
298
+ label: "[ Webhooks | a:Add Enter:Edit d:Delete Esc:Done ]",
299
+ tags: true,
300
+ style: {
301
+ border: { fg: "cyan" },
302
+ selected: { bg: "blue", fg: "white" },
303
+ item: { fg: "white" },
304
+ },
305
+ keys: true,
306
+ vi: false,
307
+ interactive: true,
308
+ });
309
+ whList.setItems(buildItems());
310
+ ctx.setEditing(true);
311
+ ctx.screen.append(whList);
312
+ whList.focus();
313
+ ctx.screen.render();
314
+ function refreshWhList() {
315
+ whList.setItems(buildItems());
316
+ whList.height = Math.min(editableWebhooks.length + 4, 20);
317
+ ctx.screen.render();
318
+ }
319
+ function stageWebhookChanges() {
320
+ pendingChanges.notifications = { webhooks: editableWebhooks.map((w) => ({ ...w, events: [...w.events] })) };
321
+ }
322
+ function promptTextbox(label, initialValue, cb) {
323
+ const input = blessed.textbox({
324
+ top: "center",
325
+ left: "center",
326
+ width: "50%",
327
+ height: 3,
328
+ border: { type: "line" },
329
+ label: `[ ${label} ]`,
330
+ tags: true,
331
+ style: { border: { fg: "yellow" }, fg: "white" },
332
+ inputOnFocus: true,
333
+ });
334
+ ctx.screen.append(input);
335
+ input.setValue(initialValue);
336
+ input.focus();
337
+ ctx.screen.render();
338
+ input.on("submit", (value) => { input.destroy(); cb(value.trim()); });
339
+ input.on("cancel", () => { input.destroy(); cb(null); });
340
+ }
341
+ function selectType(cb) {
342
+ const typeList = blessed.list({
343
+ top: "center",
344
+ left: "center",
345
+ width: 30,
346
+ height: WEBHOOK_TYPES.length + 2,
347
+ border: { type: "line" },
348
+ label: "[ Webhook Type ]",
349
+ tags: true,
350
+ style: { border: { fg: "yellow" }, selected: { bg: "blue", fg: "white" }, item: { fg: "white" } },
351
+ keys: true,
352
+ vi: false,
353
+ interactive: true,
354
+ });
355
+ typeList.setItems(WEBHOOK_TYPES);
356
+ ctx.screen.append(typeList);
357
+ typeList.focus();
358
+ ctx.screen.render();
359
+ typeList.on("select", (_item, index) => {
360
+ typeList.destroy();
361
+ cb(WEBHOOK_TYPES[index]);
362
+ });
363
+ typeList.key(["escape"], () => { typeList.destroy(); cb(null); });
364
+ }
365
+ function selectEvents(current, cb) {
366
+ const selected = new Set(current);
367
+ const evList = blessed.list({
368
+ top: "center",
369
+ left: "center",
370
+ width: 40,
371
+ height: NOTIFICATION_EVENTS.length + 3,
372
+ border: { type: "line" },
373
+ label: "[ Events | Space:Toggle Enter:Done ]",
374
+ tags: true,
375
+ style: { border: { fg: "yellow" }, selected: { bg: "blue", fg: "white" }, item: { fg: "white" } },
376
+ keys: true,
377
+ vi: false,
378
+ interactive: true,
379
+ });
380
+ function renderEvents() {
381
+ evList.setItems(NOTIFICATION_EVENTS.map((e) => ` ${selected.has(e) ? "[x]" : "[ ]"} ${e}`));
382
+ }
383
+ renderEvents();
384
+ ctx.screen.append(evList);
385
+ evList.focus();
386
+ ctx.screen.render();
387
+ evList.key(["space"], () => {
388
+ const idx = evList.selected;
389
+ if (idx >= 0 && idx < NOTIFICATION_EVENTS.length) {
390
+ const ev = NOTIFICATION_EVENTS[idx];
391
+ if (selected.has(ev))
392
+ selected.delete(ev);
393
+ else
394
+ selected.add(ev);
395
+ renderEvents();
396
+ evList.select(idx);
397
+ ctx.screen.render();
398
+ }
399
+ });
400
+ evList.key(["enter"], () => {
401
+ evList.destroy();
402
+ cb([...selected]);
403
+ });
404
+ evList.key(["escape"], () => { evList.destroy(); cb(null); });
405
+ }
406
+ function addWebhookWizard() {
407
+ selectType((type) => {
408
+ if (!type) {
409
+ whList.focus();
410
+ ctx.screen.render();
411
+ return;
412
+ }
413
+ const webhook = { type, events: [...NOTIFICATION_EVENTS] };
414
+ const askCredentials = (done) => {
415
+ if (type === "telegram") {
416
+ promptTextbox("Bot Token", "", (botToken) => {
417
+ if (botToken === null) {
418
+ done(false);
419
+ return;
420
+ }
421
+ webhook.botToken = botToken;
422
+ promptTextbox("Chat ID", "", (chatId) => {
423
+ if (chatId === null) {
424
+ done(false);
425
+ return;
426
+ }
427
+ webhook.chatId = chatId;
428
+ done(true);
429
+ });
430
+ });
431
+ }
432
+ else {
433
+ promptTextbox("Webhook URL", "", (url) => {
434
+ if (url === null) {
435
+ done(false);
436
+ return;
437
+ }
438
+ webhook.url = url;
439
+ done(true);
440
+ });
441
+ }
442
+ };
443
+ askCredentials((ok) => {
444
+ if (!ok) {
445
+ whList.focus();
446
+ ctx.screen.render();
447
+ return;
448
+ }
449
+ selectEvents(webhook.events, (events) => {
450
+ if (events === null) {
451
+ whList.focus();
452
+ ctx.screen.render();
453
+ return;
454
+ }
455
+ webhook.events = events;
456
+ editableWebhooks.push(webhook);
457
+ stageWebhookChanges();
458
+ refreshWhList();
459
+ whList.focus();
460
+ ctx.screen.render();
461
+ });
462
+ });
463
+ });
464
+ }
465
+ function editWebhook(idx) {
466
+ const webhook = editableWebhooks[idx];
467
+ selectType((type) => {
468
+ if (type === null) {
469
+ whList.focus();
470
+ ctx.screen.render();
471
+ return;
472
+ }
473
+ webhook.type = type;
474
+ const askCredentials = (done) => {
475
+ if (type === "telegram") {
476
+ promptTextbox("Bot Token", webhook.botToken || "", (botToken) => {
477
+ if (botToken === null) {
478
+ done(false);
479
+ return;
480
+ }
481
+ webhook.botToken = botToken;
482
+ webhook.url = undefined;
483
+ promptTextbox("Chat ID", webhook.chatId || "", (chatId) => {
484
+ if (chatId === null) {
485
+ done(false);
486
+ return;
487
+ }
488
+ webhook.chatId = chatId;
489
+ done(true);
490
+ });
491
+ });
492
+ }
493
+ else {
494
+ promptTextbox("Webhook URL", webhook.url || "", (url) => {
495
+ if (url === null) {
496
+ done(false);
497
+ return;
498
+ }
499
+ webhook.url = url;
500
+ webhook.botToken = undefined;
501
+ webhook.chatId = undefined;
502
+ done(true);
503
+ });
504
+ }
505
+ };
506
+ askCredentials((ok) => {
507
+ if (!ok) {
508
+ whList.focus();
509
+ ctx.screen.render();
510
+ return;
511
+ }
512
+ selectEvents(webhook.events, (events) => {
513
+ if (events === null) {
514
+ whList.focus();
515
+ ctx.screen.render();
516
+ return;
517
+ }
518
+ webhook.events = events;
519
+ stageWebhookChanges();
520
+ refreshWhList();
521
+ whList.focus();
522
+ ctx.screen.render();
523
+ });
524
+ });
525
+ });
526
+ }
527
+ whList.key(["a"], () => addWebhookWizard());
528
+ whList.key(["enter"], () => {
529
+ if (editableWebhooks.length === 0)
530
+ return;
531
+ const idx = whList.selected;
532
+ if (idx >= 0 && idx < editableWebhooks.length) {
533
+ editWebhook(idx);
534
+ }
535
+ });
536
+ whList.key(["d"], () => {
537
+ if (editableWebhooks.length === 0)
538
+ return;
539
+ const idx = whList.selected;
540
+ if (idx >= 0 && idx < editableWebhooks.length) {
541
+ editableWebhooks.splice(idx, 1);
542
+ stageWebhookChanges();
543
+ refreshWhList();
544
+ }
545
+ });
546
+ whList.key(["escape"], () => {
547
+ whList.destroy();
548
+ ctx.setEditing(false);
549
+ if (currentConfig)
550
+ refreshList(currentConfig);
551
+ configList.focus();
552
+ ctx.screen.render();
553
+ });
554
+ }
555
+ // ── GLM-5 Quick Setup ──────────────────────────────────────────────────
556
+ function showGlm5Setup(ctx) {
557
+ const inputBox = blessed.textbox({
558
+ top: "center",
559
+ left: "center",
560
+ width: "60%",
561
+ height: 3,
562
+ border: { type: "line" },
563
+ label: "[ GLM-5 Quick Setup: Enter API Key ]",
564
+ tags: true,
565
+ style: { border: { fg: "cyan" }, fg: "white" },
566
+ inputOnFocus: true,
567
+ });
568
+ ctx.setEditing(true);
569
+ ctx.screen.append(inputBox);
570
+ inputBox.setValue("");
571
+ inputBox.focus();
572
+ ctx.screen.render();
573
+ inputBox.on("submit", (value) => {
574
+ const apiKey = value.trim();
575
+ inputBox.destroy();
576
+ ctx.setEditing(false);
577
+ if (!apiKey) {
578
+ ctx.showMessage("No API key provided", "error");
579
+ configList.focus();
580
+ ctx.screen.render();
581
+ return;
582
+ }
583
+ pendingChanges.providerEnv = {
584
+ ANTHROPIC_API_KEY: apiKey,
585
+ ANTHROPIC_AUTH_TOKEN: apiKey,
586
+ ...GLM5_DEFAULTS,
587
+ };
588
+ ctx.showMessage("GLM-5 configured. Press s to save.", "success");
589
+ if (currentConfig)
590
+ refreshList(currentConfig);
591
+ configList.focus();
592
+ ctx.screen.render();
593
+ });
594
+ inputBox.on("cancel", () => {
595
+ inputBox.destroy();
596
+ ctx.setEditing(false);
597
+ configList.focus();
598
+ ctx.screen.render();
599
+ });
600
+ }
601
+ // ── Standard Editor ─────────────────────────────────────────────────────
602
+ function showEditor(ctx, field, config) {
603
+ if (field.type === "keyvalue") {
604
+ showKeyValueEditor(ctx, config);
605
+ return;
606
+ }
607
+ if (field.type === "webhooks") {
608
+ showWebhookEditor(ctx, config);
609
+ return;
610
+ }
611
+ const currentValue = field.key in pendingChanges
612
+ ? String(pendingChanges[field.key])
613
+ : formatFieldValue(config, field);
614
+ if (field.type === "enum" || field.type === "boolean") {
615
+ const options = field.type === "boolean" ? ["true", "false"] : (field.options || []);
616
+ const selectorList = blessed.list({
617
+ top: "center",
618
+ left: "center",
619
+ width: Math.max(30, ...options.map((o) => o.length + 6)),
620
+ height: options.length + 2,
621
+ border: { type: "line" },
622
+ label: `[ ${field.label} ]`,
623
+ tags: true,
624
+ style: {
625
+ border: { fg: "cyan" },
626
+ selected: { bg: "blue", fg: "white" },
627
+ item: { fg: "white" },
628
+ },
629
+ keys: true,
630
+ vi: false,
631
+ interactive: true,
632
+ });
633
+ selectorList.setItems(options);
634
+ // Pre-select current value
635
+ const currentIdx = options.indexOf(currentValue);
636
+ if (currentIdx >= 0) {
637
+ selectorList.select(currentIdx);
638
+ }
639
+ ctx.setEditing(true);
640
+ ctx.screen.append(selectorList);
641
+ selectorList.focus();
642
+ ctx.screen.render();
643
+ selectorList.on("select", (_item, index) => {
644
+ const selected = options[index];
645
+ if (field.type === "boolean") {
646
+ pendingChanges[field.key] = selected === "true";
647
+ }
648
+ else {
649
+ pendingChanges[field.key] = selected;
650
+ }
651
+ selectorList.destroy();
652
+ ctx.setEditing(false);
653
+ refreshList(config);
654
+ configList.focus();
655
+ ctx.screen.render();
656
+ });
657
+ selectorList.key(["escape"], () => {
658
+ selectorList.destroy();
659
+ ctx.setEditing(false);
660
+ configList.focus();
661
+ ctx.screen.render();
662
+ });
663
+ return;
664
+ }
665
+ // Text input for string, number, string[]
666
+ const inputBox = blessed.textbox({
667
+ top: "center",
668
+ left: "center",
669
+ width: "60%",
670
+ height: 3,
671
+ border: { type: "line" },
672
+ label: `[ ${field.label} ]`,
673
+ tags: true,
674
+ style: {
675
+ border: { fg: "cyan" },
676
+ fg: "white",
677
+ },
678
+ inputOnFocus: true,
679
+ });
680
+ ctx.setEditing(true);
681
+ ctx.screen.append(inputBox);
682
+ inputBox.setValue(currentValue);
683
+ inputBox.focus();
684
+ ctx.screen.render();
685
+ inputBox.on("submit", (value) => {
686
+ // Validate
687
+ if (field.validate) {
688
+ const error = field.validate(value);
689
+ if (error) {
690
+ ctx.showMessage(error, "error");
691
+ inputBox.destroy();
692
+ ctx.setEditing(false);
693
+ configList.focus();
694
+ ctx.screen.render();
695
+ return;
696
+ }
697
+ }
698
+ // Apply value
699
+ if (field.type === "number") {
700
+ pendingChanges[field.key] = parseInt(value, 10);
701
+ }
702
+ else if (field.type === "string[]") {
703
+ pendingChanges[field.key] = value.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
704
+ }
705
+ else {
706
+ pendingChanges[field.key] = value;
707
+ }
708
+ inputBox.destroy();
709
+ ctx.setEditing(false);
710
+ refreshList(config);
711
+ configList.focus();
712
+ ctx.screen.render();
713
+ });
714
+ inputBox.on("cancel", () => {
715
+ inputBox.destroy();
716
+ ctx.setEditing(false);
717
+ configList.focus();
718
+ ctx.screen.render();
719
+ });
720
+ }
721
+ let activeKeyHandlers = [];
722
+ let activeCtx = null;
723
+ function bindKeys(ctx) {
724
+ const handlers = [
725
+ [["enter"], () => {
726
+ const idx = configList.selected;
727
+ if (idx === undefined || idx < 0 || idx >= CONFIG_FIELDS.length)
728
+ return;
729
+ const field = CONFIG_FIELDS[idx];
730
+ if (currentConfig) {
731
+ showEditor(ctx, field, currentConfig);
732
+ }
733
+ }],
734
+ [["s"], () => {
735
+ if (Object.keys(pendingChanges).length === 0) {
736
+ ctx.showMessage("No changes to save", "info");
737
+ return;
738
+ }
739
+ // Save config
740
+ const result = saveConfig(ctx.projectDir, pendingChanges);
741
+ if (!result.success) {
742
+ ctx.showMessage(`Save failed: ${result.error}`, "error");
743
+ return;
744
+ }
745
+ // Check if schedules changed - reinstall cron
746
+ const scheduleChanged = "cronSchedule" in pendingChanges ||
747
+ "reviewerSchedule" in pendingChanges ||
748
+ "reviewerEnabled" in pendingChanges;
749
+ if (scheduleChanged) {
750
+ performUninstall(ctx.projectDir, { keepLogs: true });
751
+ const newConfig = ctx.reloadConfig();
752
+ const installResult = performInstall(ctx.projectDir, newConfig);
753
+ if (!installResult.success) {
754
+ ctx.showMessage(`Config saved but cron reinstall failed: ${installResult.error}`, "error");
755
+ }
756
+ else {
757
+ ctx.showMessage("Config saved & cron reinstalled", "success");
758
+ }
759
+ }
760
+ else {
761
+ ctx.showMessage("Config saved", "success");
762
+ }
763
+ // Reload config
764
+ currentConfig = ctx.reloadConfig();
765
+ // Clear pending
766
+ for (const key of Object.keys(pendingChanges)) {
767
+ delete pendingChanges[key];
768
+ }
769
+ refreshList(currentConfig);
770
+ ctx.screen.render();
771
+ }],
772
+ [["u"], () => {
773
+ for (const key of Object.keys(pendingChanges)) {
774
+ delete pendingChanges[key];
775
+ }
776
+ if (currentConfig) {
777
+ refreshList(currentConfig);
778
+ }
779
+ ctx.showMessage("Changes undone", "info");
780
+ ctx.screen.render();
781
+ }],
782
+ [["g"], () => {
783
+ showGlm5Setup(ctx);
784
+ }],
785
+ ];
786
+ for (const [keys, handler] of handlers) {
787
+ ctx.screen.key(keys, handler);
788
+ }
789
+ activeKeyHandlers = handlers;
790
+ }
791
+ function unbindKeys(ctx) {
792
+ for (const [keys, handler] of activeKeyHandlers) {
793
+ for (const key of keys) {
794
+ ctx.screen.unkey(key, handler);
795
+ }
796
+ }
797
+ activeKeyHandlers = [];
798
+ }
799
+ return {
800
+ name: "Config",
801
+ container,
802
+ activate(ctx) {
803
+ ctx.setFooter(" \u2191\u2193:Navigate Enter:Edit g:GLM-5 Setup s:Save u:Undo q:Quit");
804
+ currentConfig = ctx.config;
805
+ refreshList(currentConfig);
806
+ configList.focus();
807
+ activeCtx = ctx;
808
+ bindKeys(ctx);
809
+ ctx.screen.render();
810
+ },
811
+ deactivate() {
812
+ if (activeCtx) {
813
+ unbindKeys(activeCtx);
814
+ activeCtx = null;
815
+ }
816
+ },
817
+ refresh(ctx) {
818
+ // Only refresh if no pending changes (don't overwrite user edits)
819
+ if (Object.keys(pendingChanges).length === 0) {
820
+ currentConfig = ctx.config;
821
+ refreshList(currentConfig);
822
+ }
823
+ },
824
+ destroy() {
825
+ // Nothing to clean up
826
+ },
827
+ };
828
+ }
829
+ //# sourceMappingURL=tab-config.js.map