@quickchatai/quickreview 1.2.0 → 1.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (350) hide show
  1. package/README.md +116 -53
  2. package/bin/qcr.js +1 -1
  3. package/config.example.toml +25 -7
  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/selfimprove.js +18 -0
  11. package/dist/extensions/cli/selfimprove.js.map +1 -0
  12. package/dist/extensions/home.js +350 -0
  13. package/dist/extensions/home.js.map +1 -0
  14. package/dist/extensions/login-route.js +1 -1
  15. package/dist/extensions/login-route.js.map +1 -1
  16. package/dist/extensions/onboarding.js +8 -7
  17. package/dist/extensions/onboarding.js.map +1 -1
  18. package/dist/extensions/selfimprove.js +49 -0
  19. package/dist/extensions/selfimprove.js.map +1 -0
  20. package/dist/extensions/setup-scan.js +23 -592
  21. package/dist/extensions/setup-scan.js.map +1 -1
  22. package/dist/extensions/setup.js +2 -47
  23. package/dist/extensions/setup.js.map +1 -1
  24. package/dist/extensions/vendor/openviking-pi/index.js +4 -0
  25. package/dist/extensions/vendor/openviking-pi/index.js.map +1 -1
  26. package/dist/extensions/vendor/openviking-pi/qcr-hooks.js +3 -1
  27. package/dist/extensions/vendor/openviking-pi/qcr-hooks.js.map +1 -1
  28. package/dist/extensions/vendor/openviking-pi/sync.js +81 -0
  29. package/dist/extensions/vendor/openviking-pi/sync.js.map +1 -1
  30. package/dist/src/archive/restore.js +18 -15
  31. package/dist/src/archive/restore.js.map +1 -1
  32. package/dist/src/botworker.js +13 -2
  33. package/dist/src/botworker.js.map +1 -1
  34. package/dist/src/channelfs.js +3 -26
  35. package/dist/src/channelfs.js.map +1 -1
  36. package/dist/src/cli.js +14 -50
  37. package/dist/src/cli.js.map +1 -1
  38. package/dist/src/config.js +16 -0
  39. package/dist/src/config.js.map +1 -1
  40. package/dist/src/daemon.js +33 -4
  41. package/dist/src/daemon.js.map +1 -1
  42. package/dist/src/debuground.js +1 -0
  43. package/dist/src/debuground.js.map +1 -1
  44. package/dist/src/debugroundpi.js +1 -0
  45. package/dist/src/debugroundpi.js.map +1 -1
  46. package/dist/src/engine/caserunner-sdk.js +15 -251
  47. package/dist/src/engine/caserunner-sdk.js.map +1 -1
  48. package/dist/src/home/adapters.js +349 -0
  49. package/dist/src/home/adapters.js.map +1 -0
  50. package/dist/src/home/advanced.js +318 -0
  51. package/dist/src/home/advanced.js.map +1 -0
  52. package/dist/src/home/advancedtools.js +57 -0
  53. package/dist/src/home/advancedtools.js.map +1 -0
  54. package/dist/src/home/autoreview.js +325 -0
  55. package/dist/src/home/autoreview.js.map +1 -0
  56. package/dist/src/home/daemonstatus.js +32 -0
  57. package/dist/src/home/daemonstatus.js.map +1 -0
  58. package/dist/src/home/devicelogin.js +61 -0
  59. package/dist/src/home/devicelogin.js.map +1 -0
  60. package/dist/src/home/launch.js +202 -0
  61. package/dist/src/home/launch.js.map +1 -0
  62. package/dist/src/home/loginowner.js +205 -0
  63. package/dist/src/home/loginowner.js.map +1 -0
  64. package/dist/src/home/memory.js +122 -0
  65. package/dist/src/home/memory.js.map +1 -0
  66. package/dist/src/home/model.js +811 -0
  67. package/dist/src/home/model.js.map +1 -0
  68. package/dist/src/home/panel.js +520 -0
  69. package/dist/src/home/panel.js.map +1 -0
  70. package/dist/src/home/ports.js +19 -0
  71. package/dist/src/home/ports.js.map +1 -0
  72. package/dist/src/home/review.js +163 -0
  73. package/dist/src/home/review.js.map +1 -0
  74. package/dist/src/home/reviews.js +478 -0
  75. package/dist/src/home/reviews.js.map +1 -0
  76. package/dist/src/home/setup.js +507 -0
  77. package/dist/src/home/setup.js.map +1 -0
  78. package/dist/src/home/setupadapters.js +214 -0
  79. package/dist/src/home/setupadapters.js.map +1 -0
  80. package/dist/src/home/setupcard.js +199 -0
  81. package/dist/src/home/setupcard.js.map +1 -0
  82. package/dist/src/home/setuptools.js +45 -0
  83. package/dist/src/home/setuptools.js.map +1 -0
  84. package/dist/src/home/spend.js +160 -0
  85. package/dist/src/home/spend.js.map +1 -0
  86. package/dist/src/home/switches.js +209 -0
  87. package/dist/src/home/switches.js.map +1 -0
  88. package/dist/src/home/switchon.js +439 -0
  89. package/dist/src/home/switchon.js.map +1 -0
  90. package/dist/src/home/toolnames.js +41 -0
  91. package/dist/src/home/toolnames.js.map +1 -0
  92. package/dist/src/home/tune.js +341 -0
  93. package/dist/src/home/tune.js.map +1 -0
  94. package/dist/src/home/words.js +7 -0
  95. package/dist/src/home/words.js.map +1 -0
  96. package/dist/src/homes.js +14 -0
  97. package/dist/src/homes.js.map +1 -1
  98. package/dist/src/install.js +1 -1
  99. package/dist/src/install.js.map +1 -1
  100. package/dist/src/ipc/workerspec.js +8 -0
  101. package/dist/src/ipc/workerspec.js.map +1 -1
  102. package/dist/src/ledgercheck.js +39 -10
  103. package/dist/src/ledgercheck.js.map +1 -1
  104. package/dist/src/live/publish.js +1 -1
  105. package/dist/src/live/publish.js.map +1 -1
  106. package/dist/src/livecli.js +1 -1
  107. package/dist/src/livecli.js.map +1 -1
  108. package/dist/src/loginrefresh/handover.js +121 -0
  109. package/dist/src/loginrefresh/handover.js.map +1 -0
  110. package/dist/src/loginrefresh/lock.js +54 -3
  111. package/dist/src/loginrefresh/lock.js.map +1 -1
  112. package/dist/src/loginrefresh/refresher.js +33 -5
  113. package/dist/src/loginrefresh/refresher.js.map +1 -1
  114. package/dist/src/loginrefresh/supervisor.js +18 -1
  115. package/dist/src/loginrefresh/supervisor.js.map +1 -1
  116. package/dist/src/npmverbs.js +42 -12
  117. package/dist/src/npmverbs.js.map +1 -1
  118. package/dist/src/onboard/choices.js +6 -3
  119. package/dist/src/onboard/choices.js.map +1 -1
  120. package/dist/src/onboard/engine.js +33 -292
  121. package/dist/src/onboard/engine.js.map +1 -1
  122. package/dist/src/onboard/steps/bringup.js +3 -2
  123. package/dist/src/onboard/steps/bringup.js.map +1 -1
  124. package/dist/src/onboard/steps/configfill.js +18 -7
  125. package/dist/src/onboard/steps/configfill.js.map +1 -1
  126. package/dist/src/onboard/steps/loginshare.js +213 -0
  127. package/dist/src/onboard/steps/loginshare.js.map +1 -0
  128. package/dist/src/onboard/steps/memory.js +14 -2
  129. package/dist/src/onboard/steps/memory.js.map +1 -1
  130. package/dist/src/onboard/steps/repoconfig.js +72 -0
  131. package/dist/src/onboard/steps/repoconfig.js.map +1 -0
  132. package/dist/src/onboard/toolnames.js +2 -2
  133. package/dist/src/onboard/toolnames.js.map +1 -1
  134. package/dist/src/piagentdir.js +13 -0
  135. package/dist/src/piagentdir.js.map +1 -0
  136. package/dist/src/pilogin.js +31 -16
  137. package/dist/src/pilogin.js.map +1 -1
  138. package/dist/src/qcrbin.js +16 -11
  139. package/dist/src/qcrbin.js.map +1 -1
  140. package/dist/src/reviewargs.js +122 -0
  141. package/dist/src/reviewargs.js.map +1 -0
  142. package/dist/src/reviewcli.js +7 -161
  143. package/dist/src/reviewcli.js.map +1 -1
  144. package/dist/src/reviewdetach.js +76 -11
  145. package/dist/src/reviewdetach.js.map +1 -1
  146. package/dist/src/scancli.js +39 -691
  147. package/dist/src/scancli.js.map +1 -1
  148. package/dist/src/selfimprove.js +66 -0
  149. package/dist/src/selfimprove.js.map +1 -0
  150. package/dist/src/session-command-supervisor.js +38 -9
  151. package/dist/src/session-command-supervisor.js.map +1 -1
  152. package/dist/src/setup/adapters.js +5 -223
  153. package/dist/src/setup/adapters.js.map +1 -1
  154. package/dist/src/setup/caserunner.js +15 -249
  155. package/dist/src/setup/caserunner.js.map +1 -1
  156. package/dist/src/setup/engine.js +32 -341
  157. package/dist/src/setup/engine.js.map +1 -1
  158. package/dist/src/setup/facts.js +3 -592
  159. package/dist/src/setup/facts.js.map +1 -1
  160. package/dist/src/setup/findings.js +3 -947
  161. package/dist/src/setup/findings.js.map +1 -1
  162. package/dist/src/setup/presentation.js +3 -8
  163. package/dist/src/setup/presentation.js.map +1 -1
  164. package/dist/src/setup/scan.js +3 -724
  165. package/dist/src/setup/scan.js.map +1 -1
  166. package/dist/src/setup/scanbuild.js +9 -92
  167. package/dist/src/setup/scanbuild.js.map +1 -1
  168. package/dist/src/setup/scanlease.js +3 -250
  169. package/dist/src/setup/scanlease.js.map +1 -1
  170. package/dist/src/setup/scanreport.js +3 -288
  171. package/dist/src/setup/scanreport.js.map +1 -1
  172. package/dist/src/setup/scanrun.js +3 -1732
  173. package/dist/src/setup/scanrun.js.map +1 -1
  174. package/dist/src/setup/scantree.js +3 -256
  175. package/dist/src/setup/scantree.js.map +1 -1
  176. package/dist/src/setup/stage2request.js +3 -258
  177. package/dist/src/setup/stage2request.js.map +1 -1
  178. package/dist/src/setup/unitcut.js +3 -436
  179. package/dist/src/setup/unitcut.js.map +1 -1
  180. package/dist/src/state.js +75 -9
  181. package/dist/src/state.js.map +1 -1
  182. package/dist/src/takeover.js +1 -0
  183. package/dist/src/takeover.js.map +1 -1
  184. package/dist/src/takeoverpi.js +2 -1
  185. package/dist/src/takeoverpi.js.map +1 -1
  186. package/dist/src/tui/alerts.js +4 -1
  187. package/dist/src/tui/alerts.js.map +1 -1
  188. package/dist/src/tui/board.js +2 -2
  189. package/dist/src/tui/board.js.map +1 -1
  190. package/dist/src/tui/companion.js +2 -1
  191. package/dist/src/tui/companion.js.map +1 -1
  192. package/dist/src/tui/deploytab.js +2 -2
  193. package/dist/src/tui/deploytab.js.map +1 -1
  194. package/dist/src/tui/index.js +9 -6
  195. package/dist/src/tui/index.js.map +1 -1
  196. package/dist/src/tui/memory.js +3 -16
  197. package/dist/src/tui/memory.js.map +1 -1
  198. package/dist/src/tui/memoryurl.js +22 -0
  199. package/dist/src/tui/memoryurl.js.map +1 -0
  200. package/dist/src/tui/remote.js +1 -1
  201. package/dist/src/tui/remote.js.map +1 -1
  202. package/dist/src/tui/route.js +2 -2
  203. package/dist/src/tui/route.js.map +1 -1
  204. package/dist/src/tui/setupstate.js +8 -5
  205. package/dist/src/tui/setupstate.js.map +1 -1
  206. package/dist/src/tui/sshtriage.js +1 -1
  207. package/dist/src/tui/sshtriage.js.map +1 -1
  208. package/dist/src/verdicts.js +12 -0
  209. package/dist/src/verdicts.js.map +1 -1
  210. package/dist/src/wire.js +9 -4
  211. package/dist/src/wire.js.map +1 -1
  212. package/dist/src/workerbreaker.js +32 -5
  213. package/dist/src/workerbreaker.js.map +1 -1
  214. package/dist/src/workerworld.js +4 -0
  215. package/dist/src/workerworld.js.map +1 -1
  216. package/docker/README.md +1 -1
  217. package/docker/entrypoint.sh +40 -6
  218. package/docker-compose.yml +16 -1
  219. package/extensions/cli/home.ts +14 -0
  220. package/extensions/cli/selfimprove.ts +18 -0
  221. package/extensions/home.ts +484 -0
  222. package/extensions/login-route.ts +1 -1
  223. package/extensions/onboarding.ts +8 -7
  224. package/extensions/selfimprove.ts +65 -0
  225. package/extensions/setup-scan.ts +79 -766
  226. package/extensions/setup.ts +14 -70
  227. package/extensions/vendor/openviking-pi/NOTICE +5 -0
  228. package/extensions/vendor/openviking-pi/README.md +39 -5
  229. package/extensions/vendor/openviking-pi/index.ts +4 -0
  230. package/extensions/vendor/openviking-pi/patches/0014-one-turn-sync-in-flight.patch +239 -0
  231. package/extensions/vendor/openviking-pi/qcr-hooks.ts +3 -1
  232. package/extensions/vendor/openviking-pi/sync.ts +80 -0
  233. package/extensions/vendor/openviking-pi/upstream.json +2 -1
  234. package/package.json +6 -4
  235. package/prompts/onboarding.md +8 -7
  236. package/schemas/review-result.v1.json +60 -0
  237. package/scripts/deploy.sh +8 -6
  238. package/scripts/qcr-host.sh +28 -5
  239. package/scripts/vm-ghcr-login.sh +7 -4
  240. package/src/archive/restore.ts +18 -21
  241. package/src/botworker.ts +13 -2
  242. package/src/channelfs.ts +3 -26
  243. package/src/cli.ts +13 -54
  244. package/src/config.ts +38 -0
  245. package/src/daemon.ts +34 -4
  246. package/src/debuground.ts +3 -0
  247. package/src/debugroundpi.ts +1 -0
  248. package/src/engine/caserunner-sdk.ts +29 -314
  249. package/src/home/adapters.ts +418 -0
  250. package/src/home/advanced.ts +421 -0
  251. package/src/home/advancedtools.ts +96 -0
  252. package/src/home/autoreview.ts +385 -0
  253. package/src/home/daemonstatus.ts +55 -0
  254. package/src/home/devicelogin.ts +107 -0
  255. package/src/home/launch.ts +257 -0
  256. package/src/home/loginowner.ts +248 -0
  257. package/src/home/memory.ts +176 -0
  258. package/src/home/model.ts +965 -0
  259. package/src/home/panel.ts +666 -0
  260. package/src/home/ports.ts +338 -0
  261. package/src/home/review.ts +206 -0
  262. package/src/home/reviews.ts +565 -0
  263. package/src/home/setup.ts +689 -0
  264. package/src/home/setupadapters.ts +217 -0
  265. package/src/home/setupcard.ts +288 -0
  266. package/src/home/setuptools.ts +57 -0
  267. package/src/home/spend.ts +194 -0
  268. package/src/home/switches.ts +293 -0
  269. package/src/home/switchon.ts +554 -0
  270. package/src/home/toolnames.ts +46 -0
  271. package/src/home/tune.ts +405 -0
  272. package/src/home/words.ts +8 -0
  273. package/src/homes.ts +17 -0
  274. package/src/install.ts +1 -1
  275. package/src/ipc/workerspec.ts +15 -0
  276. package/src/ledgercheck.ts +45 -9
  277. package/src/live/publish.ts +4 -1
  278. package/src/livecli.ts +6 -1
  279. package/src/loginrefresh/handover.ts +138 -0
  280. package/src/loginrefresh/lock.ts +72 -3
  281. package/src/loginrefresh/refresher.ts +34 -5
  282. package/src/loginrefresh/supervisor.ts +25 -2
  283. package/src/npmverbs.ts +51 -11
  284. package/src/onboard/choices.ts +6 -3
  285. package/src/onboard/engine.ts +64 -434
  286. package/src/onboard/steps/bringup.ts +3 -2
  287. package/src/onboard/steps/configfill.ts +23 -7
  288. package/src/onboard/steps/loginshare.ts +258 -0
  289. package/src/onboard/steps/memory.ts +18 -2
  290. package/src/onboard/steps/repoconfig.ts +85 -0
  291. package/src/onboard/toolnames.ts +2 -2
  292. package/src/piagentdir.ts +12 -0
  293. package/src/pilogin.ts +32 -16
  294. package/src/qcrbin.ts +16 -11
  295. package/src/reviewargs.ts +119 -0
  296. package/src/reviewcli.ts +6 -167
  297. package/src/reviewdetach.ts +107 -13
  298. package/src/scancli.ts +75 -826
  299. package/src/selfimprove.ts +90 -0
  300. package/src/session-command-supervisor.ts +41 -8
  301. package/src/setup/adapters.ts +13 -221
  302. package/src/setup/caserunner.ts +34 -273
  303. package/src/setup/engine.ts +62 -496
  304. package/src/setup/facts.ts +3 -627
  305. package/src/setup/findings.ts +3 -1264
  306. package/src/setup/presentation.ts +3 -18
  307. package/src/setup/scan.ts +3 -1033
  308. package/src/setup/scanbuild.ts +20 -114
  309. package/src/setup/scanlease.ts +3 -297
  310. package/src/setup/scanreport.ts +3 -373
  311. package/src/setup/scanrun.ts +3 -2268
  312. package/src/setup/scantree.ts +3 -259
  313. package/src/setup/stage2request.ts +3 -347
  314. package/src/setup/unitcut.ts +3 -523
  315. package/src/state.ts +126 -11
  316. package/src/takeover.ts +5 -0
  317. package/src/takeoverpi.ts +3 -2
  318. package/src/tui/alerts.ts +9 -2
  319. package/src/tui/board.ts +2 -2
  320. package/src/tui/companion.ts +2 -1
  321. package/src/tui/deploytab.ts +2 -2
  322. package/src/tui/index.ts +9 -6
  323. package/src/tui/memory.ts +3 -15
  324. package/src/tui/memoryurl.ts +22 -0
  325. package/src/tui/remote.ts +1 -1
  326. package/src/tui/route.ts +2 -2
  327. package/src/tui/setupstate.ts +20 -16
  328. package/src/tui/sshtriage.ts +1 -1
  329. package/src/verdicts.ts +13 -0
  330. package/src/wire.ts +9 -4
  331. package/src/workerbreaker.ts +41 -5
  332. package/src/workerworld.ts +9 -1
  333. package/dist/src/live/projection.js +0 -450
  334. package/dist/src/live/projection.js.map +0 -1
  335. package/dist/src/live/read.js +0 -60
  336. package/dist/src/live/read.js.map +0 -1
  337. package/dist/src/onboard/launch.js +0 -75
  338. package/dist/src/onboard/launch.js.map +0 -1
  339. package/dist/src/setup/ledger.js +0 -207
  340. package/dist/src/setup/ledger.js.map +0 -1
  341. package/dist/src/setup/stage2.js +0 -617
  342. package/dist/src/setup/stage2.js.map +0 -1
  343. package/dist/src/setup/textsniff.js +0 -89
  344. package/dist/src/setup/textsniff.js.map +0 -1
  345. package/src/live/projection.ts +0 -491
  346. package/src/live/read.ts +0 -86
  347. package/src/onboard/launch.ts +0 -103
  348. package/src/setup/ledger.ts +0 -324
  349. package/src/setup/stage2.ts +0 -926
  350. package/src/setup/textsniff.ts +0 -95
