@quickchatai/quickreview 1.1.0 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (471) hide show
  1. package/README.md +137 -57
  2. package/bin/qcr.js +1 -1
  3. package/config.example.toml +60 -14
  4. package/deploy/ansible/README.md +20 -12
  5. package/deploy/ansible/inventory.example.ini +1 -1
  6. package/deploy/collect-secrets.sh +1 -1
  7. package/deploy/qcr-update.sh +1 -1
  8. package/dist/extensions/cli/home.js +14 -0
  9. package/dist/extensions/cli/home.js.map +1 -0
  10. package/dist/extensions/cli/login-route.js +5 -0
  11. package/dist/extensions/cli/login-route.js.map +1 -0
  12. package/dist/extensions/cli/selfimprove.js +18 -0
  13. package/dist/extensions/cli/selfimprove.js.map +1 -0
  14. package/dist/extensions/home.js +350 -0
  15. package/dist/extensions/home.js.map +1 -0
  16. package/dist/extensions/login-route.js +180 -0
  17. package/dist/extensions/login-route.js.map +1 -0
  18. package/dist/extensions/onboarding.js +35 -11
  19. package/dist/extensions/onboarding.js.map +1 -1
  20. package/dist/extensions/selfimprove.js +49 -0
  21. package/dist/extensions/selfimprove.js.map +1 -0
  22. package/dist/extensions/setup-scan.js +23 -592
  23. package/dist/extensions/setup-scan.js.map +1 -1
  24. package/dist/extensions/setup.js +2 -47
  25. package/dist/extensions/setup.js.map +1 -1
  26. package/dist/src/archive/restore.js +18 -15
  27. package/dist/src/archive/restore.js.map +1 -1
  28. package/dist/src/botworker.js +13 -2
  29. package/dist/src/botworker.js.map +1 -1
  30. package/dist/src/channelfs.js +3 -26
  31. package/dist/src/channelfs.js.map +1 -1
  32. package/dist/src/ciretry.js +2 -1
  33. package/dist/src/ciretry.js.map +1 -1
  34. package/dist/src/cli.js +54 -61
  35. package/dist/src/cli.js.map +1 -1
  36. package/dist/src/cliops.js +3 -2
  37. package/dist/src/cliops.js.map +1 -1
  38. package/dist/src/commands.js +25 -2
  39. package/dist/src/commands.js.map +1 -1
  40. package/dist/src/config.js +32 -2
  41. package/dist/src/config.js.map +1 -1
  42. package/dist/src/daemon.js +50 -8
  43. package/dist/src/daemon.js.map +1 -1
  44. package/dist/src/debuground.js +1 -0
  45. package/dist/src/debuground.js.map +1 -1
  46. package/dist/src/debugroundpi.js +6 -1
  47. package/dist/src/debugroundpi.js.map +1 -1
  48. package/dist/src/dispatch.js +5 -4
  49. package/dist/src/dispatch.js.map +1 -1
  50. package/dist/src/engine/caserunner-sdk.js +15 -251
  51. package/dist/src/engine/caserunner-sdk.js.map +1 -1
  52. package/dist/src/engine/debugpi.js +17 -5
  53. package/dist/src/engine/debugpi.js.map +1 -1
  54. package/dist/src/execboundary.js +10 -3
  55. package/dist/src/execboundary.js.map +1 -1
  56. package/dist/src/github.js +51 -16
  57. package/dist/src/github.js.map +1 -1
  58. package/dist/src/githubtoken.js +346 -0
  59. package/dist/src/githubtoken.js.map +1 -0
  60. package/dist/src/home/adapters.js +349 -0
  61. package/dist/src/home/adapters.js.map +1 -0
  62. package/dist/src/home/advanced.js +318 -0
  63. package/dist/src/home/advanced.js.map +1 -0
  64. package/dist/src/home/advancedtools.js +57 -0
  65. package/dist/src/home/advancedtools.js.map +1 -0
  66. package/dist/src/home/autoreview.js +325 -0
  67. package/dist/src/home/autoreview.js.map +1 -0
  68. package/dist/src/home/daemonstatus.js +32 -0
  69. package/dist/src/home/daemonstatus.js.map +1 -0
  70. package/dist/src/home/devicelogin.js +61 -0
  71. package/dist/src/home/devicelogin.js.map +1 -0
  72. package/dist/src/home/launch.js +202 -0
  73. package/dist/src/home/launch.js.map +1 -0
  74. package/dist/src/home/loginowner.js +205 -0
  75. package/dist/src/home/loginowner.js.map +1 -0
  76. package/dist/src/home/memory.js +122 -0
  77. package/dist/src/home/memory.js.map +1 -0
  78. package/dist/src/home/model.js +811 -0
  79. package/dist/src/home/model.js.map +1 -0
  80. package/dist/src/home/panel.js +520 -0
  81. package/dist/src/home/panel.js.map +1 -0
  82. package/dist/src/home/ports.js +19 -0
  83. package/dist/src/home/ports.js.map +1 -0
  84. package/dist/src/home/review.js +163 -0
  85. package/dist/src/home/review.js.map +1 -0
  86. package/dist/src/home/reviews.js +478 -0
  87. package/dist/src/home/reviews.js.map +1 -0
  88. package/dist/src/home/setup.js +507 -0
  89. package/dist/src/home/setup.js.map +1 -0
  90. package/dist/src/home/setupadapters.js +214 -0
  91. package/dist/src/home/setupadapters.js.map +1 -0
  92. package/dist/src/home/setupcard.js +199 -0
  93. package/dist/src/home/setupcard.js.map +1 -0
  94. package/dist/src/home/setuptools.js +45 -0
  95. package/dist/src/home/setuptools.js.map +1 -0
  96. package/dist/src/home/spend.js +160 -0
  97. package/dist/src/home/spend.js.map +1 -0
  98. package/dist/src/home/switches.js +209 -0
  99. package/dist/src/home/switches.js.map +1 -0
  100. package/dist/src/home/switchon.js +439 -0
  101. package/dist/src/home/switchon.js.map +1 -0
  102. package/dist/src/home/toolnames.js +41 -0
  103. package/dist/src/home/toolnames.js.map +1 -0
  104. package/dist/src/home/tune.js +341 -0
  105. package/dist/src/home/tune.js.map +1 -0
  106. package/dist/src/home/words.js +7 -0
  107. package/dist/src/home/words.js.map +1 -0
  108. package/dist/src/homes.js +14 -0
  109. package/dist/src/homes.js.map +1 -1
  110. package/dist/src/install.js +1 -1
  111. package/dist/src/install.js.map +1 -1
  112. package/dist/src/ipc/workerspec.js +8 -0
  113. package/dist/src/ipc/workerspec.js.map +1 -1
  114. package/dist/src/live/publish.js +1 -1
  115. package/dist/src/live/publish.js.map +1 -1
  116. package/dist/src/livecli.js +1 -1
  117. package/dist/src/livecli.js.map +1 -1
  118. package/dist/src/loginhint.js +8 -3
  119. package/dist/src/loginhint.js.map +1 -1
  120. package/dist/src/loginrefresh/handover.js +121 -0
  121. package/dist/src/loginrefresh/handover.js.map +1 -0
  122. package/dist/src/loginrefresh/lock.js +54 -3
  123. package/dist/src/loginrefresh/lock.js.map +1 -1
  124. package/dist/src/loginrefresh/pirefresh.js +3 -0
  125. package/dist/src/loginrefresh/pirefresh.js.map +1 -1
  126. package/dist/src/loginrefresh/refresher.js +33 -5
  127. package/dist/src/loginrefresh/refresher.js.map +1 -1
  128. package/dist/src/loginrefresh/supervisor.js +18 -1
  129. package/dist/src/loginrefresh/supervisor.js.map +1 -1
  130. package/dist/src/npmverbs.js +46 -14
  131. package/dist/src/npmverbs.js.map +1 -1
  132. package/dist/src/onboard/choices.js +35 -4
  133. package/dist/src/onboard/choices.js.map +1 -1
  134. package/dist/src/onboard/engine.js +32 -306
  135. package/dist/src/onboard/engine.js.map +1 -1
  136. package/dist/src/onboard/pipin.js +22 -0
  137. package/dist/src/onboard/pipin.js.map +1 -0
  138. package/dist/src/onboard/stepforward.js +41 -0
  139. package/dist/src/onboard/stepforward.js.map +1 -0
  140. package/dist/src/onboard/steps/appslug.js +4 -1
  141. package/dist/src/onboard/steps/appslug.js.map +1 -1
  142. package/dist/src/onboard/steps/bringup.js +3 -2
  143. package/dist/src/onboard/steps/bringup.js.map +1 -1
  144. package/dist/src/onboard/steps/compose.js +29 -0
  145. package/dist/src/onboard/steps/compose.js.map +1 -1
  146. package/dist/src/onboard/steps/configfill.js +120 -39
  147. package/dist/src/onboard/steps/configfill.js.map +1 -1
  148. package/dist/src/onboard/steps/githubaccount.js +595 -0
  149. package/dist/src/onboard/steps/githubaccount.js.map +1 -0
  150. package/dist/src/onboard/steps/githubapp.js +65 -7
  151. package/dist/src/onboard/steps/githubapp.js.map +1 -1
  152. package/dist/src/onboard/steps/golive.js +2 -0
  153. package/dist/src/onboard/steps/golive.js.map +1 -1
  154. package/dist/src/onboard/steps/index.js +11 -0
  155. package/dist/src/onboard/steps/index.js.map +1 -1
  156. package/dist/src/onboard/steps/ingress.js +80 -25
  157. package/dist/src/onboard/steps/ingress.js.map +1 -1
  158. package/dist/src/onboard/steps/llmauth.js +103 -30
  159. package/dist/src/onboard/steps/llmauth.js.map +1 -1
  160. package/dist/src/onboard/steps/loginshare.js +213 -0
  161. package/dist/src/onboard/steps/loginshare.js.map +1 -0
  162. package/dist/src/onboard/steps/memory.js +67 -3
  163. package/dist/src/onboard/steps/memory.js.map +1 -1
  164. package/dist/src/onboard/steps/proof.js +366 -56
  165. package/dist/src/onboard/steps/proof.js.map +1 -1
  166. package/dist/src/onboard/steps/repoconfig.js +72 -0
  167. package/dist/src/onboard/steps/repoconfig.js.map +1 -0
  168. package/dist/src/onboard/steps/topology.js +6 -6
  169. package/dist/src/onboard/steps/topology.js.map +1 -1
  170. package/dist/src/onboard/steps/transport.js +9 -1
  171. package/dist/src/onboard/steps/transport.js.map +1 -1
  172. package/dist/src/onboard/steps/tuiroute.js +102 -0
  173. package/dist/src/onboard/steps/tuiroute.js.map +1 -0
  174. package/dist/src/onboard/steps/verifyup.js +17 -1
  175. package/dist/src/onboard/steps/verifyup.js.map +1 -1
  176. package/dist/src/onboard/toolnames.js +30 -0
  177. package/dist/src/onboard/toolnames.js.map +1 -0
  178. package/dist/src/operatorwrite.js +23 -0
  179. package/dist/src/operatorwrite.js.map +1 -0
  180. package/dist/src/outcomes.js +4 -1
  181. package/dist/src/outcomes.js.map +1 -1
  182. package/dist/src/piagentdir.js +13 -0
  183. package/dist/src/piagentdir.js.map +1 -0
  184. package/dist/src/pilogin.js +31 -16
  185. package/dist/src/pilogin.js.map +1 -1
  186. package/dist/src/posting-journal.js +2 -1
  187. package/dist/src/posting-journal.js.map +1 -1
  188. package/dist/src/posting-outbox.js +6 -2
  189. package/dist/src/posting-outbox.js.map +1 -1
  190. package/dist/src/posting-receipt.js +14 -1
  191. package/dist/src/posting-receipt.js.map +1 -1
  192. package/dist/src/qcrbin.js +16 -11
  193. package/dist/src/qcrbin.js.map +1 -1
  194. package/dist/src/recheck.js +2 -1
  195. package/dist/src/recheck.js.map +1 -1
  196. package/dist/src/reconcile.js +22 -2
  197. package/dist/src/reconcile.js.map +1 -1
  198. package/dist/src/reviewargs.js +122 -0
  199. package/dist/src/reviewargs.js.map +1 -0
  200. package/dist/src/reviewcli.js +7 -161
  201. package/dist/src/reviewcli.js.map +1 -1
  202. package/dist/src/reviewdetach.js +76 -11
  203. package/dist/src/reviewdetach.js.map +1 -1
  204. package/dist/src/reviewhistory.js +32 -6
  205. package/dist/src/reviewhistory.js.map +1 -1
  206. package/dist/src/reviewnow.js +45 -0
  207. package/dist/src/reviewnow.js.map +1 -0
  208. package/dist/src/scancli.js +39 -691
  209. package/dist/src/scancli.js.map +1 -1
  210. package/dist/src/selfimprove.js +66 -0
  211. package/dist/src/selfimprove.js.map +1 -0
  212. package/dist/src/setup/adapters.js +5 -223
  213. package/dist/src/setup/adapters.js.map +1 -1
  214. package/dist/src/setup/caserunner.js +15 -249
  215. package/dist/src/setup/caserunner.js.map +1 -1
  216. package/dist/src/setup/engine.js +32 -341
  217. package/dist/src/setup/engine.js.map +1 -1
  218. package/dist/src/setup/facts.js +3 -592
  219. package/dist/src/setup/facts.js.map +1 -1
  220. package/dist/src/setup/findings.js +3 -947
  221. package/dist/src/setup/findings.js.map +1 -1
  222. package/dist/src/setup/presentation.js +3 -8
  223. package/dist/src/setup/presentation.js.map +1 -1
  224. package/dist/src/setup/scan.js +3 -724
  225. package/dist/src/setup/scan.js.map +1 -1
  226. package/dist/src/setup/scanbuild.js +9 -92
  227. package/dist/src/setup/scanbuild.js.map +1 -1
  228. package/dist/src/setup/scanlease.js +3 -250
  229. package/dist/src/setup/scanlease.js.map +1 -1
  230. package/dist/src/setup/scanreport.js +3 -288
  231. package/dist/src/setup/scanreport.js.map +1 -1
  232. package/dist/src/setup/scanrun.js +3 -1732
  233. package/dist/src/setup/scanrun.js.map +1 -1
  234. package/dist/src/setup/scantree.js +3 -256
  235. package/dist/src/setup/scantree.js.map +1 -1
  236. package/dist/src/setup/stage2request.js +3 -258
  237. package/dist/src/setup/stage2request.js.map +1 -1
  238. package/dist/src/setup/unitcut.js +3 -436
  239. package/dist/src/setup/unitcut.js.map +1 -1
  240. package/dist/src/state.js +2 -0
  241. package/dist/src/state.js.map +1 -1
  242. package/dist/src/statemachine.js +4 -1
  243. package/dist/src/statemachine.js.map +1 -1
  244. package/dist/src/takeover.js +1 -0
  245. package/dist/src/takeover.js.map +1 -1
  246. package/dist/src/takeoverpi.js +15 -1
  247. package/dist/src/takeoverpi.js.map +1 -1
  248. package/dist/src/tui/app.js +49 -0
  249. package/dist/src/tui/app.js.map +1 -1
  250. package/dist/src/tui/board.js +88 -12
  251. package/dist/src/tui/board.js.map +1 -1
  252. package/dist/src/tui/cmdbar.js +4 -1
  253. package/dist/src/tui/cmdbar.js.map +1 -1
  254. package/dist/src/tui/companion.js +76 -11
  255. package/dist/src/tui/companion.js.map +1 -1
  256. package/dist/src/tui/connect.js +7 -1
  257. package/dist/src/tui/connect.js.map +1 -1
  258. package/dist/src/tui/deploytab.js +2 -2
  259. package/dist/src/tui/deploytab.js.map +1 -1
  260. package/dist/src/tui/filters.js +5 -2
  261. package/dist/src/tui/filters.js.map +1 -1
  262. package/dist/src/tui/ghread.js +61 -5
  263. package/dist/src/tui/ghread.js.map +1 -1
  264. package/dist/src/tui/index.js +109 -20
  265. package/dist/src/tui/index.js.map +1 -1
  266. package/dist/src/tui/memforward.js +39 -2
  267. package/dist/src/tui/memforward.js.map +1 -1
  268. package/dist/src/tui/memory.js +3 -16
  269. package/dist/src/tui/memory.js.map +1 -1
  270. package/dist/src/tui/memoryurl.js +22 -0
  271. package/dist/src/tui/memoryurl.js.map +1 -0
  272. package/dist/src/tui/model.js +25 -0
  273. package/dist/src/tui/model.js.map +1 -1
  274. package/dist/src/tui/prpause.js +7 -0
  275. package/dist/src/tui/prpause.js.map +1 -1
  276. package/dist/src/tui/remote.js +7 -1
  277. package/dist/src/tui/remote.js.map +1 -1
  278. package/dist/src/tui/route.js +143 -0
  279. package/dist/src/tui/route.js.map +1 -0
  280. package/dist/src/tui/setupstate.js +32 -0
  281. package/dist/src/tui/setupstate.js.map +1 -0
  282. package/dist/src/tui/sshtriage.js +102 -5
  283. package/dist/src/tui/sshtriage.js.map +1 -1
  284. package/dist/src/tui/theme.js +2 -1
  285. package/dist/src/tui/theme.js.map +1 -1
  286. package/dist/src/tui/unwedge.js +2 -12
  287. package/dist/src/tui/unwedge.js.map +1 -1
  288. package/dist/src/verdicts.js +17 -5
  289. package/dist/src/verdicts.js.map +1 -1
  290. package/dist/src/version.js +1 -1
  291. package/dist/src/version.js.map +1 -1
  292. package/dist/src/webhook.js +12 -1
  293. package/dist/src/webhook.js.map +1 -1
  294. package/dist/src/wire.js +34 -8
  295. package/dist/src/wire.js.map +1 -1
  296. package/dist/src/workerworld.js +11 -1
  297. package/dist/src/workerworld.js.map +1 -1
  298. package/docker/README.md +1 -1
  299. package/docker/companion/entrypoint.sh +20 -1
  300. package/docker/entrypoint.sh +57 -6
  301. package/docker-compose.yml +16 -1
  302. package/extensions/cli/home.ts +14 -0
  303. package/extensions/cli/login-route.ts +10 -0
  304. package/extensions/cli/selfimprove.ts +18 -0
  305. package/extensions/home.ts +484 -0
  306. package/extensions/login-route.ts +220 -0
  307. package/extensions/onboarding.ts +41 -11
  308. package/extensions/selfimprove.ts +65 -0
  309. package/extensions/setup-scan.ts +79 -766
  310. package/extensions/setup.ts +14 -70
  311. package/package.json +6 -4
  312. package/prompts/onboarding.md +58 -18
  313. package/schemas/review-result.v1.json +60 -0
  314. package/scripts/deploy.sh +8 -6
  315. package/scripts/mint-token.sh +25 -4
  316. package/scripts/qcr-host.sh +28 -5
  317. package/scripts/vm-ghcr-login.sh +7 -4
  318. package/src/archive/restore.ts +18 -21
  319. package/src/botworker.ts +13 -2
  320. package/src/channelfs.ts +3 -26
  321. package/src/ciretry.ts +2 -1
  322. package/src/cli.ts +59 -67
  323. package/src/cliops.ts +3 -2
  324. package/src/commands.ts +28 -2
  325. package/src/config.ts +62 -4
  326. package/src/daemon.ts +58 -7
  327. package/src/debuground.ts +3 -0
  328. package/src/debugroundpi.ts +12 -1
  329. package/src/dispatch.ts +5 -4
  330. package/src/engine/caserunner-sdk.ts +29 -314
  331. package/src/engine/debugpi.ts +17 -5
  332. package/src/execboundary.ts +16 -4
  333. package/src/github.ts +57 -15
  334. package/src/githubtoken.ts +435 -0
  335. package/src/home/adapters.ts +418 -0
  336. package/src/home/advanced.ts +421 -0
  337. package/src/home/advancedtools.ts +96 -0
  338. package/src/home/autoreview.ts +385 -0
  339. package/src/home/daemonstatus.ts +55 -0
  340. package/src/home/devicelogin.ts +107 -0
  341. package/src/home/launch.ts +257 -0
  342. package/src/home/loginowner.ts +248 -0
  343. package/src/home/memory.ts +176 -0
  344. package/src/home/model.ts +965 -0
  345. package/src/home/panel.ts +666 -0
  346. package/src/home/ports.ts +338 -0
  347. package/src/home/review.ts +206 -0
  348. package/src/home/reviews.ts +565 -0
  349. package/src/home/setup.ts +689 -0
  350. package/src/home/setupadapters.ts +217 -0
  351. package/src/home/setupcard.ts +288 -0
  352. package/src/home/setuptools.ts +57 -0
  353. package/src/home/spend.ts +194 -0
  354. package/src/home/switches.ts +293 -0
  355. package/src/home/switchon.ts +554 -0
  356. package/src/home/toolnames.ts +46 -0
  357. package/src/home/tune.ts +405 -0
  358. package/src/home/words.ts +8 -0
  359. package/src/homes.ts +17 -0
  360. package/src/install.ts +1 -1
  361. package/src/ipc/workerspec.ts +15 -0
  362. package/src/live/publish.ts +4 -1
  363. package/src/livecli.ts +6 -1
  364. package/src/loginhint.ts +8 -3
  365. package/src/loginrefresh/handover.ts +138 -0
  366. package/src/loginrefresh/lock.ts +72 -3
  367. package/src/loginrefresh/pirefresh.ts +3 -0
  368. package/src/loginrefresh/refresher.ts +34 -5
  369. package/src/loginrefresh/supervisor.ts +25 -2
  370. package/src/npmverbs.ts +55 -13
  371. package/src/onboard/choices.ts +40 -4
  372. package/src/onboard/engine.ts +70 -421
  373. package/src/onboard/pipin.ts +31 -0
  374. package/src/onboard/stepforward.ts +61 -0
  375. package/src/onboard/steps/appslug.ts +4 -1
  376. package/src/onboard/steps/bringup.ts +3 -2
  377. package/src/onboard/steps/compose.ts +31 -0
  378. package/src/onboard/steps/configfill.ts +144 -40
  379. package/src/onboard/steps/githubaccount.ts +716 -0
  380. package/src/onboard/steps/githubapp.ts +92 -8
  381. package/src/onboard/steps/golive.ts +2 -0
  382. package/src/onboard/steps/index.ts +11 -0
  383. package/src/onboard/steps/ingress.ts +98 -31
  384. package/src/onboard/steps/llmauth.ts +114 -29
  385. package/src/onboard/steps/loginshare.ts +258 -0
  386. package/src/onboard/steps/memory.ts +76 -3
  387. package/src/onboard/steps/proof.ts +473 -55
  388. package/src/onboard/steps/repoconfig.ts +85 -0
  389. package/src/onboard/steps/topology.ts +7 -6
  390. package/src/onboard/steps/transport.ts +15 -0
  391. package/src/onboard/steps/tuiroute.ts +119 -0
  392. package/src/onboard/steps/verifyup.ts +20 -1
  393. package/src/onboard/toolnames.ts +34 -0
  394. package/src/operatorwrite.ts +25 -0
  395. package/src/outcomes.ts +8 -1
  396. package/src/piagentdir.ts +12 -0
  397. package/src/pilogin.ts +32 -16
  398. package/src/posting-journal.ts +6 -1
  399. package/src/posting-outbox.ts +10 -2
  400. package/src/posting-receipt.ts +17 -0
  401. package/src/qcrbin.ts +16 -11
  402. package/src/recheck.ts +2 -1
  403. package/src/reconcile.ts +29 -3
  404. package/src/reviewargs.ts +119 -0
  405. package/src/reviewcli.ts +6 -167
  406. package/src/reviewdetach.ts +107 -13
  407. package/src/reviewhistory.ts +20 -6
  408. package/src/reviewnow.ts +76 -0
  409. package/src/scancli.ts +75 -826
  410. package/src/selfimprove.ts +90 -0
  411. package/src/setup/adapters.ts +13 -221
  412. package/src/setup/caserunner.ts +34 -273
  413. package/src/setup/engine.ts +62 -496
  414. package/src/setup/facts.ts +3 -627
  415. package/src/setup/findings.ts +3 -1264
  416. package/src/setup/presentation.ts +3 -18
  417. package/src/setup/scan.ts +3 -1033
  418. package/src/setup/scanbuild.ts +20 -114
  419. package/src/setup/scanlease.ts +3 -297
  420. package/src/setup/scanreport.ts +3 -373
  421. package/src/setup/scanrun.ts +3 -2268
  422. package/src/setup/scantree.ts +3 -259
  423. package/src/setup/stage2request.ts +3 -347
  424. package/src/setup/unitcut.ts +3 -523
  425. package/src/state.ts +43 -0
  426. package/src/statemachine.ts +4 -1
  427. package/src/takeover.ts +5 -0
  428. package/src/takeoverpi.ts +16 -2
  429. package/src/tui/app.ts +47 -0
  430. package/src/tui/board.ts +111 -14
  431. package/src/tui/cmdbar.ts +4 -1
  432. package/src/tui/companion.ts +80 -11
  433. package/src/tui/connect.ts +7 -1
  434. package/src/tui/deploytab.ts +2 -2
  435. package/src/tui/filters.ts +7 -3
  436. package/src/tui/ghread.ts +74 -7
  437. package/src/tui/index.ts +117 -23
  438. package/src/tui/memforward.ts +52 -3
  439. package/src/tui/memory.ts +3 -15
  440. package/src/tui/memoryurl.ts +22 -0
  441. package/src/tui/model.ts +36 -2
  442. package/src/tui/prpause.ts +11 -2
  443. package/src/tui/remote.ts +9 -1
  444. package/src/tui/route.ts +173 -0
  445. package/src/tui/setupstate.ts +56 -0
  446. package/src/tui/sshtriage.ts +127 -6
  447. package/src/tui/theme.ts +2 -1
  448. package/src/tui/unwedge.ts +2 -14
  449. package/src/verdicts.ts +23 -5
  450. package/src/version.ts +1 -1
  451. package/src/webhook.ts +10 -1
  452. package/src/wire.ts +42 -9
  453. package/src/workerworld.ts +16 -2
  454. package/dist/src/live/projection.js +0 -450
  455. package/dist/src/live/projection.js.map +0 -1
  456. package/dist/src/live/read.js +0 -60
  457. package/dist/src/live/read.js.map +0 -1
  458. package/dist/src/onboard/launch.js +0 -83
  459. package/dist/src/onboard/launch.js.map +0 -1
  460. package/dist/src/setup/ledger.js +0 -207
  461. package/dist/src/setup/ledger.js.map +0 -1
  462. package/dist/src/setup/stage2.js +0 -617
  463. package/dist/src/setup/stage2.js.map +0 -1
  464. package/dist/src/setup/textsniff.js +0 -89
  465. package/dist/src/setup/textsniff.js.map +0 -1
  466. package/src/live/projection.ts +0 -491
  467. package/src/live/read.ts +0 -86
  468. package/src/onboard/launch.ts +0 -115
  469. package/src/setup/ledger.ts +0 -324
  470. package/src/setup/stage2.ts +0 -926
  471. package/src/setup/textsniff.ts +0 -95
