@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
package/src/cli.ts CHANGED
@@ -68,6 +68,7 @@ import { sanitizeText } from "./tui/paint.js";
68
68
  import { operatorCommandWriteScript } from "./tui/unwedge.js";
69
69
  import { shellQuote, transportOf } from "./tui/remote.js";
70
70
  import { MutationGate } from "@quickchatai/pi-quickreview/src/gate.js";
71
+ import { botAccountLogin, isPersonMode } from "@quickchatai/pi-quickreview/src/identity.js";
71
72
  import type { GitHubAdapter } from "./github.js";
72
73
  import type { OutcomeCapture } from "./outcomes.js";
73
74
  import type { Learner, MineReport } from "./learner/learner.js";
@@ -198,17 +199,18 @@ export async function cmdOutcomes(rest: string[], json: boolean): Promise<number
198
199
  const outcomes = new OutcomeCapture({
199
200
  store,
200
201
  botLogin: cfg.github.bot_login,
202
+ personMode: isPersonMode(cfg.github),
201
203
  ...(existsSync(worktrees.basePath) ? { git: worktrees } : {}),
202
204
  });
203
205
  const since = new Date(Date.now() - days * 86_400_000).toISOString();
204
206
  if (capture) {
205
207
  let read: GitHubAdapter | undefined;
206
208
  if (readGithub) {
207
- const { TokenMinter } = await import("@quickchatai/pi-quickreview/src/githubAuth.js");
209
+ const { githubCredential } = await import("@quickchatai/pi-quickreview/src/githubAuth.js");
208
210
  const { GitHubAdapter } = await import("./github.js");
209
211
  read = new GitHubAdapter(
210
212
  cfg,
211
- new TokenMinter(cfg.github.secrets_dir),
213
+ githubCredential(cfg.github),
212
214
  recordOnlyGate(join(stateDir(), "dryrun")),
213
215
  );
214
216
  }
@@ -365,7 +367,8 @@ export async function cmdLessons(rest: string[], json: boolean): Promise<number>
365
367
  root,
366
368
  eodTime: cfg.eod.time,
367
369
  timeZone: cfg.eod.timezone,
368
- botLogin: cfg.github.bot_login,
370
+ // The bot ACCOUNT ("" in person mode, where the person's words are a human's).
371
+ botLogin: botAccountLogin(cfg.github),
369
372
  baseBranch: cfg.github.base_branch,
370
373
  config: learnerKnobsOf(cfg.memory),
371
374
  ...(existsSync(worktrees.basePath) ? { git: worktrees } : {}),
@@ -703,13 +706,13 @@ export interface DoubtedFinding {
703
706
  * when the developer machine carries the app credential. */
704
707
  export async function enrichWhy(payload: WhyPayload, secretsDir?: string): Promise<WhyPayload> {
705
708
  try {
706
- const { TokenMinter } = await import("@quickchatai/pi-quickreview/src/githubAuth.js");
709
+ const { githubCredential } = await import("@quickchatai/pi-quickreview/src/githubAuth.js");
707
710
  const { GitHubAdapter } = await import("./github.js");
708
711
  const cfg = loadConfig();
709
712
  const gate = recordOnlyGate(join(stateDir(), "dryrun"), "qcr why");
710
713
  const adapter = new GitHubAdapter(
711
714
  cfg,
712
- new TokenMinter(secretsDir ?? cfg.github.secrets_dir),
715
+ githubCredential({ ...cfg.github, secrets_dir: secretsDir ?? cfg.github.secrets_dir }),
713
716
  gate,
714
717
  );
715
718
  const convo = await adapter.prConversation(payload.pr, 3);
@@ -928,14 +931,22 @@ export async function cmdBackfill(
928
931
  limit: number | undefined,
929
932
  json: boolean,
930
933
  ): Promise<void> {
931
- const { TokenMinter } = await import("@quickchatai/pi-quickreview/src/githubAuth.js");
934
+ const { githubCredential } = await import("@quickchatai/pi-quickreview/src/githubAuth.js");
932
935
  const { GitHubAdapter } = await import("./github.js");
933
936
  const { runBackfill } = await import("./backfill.js");
934
937
  const cfg = loadConfig();
938
+ // The import reads "reviews by the bot's login" as the bot's history. Under
939
+ // the person's own credential that login is the person's, and their own
940
+ // reviews are not the bot's: there is nothing this importer can tell apart.
941
+ if (isPersonMode(cfg.github))
942
+ throw new Error(
943
+ 'qcr backfill imports a GitHub App\'s review history; under [github] auth = "user" the login is ' +
944
+ "your own, so your reviews and the bot's cannot be told apart by it — nothing to import",
945
+ );
935
946
  // A gate that can only ever record: the importer performs no mutations, and
936
947
  // this keeps that structural rather than prompted.
937
948
  const gate = recordOnlyGate(join(stateDir(), "dryrun"), "qcr backfill");
938
- const adapter = new GitHubAdapter(cfg, new TokenMinter(cfg.github.secrets_dir), gate);
949
+ const adapter = new GitHubAdapter(cfg, githubCredential(cfg.github), gate);
939
950
  const store = openStore();
940
951
  try {
941
952
  const stats = await runBackfill(store, adapter, cfg, {
@@ -976,12 +987,12 @@ export async function cmdHistory(rest: string[]): Promise<number> {
976
987
  const months = intFlag(rest, "--months", HISTORY_USAGE);
977
988
  if (sub === "digest") {
978
989
  const limit = intFlag(rest, "--limit", HISTORY_USAGE);
979
- const { TokenMinter } = await import("@quickchatai/pi-quickreview/src/githubAuth.js");
990
+ const { githubCredential } = await import("@quickchatai/pi-quickreview/src/githubAuth.js");
980
991
  const { GitHubAdapter } = await import("./github.js");
981
992
  const { collectReviewHistory } = await import("./reviewhistory.js");
982
993
  const cfg = loadConfig();
983
994
  const gate = recordOnlyGate(join(stateDir(), "dryrun"), "qcr history");
984
- const adapter = new GitHubAdapter(cfg, new TokenMinter(cfg.github.secrets_dir), gate);
995
+ const adapter = new GitHubAdapter(cfg, githubCredential(cfg.github), gate);
985
996
  const store = openStore();
986
997
  try {
987
998
  const digest = await collectReviewHistory(adapter, store, cfg, {
@@ -1230,12 +1241,13 @@ async function cmdDrain(rest: string[]): Promise<number> {
1230
1241
  // until `qcr resume-pr`, and no session lifts it. The cutover wizard's stage 9
1231
1242
  // pauses every tracked PR but one with it before the drain is lifted.
1232
1243
 
1233
- export const PR_LEVER_USAGE = "usage: qcr pause-pr <pr> [--reason <text>] | qcr resume-pr <pr>";
1244
+ export const PR_LEVER_USAGE =
1245
+ "usage: qcr pause-pr <pr> [--reason <text>] | qcr resume-pr <pr> | qcr review-now <pr>";
1234
1246
 
1235
1247
  /** `qcr pause-pr|resume-pr`'s arguments: one PR number, and for a pause an
1236
1248
  * optional `--reason <text>` (the reason the board and `qcr status` show). */
1237
1249
  export function parsePrLeverArgs(
1238
- verb: "pause-pr" | "resume-pr",
1250
+ verb: "pause-pr" | "resume-pr" | "review-now",
1239
1251
  rest: readonly string[],
1240
1252
  ): import("./tui/prpause.js").PrLeverCommand {
1241
1253
  let pr: number | undefined;
@@ -1251,10 +1263,14 @@ export function parsePrLeverArgs(
1251
1263
  }
1252
1264
  if (pr === undefined) throw new Error(PR_LEVER_USAGE);
1253
1265
  if (verb === "resume-pr") return { command: "resume-pr", pr };
1266
+ if (verb === "review-now") return { command: "review-now", pr };
1254
1267
  return { command: "pause-pr", pr, reason: reason ?? "paused by the operator (qcr pause-pr)" };
1255
1268
  }
1256
1269
 
1257
- async function cmdPrLever(verb: "pause-pr" | "resume-pr", rest: string[]): Promise<number> {
1270
+ async function cmdPrLever(
1271
+ verb: "pause-pr" | "resume-pr" | "review-now",
1272
+ rest: string[],
1273
+ ): Promise<number> {
1258
1274
  const cmd = parsePrLeverArgs(verb, rest);
1259
1275
  const cfg = loadConfig();
1260
1276
  const { runnerFor } = await import("./tui/remote.js");
@@ -1540,6 +1556,7 @@ export async function cmdCompanion(sub: string | undefined): Promise<void> {
1540
1556
  dir: companionGithubDir(),
1541
1557
  repo: full.github.repo,
1542
1558
  secretsDir: full.github.secrets_dir,
1559
+ auth: full.github.auth ?? "app",
1543
1560
  }).refresh();
1544
1561
  console.log(`companion ${cfg.container_name}: ${tokenOutcomeLine(gh)}`);
1545
1562
  const mirror = lc.syncConfigMirror();
@@ -1840,55 +1857,6 @@ export async function cmdSetup(rest: string[]): Promise<number> {
1840
1857
  return 0;
1841
1858
  }
1842
1859
 
1843
- /** `qcr onboard` — the guided setup wizard: pi at the pinned
1844
- * version via npx (works from a bare clone; pi stays a non-dependency), the
1845
- * onboarding prompt + extension + a restricted tool set, then the TUI handoff
1846
- * when the checklist verifies complete. Resumable: run it again after a crash
1847
- * and completed steps are re-verified, not repeated. */
1848
- export async function cmdOnboard(): Promise<number> {
1849
- const { piPinFromPackageJson, onboardingSessionDir, runOnboard } =
1850
- await import("./onboard/launch.js");
1851
- const { checklistComplete } = await import("./onboard/engine.js");
1852
- const { spawn } = await import("node:child_process");
1853
- const root = sandboxRoot();
1854
- // Pin source: config.toml [pi] when it loads; package.json's "qcrPi" block on a
1855
- // bare clone (config.toml is written later, BY onboarding). Same pinned
1856
- // version by the rule-7 mirror discipline.
1857
- let pin: { package: string; version: string } | null = null;
1858
- try {
1859
- const { package: pkg, version } = loadConfig().pi;
1860
- pin = { package: pkg, version };
1861
- } catch {
1862
- pin = piPinFromPackageJson(root);
1863
- }
1864
- if (!pin)
1865
- throw new Error(
1866
- 'onboard: pi pin not found (need config.toml [pi] or the package.json "qcrPi" block)',
1867
- );
1868
- mkdirSync(onboardingSessionDir(root), { recursive: true });
1869
- const run = (cmd: string, args: string[]): Promise<number> =>
1870
- new Promise((resolvePromise) => {
1871
- const child = spawn(cmd, args, {
1872
- stdio: "inherit",
1873
- cwd: root,
1874
- env: { ...process.env, QCR_ROOT: root },
1875
- });
1876
- child.on("error", (err) => {
1877
- console.error(`onboard: ${cmd} failed to start: ${err.message}`);
1878
- resolvePromise(1);
1879
- });
1880
- child.on("close", (code) => resolvePromise(code ?? 1));
1881
- });
1882
- return runOnboard({
1883
- pin,
1884
- root,
1885
- spawn: run,
1886
- isComplete: () => checklistComplete(join(root, "state")),
1887
- log: (line) => console.log(line),
1888
- cliPath: join(root, "dist", "src", "cli.js"),
1889
- });
1890
- }
1891
-
1892
1860
  export const ARCHIVE_USAGE =
1893
1861
  "usage: qcr archive status [--plan] [--json] | qcr archive run | qcr archive restore <pr> | " +
1894
1862
  "qcr archive extract <pr> --to <dir>";
@@ -2137,6 +2105,16 @@ export async function main(rawArgv: string[]): Promise<void> {
2137
2105
  const { runTui } = await import("./tui/index.js");
2138
2106
  return runTui(rest);
2139
2107
  }
2108
+ case "home": {
2109
+ // the home (src/home/launch.ts): the bin routes a bare `qcr` here too
2110
+ // (src/qcrbin.ts); the image's home mode reaches it through this verb
2111
+ if (rest.length > 0) throw new Error("usage: qcr home");
2112
+ const { homeRunDeps, runHome } = await import("./home/launch.js");
2113
+ // the token taken at startup, when the wrapper handed one over
2114
+ const githubToken = takeHandoffToken();
2115
+ process.exitCode = runHome({ ...homeRunDeps(sandboxRoot()), githubToken: () => githubToken });
2116
+ return;
2117
+ }
2140
2118
  case "companion":
2141
2119
  if (rest.length !== 1) throw new Error("usage: qcr companion <up|down|status|rebuild|plan>");
2142
2120
  return cmdCompanion(rest[0]);
@@ -2144,10 +2122,6 @@ export async function main(rawArgv: string[]): Promise<void> {
2144
2122
  process.exitCode = await cmdMemory(rest);
2145
2123
  return;
2146
2124
  }
2147
- case "onboard": {
2148
- process.exitCode = await cmdOnboard();
2149
- return;
2150
- }
2151
2125
  case "setup": {
2152
2126
  process.exitCode = await cmdSetup(rest);
2153
2127
  return;
@@ -2157,10 +2131,28 @@ export async function main(rawArgv: string[]): Promise<void> {
2157
2131
  return;
2158
2132
  }
2159
2133
  case "pause-pr":
2160
- case "resume-pr": {
2134
+ case "resume-pr":
2135
+ case "review-now": {
2136
+ // review-now: the operator's "review this PR now" — queued as a
2137
+ // human-asked round through the sealed operator channel, no label.
2161
2138
  process.exitCode = await cmdPrLever(cmd, rest);
2162
2139
  return;
2163
2140
  }
2141
+ case "github-token": {
2142
+ // The own-account route's token refresher (src/githubtoken.ts): gh's
2143
+ // token → <secrets>/github_token, 0600. The crontab entry runs this.
2144
+ const { cmdGithubToken } = await import("./githubtoken.js");
2145
+ process.exitCode = await cmdGithubToken(rest, {
2146
+ secretsDir: () => {
2147
+ try {
2148
+ return loadConfig().github.secrets_dir;
2149
+ } catch {
2150
+ return locateSecrets({}).path;
2151
+ }
2152
+ },
2153
+ });
2154
+ return;
2155
+ }
2164
2156
  case "message": {
2165
2157
  process.exitCode = await cmdMessage(rest);
2166
2158
  return;
@@ -2238,7 +2230,7 @@ export async function main(rawArgv: string[]): Promise<void> {
2238
2230
  }
2239
2231
  default:
2240
2232
  console.log(
2241
- "usage: qcr <version|config check [--config-home <dir>]|onboard|setup [validate [<profile>]|commit]|status|spend|today|why <n>|metrics [--pr <n>]|events|takeover <n>|debug-round <n>|release <n>|tui [pr|overseer]|drain <on [--hold]|off>|pause-pr <pr> [--reason <text>]|resume-pr <pr>|message <pr> [--may-post] <text...>|archive <status [--plan]|run|restore <pr>|extract <pr> --to <dir>>|install <where|migrate>|" +
2233
+ "usage: qcr <version|config check [--config-home <dir>]|home|setup [validate [<profile>]|commit]|status|spend|today|why <n>|metrics [--pr <n>]|events|takeover <n>|debug-round <n>|release <n>|tui [pr|overseer]|drain <on [--hold]|off>|pause-pr <pr> [--reason <text>]|resume-pr <pr>|review-now <pr>|github-token sync [--gh <path>] [--secrets-dir <dir>]|message <pr> [--may-post] <text...>|archive <status [--plan]|run|restore <pr>|extract <pr> --to <dir>>|install <where|migrate>|" +
2242
2234
  "companion <up|down|status|rebuild>|review [start|status|stop|branch|--uncommitted|--diff FILE|-|<PR number|URL>] [--repo OWNER/NAME] [--base REF] [--preview] [--key K] [--limit USD] [--json]|live [RUN_ID|DIR] [--lens ID] [--round N] [--follow] [--json]|open [RUN_ID|DIR|FILE.jsonl|scan[:RUN]|pr N [--round R]] [--lens ID] [--write] [--print]|scan <preview|start|status|stop|findings|matches|match|suggestions|stage2 propose|stage2 start|dismiss|units show|units propose|units adjust|units save|login|doctor|rebuild>|memory reembed [--user <name>]|memory relayout [--plan|--apply]|backfill [--dry-run] [--limit <n>]|history <digest|pull>|outcomes [--days <n>] [--capture [--read-github]]|lessons [mine|shadow-report [--days <n>]|off|on|suppress <id>|unsuppress <id>]|checklist|compare <pr...>|" +
2243
2235
  "regression [--set <path>] [--clone <path>] [--change <name>] [--limit <n>] [--out <path>]>",
2244
2236
  );
package/src/cliops.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  // qcr checklist / qcr compare — production wiring of the cutover harnesses. Local
2
2
  // runs exercise the offline halves; the live halves (tunnel, real searches,
3
3
  // live rounds) light up on the VM where the daemon and pi are running.
4
+ import { botAccountLogin } from "@quickchatai/pi-quickreview/src/identity.js";
4
5
  import { existsSync, mkdirSync, readFileSync } from "node:fs";
5
6
  import { join } from "node:path";
6
7
  import { runChecklist, type ChecklistDeps } from "./checklist.js";
@@ -253,7 +254,7 @@ export async function cmdChecklist(parts?: ProductionParts): Promise<number> {
253
254
  await p.adapter.searchLabelTriggered(),
254
255
  "label",
255
256
  cfg.dry_run,
256
- cfg.github.bot_login,
257
+ botAccountLogin(cfg.github),
257
258
  store,
258
259
  ),
259
260
  teamTriggered: async () =>
@@ -261,7 +262,7 @@ export async function cmdChecklist(parts?: ProductionParts): Promise<number> {
261
262
  await p.adapter.searchTeamTriggered(),
262
263
  "team_request",
263
264
  cfg.dry_run,
264
- cfg.github.bot_login,
265
+ botAccountLogin(cfg.github),
265
266
  store,
266
267
  ),
267
268
  derivedQueue: () => derivedQueueOf(store.allPrs(), cfg.dry_run),
package/src/commands.ts CHANGED
@@ -99,7 +99,10 @@ export const CONTROL_TEXT_MAX = 8000;
99
99
  * session's command file cannot spell them: in the session directories, and
100
100
  * in an UNSEALED operator directory (served as a session's), they are refused
101
101
  * at validation, and `executeCommand` refuses
102
- * them again for any issuer but the operator. The kill switch's state lives
102
+ * them again for any issuer but the operator. `review-now` is the operator's
103
+ * "review this PR now": one PR queued as a human-asked round, exactly as a
104
+ * fresh trigger label would queue it, with no GitHub write to ask for it (the
105
+ * onboarding wizard's proof round sends it). The kill switch's state lives
103
106
  * in state.db (src/lessonledger.ts), never as a memory tag anyone holding a
104
107
  * key could write.
105
108
  */
@@ -110,6 +113,7 @@ export const OPERATOR_COMMANDS = [
110
113
  "unsuppress-lesson",
111
114
  "archive-run",
112
115
  "archive-restore",
116
+ "review-now",
113
117
  ...CONTROL_COMMANDS,
114
118
  ] as const;
115
119
 
@@ -180,6 +184,9 @@ export type Command =
180
184
  * valid in a session's file. */
181
185
  | { command: "archive-run" }
182
186
  | { command: "archive-restore"; pr: number }
187
+ /** The operator's "review this PR now" (`OPERATOR_COMMANDS`): never valid
188
+ * in a session's file. */
189
+ | { command: "review-now"; pr: number }
183
190
  /** The operator's round controls (`CONTROL_COMMANDS`): never valid in a
184
191
  * session's file. */
185
192
  | { command: "steer"; pr: number; text: string }
@@ -263,6 +270,9 @@ export interface CommandDeps {
263
270
  * only for an operator-issued command; optional like `drain`. */
264
271
  archiveRun?(file: string): Promise<FileOutcome | void> | FileOutcome | void;
265
272
  archiveRestore?(pr: number, file: string): Promise<FileOutcome | void> | FileOutcome | void;
273
+ /** The operator's "review this PR now": queue one PR for a human-asked
274
+ * round. Reached only for an operator-issued command; optional like `drain`. */
275
+ reviewNow?(pr: number, file: string): Promise<FileOutcome | void> | FileOutcome | void;
266
276
  /** The operator's round controls (`CONTROL_COMMANDS`), forwarded to the PR's
267
277
  * host. Reached only for an operator-issued command; optional like `drain`,
268
278
  * so a daemon with no worker host refuses them with a reason. */
@@ -338,7 +348,7 @@ export function validateCommand(raw: unknown, opts: ValidateOptions = {}): Valid
338
348
  ok: false,
339
349
  reason:
340
350
  `command '${name}' is the OPERATOR's alone: it is accepted only from the root-sealed commands/operator/ ` +
341
- "channel (`qcr lessons off|on|suppress|unsuppress`, `qcr archive run|restore`), never from a session's command file",
351
+ "channel (`qcr lessons off|on|suppress|unsuppress`, `qcr archive run|restore`, `qcr review-now`), never from a session's command file",
342
352
  };
343
353
  return validateOperatorCommand(name, obj);
344
354
  }
@@ -609,6 +619,12 @@ function validateOperatorCommand(
609
619
  if (!isPrNumber(obj["pr"])) return { ok: false, reason: "'pr' must be a positive integer" };
610
620
  return { ok: true, command: { command: "archive-restore", pr: obj["pr"] } };
611
621
  }
622
+ case "review-now": {
623
+ const err = fieldError(obj, ["pr"]);
624
+ if (err) return { ok: false, reason: err };
625
+ if (!isPrNumber(obj["pr"])) return { ok: false, reason: "'pr' must be a positive integer" };
626
+ return { ok: true, command: { command: "review-now", pr: obj["pr"] } };
627
+ }
612
628
  case "steer":
613
629
  case "follow-up":
614
630
  case "message": {
@@ -971,6 +987,16 @@ export async function executeCommand(
971
987
  return { ok: false, reason: "archive-restore is not wired on this daemon" };
972
988
  return normalize(await deps.archiveRestore(command.pr, ctx.delivery_key ?? ctx.file));
973
989
  }
990
+ case "review-now": {
991
+ // The belt behind validation's brace, as for the kill switch.
992
+ if (ctx.issuer !== "operator")
993
+ return {
994
+ ok: false,
995
+ reason: `'${command.command}' is the operator's alone — refused for issuer '${ctx.issuer}'`,
996
+ };
997
+ if (!deps.reviewNow) return { ok: false, reason: "review-now is not wired on this daemon" };
998
+ return normalize(await deps.reviewNow(command.pr, ctx.delivery_key ?? ctx.file));
999
+ }
974
1000
  case "steer":
975
1001
  case "follow-up":
976
1002
  case "abort-round":
package/src/config.ts CHANGED
@@ -974,8 +974,11 @@ function checkSectionTimeZone<T extends { timezone: string }>(section: T, where:
974
974
  * what an absent section means) starts nothing at all.
975
975
  */
976
976
  export interface IngressConfig {
977
- /** "none" ⇒ no watcher is ever constructed; "tunnel-file" ⇒ self-heal on. */
978
- shape: "none" | "tunnel-file";
977
+ /** "none" ⇒ no watcher is ever constructed; "tunnel-file" ⇒ self-heal on;
978
+ * "polling" ⇒ no webhooks at all: the daemon's `/webhook` route refuses
979
+ * every delivery, no webhook secret is read, and the reconcile poll is the
980
+ * only trigger. `[github] auth = "user"` requires "polling". */
981
+ shape: "none" | "tunnel-file" | "polling";
979
982
  /** File the tunnel sidecar writes the bare public hostname into. Empty ⇒
980
983
  * `<root>/state/ingress/hostname` (see ingressHostnameFile). */
981
984
  hostname_file: string;
@@ -994,6 +997,13 @@ export const INGRESS_DEFAULTS: IngressConfig = {
994
997
  sweep_interval_s: 300,
995
998
  };
996
999
 
1000
+ /** Does this install take webhooks at all? Not under the polling shape
1001
+ * (and so never under `[github] auth = "user"`, which requires it): there the
1002
+ * `/webhook` route refuses every delivery and no webhook secret is read. */
1003
+ export function webhooksEnabled(cfg: Pick<Config, "ingress">): boolean {
1004
+ return cfg.ingress.shape !== "polling";
1005
+ }
1006
+
997
1007
  /** The hostname file's resolved path — the one place the default is spelled. */
998
1008
  export function ingressHostnameFile(cfg: Config, root: string): string {
999
1009
  const configured = cfg.ingress.hostname_file.trim();
@@ -1148,6 +1158,43 @@ export interface Config extends CoreConfig {
1148
1158
  /** The bot login's single refresher (`src/loginrefresh/`). Optional
1149
1159
  * in the file; off unless the cutover's config turns it on. */
1150
1160
  login_refresh: LoginRefreshConfig;
1161
+ /** The home, the app's own pi a bare `qcr` opens (src/home/launch.ts).
1162
+ * Optional in the file and here, so hand-rolled configs keep compiling;
1163
+ * the parser fills every key. */
1164
+ home?: HomeConfig;
1165
+ }
1166
+
1167
+ /** Which tools the home's model may call: `reviewer` is the plugin's tools,
1168
+ * the home's own `home_*` tools and pi's read tools; `full` is all of pi's. */
1169
+ export type HomeToolSet = "reviewer" | "full";
1170
+
1171
+ /** `[home]`: the home a bare `qcr` opens (src/home/launch.ts reads it). */
1172
+ export interface HomeConfig {
1173
+ /** The chat's model, `provider/id`. The panel's login line counts only
1174
+ * this model's provider as signed in. */
1175
+ model: string;
1176
+ tools: HomeToolSet;
1177
+ }
1178
+
1179
+ export const HOME_DEFAULTS: Readonly<HomeConfig> = {
1180
+ model: "openai-codex/gpt-6.1-sol",
1181
+ tools: "reviewer",
1182
+ };
1183
+
1184
+ /** `[home]`, every key defaulted; a value of the wrong kind is refused. */
1185
+ export function parseHomeSection(raw: unknown): HomeConfig {
1186
+ const t = (raw ?? {}) as Record<string, unknown>;
1187
+ const model = t["model"] ?? HOME_DEFAULTS.model;
1188
+ if (typeof model !== "string" || !/^[^/\s]+\/\S+$/.test(model))
1189
+ throw new Error(
1190
+ `config.toml [home]: model must be "provider/id" (got ${JSON.stringify(model)})`,
1191
+ );
1192
+ const tools = t["tools"] ?? HOME_DEFAULTS.tools;
1193
+ if (tools !== "reviewer" && tools !== "full")
1194
+ throw new Error(
1195
+ `config.toml [home]: tools must be "reviewer" or "full" (got ${JSON.stringify(tools)})`,
1196
+ );
1197
+ return { model, tools };
1151
1198
  }
1152
1199
 
1153
1200
  /**
@@ -1729,9 +1776,19 @@ export function parseConfig(toml: string): Config {
1729
1776
  ...INGRESS_DEFAULTS,
1730
1777
  ...((raw.ingress ?? {}) as Partial<IngressConfig>),
1731
1778
  };
1732
- if (!["none", "tunnel-file"].includes(ingress.shape)) {
1779
+ if (!["none", "tunnel-file", "polling"].includes(ingress.shape)) {
1780
+ throw new Error(
1781
+ `config.toml [ingress]: shape must be "none", "tunnel-file" or "polling" (got ${JSON.stringify(ingress.shape)})`,
1782
+ );
1783
+ }
1784
+ // The person's own credential has no webhook: GitHub delivers webhooks to
1785
+ // an App, and the person has none. So that mode is polling-only, and any
1786
+ // other shape is refused here rather than left to run half-wired.
1787
+ if (github.auth === "user" && ingress.shape !== "polling") {
1733
1788
  throw new Error(
1734
- `config.toml [ingress]: shape must be "none" or "tunnel-file" (got ${JSON.stringify(ingress.shape)})`,
1789
+ `config.toml: [github] auth = "user" runs polling-only — set [ingress] shape = "polling" ` +
1790
+ `(got ${JSON.stringify(ingress.shape)}${raw.ingress === undefined ? ", the default for an absent [ingress]" : ""}). ` +
1791
+ `Webhooks and the webhook self-heal need a GitHub App: use auth = "app" for them.`,
1735
1792
  );
1736
1793
  }
1737
1794
  const cfg: Config = {
@@ -1802,6 +1859,7 @@ export function parseConfig(toml: string): Config {
1802
1859
  (raw.login_refresh ?? {}) as Record<string, unknown>,
1803
1860
  Number((req(raw, "daemon") as { round_deadline_min?: number }).round_deadline_min ?? 75),
1804
1861
  ),
1862
+ home: parseHomeSection(raw.home),
1805
1863
  };
1806
1864
  validateReviewSection(cfg);
1807
1865
  foldLegacyOwnerSlackId(raw, cfg.owner);
package/src/daemon.ts CHANGED
@@ -18,6 +18,7 @@ import {
18
18
  DEFAULT_POSTING_RETRY_WINDOW_MIN,
19
19
  loadConfig,
20
20
  sandboxRoot,
21
+ webhooksEnabled,
21
22
  type Config,
22
23
  } from "./config.js";
23
24
  import { dispatchOnce, type DispatchAdapter, type PreparedTree } from "./dispatch.js";
@@ -30,7 +31,13 @@ import {
30
31
  } from "./eventloop.js";
31
32
  import { MutationGate, type MutationIntent } from "@quickchatai/pi-quickreview/src/gate.js";
32
33
  import { GitHubAdapter } from "./github.js";
33
- import { appActorAlias, TokenMinter } from "@quickchatai/pi-quickreview/src/githubAuth.js";
34
+ import { githubCredential } from "@quickchatai/pi-quickreview/src/githubAuth.js";
35
+ import {
36
+ botAccountLogin,
37
+ isPersonMode,
38
+ ownActorAlias,
39
+ ownLogins,
40
+ } from "@quickchatai/pi-quickreview/src/identity.js";
34
41
  import { REVIEW_SET_ASIDE_STALE_WINDOW_MS, type FeedbackEvent } from "./feedback.js";
35
42
  import { enforceLedgerFreshness } from "./ledgercheck.js";
36
43
  import { readLedgerCarry } from "@quickchatai/pi-quickreview/src/ledgercarry.js";
@@ -57,7 +64,13 @@ import {
57
64
  } from "./recheck.js";
58
65
  import type { DispatchAnnouncer, VerdictAnnouncer } from "./slack/announce.js";
59
66
  import { applyEvent } from "./statemachine.js";
60
- import { buildPipelineStatus, buildStatusSnapshot, StateStore, writeStatusJson } from "./state.js";
67
+ import {
68
+ buildPipelineStatus,
69
+ buildStatusSnapshot,
70
+ StateStore,
71
+ writeStatusJson,
72
+ type OpenPrsSnapshot,
73
+ } from "./state.js";
61
74
  import { PiAuthWatch } from "@quickchatai/pi-quickreview/src/authwatch.js";
62
75
  import { SessionCommandSupervisor } from "./session-command-supervisor.js";
63
76
  import { readRoundProgress } from "./requiredpasses.js";
@@ -313,13 +326,18 @@ export interface Daemon {
313
326
  firstHealthyReconcileAt(): Date | null;
314
327
  }
315
328
 
329
+ /** The webhook HMAC secret — or "" under the polling shape, which takes no
330
+ * webhooks and needs no secret (the `/webhook` route then refuses every
331
+ * delivery, webhook.ts). `[github] auth = "user"` is always polling. */
316
332
  function readWebhookSecret(cfg: Config): string {
333
+ if (!webhooksEnabled(cfg)) return "";
317
334
  return readFileSync(join(cfg.github.secrets_dir, "webhook_secret"), "utf8").trim();
318
335
  }
319
336
 
320
- /** The real adapter: token minter → mutation gate (dry-run aware) → adapter. */
337
+ /** The real adapter: credential (`[github] auth`) → mutation gate (dry-run
338
+ * aware) → adapter. */
321
339
  function buildGitHubAdapter(cfg: Config, stateDir: string): GitHubAdapter {
322
- const minter = new TokenMinter(cfg.github.secrets_dir);
340
+ const minter = githubCredential(cfg.github);
323
341
  const gate: MutationGate = new MutationGate(
324
342
  cfg.dry_run,
325
343
  join(stateDir, "dryrun"),
@@ -496,7 +514,8 @@ export function createDaemon(deps: DaemonDeps = {}): Daemon {
496
514
  dir: join(stateDir, "posting-receipts"),
497
515
  repo: cfg.github.repo,
498
516
  dryRun: cfg.dry_run,
499
- botLogins: [cfg.github.bot_login, `${cfg.github.app_slug}[bot]`],
517
+ botLogins: ownLogins(cfg.github),
518
+ personMode: isPersonMode(cfg.github),
500
519
  adapter: deps.postingDelivery,
501
520
  current: (job) => {
502
521
  const pr = store.getPr(job.pr);
@@ -587,7 +606,7 @@ export function createDaemon(deps: DaemonDeps = {}): Daemon {
587
606
  log,
588
607
  isDraining: () => store.drainState().draining,
589
608
  botLogin: cfg.github.bot_login,
590
- ownerActorAlias: appActorAlias(cfg.github.bot_login, cfg.github.app_slug),
609
+ ownerActorAlias: ownActorAlias(cfg.github),
591
610
  // The author asked for a person on a review thread.
592
611
  //
593
612
  // There is no park to put them in: the state event is a deliberate no-op
@@ -606,7 +625,7 @@ export function createDaemon(deps: DaemonDeps = {}): Daemon {
606
625
  { type: "human_requested" },
607
626
  {},
608
627
  {
609
- botLogin: cfg.github.bot_login,
628
+ botLogin: botAccountLogin(cfg.github),
610
629
  reason: "pingpong:human-requested",
611
630
  },
612
631
  );
@@ -806,10 +825,15 @@ export function createDaemon(deps: DaemonDeps = {}): Daemon {
806
825
  const outcomes = new OutcomeCapture({
807
826
  store,
808
827
  botLogin: cfg.github.bot_login,
828
+ personMode: isPersonMode(cfg.github),
809
829
  ...(deps.outcomeGit ? { git: deps.outcomeGit } : {}),
810
830
  ...(deps.now ? { now: deps.now } : {}),
811
831
  });
812
832
 
833
+ /** The open PRs the last listing reconcile pass saw (status.json `open_prs`). */
834
+ let openPrs: OpenPrsSnapshot | undefined;
835
+ /** The last reconcile pass (status.json `poll`): null until one has ended. */
836
+ let lastPoll: { at: string; ok: boolean; error?: string } | null = null;
813
837
  let firstReconcileSeen = false;
814
838
  let firstHealthyReconcileAt: Date | null = null;
815
839
  // Deploy-window drain auto-lift: only the boot's first reconcile pass may
@@ -866,6 +890,7 @@ export function createDaemon(deps: DaemonDeps = {}): Daemon {
866
890
  const bootFirstPass = !firstReconcileSeen;
867
891
  firstReconcileSeen = true;
868
892
  let completed = false;
893
+ let thrown: unknown = undefined;
869
894
  await guard("reconcile", async () => {
870
895
  const report = await reconcileOnce({
871
896
  cfg,
@@ -892,7 +917,17 @@ export function createDaemon(deps: DaemonDeps = {}): Daemon {
892
917
  // sweep reads session transcripts and is not needed for that, so it
893
918
  // starts on the next pass to keep the boot pass short.
894
919
  receiptSweep: !bootFirstPass,
920
+ }).catch((err: unknown) => {
921
+ thrown = err;
922
+ throw err;
895
923
  });
924
+ lastPoll = {
925
+ at: report.ran_at,
926
+ ok: report.errors.length === 0,
927
+ ...(report.errors[0] !== undefined ? { error: report.errors[0] } : {}),
928
+ };
929
+ // The board's "review a PR" list: kept from the last pass that listed.
930
+ if (report.open_list) openPrs = { listed_at: report.ran_at, prs: report.open_list };
896
931
  log(
897
932
  `reconcile: open=${report.open_prs} triggered=${report.triggered.length} ` +
898
933
  `closed=${report.closed.length} captured=${report.captured_comments} ` +
@@ -933,6 +968,15 @@ export function createDaemon(deps: DaemonDeps = {}): Daemon {
933
968
  }
934
969
  completed = true;
935
970
  });
971
+ if (thrown !== undefined)
972
+ lastPoll = {
973
+ at: (deps.now ? deps.now() : new Date()).toISOString(),
974
+ ok: false,
975
+ error: `the reconcile pass failed: ${thrown instanceof Error ? thrown.message : JSON.stringify(thrown)}`,
976
+ };
977
+ // The poll's outcome reaches status.json now, not at the next heartbeat:
978
+ // "working" is judged on it.
979
+ writeStatus();
936
980
  return completed;
937
981
  };
938
982
 
@@ -1097,6 +1141,13 @@ export function createDaemon(deps: DaemonDeps = {}): Daemon {
1097
1141
  ...(job.deferred_at ? { deferred_since: job.deferred_at } : {}),
1098
1142
  };
1099
1143
  }
1144
+ if (openPrs) snapshot.open_prs = openPrs;
1145
+ snapshot.poll = {
1146
+ last_at: lastPoll?.at ?? null,
1147
+ ok: lastPoll?.ok ?? null,
1148
+ ...(lastPoll?.error !== undefined ? { error: lastPoll.error } : {}),
1149
+ interval_s: cfg.daemon.poll_interval_s,
1150
+ };
1100
1151
  if (deps.runHost) snapshot.run_host = deps.runHost;
1101
1152
  writeStatusJson(stateDir, snapshot);
1102
1153
  } catch (err) {
package/src/debuground.ts CHANGED
@@ -123,6 +123,8 @@ export interface DebugRoundSpec {
123
123
  cwd: string;
124
124
  /** The login's owner dir: read-only, borrowed from. */
125
125
  agent_dir: string;
126
+ /** The login's dir when it is not `agent_dir` (the worker spec's). */
127
+ login_dir?: string;
126
128
  /** Where the pi's own agent dir (the blanked login copy) is made. */
127
129
  borrow_dir: string;
128
130
  /** The window the borrowed login must outlast. */
@@ -603,6 +605,7 @@ export class DebugRoundManager {
603
605
  root: spec.root,
604
606
  cwd: spec.cwd,
605
607
  agent_dir: spec.agent_dir,
608
+ ...(spec.login_dir !== undefined ? { login_dir: spec.login_dir } : {}),
606
609
  borrow_dir: borrow,
607
610
  idle_ms: this.idleMs,
608
611
  ...(file !== undefined ? { session_file: file } : {}),