@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.
- package/README.md +137 -57
- package/bin/qcr.js +1 -1
- package/config.example.toml +60 -14
- package/deploy/ansible/README.md +20 -12
- package/deploy/ansible/inventory.example.ini +1 -1
- package/deploy/collect-secrets.sh +1 -1
- package/deploy/qcr-update.sh +1 -1
- package/dist/extensions/cli/home.js +14 -0
- package/dist/extensions/cli/home.js.map +1 -0
- package/dist/extensions/cli/login-route.js +5 -0
- package/dist/extensions/cli/login-route.js.map +1 -0
- package/dist/extensions/cli/selfimprove.js +18 -0
- package/dist/extensions/cli/selfimprove.js.map +1 -0
- package/dist/extensions/home.js +350 -0
- package/dist/extensions/home.js.map +1 -0
- package/dist/extensions/login-route.js +180 -0
- package/dist/extensions/login-route.js.map +1 -0
- package/dist/extensions/onboarding.js +35 -11
- package/dist/extensions/onboarding.js.map +1 -1
- package/dist/extensions/selfimprove.js +49 -0
- package/dist/extensions/selfimprove.js.map +1 -0
- package/dist/extensions/setup-scan.js +23 -592
- package/dist/extensions/setup-scan.js.map +1 -1
- package/dist/extensions/setup.js +2 -47
- package/dist/extensions/setup.js.map +1 -1
- package/dist/src/archive/restore.js +18 -15
- package/dist/src/archive/restore.js.map +1 -1
- package/dist/src/botworker.js +13 -2
- package/dist/src/botworker.js.map +1 -1
- package/dist/src/channelfs.js +3 -26
- package/dist/src/channelfs.js.map +1 -1
- package/dist/src/ciretry.js +2 -1
- package/dist/src/ciretry.js.map +1 -1
- package/dist/src/cli.js +54 -61
- package/dist/src/cli.js.map +1 -1
- package/dist/src/cliops.js +3 -2
- package/dist/src/cliops.js.map +1 -1
- package/dist/src/commands.js +25 -2
- package/dist/src/commands.js.map +1 -1
- package/dist/src/config.js +32 -2
- package/dist/src/config.js.map +1 -1
- package/dist/src/daemon.js +50 -8
- package/dist/src/daemon.js.map +1 -1
- package/dist/src/debuground.js +1 -0
- package/dist/src/debuground.js.map +1 -1
- package/dist/src/debugroundpi.js +6 -1
- package/dist/src/debugroundpi.js.map +1 -1
- package/dist/src/dispatch.js +5 -4
- package/dist/src/dispatch.js.map +1 -1
- package/dist/src/engine/caserunner-sdk.js +15 -251
- package/dist/src/engine/caserunner-sdk.js.map +1 -1
- package/dist/src/engine/debugpi.js +17 -5
- package/dist/src/engine/debugpi.js.map +1 -1
- package/dist/src/execboundary.js +10 -3
- package/dist/src/execboundary.js.map +1 -1
- package/dist/src/github.js +51 -16
- package/dist/src/github.js.map +1 -1
- package/dist/src/githubtoken.js +346 -0
- package/dist/src/githubtoken.js.map +1 -0
- package/dist/src/home/adapters.js +349 -0
- package/dist/src/home/adapters.js.map +1 -0
- package/dist/src/home/advanced.js +318 -0
- package/dist/src/home/advanced.js.map +1 -0
- package/dist/src/home/advancedtools.js +57 -0
- package/dist/src/home/advancedtools.js.map +1 -0
- package/dist/src/home/autoreview.js +325 -0
- package/dist/src/home/autoreview.js.map +1 -0
- package/dist/src/home/daemonstatus.js +32 -0
- package/dist/src/home/daemonstatus.js.map +1 -0
- package/dist/src/home/devicelogin.js +61 -0
- package/dist/src/home/devicelogin.js.map +1 -0
- package/dist/src/home/launch.js +202 -0
- package/dist/src/home/launch.js.map +1 -0
- package/dist/src/home/loginowner.js +205 -0
- package/dist/src/home/loginowner.js.map +1 -0
- package/dist/src/home/memory.js +122 -0
- package/dist/src/home/memory.js.map +1 -0
- package/dist/src/home/model.js +811 -0
- package/dist/src/home/model.js.map +1 -0
- package/dist/src/home/panel.js +520 -0
- package/dist/src/home/panel.js.map +1 -0
- package/dist/src/home/ports.js +19 -0
- package/dist/src/home/ports.js.map +1 -0
- package/dist/src/home/review.js +163 -0
- package/dist/src/home/review.js.map +1 -0
- package/dist/src/home/reviews.js +478 -0
- package/dist/src/home/reviews.js.map +1 -0
- package/dist/src/home/setup.js +507 -0
- package/dist/src/home/setup.js.map +1 -0
- package/dist/src/home/setupadapters.js +214 -0
- package/dist/src/home/setupadapters.js.map +1 -0
- package/dist/src/home/setupcard.js +199 -0
- package/dist/src/home/setupcard.js.map +1 -0
- package/dist/src/home/setuptools.js +45 -0
- package/dist/src/home/setuptools.js.map +1 -0
- package/dist/src/home/spend.js +160 -0
- package/dist/src/home/spend.js.map +1 -0
- package/dist/src/home/switches.js +209 -0
- package/dist/src/home/switches.js.map +1 -0
- package/dist/src/home/switchon.js +439 -0
- package/dist/src/home/switchon.js.map +1 -0
- package/dist/src/home/toolnames.js +41 -0
- package/dist/src/home/toolnames.js.map +1 -0
- package/dist/src/home/tune.js +341 -0
- package/dist/src/home/tune.js.map +1 -0
- package/dist/src/home/words.js +7 -0
- package/dist/src/home/words.js.map +1 -0
- package/dist/src/homes.js +14 -0
- package/dist/src/homes.js.map +1 -1
- package/dist/src/install.js +1 -1
- package/dist/src/install.js.map +1 -1
- package/dist/src/ipc/workerspec.js +8 -0
- package/dist/src/ipc/workerspec.js.map +1 -1
- package/dist/src/live/publish.js +1 -1
- package/dist/src/live/publish.js.map +1 -1
- package/dist/src/livecli.js +1 -1
- package/dist/src/livecli.js.map +1 -1
- package/dist/src/loginhint.js +8 -3
- package/dist/src/loginhint.js.map +1 -1
- package/dist/src/loginrefresh/handover.js +121 -0
- package/dist/src/loginrefresh/handover.js.map +1 -0
- package/dist/src/loginrefresh/lock.js +54 -3
- package/dist/src/loginrefresh/lock.js.map +1 -1
- package/dist/src/loginrefresh/pirefresh.js +3 -0
- package/dist/src/loginrefresh/pirefresh.js.map +1 -1
- package/dist/src/loginrefresh/refresher.js +33 -5
- package/dist/src/loginrefresh/refresher.js.map +1 -1
- package/dist/src/loginrefresh/supervisor.js +18 -1
- package/dist/src/loginrefresh/supervisor.js.map +1 -1
- package/dist/src/npmverbs.js +46 -14
- package/dist/src/npmverbs.js.map +1 -1
- package/dist/src/onboard/choices.js +35 -4
- package/dist/src/onboard/choices.js.map +1 -1
- package/dist/src/onboard/engine.js +32 -306
- package/dist/src/onboard/engine.js.map +1 -1
- package/dist/src/onboard/pipin.js +22 -0
- package/dist/src/onboard/pipin.js.map +1 -0
- package/dist/src/onboard/stepforward.js +41 -0
- package/dist/src/onboard/stepforward.js.map +1 -0
- package/dist/src/onboard/steps/appslug.js +4 -1
- package/dist/src/onboard/steps/appslug.js.map +1 -1
- package/dist/src/onboard/steps/bringup.js +3 -2
- package/dist/src/onboard/steps/bringup.js.map +1 -1
- package/dist/src/onboard/steps/compose.js +29 -0
- package/dist/src/onboard/steps/compose.js.map +1 -1
- package/dist/src/onboard/steps/configfill.js +120 -39
- package/dist/src/onboard/steps/configfill.js.map +1 -1
- package/dist/src/onboard/steps/githubaccount.js +595 -0
- package/dist/src/onboard/steps/githubaccount.js.map +1 -0
- package/dist/src/onboard/steps/githubapp.js +65 -7
- package/dist/src/onboard/steps/githubapp.js.map +1 -1
- package/dist/src/onboard/steps/golive.js +2 -0
- package/dist/src/onboard/steps/golive.js.map +1 -1
- package/dist/src/onboard/steps/index.js +11 -0
- package/dist/src/onboard/steps/index.js.map +1 -1
- package/dist/src/onboard/steps/ingress.js +80 -25
- package/dist/src/onboard/steps/ingress.js.map +1 -1
- package/dist/src/onboard/steps/llmauth.js +103 -30
- package/dist/src/onboard/steps/llmauth.js.map +1 -1
- package/dist/src/onboard/steps/loginshare.js +213 -0
- package/dist/src/onboard/steps/loginshare.js.map +1 -0
- package/dist/src/onboard/steps/memory.js +67 -3
- package/dist/src/onboard/steps/memory.js.map +1 -1
- package/dist/src/onboard/steps/proof.js +366 -56
- package/dist/src/onboard/steps/proof.js.map +1 -1
- package/dist/src/onboard/steps/repoconfig.js +72 -0
- package/dist/src/onboard/steps/repoconfig.js.map +1 -0
- package/dist/src/onboard/steps/topology.js +6 -6
- package/dist/src/onboard/steps/topology.js.map +1 -1
- package/dist/src/onboard/steps/transport.js +9 -1
- package/dist/src/onboard/steps/transport.js.map +1 -1
- package/dist/src/onboard/steps/tuiroute.js +102 -0
- package/dist/src/onboard/steps/tuiroute.js.map +1 -0
- package/dist/src/onboard/steps/verifyup.js +17 -1
- package/dist/src/onboard/steps/verifyup.js.map +1 -1
- package/dist/src/onboard/toolnames.js +30 -0
- package/dist/src/onboard/toolnames.js.map +1 -0
- package/dist/src/operatorwrite.js +23 -0
- package/dist/src/operatorwrite.js.map +1 -0
- package/dist/src/outcomes.js +4 -1
- package/dist/src/outcomes.js.map +1 -1
- package/dist/src/piagentdir.js +13 -0
- package/dist/src/piagentdir.js.map +1 -0
- package/dist/src/pilogin.js +31 -16
- package/dist/src/pilogin.js.map +1 -1
- package/dist/src/posting-journal.js +2 -1
- package/dist/src/posting-journal.js.map +1 -1
- package/dist/src/posting-outbox.js +6 -2
- package/dist/src/posting-outbox.js.map +1 -1
- package/dist/src/posting-receipt.js +14 -1
- package/dist/src/posting-receipt.js.map +1 -1
- package/dist/src/qcrbin.js +16 -11
- package/dist/src/qcrbin.js.map +1 -1
- package/dist/src/recheck.js +2 -1
- package/dist/src/recheck.js.map +1 -1
- package/dist/src/reconcile.js +22 -2
- package/dist/src/reconcile.js.map +1 -1
- package/dist/src/reviewargs.js +122 -0
- package/dist/src/reviewargs.js.map +1 -0
- package/dist/src/reviewcli.js +7 -161
- package/dist/src/reviewcli.js.map +1 -1
- package/dist/src/reviewdetach.js +76 -11
- package/dist/src/reviewdetach.js.map +1 -1
- package/dist/src/reviewhistory.js +32 -6
- package/dist/src/reviewhistory.js.map +1 -1
- package/dist/src/reviewnow.js +45 -0
- package/dist/src/reviewnow.js.map +1 -0
- package/dist/src/scancli.js +39 -691
- package/dist/src/scancli.js.map +1 -1
- package/dist/src/selfimprove.js +66 -0
- package/dist/src/selfimprove.js.map +1 -0
- package/dist/src/setup/adapters.js +5 -223
- package/dist/src/setup/adapters.js.map +1 -1
- package/dist/src/setup/caserunner.js +15 -249
- package/dist/src/setup/caserunner.js.map +1 -1
- package/dist/src/setup/engine.js +32 -341
- package/dist/src/setup/engine.js.map +1 -1
- package/dist/src/setup/facts.js +3 -592
- package/dist/src/setup/facts.js.map +1 -1
- package/dist/src/setup/findings.js +3 -947
- package/dist/src/setup/findings.js.map +1 -1
- package/dist/src/setup/presentation.js +3 -8
- package/dist/src/setup/presentation.js.map +1 -1
- package/dist/src/setup/scan.js +3 -724
- package/dist/src/setup/scan.js.map +1 -1
- package/dist/src/setup/scanbuild.js +9 -92
- package/dist/src/setup/scanbuild.js.map +1 -1
- package/dist/src/setup/scanlease.js +3 -250
- package/dist/src/setup/scanlease.js.map +1 -1
- package/dist/src/setup/scanreport.js +3 -288
- package/dist/src/setup/scanreport.js.map +1 -1
- package/dist/src/setup/scanrun.js +3 -1732
- package/dist/src/setup/scanrun.js.map +1 -1
- package/dist/src/setup/scantree.js +3 -256
- package/dist/src/setup/scantree.js.map +1 -1
- package/dist/src/setup/stage2request.js +3 -258
- package/dist/src/setup/stage2request.js.map +1 -1
- package/dist/src/setup/unitcut.js +3 -436
- package/dist/src/setup/unitcut.js.map +1 -1
- package/dist/src/state.js +2 -0
- package/dist/src/state.js.map +1 -1
- package/dist/src/statemachine.js +4 -1
- package/dist/src/statemachine.js.map +1 -1
- package/dist/src/takeover.js +1 -0
- package/dist/src/takeover.js.map +1 -1
- package/dist/src/takeoverpi.js +15 -1
- package/dist/src/takeoverpi.js.map +1 -1
- package/dist/src/tui/app.js +49 -0
- package/dist/src/tui/app.js.map +1 -1
- package/dist/src/tui/board.js +88 -12
- package/dist/src/tui/board.js.map +1 -1
- package/dist/src/tui/cmdbar.js +4 -1
- package/dist/src/tui/cmdbar.js.map +1 -1
- package/dist/src/tui/companion.js +76 -11
- package/dist/src/tui/companion.js.map +1 -1
- package/dist/src/tui/connect.js +7 -1
- package/dist/src/tui/connect.js.map +1 -1
- package/dist/src/tui/deploytab.js +2 -2
- package/dist/src/tui/deploytab.js.map +1 -1
- package/dist/src/tui/filters.js +5 -2
- package/dist/src/tui/filters.js.map +1 -1
- package/dist/src/tui/ghread.js +61 -5
- package/dist/src/tui/ghread.js.map +1 -1
- package/dist/src/tui/index.js +109 -20
- package/dist/src/tui/index.js.map +1 -1
- package/dist/src/tui/memforward.js +39 -2
- package/dist/src/tui/memforward.js.map +1 -1
- package/dist/src/tui/memory.js +3 -16
- package/dist/src/tui/memory.js.map +1 -1
- package/dist/src/tui/memoryurl.js +22 -0
- package/dist/src/tui/memoryurl.js.map +1 -0
- package/dist/src/tui/model.js +25 -0
- package/dist/src/tui/model.js.map +1 -1
- package/dist/src/tui/prpause.js +7 -0
- package/dist/src/tui/prpause.js.map +1 -1
- package/dist/src/tui/remote.js +7 -1
- package/dist/src/tui/remote.js.map +1 -1
- package/dist/src/tui/route.js +143 -0
- package/dist/src/tui/route.js.map +1 -0
- package/dist/src/tui/setupstate.js +32 -0
- package/dist/src/tui/setupstate.js.map +1 -0
- package/dist/src/tui/sshtriage.js +102 -5
- package/dist/src/tui/sshtriage.js.map +1 -1
- package/dist/src/tui/theme.js +2 -1
- package/dist/src/tui/theme.js.map +1 -1
- package/dist/src/tui/unwedge.js +2 -12
- package/dist/src/tui/unwedge.js.map +1 -1
- package/dist/src/verdicts.js +17 -5
- package/dist/src/verdicts.js.map +1 -1
- package/dist/src/version.js +1 -1
- package/dist/src/version.js.map +1 -1
- package/dist/src/webhook.js +12 -1
- package/dist/src/webhook.js.map +1 -1
- package/dist/src/wire.js +34 -8
- package/dist/src/wire.js.map +1 -1
- package/dist/src/workerworld.js +11 -1
- package/dist/src/workerworld.js.map +1 -1
- package/docker/README.md +1 -1
- package/docker/companion/entrypoint.sh +20 -1
- package/docker/entrypoint.sh +57 -6
- package/docker-compose.yml +16 -1
- package/extensions/cli/home.ts +14 -0
- package/extensions/cli/login-route.ts +10 -0
- package/extensions/cli/selfimprove.ts +18 -0
- package/extensions/home.ts +484 -0
- package/extensions/login-route.ts +220 -0
- package/extensions/onboarding.ts +41 -11
- package/extensions/selfimprove.ts +65 -0
- package/extensions/setup-scan.ts +79 -766
- package/extensions/setup.ts +14 -70
- package/package.json +6 -4
- package/prompts/onboarding.md +58 -18
- package/schemas/review-result.v1.json +60 -0
- package/scripts/deploy.sh +8 -6
- package/scripts/mint-token.sh +25 -4
- package/scripts/qcr-host.sh +28 -5
- package/scripts/vm-ghcr-login.sh +7 -4
- package/src/archive/restore.ts +18 -21
- package/src/botworker.ts +13 -2
- package/src/channelfs.ts +3 -26
- package/src/ciretry.ts +2 -1
- package/src/cli.ts +59 -67
- package/src/cliops.ts +3 -2
- package/src/commands.ts +28 -2
- package/src/config.ts +62 -4
- package/src/daemon.ts +58 -7
- package/src/debuground.ts +3 -0
- package/src/debugroundpi.ts +12 -1
- package/src/dispatch.ts +5 -4
- package/src/engine/caserunner-sdk.ts +29 -314
- package/src/engine/debugpi.ts +17 -5
- package/src/execboundary.ts +16 -4
- package/src/github.ts +57 -15
- package/src/githubtoken.ts +435 -0
- package/src/home/adapters.ts +418 -0
- package/src/home/advanced.ts +421 -0
- package/src/home/advancedtools.ts +96 -0
- package/src/home/autoreview.ts +385 -0
- package/src/home/daemonstatus.ts +55 -0
- package/src/home/devicelogin.ts +107 -0
- package/src/home/launch.ts +257 -0
- package/src/home/loginowner.ts +248 -0
- package/src/home/memory.ts +176 -0
- package/src/home/model.ts +965 -0
- package/src/home/panel.ts +666 -0
- package/src/home/ports.ts +338 -0
- package/src/home/review.ts +206 -0
- package/src/home/reviews.ts +565 -0
- package/src/home/setup.ts +689 -0
- package/src/home/setupadapters.ts +217 -0
- package/src/home/setupcard.ts +288 -0
- package/src/home/setuptools.ts +57 -0
- package/src/home/spend.ts +194 -0
- package/src/home/switches.ts +293 -0
- package/src/home/switchon.ts +554 -0
- package/src/home/toolnames.ts +46 -0
- package/src/home/tune.ts +405 -0
- package/src/home/words.ts +8 -0
- package/src/homes.ts +17 -0
- package/src/install.ts +1 -1
- package/src/ipc/workerspec.ts +15 -0
- package/src/live/publish.ts +4 -1
- package/src/livecli.ts +6 -1
- package/src/loginhint.ts +8 -3
- package/src/loginrefresh/handover.ts +138 -0
- package/src/loginrefresh/lock.ts +72 -3
- package/src/loginrefresh/pirefresh.ts +3 -0
- package/src/loginrefresh/refresher.ts +34 -5
- package/src/loginrefresh/supervisor.ts +25 -2
- package/src/npmverbs.ts +55 -13
- package/src/onboard/choices.ts +40 -4
- package/src/onboard/engine.ts +70 -421
- package/src/onboard/pipin.ts +31 -0
- package/src/onboard/stepforward.ts +61 -0
- package/src/onboard/steps/appslug.ts +4 -1
- package/src/onboard/steps/bringup.ts +3 -2
- package/src/onboard/steps/compose.ts +31 -0
- package/src/onboard/steps/configfill.ts +144 -40
- package/src/onboard/steps/githubaccount.ts +716 -0
- package/src/onboard/steps/githubapp.ts +92 -8
- package/src/onboard/steps/golive.ts +2 -0
- package/src/onboard/steps/index.ts +11 -0
- package/src/onboard/steps/ingress.ts +98 -31
- package/src/onboard/steps/llmauth.ts +114 -29
- package/src/onboard/steps/loginshare.ts +258 -0
- package/src/onboard/steps/memory.ts +76 -3
- package/src/onboard/steps/proof.ts +473 -55
- package/src/onboard/steps/repoconfig.ts +85 -0
- package/src/onboard/steps/topology.ts +7 -6
- package/src/onboard/steps/transport.ts +15 -0
- package/src/onboard/steps/tuiroute.ts +119 -0
- package/src/onboard/steps/verifyup.ts +20 -1
- package/src/onboard/toolnames.ts +34 -0
- package/src/operatorwrite.ts +25 -0
- package/src/outcomes.ts +8 -1
- package/src/piagentdir.ts +12 -0
- package/src/pilogin.ts +32 -16
- package/src/posting-journal.ts +6 -1
- package/src/posting-outbox.ts +10 -2
- package/src/posting-receipt.ts +17 -0
- package/src/qcrbin.ts +16 -11
- package/src/recheck.ts +2 -1
- package/src/reconcile.ts +29 -3
- package/src/reviewargs.ts +119 -0
- package/src/reviewcli.ts +6 -167
- package/src/reviewdetach.ts +107 -13
- package/src/reviewhistory.ts +20 -6
- package/src/reviewnow.ts +76 -0
- package/src/scancli.ts +75 -826
- package/src/selfimprove.ts +90 -0
- package/src/setup/adapters.ts +13 -221
- package/src/setup/caserunner.ts +34 -273
- package/src/setup/engine.ts +62 -496
- package/src/setup/facts.ts +3 -627
- package/src/setup/findings.ts +3 -1264
- package/src/setup/presentation.ts +3 -18
- package/src/setup/scan.ts +3 -1033
- package/src/setup/scanbuild.ts +20 -114
- package/src/setup/scanlease.ts +3 -297
- package/src/setup/scanreport.ts +3 -373
- package/src/setup/scanrun.ts +3 -2268
- package/src/setup/scantree.ts +3 -259
- package/src/setup/stage2request.ts +3 -347
- package/src/setup/unitcut.ts +3 -523
- package/src/state.ts +43 -0
- package/src/statemachine.ts +4 -1
- package/src/takeover.ts +5 -0
- package/src/takeoverpi.ts +16 -2
- package/src/tui/app.ts +47 -0
- package/src/tui/board.ts +111 -14
- package/src/tui/cmdbar.ts +4 -1
- package/src/tui/companion.ts +80 -11
- package/src/tui/connect.ts +7 -1
- package/src/tui/deploytab.ts +2 -2
- package/src/tui/filters.ts +7 -3
- package/src/tui/ghread.ts +74 -7
- package/src/tui/index.ts +117 -23
- package/src/tui/memforward.ts +52 -3
- package/src/tui/memory.ts +3 -15
- package/src/tui/memoryurl.ts +22 -0
- package/src/tui/model.ts +36 -2
- package/src/tui/prpause.ts +11 -2
- package/src/tui/remote.ts +9 -1
- package/src/tui/route.ts +173 -0
- package/src/tui/setupstate.ts +56 -0
- package/src/tui/sshtriage.ts +127 -6
- package/src/tui/theme.ts +2 -1
- package/src/tui/unwedge.ts +2 -14
- package/src/verdicts.ts +23 -5
- package/src/version.ts +1 -1
- package/src/webhook.ts +10 -1
- package/src/wire.ts +42 -9
- package/src/workerworld.ts +16 -2
- package/dist/src/live/projection.js +0 -450
- package/dist/src/live/projection.js.map +0 -1
- package/dist/src/live/read.js +0 -60
- package/dist/src/live/read.js.map +0 -1
- package/dist/src/onboard/launch.js +0 -83
- package/dist/src/onboard/launch.js.map +0 -1
- package/dist/src/setup/ledger.js +0 -207
- package/dist/src/setup/ledger.js.map +0 -1
- package/dist/src/setup/stage2.js +0 -617
- package/dist/src/setup/stage2.js.map +0 -1
- package/dist/src/setup/textsniff.js +0 -89
- package/dist/src/setup/textsniff.js.map +0 -1
- package/src/live/projection.ts +0 -491
- package/src/live/read.ts +0 -86
- package/src/onboard/launch.ts +0 -115
- package/src/setup/ledger.ts +0 -324
- package/src/setup/stage2.ts +0 -926
- package/src/setup/textsniff.ts +0 -95
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// The repository the home's setup checklist picks, written into config.toml:
|
|
2
|
+
// the config fill's writer (configfill.ts) over the template or the
|
|
3
|
+
// install's config, never touching dry_run.
|
|
4
|
+
import { parse } from "smol-toml";
|
|
5
|
+
import { assertNoDryRunEdit, changedEdits, patchToml, tomlString } from "./configfill.js";
|
|
6
|
+
|
|
7
|
+
const message = (err: unknown): string => (err instanceof Error ? err.message : String(err));
|
|
8
|
+
|
|
9
|
+
/** The repository pick, as the config takes it. */
|
|
10
|
+
export interface RepoConfigChoice {
|
|
11
|
+
repo: string;
|
|
12
|
+
baseBranch: string | null;
|
|
13
|
+
timezone: string | null;
|
|
14
|
+
ownerName: string | null;
|
|
15
|
+
ownerLogin: string | null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** A config value as text, "" when absent or not a string. */
|
|
19
|
+
function valueIn(doc: Record<string, unknown> | null, section: string, key: string): string {
|
|
20
|
+
const table = doc?.[section];
|
|
21
|
+
if (typeof table !== "object" || table === null) return "";
|
|
22
|
+
const v = (table as Record<string, unknown>)[key];
|
|
23
|
+
return typeof v === "string" ? v.trim() : "";
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** The config text with the choice in it, from `existing` (null: none yet,
|
|
27
|
+
* so the template). What changed is named; a config that is not TOML is
|
|
28
|
+
* refused, since what it holds cannot be seen. */
|
|
29
|
+
export function renderRepoConfig(
|
|
30
|
+
existing: string | null,
|
|
31
|
+
template: string,
|
|
32
|
+
choice: RepoConfigChoice,
|
|
33
|
+
): { ok: true; text: string; wrote: string[] } | { ok: false; why: string } {
|
|
34
|
+
const fresh = existing === null;
|
|
35
|
+
const source = existing ?? template;
|
|
36
|
+
let doc: Record<string, unknown> | null = null;
|
|
37
|
+
try {
|
|
38
|
+
doc = parse(source);
|
|
39
|
+
} catch (err) {
|
|
40
|
+
return {
|
|
41
|
+
ok: false,
|
|
42
|
+
why: `${fresh ? "the shipped template" : "the config"} is not valid TOML (${message(err)}); nothing was written`,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
const fill = (section: string, key: string): boolean =>
|
|
46
|
+
fresh || valueIn(doc, section, key) === "";
|
|
47
|
+
const wanted = [
|
|
48
|
+
{ section: "github", key: "repo", value: tomlString(choice.repo) },
|
|
49
|
+
...(choice.baseBranch
|
|
50
|
+
? [{ section: "github", key: "base_branch", value: tomlString(choice.baseBranch) }]
|
|
51
|
+
: []),
|
|
52
|
+
...(choice.timezone && fill("ops", "timezone")
|
|
53
|
+
? [{ section: "ops", key: "timezone", value: tomlString(choice.timezone) }]
|
|
54
|
+
: []),
|
|
55
|
+
...(choice.ownerName && fill("owner", "name")
|
|
56
|
+
? [{ section: "owner", key: "name", value: tomlString(choice.ownerName) }]
|
|
57
|
+
: []),
|
|
58
|
+
...(choice.ownerLogin && fill("owner", "github_login")
|
|
59
|
+
? [{ section: "owner", key: "github_login", value: tomlString(choice.ownerLogin) }]
|
|
60
|
+
: []),
|
|
61
|
+
];
|
|
62
|
+
assertNoDryRunEdit(wanted);
|
|
63
|
+
const edits = changedEdits(source, wanted);
|
|
64
|
+
const text = edits.length > 0 ? patchToml(source, edits) : source;
|
|
65
|
+
let after: Record<string, unknown>;
|
|
66
|
+
try {
|
|
67
|
+
after = parse(text);
|
|
68
|
+
} catch (err) {
|
|
69
|
+
return {
|
|
70
|
+
ok: false,
|
|
71
|
+
why: `the patched config would not parse (${message(err)}); nothing was written`,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
if (valueIn(after, "github", "repo") !== choice.repo)
|
|
75
|
+
return {
|
|
76
|
+
ok: false,
|
|
77
|
+
why: "the patched config does not carry the repository; nothing was written",
|
|
78
|
+
};
|
|
79
|
+
if (fresh && after["dry_run"] !== true)
|
|
80
|
+
return {
|
|
81
|
+
ok: false,
|
|
82
|
+
why: "the shipped template no longer ships dry_run = true; nothing was written (invariant 6)",
|
|
83
|
+
};
|
|
84
|
+
return { ok: true, text, wrote: edits.map((e) => `[${e.section}] ${e.key}`) };
|
|
85
|
+
}
|
|
@@ -81,7 +81,7 @@ export interface TopologyOption {
|
|
|
81
81
|
const TOPOLOGY_OPTION_METADATA = {
|
|
82
82
|
vm: {
|
|
83
83
|
headline:
|
|
84
|
-
"A VM or server over ssh — RECOMMENDED, because a reviewer that is asleep reviews nothing",
|
|
84
|
+
"A VM or server over ssh — RECOMMENDED when this computer sleeps (a laptop), because a reviewer that is asleep reviews nothing",
|
|
85
85
|
tradeoffs: [
|
|
86
86
|
"it is on 24/7: pull requests opened while you sleep, while your laptop is shut, or while you are on a plane still get reviewed",
|
|
87
87
|
"you bring the box (any ssh-reachable Linux machine — a cheap cloud VM is plenty); the wizard does not create one for you",
|
|
@@ -91,12 +91,13 @@ const TOPOLOGY_OPTION_METADATA = {
|
|
|
91
91
|
],
|
|
92
92
|
},
|
|
93
93
|
local: {
|
|
94
|
-
headline:
|
|
94
|
+
headline:
|
|
95
|
+
"This computer — everything in Docker on the machine this wizard runs on; THE pick when it is itself an always-on server (a cloud VM you are logged into)",
|
|
95
96
|
tradeoffs: [
|
|
96
|
-
"nothing else to own: `docker compose up` and the whole system is here — daemon
|
|
97
|
-
"it reviews only while this machine is
|
|
98
|
-
"
|
|
99
|
-
"the honest way to try QuickReview, and a perfectly good permanent setup
|
|
97
|
+
"nothing else to own: `docker compose up` and the whole system is here — daemon and memory engine, and the TUI reads them locally with no ssh",
|
|
98
|
+
"it reviews only while this machine is on and the containers are running: on a server that is always; on a laptop, close the lid and the reviewer stops",
|
|
99
|
+
"nothing to expose on the default path: the daemon polls GitHub for new pull requests every few minutes, with no webhook, tunnel or public URL",
|
|
100
|
+
"the honest way to try QuickReview, and a perfectly good permanent setup on a desktop that stays on or a cloud VM",
|
|
100
101
|
"you can switch to a server later by re-running this step — the answers you gave (repo, App, keys) are kept",
|
|
101
102
|
],
|
|
102
103
|
},
|
|
@@ -37,6 +37,7 @@ import {
|
|
|
37
37
|
composePs,
|
|
38
38
|
containerStateCheck,
|
|
39
39
|
daemonExecArgs,
|
|
40
|
+
daemonRootExecArgs,
|
|
40
41
|
healthUrl,
|
|
41
42
|
memoryOn,
|
|
42
43
|
isUp,
|
|
@@ -87,6 +88,10 @@ export interface StackTransport {
|
|
|
87
88
|
daemonState(): Promise<DaemonState>;
|
|
88
89
|
/** Run an argv INSIDE the daemon container. */
|
|
89
90
|
exec(argv: readonly string[]): Promise<ProcessResult>;
|
|
91
|
+
/** The same, as uid 0: the one way to write the root-sealed operator
|
|
92
|
+
* command directory (`commands/operator/`), as `qcr drain` and the TUI's
|
|
93
|
+
* levers do. Used for the operator's own commands only. */
|
|
94
|
+
execAsRoot(argv: readonly string[]): Promise<ProcessResult>;
|
|
90
95
|
/** Run an argv INSIDE the container that owns the memory engine: the memory
|
|
91
96
|
* sidecar on the compose stack, `[tui] memory_container` (else the daemon's
|
|
92
97
|
* container) on the server route. */
|
|
@@ -155,6 +160,10 @@ function composeTransport(ctx: StepContext, opts: TransportOptions): StackTransp
|
|
|
155
160
|
return ctx.adapters.runner.run("docker", daemonExecArgs(root, argv));
|
|
156
161
|
},
|
|
157
162
|
|
|
163
|
+
async execAsRoot(argv) {
|
|
164
|
+
return ctx.adapters.runner.run("docker", daemonRootExecArgs(root, argv));
|
|
165
|
+
},
|
|
166
|
+
|
|
158
167
|
async execMemory(argv) {
|
|
159
168
|
return ctx.adapters.runner.run(
|
|
160
169
|
"docker",
|
|
@@ -465,6 +474,12 @@ function sshTransport(ctx: StepContext, opts: TransportOptions): StackTransport
|
|
|
465
474
|
return remote([...dockerPrefix, "exec", facts.container, ...argv]);
|
|
466
475
|
},
|
|
467
476
|
|
|
477
|
+
async execAsRoot(argv) {
|
|
478
|
+
if (facts.alias === "")
|
|
479
|
+
return { code: 1, stdout: "", stderr: "no ssh alias recorded for the server route" };
|
|
480
|
+
return remote([...dockerPrefix, "exec", "-u", "0", facts.container, ...argv]);
|
|
481
|
+
},
|
|
482
|
+
|
|
468
483
|
async execMemory(argv) {
|
|
469
484
|
if (facts.alias === "")
|
|
470
485
|
return { code: 1, stdout: "", stderr: "no ssh alias recorded for the server route" };
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
// The TUI's own route, checked by the layered verification.
|
|
2
|
+
//
|
|
3
|
+
// The containers being up and the daemon answering /health prove the stack;
|
|
4
|
+
// they do not prove that `qcr tui`, opened right after the wizard, can READ
|
|
5
|
+
// it. The TUI reads status.json its own way — the [tui] block, resolved by
|
|
6
|
+
// src/tui/route.ts, through `runnerFor` (local docker, or ssh to the VM) — so
|
|
7
|
+
// this check runs exactly that: the same resolution, the same argv
|
|
8
|
+
// (`statusArgv`), the same runner, and it needs a status.json that parses.
|
|
9
|
+
// A red check here is the board that would open on "✖ unreachable".
|
|
10
|
+
import { loadConfig, type TuiConfig } from "../../config.js";
|
|
11
|
+
import { runnerFor, statusArgv, transportOf, type SshRunner } from "../../tui/remote.js";
|
|
12
|
+
import { readRouteFacts, resolveTuiRoute, type RouteFacts } from "../../tui/route.js";
|
|
13
|
+
import { sandboxRoot, type CheckResult, type StepContext } from "../engine.js";
|
|
14
|
+
|
|
15
|
+
export const TUI_ROUTE_CHECK = "tui-route";
|
|
16
|
+
|
|
17
|
+
export interface TuiRouteCheckOptions {
|
|
18
|
+
/** The runner for a resolved route; default `runnerFor` (spawns docker/ssh). */
|
|
19
|
+
runner?: (cfg: TuiConfig) => SshRunner;
|
|
20
|
+
/** Route facts that override what is read from the sandbox root. */
|
|
21
|
+
facts?: Partial<RouteFacts>;
|
|
22
|
+
/** Attempts while status.json is not written yet (a daemon just booted). */
|
|
23
|
+
attempts?: number;
|
|
24
|
+
intervalMs?: number;
|
|
25
|
+
sleep?: (ms: number) => Promise<void>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function firstLine(text: string): string {
|
|
29
|
+
return (
|
|
30
|
+
text
|
|
31
|
+
.split("\n")
|
|
32
|
+
.map((l) => l.trim())
|
|
33
|
+
.find((l) => l !== "") ?? ""
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Total: every outcome is a check, never a throw. */
|
|
38
|
+
export async function tuiRouteCheck(
|
|
39
|
+
ctx: StepContext,
|
|
40
|
+
opts: TuiRouteCheckOptions = {},
|
|
41
|
+
): Promise<CheckResult> {
|
|
42
|
+
if (ctx.adapters.host.inContainer) {
|
|
43
|
+
return {
|
|
44
|
+
name: TUI_ROUTE_CHECK,
|
|
45
|
+
ok: true,
|
|
46
|
+
detail:
|
|
47
|
+
"not checked here — this wizard runs inside the image, and the TUI runs on the host: open `qcr tui` there",
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
let tui: TuiConfig;
|
|
51
|
+
try {
|
|
52
|
+
tui = loadConfig(ctx.adapters.paths.configPath).tui;
|
|
53
|
+
} catch (err) {
|
|
54
|
+
return {
|
|
55
|
+
name: TUI_ROUTE_CHECK,
|
|
56
|
+
ok: false,
|
|
57
|
+
detail: `config.toml does not load, so the TUI could not start either: ${String(err instanceof Error ? err.message : err)}`,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
const route = resolveTuiRoute(tui, {
|
|
61
|
+
...readRouteFacts(sandboxRoot(ctx.adapters.paths)),
|
|
62
|
+
...opts.facts,
|
|
63
|
+
});
|
|
64
|
+
if (route.unconfigured !== undefined)
|
|
65
|
+
return {
|
|
66
|
+
name: TUI_ROUTE_CHECK,
|
|
67
|
+
ok: false,
|
|
68
|
+
detail: `${route.unconfigured} — Fix: run the config_fill step again`,
|
|
69
|
+
};
|
|
70
|
+
const cfg = route.cfg;
|
|
71
|
+
const run = (opts.runner ?? runnerFor)(cfg);
|
|
72
|
+
const argv = statusArgv(cfg);
|
|
73
|
+
const where =
|
|
74
|
+
transportOf(cfg) === "local"
|
|
75
|
+
? `local docker, container ${cfg.container}`
|
|
76
|
+
: `ssh ${cfg.ssh_host}`;
|
|
77
|
+
const attempts = Math.max(1, opts.attempts ?? 4);
|
|
78
|
+
const sleep = opts.sleep ?? ((ms: number) => new Promise<void>((r) => setTimeout(r, ms)));
|
|
79
|
+
let last = "";
|
|
80
|
+
for (let i = 1; i <= attempts; i++) {
|
|
81
|
+
let res;
|
|
82
|
+
try {
|
|
83
|
+
res = await run(cfg.ssh_host, argv, { timeoutMs: 15_000 });
|
|
84
|
+
} catch (err) {
|
|
85
|
+
res = { ok: false, stdout: "", stderr: String(err), code: null };
|
|
86
|
+
}
|
|
87
|
+
if (res.ok) {
|
|
88
|
+
try {
|
|
89
|
+
const parsed = JSON.parse(res.stdout) as unknown;
|
|
90
|
+
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
|
|
91
|
+
const prs = (parsed as { prs?: unknown }).prs;
|
|
92
|
+
return {
|
|
93
|
+
name: TUI_ROUTE_CHECK,
|
|
94
|
+
ok: true,
|
|
95
|
+
detail:
|
|
96
|
+
`the TUI's own read works (${where}: \`${argv.join(" ")}\`) — status.json parses` +
|
|
97
|
+
`${Array.isArray(prs) ? `, ${prs.length} PR${prs.length === 1 ? "" : "s"} on the board` : ""}`,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
last = "status.json is not a JSON object";
|
|
101
|
+
} catch {
|
|
102
|
+
last = "status.json is not valid JSON";
|
|
103
|
+
}
|
|
104
|
+
} else {
|
|
105
|
+
last = firstLine(res.stderr) || `exit ${String(res.code)}`;
|
|
106
|
+
}
|
|
107
|
+
if (i < attempts) await sleep(opts.intervalMs ?? 5_000);
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
name: TUI_ROUTE_CHECK,
|
|
111
|
+
ok: false,
|
|
112
|
+
detail:
|
|
113
|
+
`the TUI could not read status.json the way it will (${where}: \`${argv.join(" ")}\`): ${last} — Fix: ` +
|
|
114
|
+
(transportOf(cfg) === "local"
|
|
115
|
+
? "the [tui] container must be the running daemon container and remote_state_dir its state dir " +
|
|
116
|
+
"(run the config_fill step again to rewrite both)"
|
|
117
|
+
: `\`ssh ${cfg.ssh_host} true\` must work without a prompt, and remote_state_dir must be the VM's state dir`),
|
|
118
|
+
};
|
|
119
|
+
}
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
// server, a curl on the box). Layer 3 needs no transport at all: it reads
|
|
35
35
|
// GitHub's OWN delivery log, which is the same record wherever the daemon
|
|
36
36
|
// runs, and that is exactly why it is the layer that proves the path.
|
|
37
|
-
import { CHOICE_KEYS } from "../choices.js";
|
|
37
|
+
import { CHOICE_KEYS, userAuthRecorded } from "../choices.js";
|
|
38
38
|
import { mintAppJwt } from "@quickchatai/pi-quickreview/src/githubAuth.js";
|
|
39
39
|
import type { CheckResult, OnboardingStep, StepContext, StepReport } from "../engine.js";
|
|
40
40
|
import { ingressOptionOf, secretsDirFor, readConfigText } from "./compose.js";
|
|
@@ -42,6 +42,7 @@ import { api as githubApi, readCredentials } from "./githubapp.js";
|
|
|
42
42
|
import { readTomlKey, WEBHOOK_PATH } from "./ingress.js";
|
|
43
43
|
import { sshCommandLine } from "./ssh.js";
|
|
44
44
|
import { sshAliasOf, topologyOf } from "./topology.js";
|
|
45
|
+
import { tuiRouteCheck, type TuiRouteCheckOptions } from "./tuiroute.js";
|
|
45
46
|
import {
|
|
46
47
|
DEFAULT_HEALTH_TIMEOUT_MS,
|
|
47
48
|
stackTransportFor,
|
|
@@ -67,6 +68,8 @@ export interface VerifyStackStepOptions {
|
|
|
67
68
|
* WHICH transport is never an option: `stackTransportFor` decides from the
|
|
68
69
|
* recorded topology, and this step never learns which one it got. */
|
|
69
70
|
transport?: TransportOptions;
|
|
71
|
+
/** The TUI-route check's seams (tuiroute.ts): its runner and route facts. */
|
|
72
|
+
tuiRoute?: TuiRouteCheckOptions;
|
|
70
73
|
}
|
|
71
74
|
|
|
72
75
|
// ---- GitHub's delivery log ---------------------------------------------------
|
|
@@ -415,6 +418,22 @@ export function verifyStackStep(opts: VerifyStackStepOptions = {}): OnboardingSt
|
|
|
415
418
|
});
|
|
416
419
|
return { ok: false, checks };
|
|
417
420
|
}
|
|
421
|
+
// The operator's own window onto the stack: can `qcr tui` read it the way
|
|
422
|
+
// it will? Independent of webhook delivery, so it never stops layer 3.
|
|
423
|
+
checks.push(await tuiRouteCheck(ctx, opts.tuiRoute));
|
|
424
|
+
// Layer 3 reads the App's own hook config and delivery log — App-JWT
|
|
425
|
+
// calls. The own-account route has no App, so there is no hook to read
|
|
426
|
+
// and nothing to deliver: the layer is skipped, and the reason said.
|
|
427
|
+
if (userAuthRecorded(ctx.choices)) {
|
|
428
|
+
checks.push({
|
|
429
|
+
name: "ping-delivery",
|
|
430
|
+
ok: true,
|
|
431
|
+
detail:
|
|
432
|
+
'not checked — this install runs on your own GitHub account ([github] auth = "user"), which has no GitHub App, ' +
|
|
433
|
+
"so there is no webhook and no App delivery log to read; the reconcile poll is the only trigger path",
|
|
434
|
+
});
|
|
435
|
+
return { ok: checks.every((c) => c.ok), checks };
|
|
436
|
+
}
|
|
418
437
|
// Layer 3 exists only where GitHub can deliver. The polling topology
|
|
419
438
|
// deliberately exposes nothing, so a ping can never land there — requiring
|
|
420
439
|
// one would keep this step (and so onboarding's finish) red forever.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// The onboarding session's tool names: one source for the extension that
|
|
2
|
+
// registers them (extensions/onboarding.ts) and the `--tools` allowlist that
|
|
3
|
+
// activates them (`onboardingToolAllowlist`).
|
|
4
|
+
//
|
|
5
|
+
// pi's `--tools <list>` is an allowlist over EVERY tool, extension tools
|
|
6
|
+
// included (pi 1.4.0: createAgentSession({ tools }) leaves an extension tool
|
|
7
|
+
// that is not on the list inactive). So the argv must name each wizard tool,
|
|
8
|
+
// or the guide runs with no tools at all. The built-in belt stays read-only:
|
|
9
|
+
// bash, write and edit are never on the list.
|
|
10
|
+
|
|
11
|
+
/** pi's built-in tools the wizard may use: reading only. */
|
|
12
|
+
export const ONBOARD_BUILTIN_TOOLS: readonly string[] = ["read", "ls", "grep"];
|
|
13
|
+
|
|
14
|
+
/** The checklist tool. */
|
|
15
|
+
export const ONBOARD_STATUS_TOOL = "onboard_status";
|
|
16
|
+
/** The verify-everything-and-exit tool. */
|
|
17
|
+
export const ONBOARD_FINISH_TOOL = "finish_and_exit";
|
|
18
|
+
|
|
19
|
+
/** A step's tool name. */
|
|
20
|
+
export function onboardStepToolName(stepId: string): string {
|
|
21
|
+
return `onboard_${stepId}`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Every tool the onboarding extension registers over these step ids, in
|
|
25
|
+
* registration order. */
|
|
26
|
+
export function onboardingExtensionToolNames(stepIds: readonly string[]): string[] {
|
|
27
|
+
return [...stepIds.map(onboardStepToolName), ONBOARD_STATUS_TOOL, ONBOARD_FINISH_TOOL];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** The whole `--tools` allowlist for the onboarding pi: the read-only belt,
|
|
31
|
+
* then every wizard tool. */
|
|
32
|
+
export function onboardingToolAllowlist(stepIds: readonly string[]): string[] {
|
|
33
|
+
return [...ONBOARD_BUILTIN_TOOLS, ...onboardingExtensionToolNames(stepIds)];
|
|
34
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// The one in-container shell that writes an operator command file as root
|
|
2
|
+
// into the root-sealed `<commands>/operator/` — its full rationale is on
|
|
3
|
+
// `operatorCommandWriteScript` in src/tui/unwedge.ts, the TUI's caller. A
|
|
4
|
+
// leaf module (no imports) so the onboarding wizard's proof round, which knows
|
|
5
|
+
// the release image's command root rather than a TUI config, uses the same
|
|
6
|
+
// script without reaching the TUI's module graph.
|
|
7
|
+
|
|
8
|
+
/** `operatorCommandWriteScript` for a known command root (`<root>/commands`
|
|
9
|
+
* inside the container) — the onboarding wizard's caller, which knows the
|
|
10
|
+
* release image's layout rather than a TUI config. */
|
|
11
|
+
export function operatorCommandWriteScriptAt(root: string, name: string): string {
|
|
12
|
+
const dir = `${root}/operator`;
|
|
13
|
+
const tmp = `.${name}.tmp`;
|
|
14
|
+
return (
|
|
15
|
+
`[ -d '${root}' ] || { echo 'daemon commands dir ${root} missing - is the daemon running?' >&2; exit 1; }; ` +
|
|
16
|
+
`mkdir -p '${dir}' || exit 1; ` +
|
|
17
|
+
`[ ! -L '${dir}' ] || { echo '${dir} is a symlink - refusing to write the operator command' >&2; exit 1; }; ` +
|
|
18
|
+
`R=$(cd -P '${root}' && pwd -P) || exit 1; ` +
|
|
19
|
+
`cd -P '${dir}' || exit 1; ` +
|
|
20
|
+
`[ "$(pwd -P)" = "$R/operator" ] || { echo '${dir} was replaced while writing - refusing to write the operator command' >&2; exit 1; }; ` +
|
|
21
|
+
`chown -h 0:0 . && chmod 0755 . || exit 1; ` +
|
|
22
|
+
`find . -maxdepth 1 -name '*.json' -mmin +1440 -delete 2>/dev/null; ` +
|
|
23
|
+
`rm -f '${tmp}' '${name}' && cat > '${tmp}' && chmod 0644 '${tmp}' && mv '${tmp}' '${name}'`
|
|
24
|
+
);
|
|
25
|
+
}
|
package/src/outcomes.ts
CHANGED
|
@@ -97,6 +97,7 @@ import { prActivity, type PrActivity } from "./pr-activity.js";
|
|
|
97
97
|
// read-only and memoised; a commit the clone does not hold is UNKNOWN, never a
|
|
98
98
|
// change and never no-change. Every failure is the sweep's own — reconcile's
|
|
99
99
|
// health (which lifts a deploy drain) never waits on it.
|
|
100
|
+
import { carriesOwnMarker } from "@quickchatai/pi-quickreview/src/identity.js";
|
|
100
101
|
import {
|
|
101
102
|
carriedToRef,
|
|
102
103
|
isPostedRow,
|
|
@@ -519,6 +520,10 @@ export interface OutcomeCaptureDeps {
|
|
|
519
520
|
store: StateStore;
|
|
520
521
|
/** `[github] bot_login` — whose root comments are findings. */
|
|
521
522
|
botLogin: string;
|
|
523
|
+
/** `[github] auth = "user"`: the login is the person's, so a root comment is
|
|
524
|
+
* a finding only when it carries the reviewer's marker, and the person's
|
|
525
|
+
* own replies are a human's. */
|
|
526
|
+
personMode?: boolean;
|
|
522
527
|
/** Absent ⇒ every "did the code change" question is unknown. */
|
|
523
528
|
git?: AnchorGit;
|
|
524
529
|
now?: () => Date;
|
|
@@ -585,6 +590,7 @@ export class OutcomeCapture {
|
|
|
585
590
|
this.store.db.transaction(() => {
|
|
586
591
|
for (const c of comments) {
|
|
587
592
|
if (c.user !== this.deps.botLogin || c.in_reply_to_id !== null) continue;
|
|
593
|
+
if (this.deps.personMode && !carriesOwnMarker(c.body)) continue;
|
|
588
594
|
const marker = parseFindingMarker(c.body);
|
|
589
595
|
const head = commentHead(c.body);
|
|
590
596
|
const info = stmt.run({
|
|
@@ -823,7 +829,8 @@ export class OutcomeCapture {
|
|
|
823
829
|
const replies = new Map<number, PrCommentRow[]>();
|
|
824
830
|
for (const c of this.store.prComments(pr)) {
|
|
825
831
|
if (c.source !== "review_comment" || c.in_reply_to_id === null) continue;
|
|
826
|
-
if (c.author === this.deps.botLogin)
|
|
832
|
+
if (c.author === this.deps.botLogin && (!this.deps.personMode || carriesOwnMarker(c.body)))
|
|
833
|
+
continue;
|
|
827
834
|
replies.set(c.in_reply_to_id, [...(replies.get(c.in_reply_to_id) ?? []), c]);
|
|
828
835
|
}
|
|
829
836
|
const events = this.store.prEvents(pr, [
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// pi's agent dir, the way pi itself finds it: a leaf, so the npm verbs
|
|
2
|
+
// (src/npmverbs.ts) and the home's login reader (src/home/adapters.ts) read
|
|
3
|
+
// one rule without importing each other.
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
|
|
7
|
+
/** pi's agent dir: `$PI_CODING_AGENT_DIR`, else `~/.pi/agent`, as pi reads it. */
|
|
8
|
+
export function piAgentDir(env: Readonly<Record<string, string | undefined>>): string {
|
|
9
|
+
const set = (env["PI_CODING_AGENT_DIR"] ?? "").trim();
|
|
10
|
+
if (set !== "") return set;
|
|
11
|
+
return join((env["HOME"] ?? "").trim() || homedir(), ".pi", "agent");
|
|
12
|
+
}
|
package/src/pilogin.ts
CHANGED
|
@@ -4,15 +4,24 @@
|
|
|
4
4
|
// (src/scanbox.ts) share it without one reaching the other.
|
|
5
5
|
|
|
6
6
|
/** pi itself, with `/login` (or `/login <provider>`) typed and sent for the
|
|
7
|
-
* user: pi runs in a fresh tmux session
|
|
8
|
-
* editor borders on screen
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
7
|
+
* user: pi runs in a fresh tmux session, and qcr reads pi's input box, the
|
|
8
|
+
* lines between the last two editor borders on screen, every 0.3 s (a second
|
|
9
|
+
* after a key, which counts as three polls toward the 30 s). Empty, it gets the keys and an Enter; still holding exactly
|
|
10
|
+
* the keys, one more Enter (an Enter sent while pi still starts is lost, and
|
|
11
|
+
* the first one may only accept the autocomplete). Anything else in the box is
|
|
12
|
+
* one of pi's menus (the provider list, a provider's own login methods), and
|
|
13
|
+
* no key goes in again. pi draws every selection in that box, so this holds
|
|
14
|
+
* whatever title pi or a provider gives the menu, where an Enter would pick
|
|
15
|
+
* its default: a browser login, opened again on every poll. pi starts with
|
|
16
|
+
* `--no-approve`: a login needs nothing from the folder qcr was run in, so a
|
|
17
|
+
* project folder neither stops it at pi's trust prompt nor gets trusted. A pi
|
|
18
|
+
* that has already exited ends the wait at once. After 30 s the terminal
|
|
19
|
+
* attaches regardless, so every login pi offers is there and quitting pi ends
|
|
20
|
+
* it; a failed attach (no terminal) ends the session instead of leaving pi
|
|
21
|
+
* running where nobody can reach it. `pi` is the pi to start: the
|
|
22
|
+
* companion image's CLI on its PATH by default; the release image's entrypoint
|
|
23
|
+
* passes its one pi, node_modules/.bin/pi (src/picli.ts `piCliIn`), and
|
|
24
|
+
* `qcr login` the pinned package's own bin (src/picli.ts `piCliPath`). */
|
|
16
25
|
export function loginInPi(provider?: string, pi: string = "pi"): string[] {
|
|
17
26
|
if (provider !== undefined && !/^[A-Za-z0-9._-]+$/.test(provider))
|
|
18
27
|
throw new Error(
|
|
@@ -22,16 +31,23 @@ export function loginInPi(provider?: string, pi: string = "pi"): string[] {
|
|
|
22
31
|
if (!/^[A-Za-z0-9._/@+-]+$/.test(pi))
|
|
23
32
|
throw new Error(`scan login: pi ${JSON.stringify(pi)} is not a plain path`);
|
|
24
33
|
const keys = provider ? `/login ${provider}` : "/login";
|
|
34
|
+
// The input box, its side padding (pi's editorPaddingX) trimmed and blank
|
|
35
|
+
// lines dropped; awk fails while fewer than two borders are drawn (pi still
|
|
36
|
+
// starting), and the loop then only waits.
|
|
37
|
+
const box =
|
|
38
|
+
`tmux capture-pane -p -t "$s" 2>/dev/null | awk '/──────────/ { b[++n] = NR } { l[NR] = $0 } ` +
|
|
39
|
+
`END { if (n < 2) exit 1; for (i = b[n - 1] + 1; i < b[n]; i++) ` +
|
|
40
|
+
`{ sub(/^ +/, "", l[i]); sub(/ +$/, "", l[i]); if (l[i] != "") print l[i] } }'`;
|
|
25
41
|
return [
|
|
26
42
|
"sh",
|
|
27
43
|
"-c",
|
|
28
|
-
`s=qcr-login-$$; tmux new-session -d -s "$s" ${pi} || exit 1; i=0; ` +
|
|
29
|
-
`while [ $i -lt 100 ]; do
|
|
30
|
-
`
|
|
31
|
-
`if [ "$
|
|
32
|
-
`
|
|
33
|
-
`else
|
|
44
|
+
`s=qcr-login-$$; tmux new-session -d -s "$s" ${pi} --no-approve || exit 1; i=0; ` +
|
|
45
|
+
`while [ $i -lt 100 ]; do tmux has-session -t "$s" 2>/dev/null || break; ` +
|
|
46
|
+
`if e=$(${box}); then ` +
|
|
47
|
+
`if [ -z "$e" ]; then tmux send-keys -t "$s" -l '${keys}'; tmux send-keys -t "$s" Enter; sleep 1; i=$((i+3)); ` +
|
|
48
|
+
`elif [ "$e" = '${keys}' ]; then tmux send-keys -t "$s" Enter; sleep 1; i=$((i+3)); ` +
|
|
49
|
+
`else break; fi; fi; ` +
|
|
34
50
|
`sleep 0.3; i=$((i+1)); done; ` +
|
|
35
|
-
`
|
|
51
|
+
`tmux attach -t "$s" || { tmux kill-session -t "$s" 2>/dev/null; exit 1; }`,
|
|
36
52
|
];
|
|
37
53
|
}
|
package/src/posting-journal.ts
CHANGED
|
@@ -106,6 +106,10 @@ export interface JournalOptions {
|
|
|
106
106
|
/** `<state>/posting-receipts`, the dir the verdict watcher and the outbox read. */
|
|
107
107
|
dir: string;
|
|
108
108
|
repo: string;
|
|
109
|
+
/** `[github] auth = "user"`: every receipt says `person_mode`, the posting
|
|
110
|
+
* guard posts every event as a COMMENT, and an APPROVE posted as a COMMENT
|
|
111
|
+
* is NOT the withheld-approval shape (posting-receipt.ts). */
|
|
112
|
+
personMode?: boolean;
|
|
109
113
|
now?: () => Date;
|
|
110
114
|
}
|
|
111
115
|
|
|
@@ -357,7 +361,7 @@ export class PostingJournal {
|
|
|
357
361
|
// An APPROVE that GitHub holds as a COMMENT was softened by guard 4 on
|
|
358
362
|
// the gate after the CI preflight: the withheld-approval shape, which
|
|
359
363
|
// the outbox's confirmation stamps the same way.
|
|
360
|
-
...(o.intended === "APPROVE" && actual === "COMMENT"
|
|
364
|
+
...(o.intended === "APPROVE" && actual === "COMMENT" && !this.o.personMode
|
|
361
365
|
? { delivered_by: "daemon_retry" as const }
|
|
362
366
|
: {}),
|
|
363
367
|
}),
|
|
@@ -519,6 +523,7 @@ export class PostingJournal {
|
|
|
519
523
|
actual_event: actual,
|
|
520
524
|
payload_sha256: payloadHash(payload),
|
|
521
525
|
prepared_at: base.prepared_at ?? this.iso(),
|
|
526
|
+
...(this.o.personMode ? { person_mode: true } : {}),
|
|
522
527
|
...extra,
|
|
523
528
|
};
|
|
524
529
|
}
|
package/src/posting-outbox.ts
CHANGED
|
@@ -285,6 +285,10 @@ export class PostingOutbox {
|
|
|
285
285
|
repo: string;
|
|
286
286
|
dryRun: boolean;
|
|
287
287
|
botLogins: string[];
|
|
288
|
+
/** `[github] auth = "user"`: receipts say `person_mode`, and an APPROVE
|
|
289
|
+
* delivered as a COMMENT is the person-mode rule, not a guard-4
|
|
290
|
+
* softening (posting-receipt.ts). */
|
|
291
|
+
personMode?: boolean;
|
|
288
292
|
adapter: PostingDeliveryAdapter;
|
|
289
293
|
current: (job: PostingJob) => boolean;
|
|
290
294
|
now?: () => Date;
|
|
@@ -432,10 +436,13 @@ export class PostingOutbox {
|
|
|
432
436
|
gh_state: state,
|
|
433
437
|
// An APPROVE that GitHub holds as a COMMENT was softened by guard 4 on
|
|
434
438
|
// the gate, which only the daemon's delivery passes through (the shell
|
|
435
|
-
// route refuses a red-CI approval): the withheld-approval shape.
|
|
436
|
-
|
|
439
|
+
// route refuses a red-CI approval): the withheld-approval shape. Not in
|
|
440
|
+
// person mode, where every review is a COMMENT by rule.
|
|
441
|
+
...(deliveredBy ||
|
|
442
|
+
(j.intended_event === "APPROVE" && event === "COMMENT" && !this.deps.personMode)
|
|
437
443
|
? { delivered_by: "daemon_retry" as const }
|
|
438
444
|
: {}),
|
|
445
|
+
...(this.deps.personMode ? { person_mode: true as const } : {}),
|
|
439
446
|
} satisfies PostingReceipt);
|
|
440
447
|
j.status = "done";
|
|
441
448
|
writePostingFile(path, j);
|
|
@@ -762,6 +769,7 @@ export class PostingOutbox {
|
|
|
762
769
|
status: "prepared",
|
|
763
770
|
payload_sha256: j.payload_sha256,
|
|
764
771
|
prepared_at: j.attempted_at,
|
|
772
|
+
...(this.deps.personMode ? { person_mode: true as const } : {}),
|
|
765
773
|
} satisfies PostingReceipt);
|
|
766
774
|
try {
|
|
767
775
|
const result = await this.deps.adapter.createReview(j.pr, j.payload);
|
package/src/posting-receipt.ts
CHANGED
|
@@ -52,6 +52,11 @@ export interface PostingReceipt {
|
|
|
52
52
|
* (APPROVE posted as a COMMENT, approval withheld for red CI) is something
|
|
53
53
|
* the verdict file cannot know: see `retrySoftenedApproval`. */
|
|
54
54
|
delivered_by?: "daemon_retry";
|
|
55
|
+
/** Written by a person-mode install (`[github] auth = "user"`): the posting
|
|
56
|
+
* guard posts EVERY review as a COMMENT there, so `actual_event` COMMENT
|
|
57
|
+
* proves nothing about the decision — `intended_event` carries it, and an
|
|
58
|
+
* APPROVE posted as a COMMENT is not a withheld approval. */
|
|
59
|
+
person_mode?: true;
|
|
55
60
|
/** In memory only, never written by the posting scripts: the
|
|
56
61
|
* daemon SYNTHESISED this receipt from a bot review carrying the round's
|
|
57
62
|
* marker, because the shell never persisted its own. `intended_event` is
|
|
@@ -209,6 +214,7 @@ export function parsePostingReceipt(raw: string): PostingOutcome | undefined {
|
|
|
209
214
|
value.status !== "deferred") ||
|
|
210
215
|
(value.status === "deferred" && !isoDate(value.deferred_at)) ||
|
|
211
216
|
(value.delivered_by !== undefined && value.delivered_by !== "daemon_retry") ||
|
|
217
|
+
(value.person_mode !== undefined && value.person_mode !== true) ||
|
|
212
218
|
typeof value.payload_sha256 !== "string" ||
|
|
213
219
|
!/^[a-f0-9]{64}$/.test(value.payload_sha256) ||
|
|
214
220
|
!isoDate(value.prepared_at)
|
|
@@ -245,6 +251,7 @@ export function parsePostingReceipt(raw: string): PostingOutcome | undefined {
|
|
|
245
251
|
* the receipt is the evidence that the approval was withheld. */
|
|
246
252
|
export function retrySoftenedApproval(receipt: PostingReceipt): boolean {
|
|
247
253
|
return (
|
|
254
|
+
receipt.person_mode !== true &&
|
|
248
255
|
receipt.delivered_by === "daemon_retry" &&
|
|
249
256
|
receipt.status === "confirmed" &&
|
|
250
257
|
receipt.intended_event === "APPROVE" &&
|
|
@@ -259,6 +266,16 @@ export function receiptMatchesVerdict(receipt: PostingReceipt, verdict: VerdictF
|
|
|
259
266
|
receipt.head_sha.toLowerCase() !== verdict.head_sha.toLowerCase()
|
|
260
267
|
)
|
|
261
268
|
return false;
|
|
269
|
+
// Person mode posts every review as a COMMENT. A receipt synthesised from
|
|
270
|
+
// GitHub knows nothing more than that; any other one is matched on what
|
|
271
|
+
// the session asked for, as if it had been posted as asked.
|
|
272
|
+
if (receipt.person_mode === true && receipt.actual_event === "COMMENT") {
|
|
273
|
+
if (receipt.reconciled === true) return true;
|
|
274
|
+
const asAsked: PostingReceipt = { ...receipt, actual_event: receipt.intended_event };
|
|
275
|
+
delete asAsked.person_mode;
|
|
276
|
+
delete asAsked.delivered_by;
|
|
277
|
+
return receiptMatchesVerdict(asAsked, verdict);
|
|
278
|
+
}
|
|
262
279
|
if (verdict.verdict === "REQUEST_CHANGES")
|
|
263
280
|
return receipt.actual_event === "REQUEST_CHANGES" || receipt.actual_event === "COMMENT";
|
|
264
281
|
if (verdict.verdict === "APPROVE" && retrySoftenedApproval(receipt)) return true;
|