@@ -6,7 +6,7 @@
6
6
  # Nothing here builds the app or needs a checkout: `daemon` and `memory` are
7
7
  # pulled, `QCR_VERSION` pins them, and the project directory holds this file,
8
8
  # a .env and (for a tunnel) docker/tunnel/ -- all of which the image carries and
9
- # the onboarding wizard hands you the command to extract (qcr onboard, step 70):
9
+ # the onboarding wizard hands you the command to extract (its step 70):
10
10
  #
11
11
  # docker run --rm --entrypoint tar ghcr.io/quickchatai/quickreview:<version> \
12
12
  # -C /opt/qcr -cf - docker-compose.yml docker/tunnel | tar -xf -
@@ -170,6 +170,16 @@ services:
170
170
  # pi's home: OAuth tokens (auth.json), session JSONLs, models.json --
171
171
  # must survive container recreates (sticky sessions + /login tokens).
172
172
  - qcr-pihome:/home/qcr/.pi
173
+ # The OpenAI login, on a single box: the host pi home's agent dir, the
174
+ # one the home signed in to, so the box has one login (FT-0658). The
175
+ # home's switch-on writes QCR_PI_LOGIN_DIR into the project .env and
176
+ # turns [login_refresh] on: the daemon's refresher then owns that
177
+ # auth.json while auto-review runs, and the home's pi takes it back when
178
+ # auto-review stops (src/home/loginowner.ts). Only the login is read
179
+ # there; pi's settings stay in the volume above. Unset (the VM, a
180
+ # wizard-made stack), a volume of its own nobody reads, and the daemon
181
+ # keeps its login in pi's home above.
182
+ - ${QCR_PI_LOGIN_DIR:-qcr-login}:/opt/qcr/login
173
183
  # Everything else the daemon writes under the read-only app root, the