@@ -80,13 +80,14 @@ import {
80
80
  embedderInputsCheck,
81
81
  localEmbedderEnvEntries,
82
82
  } from "./embeddings.js";
83
+ import { CHOICE_KEYS } from "../choices.js";
83
84
  import { readEnvKey, readTomlKey, setEnvKey } from "./ingress.js";
84
85
  import { UID_CHOICE_KEY } from "./preflight.js";
85
86
  import { topologyOf } from "./topology.js";
86
87
 
87
88
  export const BRINGUP_STEP_ID = "bringup";
88
89
  /** Recorded timestamp of this step's successful bring-up. */
89
- export const BROUGHT_UP_CHOICE_KEY = "stack_brought_up_at";
90
+ export const BROUGHT_UP_CHOICE_KEY = CHOICE_KEYS.broughtUpAt;
90
91
 
91
92
  /**
92
93
  * The note, in the operator's words, emitted before the pull begins.
@@ -442,7 +443,7 @@ export function bringUpStep(opts: BringUpStepOptions = {}): OnboardingStep {
442
443
  }),
443
444
  );
444
445
  const ok = checks.every((c) => c.ok);
445
- if (ok) ctx.recordChoice(BROUGHT_UP_CHOICE_KEY, ctx.adapters.clock().toISOString());
446
+ if (ok) ctx.recordChoice(CHOICE_KEYS.broughtUpAt, ctx.adapters.clock().toISOString());
446
447
  return { ok, checks, data: { profile, up_at: ctx.adapters.clock().toISOString() } };
447
448
  },
448
449
 
@@ -74,6 +74,9 @@ export const MEMORY_IMAGE_REPO = "ghcr.io/quickchatai/quickreview-memory";
74
74
 
75
75
  export { CONTAINER_STATE_DIR };
76
76
  export const CONTAINER_DRYRUN_DIR = `${CONTAINER_STATE_DIR}/dryrun`;
77
+ /** The daemon's command root in the release image (`<root>/commands`, with the
78
+ * root-sealed `operator/` under it and the result files in `results/`). */
79
+ export const CONTAINER_COMMANDS_DIR = "/opt/qcr/commands";
77
80
 