174
184
  # same set the VM's run spec mounts (deploy/qcr-update.sh; the two are
175
185
  # held equal by tests/compose-stack.test.ts): the PR worktrees, the
@@ -200,6 +210,9 @@ services:
200
210
  QCR_SECRETS_MOUNT: /opt/quickreview/secrets
201
211
  # The uid the model's tools run as: never QCR_UID.
202
212
  QCR_TOOLS_AS: ${QCR_TOOLS_AS:-65534}
213
+ # Where the login is (src/loginrefresh/supervisor.ts loginDirOf): the
214
+ # mount above when the host's login is shared, else empty (pi's home).
215
+ QCR_LOGIN_DIR: ${QCR_PI_LOGIN_DIR:+/opt/qcr/login}
203
216
 
204
217
  # ---- the memory sidecar --------------------------------------------------
205
218
  # OpenViking, its venv, llama-cpp-python and the pinned GGUF in a container
@@ -341,6 +354,8 @@ volumes:
341
354
  qcr-verdict:
342
355
  qcr-sessions-home:
343
356
  qcr-base-clone:
357
+ # The login mount's stand-in while no host login is shared (see the daemon).
358
+ qcr-login:
344
359
  # The memory store, the sidecar's alone.
345
360
  qcr-memory:
346
361
  # Shared by the daemon (read) and a tunnel sidecar (write): the hostname
@@ -0,0 +1,14 @@
1
+ // The env shim for the home's extension: the one place the process's env
2
+ // becomes its options (extensions/home.ts). A bare `qcr` loads it by path
3
+ // (src/home/launch.ts), with the model and tool set it launched on in the env.
4
+ import { VERSION, type ExtensionAPI } from "@earendil-works/pi-coding-agent";
5
+ import { homeOptionsFromEnv } from "../../src/home/adapters.js";
6
+ import { homeSetupFromEnv } from "../../src/home/setupadapters.js";
7
+ import { createHome } from "../home.js";
8
+
9
+ export default function activate(pi: ExtensionAPI): void {
10
+ void createHome({
11
+ ...homeOptionsFromEnv(process.env),
12
+ setup: homeSetupFromEnv(process.env, VERSION),
13
+ })(pi);
14
+ }
@@ -0,0 +1,18 @@
1
+ // The env shim for the app's /selfimprove (extensions/selfimprove.ts): the
2
+ // one place the process's env, home and standard output become its config.
3
+ // `qcr pi` loads this file beside the plugin.
4
+ import { homedir } from "node:os";
5
+ import { VERSION, type ExtensionAPI } from "@earendil-works/pi-coding-agent";
6
+ import { nodeRunner } from "@quickchatai/pi-quickreview/src/plugin/runner.js";
7
+ import { createSelfImprove } from "../selfimprove.js";
8
+
9
+ export default function activate(pi: ExtensionAPI): void {
10
+ const env = process.env;
11
+ createSelfImprove({
12
+ env,
13
+ home: (env["HOME"] ?? "").trim() || homedir(),
14
+ runner: nodeRunner(env),
15
+ print: (text) => void process.stdout.write(text.endsWith("\n") ? text : `${text}\n`),
16
+ piVersion: VERSION,
17
+ })(pi);
18
+ }
@@ -0,0 +1,484 @@
1
+ // home — the app's home: the panel at the top right of the app's own pi, and
2
+ // the `home_*` tools. Loaded by a bare `qcr` (src/home/launch.ts) through its
3
+ // env shim, extensions/cli/home.ts, beside the plugin; a factory over explicit
4
+ // options, so it reads no process state.
5
+ //
6
+ // Every action is one HomeModel method (src/home/model.ts), called by a key
7
+ // and by a tool alike:
8
+ //
9
+ // action key, while typing key, panel focused tool
10
+ // move the cursor alt+j / alt+k j / k, ↓ / ↑ home_move
11
+ // select a PR (hover, click) (hover, click) home_select
12
+ // open it on GitHub alt+o o home_open_pr
13
+ // read it all again alt+u u home_refresh
14
+ // review it alt+r ⏎, r home_review_pr
15
+ // show its review alt+r (reviewed) ⏎ (reviewed) home_show_review
16
+ // stop its review alt+s s home_stop_review
17
+ // what it shows — — home_status
18
+ // auto-review on/off alt+a a home_auto_review
19
+ // memory on/off alt+m m home_memory
20
+ // sign in again alt+l l home_relogin
21
+ //
22
+ // Signing in again is for a login that expired or was revoked: the login
23
+ // line says "login expired" (src/home/loginowner.ts reads the refresher's
24
+ // record, pi's own error in the chat and auto-review's pause). It is pi's
25
+ // device login, with auto-review's login refresher paused around it when it
26
+ // runs, so the box keeps one login that one process refreshes.
27
+ // tune to this repo alt+t t home_tune,
28
+ // home_tune_write
29
+ // advanced alt+x x home_advanced
30
+ //
31
+ // The two switches ask the person every question in the home (pi's select):
32
+ // a tool starts one but cannot answer it, so the confirm that turns
33
+ // auto-review on (in dry-run) and going live's own, after its proof round,
34
+ // are the person's (invariant 6).
35
+ //
36
+ // The review runs detached (the app's `qcr review start`, on the person's own
37
+ // pi login), writes its report to a file and never posts by itself; once it
38
+ // is done the person's posting rule (pi's settings, `quickreview.posting`)
39
+ // applies, asking with "No, keep it here" first; a rule pi's settings spell
40
+ // wrong is said, as the plugin says it. A review's findings go to the chat as
41
+ // the plugin's review message: shown, never a turn of its own, so a review
42
+ // that finishes while the agent works never steers it.
43
+ // next setup step alt+n ⏎ (on the card) home_setup_next
44
+ // each setup step — — home_sign_in,
45
+ // home_check_github,
46
+ // home_choose_repo
47
+ //
48
+ // On a machine that is not set up, the setup checklist (src/home/setup.ts)
49
+ // shows as a centred card (src/home/setupcard.ts) instead of the panel; when
50
+ // its last step is done the card gives way to the panel, and back again if a
51
+ // read shows a step missing once more.
52
+ //
53
+ // f2 or a click focuses the panel for its plain keys, and Esc gives the
54
+ // editor back: the panel never takes the keyboard by itself (nonCapturing).
55
+ // The first frame is drawn before any port is read; each read lands into it.
56
+ //
57
+ // The session's end (a quit, or /new replacing it) makes pi's context for it
58
+ // stale: the home lets go of it there, and a read still in flight lands into
59
+ // nothing.
60
+ import type {
61
+ ExtensionAPI,
62
+ ExtensionContext,
63
+ ExtensionFactory,
64
+ } from "@earendil-works/pi-coding-agent";
65
+ import type { KeyId, OverlayHandle, TUI } from "@earendil-works/pi-tui";
66
+ import {
67
+ registerQcrTool,
68
+ textResult,
69
+ type QcrTool,
70
+ } from "@quickchatai/pi-quickreview/extensions/pi-tool.js";
71
+ import { REVIEW_MESSAGE } from "@quickchatai/pi-quickreview/src/plugin/messages.js";
72
+ import { readQuickReviewSettings } from "@quickchatai/pi-quickreview/src/plugin/settings.js";
73
+ import type { HomeToolSet } from "../src/config.js";
74
+ import { piDeviceLogin } from "../src/home/devicelogin.js";
75
+ import { Relogin, RELOGIN_KEY, isLoginError } from "../src/home/loginowner.js";
76
+ import { HomeAdvanced, type AdvancedAsk, type AdvancedPort } from "../src/home/advanced.js";
77
+ import { advancedTools } from "../src/home/advancedtools.js";
78
+ import { HomeModel } from "../src/home/model.js";
79
+ import { HOME_OVERLAY, HomePanel } from "../src/home/panel.js";
80
+ import type { HomeAdapters } from "../src/home/ports.js";
81
+ import type { Ask } from "../src/home/switches.js";
82
+ import { HomeSetup, type HomeSetupPorts, type SetupUi } from "../src/home/setup.js";
83
+ import { SETUP_OVERLAY, SetupCard } from "../src/home/setupcard.js";
84
+ import { setupTools } from "../src/home/setuptools.js";
85
+ import { HOME_TOOL_NAMES } from "../src/home/toolnames.js";
86
+ import { HomeTune, type TunePort } from "../src/home/tune.js";
87
+
88
+ type HomeToolName = (typeof HOME_TOOL_NAMES)[number];
89
+
90
+ export interface HomeOptions {
91
+ adapters: HomeAdapters;
92
+ /** The model the home was launched on, `provider/id`. */
93
+ model: string;
94
+ /** The tool set it was launched with, shown on the login line. */
95
+ tools: HomeToolSet;
96
+ /** Run `fn` every `ms` until the returned stop is called. */
97
+ every?: (ms: number, fn: () => void) => () => void;
98
+ /** Run `fn` once after `ms`; the returned function cancels it. */
99
+ after?: (ms: number, fn: () => void) => () => void;
100
+ /** The setup checklist's ports beyond the home's own. */
101
+ setup?: HomeSetupPorts;
102
+ /** "Tune to this repo": Setup over a checkout (src/home/tune.ts). */
103
+ tune?: TunePort;
104
+ /** Advanced: the wizard's steps beyond switch-on (src/home/advanced.ts). */
105
+ advanced?: AdvancedPort;
106
+ }
107
+
108
+ /** How often the home reads its sources again by itself. */
109
+ export const REFRESH_MS = 60_000;
110
+ const SPIN_MS = 100;
111
+
112
+ const realEvery = (ms: number, fn: () => void): (() => void) => {
113
+ const t = setInterval(fn, ms);
114
+ t.unref?.();
115
+ return () => clearInterval(t);
116
+ };
117
+
118
+ const realAfter = (ms: number, fn: () => void): (() => void) => {
119
+ const t = setTimeout(fn, ms);
120
+ t.unref?.();
121
+ return () => clearTimeout(t);
122
+ };
123
+
124
+ const NO_PARAMS = { type: "object", properties: {}, additionalProperties: false } as const;
125
+ const PR_PARAM = {
126
+ type: "integer",
127
+ minimum: 1,
128
+ description: "The pull request's number.",
129
+ } as const;
130
+
131
+ export function createHome(o: HomeOptions): ExtensionFactory {
132
+ return (pi: ExtensionAPI) => {
133
+ const every = o.every ?? realEvery;
134
+ let tui: TUI | undefined;
135
+ let handle: OverlayHandle | undefined;
136
+ let close: (() => void) | undefined;
137
+ let mounted: "setup" | "home" | undefined;
138
+ let session: ExtensionContext | undefined;
139
+ let frame = 0;
140
+ /** The session's UI, for the switches' questions. */
141
+ let sessionUi: Ask | undefined;
142
+ const askOf = (ctx: ExtensionContext | undefined): Ask | undefined =>
143
+ ctx?.hasUI ? ctx.ui : sessionUi;
144
+ const stops: Array<() => void> = [];
145
+ const redraw = (): void => tui?.requestRender();
146
+ // the session's UI, for the post question; none outside the TUI
147
+ let ui: ExtensionContext["ui"] | undefined;
148
+ const message = (err: unknown): string => (err instanceof Error ? err.message : String(err));
149
+ // a read nobody awaits: what went wrong is said, while the session lasts
150
+ const refresh = (): void => {
151
+ model.refresh().catch((err: unknown) => {
152
+ if (session) model.say(`Couldn't read everything again: ${message(err)}`, "warn");
153
+ });
154
+ };
155
+ const model: HomeModel = new HomeModel({
156
+ adapters: o.adapters,
157
+ model: o.model,
158
+ tools: o.tools,
159
+ onChange: redraw,
160
+ after: o.after ?? realAfter,
161
+ ask: async (question, options) => ui?.select(question, options),
162
+ // shown, never a turn of its own: while the agent streams, the message
163
+ // waits for the turn to end instead of steering it
164
+ send: (m) =>
165
+ pi.sendMessage(
166
+ { customType: REVIEW_MESSAGE, content: m.content, display: true, details: m.details },
167
+ { triggerTurn: false },
168
+ ),
169
+ postingRule: () => {
170
+ let all: unknown;
171
+ try {
172
+ all = pi.getSettings();
173
+ } catch {
174
+ all = undefined;
175
+ }
176
+ const read = readQuickReviewSettings(all);
177
+ for (const w of read.warnings) warn(w);
178
+ return read.settings.posting;
179
+ },
180
+ gaps: (): string[] => setup.gaps(),
181
+ onRead: (): Promise<void> => setup.sync(),
182
+ });
183
+ // a word to the person: pi's notice, else the panel's line
184
+ const warn = (text: string): void => {
185
+ if (session?.hasUI) session.ui.notify(text, "warning");
186
+ else model.say(text, "warn");
187
+ };
188
+ const setup: HomeSetup = new HomeSetup({
189
+ model,
190
+ github: o.adapters.github,
191
+ ports: o.setup ?? {},
192
+ deviceLogin: () => deviceLogin(),
193
+ onChange: redraw,
194
+ onPhase: () => {
195
+ if (session?.mode === "tui") mount(session);
196
+ },
197
+ interactive: () => session?.mode === "tui",
198
+ });
199
+ const deviceLogin = () =>
200
+ o.setup?.deviceLogin ?? (session ? piDeviceLogin(session.modelRegistry) : undefined);
201
+ const relogin = new Relogin({
202
+ model,
203
+ owner: o.adapters.loginOwner,
204
+ deviceLogin,
205
+ onChange: redraw,
206
+ });
207
+ const tune = new HomeTune({
208
+ ...(o.tune ? { port: o.tune } : {}),
209
+ repo: () => model.repo(),
210
+ say: (text, kind) => model.say(text, kind),
211
+ after: o.after ?? realAfter,
212
+ onChange: redraw,
213
+ });
214
+ const advanced = new HomeAdvanced({
215
+ ...(o.advanced ? { port: o.advanced } : {}),
216
+ ...(o.adapters.autoReview.control ? { autoReview: o.adapters.autoReview.control } : {}),
217
+ say: (text, kind) => model.say(text, kind),
218
+ refresh: () => model.refresh(),
219
+ selected: () => model.selected,
220
+ });
221
+ // Setup's brief goes to the chat as the person's message, after the
222
+ // agent's turn when one is running
223
+ const tellAgent = (text: string): void => pi.sendUserMessage(text, { deliverAs: "followUp" });
224
+ const advancedAsk = (ctx: ExtensionContext | undefined): AdvancedAsk | undefined =>
225
+ ctx?.hasUI ? ctx.ui : session?.hasUI ? session.ui : undefined;
226
+ const dialogs = (ctx: ExtensionContext | undefined): SetupUi | undefined =>
227
+ ctx?.hasUI
228
+ ? {
229
+ select: (title, options) => ctx.ui.select(title, options),
230
+ input: (title, placeholder) => ctx.ui.input(title, placeholder),
231
+ }
232
+ : undefined;
233
+ const nextStep = (ctx: ExtensionContext | undefined): Promise<string> =>
234
+ setup.next(dialogs(ctx));
235
+
236
+ // The card while setup has a gap, the panel otherwise; a change of phase
237
+ // closes the one and opens the other.
238
+ const mount = (ctx: ExtensionContext): void => {
239
+ const want = setup.phase();
240
+ if (close && mounted === want) return;
241
+ close?.();
242
+ close = undefined;
243
+ handle = undefined;
244
+ mounted = want;
245
+ const focused = (): boolean => handle?.isFocused() ?? false;
246
+ void ctx.ui.custom<void>(
247
+ (t, theme, _keys, done) => {
248
+ tui = t;
249
+ close = () => done();
250
+ return want === "setup"
251
+ ? new SetupCard(theme, {
252
+ setup,
253
+ model,
254
+ focused,
255
+ rows: () => t.terminal.rows,
256
+ unfocus: () => handle?.unfocus(),
257
+ frame: () => frame,
258
+ next: () => void nextStep(session),
259
+ })
260
+ : new HomePanel(theme, {
261
+ model,
262
+ focused,
263
+ rows: () => t.terminal.rows,
264
+ unfocus: () => handle?.unfocus(),
265
+ frame: () => frame,
266
+ ask: () => sessionUi,
267
+ relogin: () => void relogin.start(),
268
+ signingAgain: () => relogin.running() !== null,
269
+ tune: () => void tune.tune(tellAgent),
270
+ advanced: () => void advanced.open(advancedAsk(session)),
271
+ });
272
+ },
273
+ {
274
+ overlay: true,
275
+ overlayOptions:
276
+ want === "setup"
277
+ ? { ...SETUP_OVERLAY }
278
+ : { ...HOME_OVERLAY, margin: { ...HOME_OVERLAY.margin } },
279
+ onHandle: (h) => (handle = h),
280
+ },
281
+ );
282
+ };
283
+
284
+ pi.on("session_start", (_e, ctx) => {
285
+ session = ctx;
286
+ model.setChatModel(ctx.model?.provider);
287
+ ui = ctx.mode === "tui" ? ctx.ui : undefined;
288
+ sessionUi = ctx.hasUI ? ctx.ui : undefined;
289
+ if (ctx.mode === "tui") {
290
+ mount(ctx);
291
+ // the spinner turns only while a read or a setup step is in flight
292
+ stops.push(
293
+ every(SPIN_MS, () => {
294
+ if (!model.busy() && !setup.busy() && relogin.running() === null) return;
295
+ frame++;
296
+ redraw();
297
+ }),
298
+ );
299
+ }
300
+ stops.push(every(REFRESH_MS, refresh));
301
+ refresh();
302
+ });
303
+ pi.on("model_select", (e) => model.setChatModel(e.model.provider));
304
+ // pi's own word on the login: a reply refused for it, or one that came back
305
+ pi.on("message_end", (e) => {
306
+ const m = e.message as { role?: unknown; stopReason?: unknown; errorMessage?: unknown };
307
+ if (m.role !== "assistant") return;
308
+ if (m.stopReason === "error") {
309
+ if (typeof m.errorMessage === "string" && isLoginError(m.errorMessage))
310
+ model.loginFailed(m.errorMessage);
311
+ } else if (m.stopReason !== "aborted") model.chatAnswered();
312
+ });
313
+ pi.on("session_shutdown", () => {
314
+ // the session's timers and its pricing stop; a running review goes on
315
+ model.dispose();
316
+ relogin.close();
317
+ tune.dispose();
318
+ ui = undefined;
319
+ for (const stop of stops.splice(0)) stop();
320
+ setup.close();
321
+ close?.();
322
+ close = undefined;
323
+ mounted = undefined;
324
+ // pi's context for this session is stale now: nothing may reach it
325
+ session = undefined;
326
+ sessionUi = undefined;
327
+ tui = undefined;
328
+ });
329
+
330
+ const key = (
331
+ id: string,
332
+ description: string,
333
+ run: (ctx: ExtensionContext | undefined) => unknown,
334
+ ): void =>
335
+ pi.registerShortcut(id as KeyId, {
336
+ description,
337
+ handler: (ctx) => {
338
+ void new Promise((resolve) => resolve(run(ctx))).catch((err: unknown) => {
339
+ if (session) model.say(message(err), "warn");
340
+ });
341
+ },
342
+ });
343
+ key("alt+j", "quickreview home: next pull request", () => model.move(1));
344
+ key("alt+k", "quickreview home: previous pull request", () => model.move(-1));
345
+ key("alt+o", "quickreview home: open the pull request on GitHub", () => model.openPr());
346
+ key("alt+u", "quickreview home: read everything again", () => refresh());
347
+ key("alt+r", "quickreview home: review the pull request, or show its review", () =>
348
+ model.enter(),
349
+ );
350
+ key("alt+s", "quickreview home: stop the pull request's review", () => model.stopReview());
351
+ key("f2", "quickreview home: focus the panel", () => handle?.focus());
352
+ key("alt+a", "quickreview home: switch auto-review on or off", (ctx) =>
353
+ model.switchAutoReview(askOf(ctx)),
354
+ );
355
+ key("alt+m", "quickreview home: switch memory on or off", (ctx) =>
356
+ model.switchMemory(askOf(ctx)),
357
+ );
358
+ key(RELOGIN_KEY, "quickreview home: sign in again (an expired login)", () => relogin.start());
359
+ key("alt+t", "quickreview home: tune the review to this repo (Setup)", () =>
360
+ tune.tune(tellAgent),
361
+ );
362
+ key(
363
+ "alt+x",
364
+ "quickreview home: advanced (a GitHub App, webhooks, a server, Slack, live)",
365
+ (ctx) => advanced.open(advancedAsk(ctx)),
366
+ );
367
+ pi.registerShortcut("alt+n", {
368
+ description: "quickreview home: run the next setup step",
369
+ handler: (ctx) => {
370
+ void nextStep(ctx);
371
+ },
372
+ });
373
+
374
+ // one definition per HOME_TOOL_NAMES entry: a name without one does not compile
375
+ const tools: { [N in HomeToolName]: QcrTool & { name: N } } = {
376
+ home_status: {
377
+ name: "home_status",
378
+ label: "Home status",
379
+ description:
380
+ "Read what the quickreview home shows: the repository, the logins, auto-review, memory, spend and the open pull requests (To review, Reviewed). A value that could not be read is null.",
381
+ parameters: NO_PARAMS,
382
+ execute: () => textResult(JSON.stringify(model.status(), null, 1)),
383
+ },
384
+ home_refresh: {
385
+ name: "home_refresh",
386
+ label: "Home refresh",
387
+ description:
388
+ "Read the home's sources again: the logins, the repository and its open pull requests.",
389
+ parameters: NO_PARAMS,
390
+ execute: async () => textResult(await model.refresh()),
391
+ },
392
+ home_move: {
393
+ name: "home_move",
394
+ label: "Home move",
395
+ description:
396
+ "Move the home's selection through its pull request lists: 1 is the next row, -1 the previous.",
397
+ parameters: {
398
+ type: "object",
399
+ properties: { by: { type: "integer", description: "Rows to move; negative moves up." } },
400
+ required: ["by"],
401
+ additionalProperties: false,
402
+ },
403
+ execute: (_id, p) => textResult(model.move(Number(p["by"]))),
404
+ },
405
+ home_select: {
406
+ name: "home_select",
407
+ label: "Home select",
408
+ description: "Select one open pull request in the home's lists, by number.",
409
+ parameters: {
410
+ type: "object",
411
+ properties: { pr: PR_PARAM },
412
+ required: ["pr"],
413
+ additionalProperties: false,
414
+ },
415
+ execute: (_id, p) => textResult(model.select(Number(p["pr"]))),
416
+ },
417
+ home_open_pr: {
418
+ name: "home_open_pr",
419
+ label: "Home open PR",
420
+ description:
421
+ "Open a pull request on GitHub in the person's browser: the one given, or the home's selected one.",
422
+ parameters: { type: "object", properties: { pr: PR_PARAM }, additionalProperties: false },
423
+ execute: async (_id, p) =>
424
+ textResult(await model.openPr(typeof p["pr"] === "number" ? p["pr"] : undefined)),
425
+ },
426
+ home_review_pr: {
427
+ name: "home_review_pr",
428
+ label: "Home review PR",
429
+ description:
430
+ "Review a pull request with QuickReview in the background: the one given, or the home's selected one. It previews the price, starts the review and returns at once; the home shows its progress and findings, and nothing is posted to GitHub unless the person's posting rule says so.",
431
+ parameters: { type: "object", properties: { pr: PR_PARAM }, additionalProperties: false },
432
+ execute: async (_id, p) =>
433
+ textResult(await model.review(typeof p["pr"] === "number" ? p["pr"] : undefined)),
434
+ },
435
+ home_show_review: {
436
+ name: "home_show_review",
437
+ label: "Home show review",
438
+ description:
439
+ "Put a reviewed pull request's findings in the chat: the one given, or the home's selected one.",
440
+ parameters: { type: "object", properties: { pr: PR_PARAM }, additionalProperties: false },
441
+ execute: (_id, p) =>
442
+ textResult(model.showReview(typeof p["pr"] === "number" ? p["pr"] : undefined)),
443
+ },
444
+ home_stop_review: {
445
+ name: "home_stop_review",
446
+ label: "Home stop review",
447
+ description:
448
+ "Stop the running QuickReview review of a pull request: the one given, or the home's selected one.",
449
+ parameters: { type: "object", properties: { pr: PR_PARAM }, additionalProperties: false },
450
+ execute: async (_id, p) =>
451
+ textResult(await model.stopReview(typeof p["pr"] === "number" ? p["pr"] : undefined)),
452
+ },
453
+ home_auto_review: {
454
+ name: "home_auto_review",
455
+ label: "Home auto-review",
456
+ description:
457
+ "Switch auto-review on or off. Switching on checks this machine, then asks the person where it runs, whether to turn memory on too, and for their one confirm, and it comes up in dry-run; switching off warns them which review is running, and from dry-run offers going live (a proof round on the selected pull request, then one more confirm). Every question is the person's to answer in the home: this tool cannot answer or confirm for them.",
458
+ parameters: NO_PARAMS,
459
+ execute: async (_id, _p, _signal, _onUpdate, ctx) =>
460
+ textResult(await model.switchAutoReview(askOf(ctx))),
461
+ },
462
+ home_memory: {
463
+ name: "home_memory",
464
+ label: "Home memory",
465
+ description:
466
+ "Switch reviewer memory on or off, on its own: auto-review is not touched. The person confirms in the home, after seeing its RAM cost.",
467
+ parameters: NO_PARAMS,
468
+ execute: async (_id, _p, _signal, _onUpdate, ctx) =>
469
+ textResult(await model.switchMemory(askOf(ctx))),
470
+ },
471
+ home_relogin: {
472
+ name: "home_relogin",
473
+ label: "Home sign in again",
474
+ description:
475
+ "Sign in again with pi's own device-code login, for the provider the chat runs on, when the login has expired or was revoked (the home's login line says \"login expired\"). When auto-review runs on this machine its login refresher is paused during the sign-in and resumes on the new login. Answers with the URL and the code to enter once pi has them; the sign-in finishes in the background when the person approves it.",
476
+ parameters: NO_PARAMS,
477
+ execute: async () => textResult(await relogin.start()),
478
+ },
479
+ ...setupTools(setup),
480
+ ...advancedTools({ tune, advanced, tellAgent, ask: (ctx) => advancedAsk(ctx) }),
481
+ };
482
+ for (const name of HOME_TOOL_NAMES) registerQcrTool(pi, tools[name]);
483
+ };
484
+ }
@@ -190,7 +190,7 @@ export function openLogin(ui: HintUi, open: LoginOpen): "submitted" | "prefilled
190
190
 