78
81
  /** `<sandbox root>` — the engine's contract is stateDir = `<root>/state`. */
79
82
  export function rootOf(adapters: OnboardAdapters): string {
@@ -157,6 +160,12 @@ export function daemonExecArgs(root: string, argv: readonly string[]): string[]
157
160
  return composeArgs(root, ["exec", "-T", DAEMON_SERVICE, ...argv]);
158
161
  }
159
162
 
163
+ /** The same exec as uid 0 — the operator seal on `commands/operator/`, which
164
+ * the daemon's own uid (every session's) can read but not write. */
165
+ export function daemonRootExecArgs(root: string, argv: readonly string[]): string[] {
166
+ return composeArgs(root, ["exec", "-T", "-u", "0", DAEMON_SERVICE, ...argv]);
167
+ }
168
+
160
169
  export async function daemonExec(
161
170
  runner: ProcessRunner,
162
171
  root: string,
@@ -172,9 +181,31 @@ export interface PsReport {
172
181
  detail: string;
173
182
  }
174
183
 
184
+ /** docker-compose.yml's `name:` — the project label on its containers. */
185
+ export const COMPOSE_PROJECT = "quickreview";
186
+
187
+ /** `docker ps` over the compose project label, one compose-ps-shaped JSON row
188
+ * per running container: asks Docker without reading the compose file. */
189
+ export function projectPsArgs(): string[] {
190
+ return [
191
+ "ps",
192
+ "--filter",
193
+ `label=com.docker.compose.project=${COMPOSE_PROJECT}`,
194
+ "--format",
195
+ '{"Service":"{{.Label "com.docker.compose.service"}}","State":"{{.State}}"}',
196
+ ];
197
+ }
198
+
175
199
  export async function composePs(runner: ProcessRunner, root: string): Promise<PsReport> {
176
200
  const ps = await runner.run("docker", composeArgs(root, ["ps", "--format", "json"]));
177
201
  if (ps.code !== 0) {
202
+ // `docker compose ps` interpolates the compose file first, and before the
203
+ // bring-up has written the project .env (QCR_SECRETS_DIR and the rest) it
204
+ // refuses to run at all. That is a question about the file, not the
205
+ // stack: Docker itself still answers which of the project's containers
206
+ // run. Only when Docker cannot answer either is the state unknown.
207
+ const plain = await runner.run("docker", projectPsArgs());
208
+ if (plain.code === 0) return { ok: true, rows: parseComposePs(plain.stdout), detail: "" };
178
209
  return {
179
210
  ok: false,
180
211
  rows: [],
@@ -63,6 +63,7 @@ import {
63
63
  type ChoiceName,
64
64
  botLoginFor,
65
65
  ingressChoiceOf,
66
+ userAuthRecorded,
66
67
  type IngressChoice,
67
68
  } from "../choices.js";
68
69
  import {
@@ -76,8 +77,8 @@ import {
76
77
  } from "../engine.js";
77
78
  import { deliverConfigChange, configDeliveryChecks } from "./configdelivery.js";
78
79
  import type { TransportOptions } from "./transport.js";
79
- import { BARE_HOSTNAME_RE, readTomlKey, secretsDirFor } from "./ingress.js";
80
- import { SSH_ALIAS_RE } from "./topology.js";
80
+ import { BARE_HOSTNAME_RE, CONTAINER_STATE_DIR, readTomlKey, secretsDirFor } from "./ingress.js";
81
+ import { SSH_ALIAS_RE, topologyOf, type Topology } from "./topology.js";
81
82
 
82
83
  export const CONFIG_FILL_STEP_ID = "config_fill";
83
84
 
@@ -120,9 +121,12 @@ function escapeRe(s: string): string {
120
121
  * marker in place — the EXPLANATION after it is the part worth keeping. */
121
122
  export function rewriteFillMarker(comment: string): string {
122
123
  if (!/←\s*FILL/.test(comment)) return comment;
123
- return comment.replace(/←\s*FILL(\s*\(\d+\))?\s*:?\s*/, "filled by qcr onboard: ");
124
+ return comment.replace(/←\s*FILL(\s*\(\d+\))?\s*:?\s*/, "filled by qcr: ");
124
125
  }
125
126
 
127
+ /** A line without the CR of its CRLF ending. */
128
+ const bare = (line: string): string => line.replace(/\r$/, "");
129
+
126
130
  function sectionAt(lines: string[]): string[] {
127
131
  const out: string[] = [];
128
132
  let cur = "";
@@ -134,10 +138,12 @@ function sectionAt(lines: string[]): string[] {
134
138
  if (splitTail(continuation) !== null) continuation = null;
135
139
  continue;
136
140
  }
137
- const m = HEADER_RE.exec(line);
141
+ const m = HEADER_RE.exec(bare(line));
138
142
  if (m) cur = m[1]!.trim();
139
143
  out.push(cur);
140
- const assignment = /^\s*(?:"(?:[^"\\]|\\.)*"|'[^']*'|[A-Za-z0-9_-]+)\s*=\s*(.*)$/.exec(line);
144
+ const assignment = /^\s*(?:"(?:[^"\\]|\\.)*"|'[^']*'|[A-Za-z0-9_-]+)\s*=\s*(.*)$/.exec(
145
+ bare(line),
146
+ );
141
147
  if (assignment && splitTail(assignment[1]!) === null) continuation = assignment[1]!;
142
148
  }
143
149
  return out;
@@ -148,9 +154,18 @@ function sectionAt(lines: string[]): string[] {
148
154
  * key order. An existing assignment has only its VALUE replaced (its trailing
149
155
  * comment survives, with a spent `← FILL` marker rewritten); a commented-out
150
156
  * assignment is uncommented in place; a key absent from its section is appended
151
- * to the end of that section; a missing section is appended at EOF.
157
+ * to the end of that section; a missing section is appended at EOF. Line
158
+ * endings survive too: a CRLF file is patched as LF and given its CRs back, so
159
+ * an edited or added line ends the way every other line does, and in a file of
160
+ * mixed endings the edited line keeps its own.
152
161
  */
153
162
  export function patchToml(text: string, edits: readonly TomlEdit[]): string {
163
+ if (text.includes("\r\n") && !/(^|[^\r])\n/.test(text))
164
+ return patchLines(text.replace(/\r\n/g, "\n"), edits).replace(/\n/g, "\r\n");
165
+ return patchLines(text, edits);
166
+ }
167
+
168
+ function patchLines(text: string, edits: readonly TomlEdit[]): string {
154
169
  const lines = text.split("\n");
155
170
  for (const edit of edits) {
156
171
  const sections = sectionAt(lines);
@@ -161,7 +176,7 @@ export function patchToml(text: string, edits: readonly TomlEdit[]): string {
161
176
  for (const re of [live, commented]) {
162
177
  for (let i = 0; i < lines.length && !applied; i++) {
163
178
  if (sections[i] !== edit.section) continue;
164
- const m = re.exec(lines[i]!);
179
+ const m = re.exec(bare(lines[i]!));
165
180
  if (!m) continue;
166
181
  try {
167
182
  if (!Object.hasOwn(parseToml(`${m[2]} = 0`), edit.key)) continue;
@@ -170,11 +185,13 @@ export function patchToml(text: string, edits: readonly TomlEdit[]): string {
170
185
  }
171
186
  let end = i;
172
187
  let tail = m[4]!;
173
- while (splitTail(tail) === null && end + 1 < lines.length) tail += "\n" + lines[++end]!;
188
+ while (splitTail(tail) === null && end + 1 < lines.length)
189
+ tail += "\n" + bare(lines[++end]!);
174
190
  const parts = splitTail(tail);
175
191
  if (!parts) throw new Error(`cannot patch unterminated TOML assignment ${edit.key}`);
176
192
  const comment = parts.comment ? `${parts.gap}${rewriteFillMarker(parts.comment)}` : "";
177
- lines.splice(i, end - i + 1, `${m[1]}${m[2]}${m[3]}${edit.value}${comment}`);
193
+ const cr = lines[end]!.endsWith("\r") ? "\r" : "";
194
+ lines.splice(i, end - i + 1, `${m[1]}${m[2]}${m[3]}${edit.value}${comment}${cr}`);
178
195
  applied = true;
179
196
  }
180
197
  if (applied) break;
@@ -228,9 +245,17 @@ export interface FillFacts {
228
245
  secrets_dir: string;
229
246
  topology: string;
230
247
  ingress: string;
248
+ /** The GitHub route the github_account step recorded ("user" | "app"), ""
249
+ * while unanswered — which is the App route's interview. */
250
+ github_auth?: string;
251
+ /** The person's own login on the "user" route (`GET /user`). */
252
+ account_login?: string;
231
253
  }
232
254
 
233
- export type AnswerKey = Exclude<keyof FillFacts, "secrets_dir" | "topology" | "ingress">;
255
+ export type AnswerKey = Exclude<
256
+ keyof FillFacts,
257
+ "secrets_dir" | "topology" | "ingress" | "github_auth" | "account_login"
258
+ >;
234
259
 
235
260
  export interface Question {
236
261
  id: AnswerKey;
@@ -282,7 +307,9 @@ export const QUESTIONS: readonly Question[] = [
282
307
  "clone directory, backfill identity — is derived from this one answer.",
283
308
  prompt: 'Which repository should the reviewer watch? ("owner/name")',
284
309
  optional: false,
285
- from: [CHOICE_KEYS.repo],
310
+ // On the own-account route the github_account step already asked, from
311
+ // the repos the person's account reaches.
312
+ from: [CHOICE_KEYS.accountRepo, CHOICE_KEYS.repo],
286
313
  read: (cfg) => cfg.github.repo,
287
314
  validate: (v) =>
288
315
  REPO_SLUG_RE.test(v)
@@ -298,8 +325,10 @@ export const QUESTIONS: readonly Question[] = [
298
325
  "The companion — the local pi that sits beside the TUI — reads the code a review is about. Your checkout is " +
299
326
  "mounted READ-ONLY into its container: it can read every file and change none of them. A missing checkout only " +
300
327
  "makes the companion less informed; it never breaks the reviewer.",
301
- prompt: "Where is your local checkout of that repository?",
302
- optional: false,
328
+ prompt:
329
+ "Where is your local checkout of that repository? (Enter if there is none here — the companion starts without it)",
330
+ // A fresh box has no checkout, and the daemon clones the repository itself.
331
+ optional: true,
303
332
  from: [CHOICE_KEYS.repoDir],
304
333
  read: (cfg) => cfg.companion.reviewed_repo_dir,
305
334
  validate: (v) => {
@@ -325,6 +354,8 @@ export const QUESTIONS: readonly Question[] = [
325
354
  prompt: "What is your GitHub App's slug?",
326
355
  optional: false,
327
356
  from: [CHOICE_KEYS.appSlug],
357
+ // No App exists on the own-account route: `[github] app_slug` stays "".
358
+ asked: (choices) => !userAuthRecorded(choices),
328
359
  read: (cfg) => cfg.github.app_slug,
329
360
  validate: (v) =>
330
361
  SLUG_RE.test(v)
@@ -390,7 +421,7 @@ export const QUESTIONS: readonly Question[] = [
390
421
  "It is never used to authenticate anything — the App credential is the only identity that acts.",
391
422
  prompt: "What is your GitHub login? (Enter to skip)",
392
423
  optional: true,
393
- from: [CHOICE_KEYS.ownerLogin],
424
+ from: [CHOICE_KEYS.accountLogin, CHOICE_KEYS.ownerLogin],
394
425
  read: (cfg) => cfg.owner.github_login,
395
426
  validate: validateGithubLogin,
396
427
  },
@@ -493,27 +524,51 @@ export interface Derivable {
493
524
  holds(cfg: Config, facts: FillFacts): string | null;
494
525
  }
495
526
 
496
- /** The TUI transport a recorded topology implies; null while it is unrecorded. */
497
- function tuiTargetFor(topology: string): "local" | "ssh" | null {
498
- return topology === "local" ? "local" : topology === "vm" ? "ssh" : null;
527
+ /** The topology a fill is for: the recorded one, and "local" while none is
528
+ * recorded — `topologyOf`'s reading, the one every other step uses. */
529
+ function fillTopology(topology: string): Topology {
530
+ return topologyOf({ [CHOICE_KEYS.topology]: topology });
531
+ }
532
+
533
+ /** The TUI transport a topology implies (an unrecorded one is local). */
534
+ function tuiTargetFor(topology: string): "local" | "ssh" {
535
+ return fillTopology(topology) === "vm" ? "ssh" : "local";
499
536
  }
500
537
 
538
+ /** The release image's state dir inside the compose daemon container (the
539
+ * `qcr-state` volume), where the local TUI reads status.json. */
540
+ const LOCAL_TUI_STATE_DIR = CONTAINER_STATE_DIR;
541
+
501
542
  export const DERIVABLES: readonly Derivable[] = [
502
543
  {
503
544
  id: "bot_login",
504
545
  path: "[github] bot_login",
505
546
  explain:
506
- "the bot login is the App slug plus the [bot] suffix — asking for it twice is how the two drift apart",
507
- write: (f) => ({
508
- section: "github",
509
- key: "bot_login",
510
- value: tomlString(botLoginFor(f.app_slug)),
511
- }),
547
+ "the bot login is the App slug plus the [bot] suffix — asking for it twice is how the two drift apart. On your own " +
548
+ "GitHub account it is your login: the reviews are posted as you",
549
+ write: (f) =>
550
+ f.github_auth === "user"
551
+ ? f.account_login
552
+ ? { section: "github", key: "bot_login", value: tomlString(f.account_login) }
553
+ : null
554
+ : {
555
+ section: "github",
556
+ key: "bot_login",
557
+ value: tomlString(botLoginFor(f.app_slug)),
558
+ },
512
559
  stable: true,
513
- holds: (cfg) =>
514
- cfg.github.bot_login === botLoginFor(cfg.github.app_slug)
560
+ holds: (cfg, f) => {
561
+ if (f.github_auth === "user") {
562
+ if (!cfg.github.bot_login.trim())
563
+ return "[github] bot_login is empty — on your own account it is your GitHub login; run the github_account step";
564
+ return f.account_login && cfg.github.bot_login !== f.account_login
565
+ ? `[github] bot_login is "${cfg.github.bot_login}" but your account is "${f.account_login}" — run the github_account step again`
566
+ : null;
567
+ }
568
+ return cfg.github.bot_login === botLoginFor(cfg.github.app_slug)
515
569
  ? null
516
- : `[github] bot_login is "${cfg.github.bot_login}" but the App slug "${cfg.github.app_slug}" implies "${botLoginFor(cfg.github.app_slug)}"`,
570
+ : `[github] bot_login is "${cfg.github.bot_login}" but the App slug "${cfg.github.app_slug}" implies "${botLoginFor(cfg.github.app_slug)}"`;
571
+ },
517
572
  },
518
573
  {
519
574
  id: "clone_dir",
@@ -547,7 +602,7 @@ export const DERIVABLES: readonly Derivable[] = [
547
602
  holds: (cfg) =>
548
603
  cfg.github.secrets_dir.trim()
549
604
  ? null
550
- : "[github] secrets_dir is empty — the App credential has nowhere to live",
605
+ : "[github] secrets_dir is empty — the GitHub credential has nowhere to live",
551
606
  },
552
607
  {
553
608
  id: "review_team",
@@ -588,10 +643,12 @@ export const DERIVABLES: readonly Derivable[] = [
588
643
  "the history importer's identity and labels cascade off [github] — never a second copy to keep in sync",
589
644
  write: () => null,
590
645
  stable: true,
591
- holds: (cfg) => {
646
+ holds: (cfg, f) => {
592
647
  if (cfg.backfill.bot_login !== cfg.github.bot_login)
593
648
  return "[backfill] bot_login no longer follows [github] bot_login";
594
- if (cfg.backfill.bot_actor !== cfg.github.app_slug)
649
+ // The actor is the App slug; the own-account route has no App, and the
650
+ // loader decides what stands in for it there.
651
+ if (f.github_auth !== "user" && cfg.backfill.bot_actor !== cfg.github.app_slug)
595
652
  return "[backfill] bot_actor no longer follows [github] app_slug";
596
653
  if (cfg.backfill.trigger_label !== cfg.github.trigger_label)
597
654
  return "[backfill] trigger_label no longer follows [github] trigger_label";
@@ -615,15 +672,12 @@ export const DERIVABLES: readonly Derivable[] = [
615
672
  path: "[tui] target",
616
673
  explain:
617
674
  'follows the recorded topology: "local" when the daemon runs on this machine (the TUI talks to local Docker), "ssh" on the VM route',
618
- write: (f) => {
619
- const target = tuiTargetFor(f.topology);
620
- return target ? { section: "tui", key: "target", value: tomlString(target) } : null;
621
- },
675
+ write: (f) => ({ section: "tui", key: "target", value: tomlString(tuiTargetFor(f.topology)) }),
622
676
  stable: true,
623
677
  holds: (cfg, f) => {
624
678
  const want = tuiTargetFor(f.topology);
625
- return want && cfg.tui.target !== want
626
- ? `[tui] target = "${cfg.tui.target}", but the recorded topology is ${f.topology} — the TUI would ` +
679
+ return cfg.tui.target !== want
680
+ ? `[tui] target = "${cfg.tui.target}", but the topology is ${f.topology || "local (none recorded)"} — the TUI would ` +
627
681
  `${want === "local" ? "look for the daemon over ssh" : "look for the daemon in local Docker"}. ` +
628
682
  `Fix: run this step again (it writes target = "${want}")`
629
683
  : null;
@@ -635,7 +689,8 @@ export const DERIVABLES: readonly Derivable[] = [
635
689
  explain:
636
690
  "the bundled local Compose project runs service daemon as quickreview-daemon-1; preserve an operator's custom container",
637
691
  write: (f, fresh, existing) =>
638
- f.topology === "local" && (fresh || existing?.tui.container === TUI_DEFAULTS.container)
692
+ fillTopology(f.topology) === "local" &&
693
+ (fresh || existing?.tui.container === TUI_DEFAULTS.container)
639
694
  ? { section: "tui", key: "container", value: tomlString("quickreview-daemon-1") }
640
695
  : null,
641
696
  stable: false,
@@ -647,12 +702,41 @@ export const DERIVABLES: readonly Derivable[] = [
647
702
  explain:
648
703
  "the bundled local Compose project runs the memory sidecar as quickreview-memory-1, where the memory forward must relay; preserve an operator's custom container",
649
704
  write: (f, fresh, existing) =>
650
- f.topology === "local" && (fresh || (existing?.tui.memory_container ?? "") === "")
705
+ fillTopology(f.topology) === "local" &&
706
+ (fresh || (existing?.tui.memory_container ?? "") === "")
651
707
  ? { section: "tui", key: "memory_container", value: tomlString("quickreview-memory-1") }
652
708
  : null,
653
709
  stable: false,
654
710
  holds: () => null,
655
711
  },
712
+ {
713
+ id: "tui_remote_state_dir",
714
+ path: "[tui] remote_state_dir",
715
+ explain:
716
+ `on this machine the TUI reads status.json inside the daemon container, where the release image keeps its state (${LOCAL_TUI_STATE_DIR}, ` +
717
+ "the compose state volume) — the shipped default is the VM host's path; preserve an operator's custom path",
718
+ write: (f, fresh, existing) =>
719
+ fillTopology(f.topology) === "local" &&
720
+ (fresh || existing?.tui.remote_state_dir === TUI_DEFAULTS.remote_state_dir)
721
+ ? { section: "tui", key: "remote_state_dir", value: tomlString(LOCAL_TUI_STATE_DIR) }
722
+ : null,
723
+ stable: false,
724
+ holds: () => null,
725
+ },
726
+ {
727
+ id: "tui_sudo_docker",
728
+ path: "[tui] sudo_docker",
729
+ explain:
730
+ "off on this machine: the wizard's own bring-up drives docker without sudo, so the TUI does too (a sudo password " +
731
+ "prompt behind the TUI's screen can only fail); on the VM route sudo stays the shipped default",
732
+ write: (f, fresh, existing) =>
733
+ fillTopology(f.topology) === "local" &&
734
+ (fresh || existing?.tui.sudo_docker === TUI_DEFAULTS.sudo_docker)
735
+ ? { section: "tui", key: "sudo_docker", value: "false" }
736
+ : null,
737
+ stable: false,
738
+ holds: () => null,
739
+ },
656
740
  {
657
741
  id: "tui_ssh_host",
658
742
  path: "[tui] ssh_host",
@@ -729,6 +813,9 @@ export const DEFAULT_SECRETS_DIR = "";
729
813
  * Read from ITS artefacts (the `deploy_shape` marker it writes) so this step
730
814
  * reports the same required set without duplicating the wizard's validators. */
731
815
  export const REQUIRED_SECRETS: readonly string[] = ["app_id", "app.pem", "webhook_secret"];
816
+ /** The own-account route's one secret: the person's token, written by
817
+ * `qcr github-token sync` (the github_account step runs it). */
818
+ export const USER_ROUTE_SECRETS: readonly string[] = ["github_token"];
732
819
  export const TUNNEL_SHAPE_SECRET = "tunnel_token";
733
820
 
734
821
  /** Unlike `readTextOrNull`, only ABSENCE is null: a file that exists and
@@ -918,6 +1005,8 @@ export async function collectAnswers(
918
1005
  DEFAULT_SECRETS_DIR,
919
1006
  topology: str(choices, CHOICE_KEYS.topology),
920
1007
  ingress: str(choices, CHOICE_KEYS.ingress),
1008
+ github_auth: str(choices, CHOICE_KEYS.githubAuth),
1009
+ account_login: str(choices, CHOICE_KEYS.accountLogin),
921
1010
  };
922
1011
  return { facts, missing, rejected, asked, ignored };
923
1012
  }
@@ -992,7 +1081,17 @@ export function changedEdits(text: string, edits: readonly TomlEdit[]): TomlEdit
992
1081
  }
993
1082
 
994
1083
  /** What the secrets dir holds, at the one resolution every step uses. */
995
- function secretsReport(dir: string): CheckResult {
1084
+ function secretsReport(dir: string, userRoute: boolean): CheckResult {
1085
+ if (userRoute) {
1086
+ const missing = USER_ROUTE_SECRETS.filter((f) => !existsSync(join(dir, f)));
1087
+ return {
1088
+ name: "secrets",
1089
+ ok: true,
1090
+ detail: missing.length
1091
+ ? `${dir}: still missing ${missing.join(", ")} — the github_account step writes it from your GitHub sign-in (no App secrets on this route)`
1092
+ : `${dir}: your GitHub token is present — the own-account route needs no App secrets`,
1093
+ };
1094
+ }
996
1095
  if (!existsSync(dir)) {
997
1096
  return {
998
1097
  name: "secrets",
@@ -1221,11 +1320,14 @@ export function configFillStep(opts: { transport?: TransportOptions } = {}): Onb
1221
1320
  ok: true,
1222
1321
  detail:
1223
1322
  `topology ${facts.topology || "(unrecorded — filled for a daemon on this machine, so [tui] ssh_host stays empty)"}, ` +
1224
- `ingress ${facts.ingress || "(unrecorded — filled for an ingress that assigns its own hostname, so [server] hostname stays empty)"}` +
1323
+ `ingress ${facts.ingress || "(unrecorded — filled for an ingress that assigns its own hostname, so [server] hostname stays empty)"}, ` +
1324
+ `github ${facts.github_auth === "user" ? "own account (no App questions, no App secrets)" : "App"}` +
1225
1325
  `${str(ctx.choices, CHOICE_KEYS.authRoute) ? `, auth route ${str(ctx.choices, CHOICE_KEYS.authRoute)}` : ""}`,
1226
1326
  });
1227
1327
  // Read after the write, so the dir is the one the written config names.
1228
- checks.push(secretsReport(secretsDirFor(ctx.adapters, ctx.choices)));
1328
+ checks.push(
1329
+ secretsReport(secretsDirFor(ctx.adapters, ctx.choices), userAuthRecorded(ctx.choices)),
1330
+ );
1229
1331
 
1230
1332
  const ok = checks.every((c) => c.ok);
1231
1333
  if (ok) recordChoices(ctx, facts, configPath, parsed);
@@ -1320,6 +1422,8 @@ export function configFillStep(opts: { transport?: TransportOptions } = {}): Onb
1320
1422
  secrets_dir: cfg.github.secrets_dir,
1321
1423
  topology: str(ctx.choices, CHOICE_KEYS.topology),
1322
1424
  ingress: str(ctx.choices, CHOICE_KEYS.ingress),
1425
+ github_auth: str(ctx.choices, CHOICE_KEYS.githubAuth),
1426
+ account_login: str(ctx.choices, CHOICE_KEYS.accountLogin),
1323
1427
  };
1324
1428
  for (const d of DERIVABLES) {
1325
1429
  if (!d.stable) continue;