191
191
  /** Register the model route and the login hint on `pi`. Exported so a host that
192
192
  * builds its own onboarding extension (over `toolDefinitions`) gets the same
193
- * first-run login as `qcr onboard`. */
193
+ * first-run login as the onboarding wizard. */
194
194
  export function registerLoginRoute(pi: ExtensionAPI, config: LoginRouteConfig): void {
195
195
  if (typeof pi.on !== "function") return;
196
196
  let shown = false;
@@ -1,6 +1,8 @@
1
- // onboarding — the wizard's tool belt. Loaded ONLY by `qcr onboard`
2
- // (per spawn, `-e`, alongside --no-extensions — no ambient extension rides the
3
- // onboarding session), never registered globally. pi jiti-loads this TS source
1
+ // onboarding — the wizard's tool belt. Loaded ONLY by a pi started with it
2
+ // named by path (its env shim, extensions/cli/onboarding.ts, as `-e` alongside
3
+ // --no-extensions, so no ambient extension rides that session), never
4
+ // registered globally. No qcr verb starts such a session any more: the home
5
+ // runs the wizard's steps itself (src/home/switchon.ts, Advanced). pi jiti-loads this TS source
4
6
  // on the HOST, so it imports the OnboardingEngine source directly and runs it
5
7
  // in-process: pi talks, these deterministic tools act, and the LLM never
6
8
  // freestyles a shell command (the built-in belt is read-only by argv).
@@ -11,8 +13,7 @@
11
13
  // - finish_and_exit: re-runs the FULL verification; anything red comes back
12
14
  // as the tool result (no exit, ever, while red); all green marks the
13
15
  // checklist complete, writes the companion's first-run marker
14
- // (state/companion/first-run.json), and triggers pi's deferred shutdown —
15
- // the `qcr onboard` process then opens the TUI.
16
+ // (state/companion/first-run.json), and triggers pi's deferred shutdown.
16
17
  //
17
18
  // Convention notes (same shape as companion-guard/tui-view): degrade, never
18
19
  // wedge — a pi without registerTool registers nothing and says so; a missing
@@ -228,8 +229,8 @@ export function createOnboarding(config: OnboardingConfig): ExtensionFactory {
228
229
  * The onboarding session's first-run login: when pi has no usable model
229
230
  * login, pi's own /login menu opens with a widget that says which logins work
230
231
  * (any OpenAI one), and an OpenAI model whose provider has no login runs on
231
- * the twin login that exists (extensions/login-route.ts). `qcr onboard`
232
- * registers it with the tools; a host that builds its own onboarding
232
+ * the twin login that exists (extensions/login-route.ts). The wizard's
233
+ * session registers it with the tools; a host that builds its own onboarding
233
234
  * extension over `toolDefinitions` + `registerQcrTool` calls it too.
234
235
  */
235
236
  export function registerOnboardingSession(pi: ExtensionAPI): void {
@@ -0,0 +1,65 @@
1
+ // /selfimprove in the app's own pi (`qcr pi`, src/npmverbs.ts): scan this
2
+ // repo, then have this session's agent fix the findings (src/selfimprove.ts).
3
+ // The plugin, loaded beside it, carries /qcr-review, /qcr-scan and /qcr-setup;
4
+ // this command is the app's (ADR 0001). A factory over its config: the env
5
+ // shim extensions/cli/selfimprove.ts reads the process.
6
+ import {
7
+ getAgentDir,
8
+ type ExtensionAPI,
9
+ type ExtensionCommandContext,
10
+ } from "@earendil-works/pi-coding-agent";
11
+ import { hostAuth, sessionUi } from "@quickchatai/pi-quickreview/extensions/quickreview.js";
12
+ import type { PluginHost, Runner } from "@quickchatai/pi-quickreview/src/plugin/session.js";
13
+ import { runSelfImproveCommand } from "../src/selfimprove.js";
14
+
15
+ export interface SelfImproveConfig {
16
+ /** The process environment, read once by the shim. */
17
+ env: NodeJS.ProcessEnv;
18
+ /** The user's home. */
19
+ home: string;
20
+ runner: Runner;
21
+ /** Standard output (pi's print mode shows a command's result there). */
22
+ print: (text: string) => void;
23
+ /** Test seams: the running pi's version and its agent dir. */
24
+ piVersion?: string;
25
+ agentDir?: string;
26
+ }
27
+
28
+ /** The extension factory: registers /selfimprove, one run at a time. */
29
+ export function createSelfImprove(config: SelfImproveConfig): (pi: ExtensionAPI) => void {
30
+ return (pi) => {
31
+ // The current session's end: a scan in flight stops with it.
32
+ let shutdown = new AbortController();
33
+ pi.on("session_start", () => {
34
+ if (shutdown.signal.aborted) shutdown = new AbortController();
35
+ });
36
+ pi.on("session_shutdown", () => shutdown.abort());
37
+ let busy = false;
38
+ pi.registerCommand("selfimprove", {
39
+ description: "QuickReview: scan this repo, then have this session's agent fix the findings",
40
+ handler: async (args: string, ctx: ExtensionCommandContext) => {
41
+ if (busy) {
42
+ ctx.ui.notify("/selfimprove is already running in this session", "warning");
43
+ return;
44
+ }
45
+ busy = true;
46
+ try {
47
+ const agentDir = config.agentDir ?? getAgentDir();
48
+ const host: PluginHost = {
49
+ cwd: ctx.cwd,
50
+ env: config.env,
51
+ home: config.home,
52
+ piVersion: config.piVersion ?? "",
53
+ agentDir,
54
+ auth: hostAuth(ctx, agentDir),
55
+ runner: config.runner,
56
+ shutdown: shutdown.signal,
57
+ };
58
+ await runSelfImproveCommand(args.trim(), host, sessionUi(pi, ctx, config.print));
59
+ } finally {
60
+ busy = false;
61
+ }
62
+ },
63
+ });
64
+ };
65
+ }