@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
package/src/tui/remote.ts
CHANGED
|
@@ -337,8 +337,16 @@ export function killInFlightSsh(): number {
|
|
|
337
337
|
return killed;
|
|
338
338
|
}
|
|
339
339
|
|
|
340
|
+
/** What the ssh runner answers for an empty host — spawned, `ssh "" …` would
|
|
341
|
+
* die on "Could not resolve hostname" and every surface would print an ssh
|
|
342
|
+
* remedy with no host in it. */
|
|
343
|
+
export const NO_SSH_HOST_STDERR =
|
|
344
|
+
'[tui] ssh_host is empty — set it to the VM\'s ssh alias, or set [tui] target = "local"';
|
|
345
|
+
|
|
340
346
|
export const realSshRunner: SshRunner = (host, argv, opts) =>
|
|
341
|
-
|
|
347
|
+
host.trim() === ""
|
|
348
|
+
? Promise.resolve({ ok: false, stdout: "", stderr: NO_SSH_HOST_STDERR, code: null })
|
|
349
|
+
: runChild("ssh", ["-o", "BatchMode=yes", host, argv.map(shellQuote).join(" ")], opts);
|
|
342
350
|
|
|
343
351
|
/**
|
|
344
352
|
* The laptop topology's runner — the same argv every builder above
|
package/src/tui/route.ts
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
// The TUI's route — which daemon the board reads, and how — resolved ONCE,
|
|
2
|
+
// before the first frame, from config.toml's [tui] block plus three local
|
|
3
|
+
// facts that cost no docker, network or directory walk: the compose
|
|
4
|
+
// project's `.env`, the onboarding checklist, and whether this user can open
|
|
5
|
+
// the Docker socket without sudo.
|
|
6
|
+
//
|
|
7
|
+
// Why a resolution step at all. `[tui]` ships VM-shaped defaults (target
|
|
8
|
+
// "ssh", sudo docker, the VM host's state path), and on one box a fresh
|
|
9
|
+
// install opens the TUI before onboarding's config_fill has written its
|
|
10
|
+
// local values — or with a hand-written config that never names them. Every
|
|
11
|
+
// builder in remote.ts reads the config it is handed, and the transport is
|
|
12
|
+
// still decided by the explicit `target` knob alone (`transportOf`); this
|
|
13
|
+
// module only decides which config that is, and says so once when it departs
|
|
14
|
+
// from the file:
|
|
15
|
+
//
|
|
16
|
+
// * `target = "ssh"` with an EMPTY `ssh_host` cannot reach anything. When
|
|
17
|
+
// this checkout is a compose project (the checklist recorded the local
|
|
18
|
+
// topology, or none was recorded and the project `.env` pins a release)
|
|
19
|
+
// the route is the local stack, said once; otherwise the route is
|
|
20
|
+
// `unconfigured` and every surface shows the one precise remedy instead
|
|
21
|
+
// of an ssh command with no host in it.
|
|
22
|
+
// * On the local route the state dir is the release image's
|
|
23
|
+
// (`/opt/qcr/state`, the compose `qcr-state` volume) whenever the knob
|
|
24
|
+
// still holds the VM default, the daemon's `-daemon-N` container implies
|
|
25
|
+
// its `-memory-N` sidecar for the memory forward, and sudo is dropped
|
|
26
|
+
// when the socket answers this user without it (a non-interactive
|
|
27
|
+
// `sudo docker` behind the alt screen can only fail).
|
|
28
|
+
//
|
|
29
|
+
// An operator's explicit, non-default value always wins: nothing here
|
|
30
|
+
// overwrites a value the file states differently from the shipped default.
|
|
31
|
+
import { accessSync, constants, readFileSync } from "node:fs";
|
|
32
|
+
import { join } from "node:path";
|
|
33
|
+
import { TUI_DEFAULTS, type TuiConfig } from "../config.js";
|
|
34
|
+
import { readChecklist, type ChecklistFile } from "../onboard/engine.js";
|
|
35
|
+
import { CHOICE_KEYS } from "../onboard/choices.js";
|
|
36
|
+
import { NO_SSH_HOST_STDERR } from "./remote.js";
|
|
37
|
+
|
|
38
|
+
/** The release image's state dir inside the daemon container (the compose
|
|
39
|
+
* `qcr-state` volume; src/onboard/steps/ingress.ts CONTAINER_STATE_DIR). */
|
|
40
|
+
export const RELEASE_STATE_DIR = "/opt/qcr/state";
|
|
41
|
+
|
|
42
|
+
/** The bundled compose project's daemon and memory containers
|
|
43
|
+
* (docker-compose.yml `name: quickreview`). */
|
|
44
|
+
export const COMPOSE_DAEMON_CONTAINER = "quickreview-daemon-1";
|
|
45
|
+
export const COMPOSE_MEMORY_CONTAINER = "quickreview-memory-1";
|
|
46
|
+
|
|
47
|
+
/** The legacy daemon container (no `qcr.layout` label): the one container whose
|
|
48
|
+
* in-container state dir is still the VM-shaped default. */
|
|
49
|
+
const LEGACY_CONTAINER = "quickreview-sandbox";
|
|
50
|
+
|
|
51
|
+
export interface RouteFacts {
|
|
52
|
+
/** The compose project directory (this checkout): remedy text names it. */
|
|
53
|
+
projectDir: string;
|
|
54
|
+
/** `<project>/.env`, or null when there is none. */
|
|
55
|
+
projectEnv: string | null;
|
|
56
|
+
/** The onboarding checklist, or null when absent or unreadable. */
|
|
57
|
+
checklist: ChecklistFile | null;
|
|
58
|
+
/** Can this user open the Docker socket without sudo? undefined = no socket
|
|
59
|
+
* found to ask (no local Docker, or a remote DOCKER_HOST). */
|
|
60
|
+
dockerSocketAccess: boolean | undefined;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface TuiRoute {
|
|
64
|
+
/** The config every TUI remote op is built from. */
|
|
65
|
+
cfg: TuiConfig;
|
|
66
|
+
/** One-time lines for the notice line: where the route departs from the file. */
|
|
67
|
+
notes: string[];
|
|
68
|
+
/** Set when nothing can be reached: the precise reason and remedy, shown in
|
|
69
|
+
* place of any ssh diagnosis. */
|
|
70
|
+
unconfigured?: string;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** The precise message for `target = "ssh"` with no host and no local stack —
|
|
74
|
+
* the same words the ssh runner answers an empty host with. */
|
|
75
|
+
export const EMPTY_SSH_HOST = NO_SSH_HOST_STDERR;
|
|
76
|
+
|
|
77
|
+
/** `QCR_VERSION=` from a project `.env` — "" when absent. */
|
|
78
|
+
export function pinnedVersion(envText: string | null): string {
|
|
79
|
+
if (envText === null) return "";
|
|
80
|
+
const m = /^QCR_VERSION=(.*)$/m.exec(envText);
|
|
81
|
+
return m ? (m[1] ?? "").trim().replace(/^["']|["']$/g, "") : "";
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** The recorded topology, or null when the checklist recorded none. */
|
|
85
|
+
function recordedTopology(checklist: ChecklistFile | null): "local" | "vm" | null {
|
|
86
|
+
const raw = checklist?.choices[CHOICE_KEYS.topology];
|
|
87
|
+
return raw === "local" || raw === "vm" ? raw : null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** The `-memory-N` sidecar a compose `-daemon-N` container implies, or "". */
|
|
91
|
+
export function composeMemoryFor(container: string): string {
|
|
92
|
+
const m = /^(.+)-daemon-(\d+)$/.exec(container);
|
|
93
|
+
return m ? `${m[1]}-memory-${m[2]}` : "";
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Pure: the effective [tui] config for these facts. */
|
|
97
|
+
export function resolveTuiRoute(file: TuiConfig, facts: RouteFacts): TuiRoute {
|
|
98
|
+
const notes: string[] = [];
|
|
99
|
+
let cfg: TuiConfig = { ...file };
|
|
100
|
+
if (cfg.target === "ssh" && cfg.ssh_host.trim() === "") {
|
|
101
|
+
const topology = recordedTopology(facts.checklist);
|
|
102
|
+
const composeProject = pinnedVersion(facts.projectEnv) !== "";
|
|
103
|
+
if (topology === "local" || (topology === null && composeProject)) {
|
|
104
|
+
cfg = {
|
|
105
|
+
...cfg,
|
|
106
|
+
target: "local",
|
|
107
|
+
container:
|
|
108
|
+
cfg.container === TUI_DEFAULTS.container ? COMPOSE_DAEMON_CONTAINER : cfg.container,
|
|
109
|
+
};
|
|
110
|
+
notes.push(
|
|
111
|
+
`[tui] target is "ssh" with no ssh_host — reading the local compose stack in ${facts.projectDir} instead ` +
|
|
112
|
+
'(set [tui] target = "local" to make it explicit)',
|
|
113
|
+
);
|
|
114
|
+
} else {
|
|
115
|
+
return {
|
|
116
|
+
cfg,
|
|
117
|
+
notes,
|
|
118
|
+
unconfigured:
|
|
119
|
+
topology === "vm"
|
|
120
|
+
? "[tui] ssh_host is empty — set it to the VM's ssh alias in config.toml"
|
|
121
|
+
: EMPTY_SSH_HOST,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if (cfg.target !== "local") return { cfg, notes };
|
|
126
|
+
if (cfg.remote_state_dir === TUI_DEFAULTS.remote_state_dir && cfg.container !== LEGACY_CONTAINER)
|
|
127
|
+
cfg = { ...cfg, remote_state_dir: RELEASE_STATE_DIR };
|
|
128
|
+
if ((cfg.memory_container ?? "").trim() === "") {
|
|
129
|
+
const memory = composeMemoryFor(cfg.container);
|
|
130
|
+
if (memory !== "") cfg = { ...cfg, memory_container: memory };
|
|
131
|
+
}
|
|
132
|
+
if (cfg.sudo_docker && facts.dockerSocketAccess === true) cfg = { ...cfg, sudo_docker: false };
|
|
133
|
+
return { cfg, notes };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** The Docker socket this process would talk to: `DOCKER_HOST` when it is a
|
|
137
|
+
* unix socket, the default path otherwise; undefined for a remote daemon. */
|
|
138
|
+
export function dockerSocketPath(env: NodeJS.ProcessEnv): string | undefined {
|
|
139
|
+
const host = (env["DOCKER_HOST"] ?? "").trim();
|
|
140
|
+
if (host === "") return "/var/run/docker.sock";
|
|
141
|
+
return host.startsWith("unix://") ? host.slice("unix://".length) : undefined;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** One `access(2)` on the socket — no docker call, no walk. */
|
|
145
|
+
export function probeSocketAccess(
|
|
146
|
+
path: string | undefined,
|
|
147
|
+
access: (p: string, mode: number) => void = accessSync,
|
|
148
|
+
): boolean | undefined {
|
|
149
|
+
if (path === undefined) return undefined;
|
|
150
|
+
try {
|
|
151
|
+
access(path, constants.R_OK | constants.W_OK);
|
|
152
|
+
return true;
|
|
153
|
+
} catch (err) {
|
|
154
|
+
const code = (err as NodeJS.ErrnoException).code;
|
|
155
|
+
return code === "EACCES" || code === "EPERM" ? false : undefined;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** The facts, read from this checkout: two small files and one access(2). */
|
|
160
|
+
export function readRouteFacts(root: string, env: NodeJS.ProcessEnv = process.env): RouteFacts {
|
|
161
|
+
let projectEnv: string | null = null;
|
|
162
|
+
try {
|
|
163
|
+
projectEnv = readFileSync(join(root, ".env"), "utf8");
|
|
164
|
+
} catch {
|
|
165
|
+
projectEnv = null;
|
|
166
|
+
}
|
|
167
|
+
return {
|
|
168
|
+
projectDir: root,
|
|
169
|
+
projectEnv,
|
|
170
|
+
checklist: readChecklist(join(root, "state")),
|
|
171
|
+
dockerSocketAccess: probeSocketAccess(dockerSocketPath(env)),
|
|
172
|
+
};
|
|
173
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// Is setup finished? Read from the checklist file switch-on and Advanced keep
|
|
2
|
+
// (src/onboard/engine.ts), so a TUI opened halfway through says so on its
|
|
3
|
+
// first frame instead of troubleshooting a daemon nobody has started yet.
|
|
4
|
+
//
|
|
5
|
+
// Only a checklist that EXISTS and is not complete counts: an install that
|
|
6
|
+
// never switched auto-review on (a hand-written config, the VM operator's own
|
|
7
|
+
// setup) has no checklist and gets no banner. Setup is finished in the home
|
|
8
|
+
// (a bare `qcr`), so the steps judged are the home's: switch-on's and
|
|
9
|
+
// Advanced's registry (src/home/advanced.ts), with the engine's own
|
|
10
|
+
// predicates. The next step is the first enabled, required one the file does
|
|
11
|
+
// not record as done; when there is none, nothing is left that the home could
|
|
12
|
+
// finish, and there is no banner (the home never runs the wizard's finish, so
|
|
13
|
+
// its checklist need not carry the wizard's completion marker).
|
|
14
|
+
import { type ChecklistFile } from "../onboard/engine.js";
|
|
15
|
+
|
|
16
|
+
export interface SetupState {
|
|
17
|
+
/** The title of the first pending step, or undefined when it could not be
|
|
18
|
+
* computed. */
|
|
19
|
+
next: string | undefined;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** The one sentence every surface uses. */
|
|
23
|
+
export function setupLine(s: SetupState): string {
|
|
24
|
+
return `setup not finished — run \`qcr\` to finish setting up${s.next ? ` (next: ${s.next})` : ""}`;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Complete now: finished once and not reopened since (engine.ts `completeNow`). */
|
|
28
|
+
function completeNow(file: ChecklistFile): boolean {
|
|
29
|
+
return typeof file.completed_at === "string" && !file.reopened_at;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** undefined = nothing to say (no checklist, setup is complete, or the home
|
|
33
|
+
* has no step left to finish). */
|
|
34
|
+
export async function setupStateOf(
|
|
35
|
+
checklist: ChecklistFile | null,
|
|
36
|
+
): Promise<SetupState | undefined> {
|
|
37
|
+
if (checklist === null || completeNow(checklist)) return undefined;
|
|
38
|
+
let next: string | undefined;
|
|
39
|
+
try {
|
|
40
|
+
const { advancedRegistry } = await import("../home/advanced.js");
|
|
41
|
+
const steps = [...advancedRegistry().steps()].sort((a, b) => a.order - b.order);
|
|
42
|
+
const pending = steps.find(
|
|
43
|
+
(s) =>
|
|
44
|
+
!s.optional &&
|
|
45
|
+
(s.enabled ? s.enabled(checklist.choices) : true) &&
|
|
46
|
+
checklist.steps[s.id]?.status !== "done",
|
|
47
|
+
);
|
|
48
|
+
if (pending === undefined) return undefined;
|
|
49
|
+
next = pending.title;
|
|
50
|
+
} catch {
|
|
51
|
+
// The step registry could not be loaded: the banner still says setup is
|
|
52
|
+
// unfinished, without naming a step it could not compute.
|
|
53
|
+
next = undefined;
|
|
54
|
+
}
|
|
55
|
+
return { next };
|
|
56
|
+
}
|
package/src/tui/sshtriage.ts
CHANGED
|
@@ -25,13 +25,22 @@ import { existsSync } from "node:fs";
|
|
|
25
25
|
import { homedir } from "node:os";
|
|
26
26
|
import type { TuiConfig } from "../config.js";
|
|
27
27
|
import { masterCheckArgv } from "./connect.js";
|
|
28
|
-
import { shellQuote, transportOf } from "./remote.js";
|
|
28
|
+
import { NO_SSH_HOST_STDERR, shellQuote, transportOf } from "./remote.js";
|
|
29
29
|
|
|
30
30
|
export type ConnectionClass =
|
|
31
31
|
/** The VM answers — master alive, or a fresh connection succeeded. */
|
|
32
32
|
| "ok"
|
|
33
|
-
/** `[tui] target = "local"`: there is no ssh anywhere
|
|
33
|
+
/** `[tui] target = "local"`: there is no ssh anywhere, and the daemon
|
|
34
|
+
* container is running on this machine. */
|
|
34
35
|
| "local"
|
|
36
|
+
/** Local route: Docker answers, but the daemon container is absent or not
|
|
37
|
+
* running (the stack is not up yet, or it stopped). */
|
|
38
|
+
| "local-down"
|
|
39
|
+
/** Local route: the local Docker itself did not answer this user. */
|
|
40
|
+
| "local-docker"
|
|
41
|
+
/** Nothing to connect to: `target = "ssh"` with no `ssh_host` (onboarding
|
|
42
|
+
* has not written the route yet). No ssh is ever spawned for it. */
|
|
43
|
+
| "unconfigured"
|
|
35
44
|
/** The agent cannot offer the key ssh wants for this host. */
|
|
36
45
|
| "no-agent-key"
|
|
37
46
|
/** A ControlMaster socket is there and does not answer. */
|
|
@@ -413,6 +422,112 @@ export interface TriageDeps {
|
|
|
413
422
|
run?: ProbeRunner;
|
|
414
423
|
exists?: (path: string) => boolean;
|
|
415
424
|
home?: string;
|
|
425
|
+
/** The compose project directory, for the local route's remedy line. */
|
|
426
|
+
projectDir?: string;
|
|
427
|
+
/** The route resolver's reason nothing can be reached (src/tui/route.ts);
|
|
428
|
+
* defaults to the empty-ssh_host sentence. */
|
|
429
|
+
unconfigured?: string;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/** Is this a local-route diagnosis (no ssh anywhere)? */
|
|
433
|
+
export function isLocalDiagnosis(d: Pick<ConnectionDiagnosis, "kind">): boolean {
|
|
434
|
+
return d.kind === "local" || d.kind === "local-down" || d.kind === "local-docker";
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/** A diagnosis under which the daemon can answer: the board's "connected". */
|
|
438
|
+
export function connectionUsable(d: Pick<ConnectionDiagnosis, "kind">): boolean {
|
|
439
|
+
return d.kind === "ok" || d.kind === "local";
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/** `docker inspect` of the daemon container's state, for the local route.
|
|
443
|
+
* `sudo -n` when sudo is configured: a password prompt behind the alt screen
|
|
444
|
+
* is a hang, so a sudo that would ask fails instead. */
|
|
445
|
+
export function localDaemonProbeArgv(cfg: TuiConfig): { cmd: string; argv: string[] } {
|
|
446
|
+
const inspect = ["inspect", "-f", "{{.State.Status}}", cfg.container];
|
|
447
|
+
return cfg.sudo_docker
|
|
448
|
+
? { cmd: "sudo", argv: ["-n", "docker", ...inspect] }
|
|
449
|
+
: { cmd: "docker", argv: inspect };
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/** How to bring the stack up from here. */
|
|
453
|
+
export function composeUpRemedy(projectDir: string | undefined, container: string): string {
|
|
454
|
+
return projectDir
|
|
455
|
+
? `docker compose --project-directory ${shellQuote(projectDir)} up -d`
|
|
456
|
+
: `docker start ${shellQuote(container)}`;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/** Pure: the local route's diagnosis from one `docker inspect`. */
|
|
460
|
+
export function classifyLocal(
|
|
461
|
+
cfg: TuiConfig,
|
|
462
|
+
res: ProbeResult,
|
|
463
|
+
projectDir?: string,
|
|
464
|
+
): ConnectionDiagnosis {
|
|
465
|
+
const c = cfg.container;
|
|
466
|
+
const out = res.stdout.trim();
|
|
467
|
+
const evidence = firstLine(res.stderr);
|
|
468
|
+
const detail = evidence ? { detail: evidence } : {};
|
|
469
|
+
if (res.status === 0 && out === "running") {
|
|
470
|
+
return {
|
|
471
|
+
kind: "local",
|
|
472
|
+
cause: `local docker — the daemon container ${c} is running on this machine (no ssh)`,
|
|
473
|
+
remedy: "",
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
if (res.status === 0) {
|
|
477
|
+
return {
|
|
478
|
+
kind: "local-down",
|
|
479
|
+
cause: `the daemon container ${c} is ${out || "not running"} — start it`,
|
|
480
|
+
remedy: composeUpRemedy(projectDir, c),
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
if (res.timedOut) {
|
|
484
|
+
return {
|
|
485
|
+
kind: "local-docker",
|
|
486
|
+
cause: "local Docker did not answer in time",
|
|
487
|
+
remedy: "docker info",
|
|
488
|
+
...detail,
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
const text = `${res.stderr}
|
|
492
|
+
${res.stdout}`;
|
|
493
|
+
if (/no such (object|container)/i.test(text)) {
|
|
494
|
+
return {
|
|
495
|
+
kind: "local-down",
|
|
496
|
+
cause: `no daemon container ${c} on this machine — the compose stack is not up (it starts at onboarding's bring-up step)`,
|
|
497
|
+
remedy: composeUpRemedy(projectDir, c),
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
if (/a password is required|a terminal is required/i.test(text)) {
|
|
501
|
+
return {
|
|
502
|
+
kind: "local-docker",
|
|
503
|
+
cause: "[tui] sudo_docker is on and sudo wants a password the TUI cannot type",
|
|
504
|
+
remedy:
|
|
505
|
+
'sudo usermod -aG docker "$USER" # then log in again, and set [tui] sudo_docker = false',
|
|
506
|
+
...detail,
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
if (/permission denied/i.test(text)) {
|
|
510
|
+
return {
|
|
511
|
+
kind: "local-docker",
|
|
512
|
+
cause: "this user cannot open the local Docker socket",
|
|
513
|
+
remedy: 'sudo usermod -aG docker "$USER" # then log in again',
|
|
514
|
+
...detail,
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
if (/ENOENT|not found|no such file/i.test(text) && !/daemon/i.test(text)) {
|
|
518
|
+
return {
|
|
519
|
+
kind: "local-docker",
|
|
520
|
+
cause: "there is no docker command on this machine",
|
|
521
|
+
remedy: "docker --version",
|
|
522
|
+
...detail,
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
return {
|
|
526
|
+
kind: "local-docker",
|
|
527
|
+
cause: `local Docker is not answering — ${evidence || "no error text"}`,
|
|
528
|
+
remedy: "docker info",
|
|
529
|
+
...detail,
|
|
530
|
+
};
|
|
416
531
|
}
|
|
417
532
|
|
|
418
533
|
const CONFIG_TIMEOUT_MS = 5_000;
|
|
@@ -458,14 +573,20 @@ export async function triageConnection(
|
|
|
458
573
|
cfg: TuiConfig,
|
|
459
574
|
deps: TriageDeps = {},
|
|
460
575
|
): Promise<ConnectionDiagnosis> {
|
|
576
|
+
const run = deps.run ?? realProbeRunner;
|
|
461
577
|
if (transportOf(cfg) === "local") {
|
|
578
|
+
// No ssh anywhere: the one probe is local docker's own answer about the
|
|
579
|
+
// daemon container.
|
|
580
|
+
const probe = localDaemonProbeArgv(cfg);
|
|
581
|
+
return classifyLocal(cfg, await run(probe.cmd, probe.argv, CONFIG_TIMEOUT_MS), deps.projectDir);
|
|
582
|
+
}
|
|
583
|
+
if (cfg.ssh_host.trim() === "") {
|
|
462
584
|
return {
|
|
463
|
-
kind: "
|
|
464
|
-
cause:
|
|
465
|
-
remedy: "",
|
|
585
|
+
kind: "unconfigured",
|
|
586
|
+
cause: deps.unconfigured ?? NO_SSH_HOST_STDERR,
|
|
587
|
+
remedy: "set [tui] ssh_host in config.toml",
|
|
466
588
|
};
|
|
467
589
|
}
|
|
468
|
-
const run = deps.run ?? realProbeRunner;
|
|
469
590
|
const exists = deps.exists ?? existsSync;
|
|
470
591
|
const home = deps.home ?? homedir();
|
|
471
592
|
const host = cfg.ssh_host;
|
package/src/tui/theme.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// legend — a glance at the color tells the state without reading. Verdicts
|
|
4
4
|
// and anomalies get their own fixed colors.
|
|
5
5
|
import { ACCENT, MUTED } from "./design.js";
|
|
6
|
-
import { STATE_GLYPHS } from "./model.js";
|
|
6
|
+
import { OFFERED_GLYPH, STATE_GLYPHS } from "./model.js";
|
|
7
7
|
import { ESC, rgb } from "./paint.js";
|
|
8
8
|
|
|
9
9
|
export const FG = {
|
|
@@ -78,6 +78,7 @@ export function legendLine(): string {
|
|
|
78
78
|
[STATE_GLYPHS.queued, "queued", FG.queued],
|
|
79
79
|
[STATE_GLYPHS.awaiting_author, "author", FG.awaiting_author],
|
|
80
80
|
[STATE_GLYPHS.tracked, "tracked", FG.tracked],
|
|
81
|
+
[OFFERED_GLYPH, "not reviewed", FG.dim],
|
|
81
82
|
["↻", "stale", FG.accent],
|
|
82
83
|
];
|
|
83
84
|
return parts
|
package/src/tui/unwedge.ts
CHANGED
|
@@ -26,6 +26,7 @@ import type { App } from "./app.js";
|
|
|
26
26
|
import { sanitizeText } from "./paint.js";
|
|
27
27
|
import type { SshRunner } from "./remote.js";
|
|
28
28
|
import { remoteSandboxRoot, resolveLayout } from "./layout.js";
|
|
29
|
+
import { operatorCommandWriteScriptAt } from "../operatorwrite.js";
|
|
29
30
|
|
|
30
31
|
/** The commands this channel may produce. Deliberately a closed set: the
|
|
31
32
|
* daemon's whitelist is the authority, and this is the narrower subset that
|
|
@@ -94,20 +95,7 @@ export function commandFileName(now: Date, seq: number, client?: string): string
|
|
|
94
95
|
* (`tests/levers.test.ts`).
|
|
95
96
|
*/
|
|
96
97
|
export function operatorCommandWriteScript(cfg: TuiConfig, name: string): string {
|
|
97
|
-
|
|
98
|
-
const dir = operatorCommandDirFor(cfg);
|
|
99
|
-
const tmp = `.${name}.tmp`;
|
|
100
|
-
return (
|
|
101
|
-
`[ -d '${root}' ] || { echo 'daemon commands dir ${root} missing - is the daemon running?' >&2; exit 1; }; ` +
|
|
102
|
-
`mkdir -p '${dir}' || exit 1; ` +
|
|
103
|
-
`[ ! -L '${dir}' ] || { echo '${dir} is a symlink - refusing to write the operator command' >&2; exit 1; }; ` +
|
|
104
|
-
`R=$(cd -P '${root}' && pwd -P) || exit 1; ` +
|
|
105
|
-
`cd -P '${dir}' || exit 1; ` +
|
|
106
|
-
`[ "$(pwd -P)" = "$R/operator" ] || { echo '${dir} was replaced while writing - refusing to write the operator command' >&2; exit 1; }; ` +
|
|
107
|
-
`chown -h 0:0 . && chmod 0755 . || exit 1; ` +
|
|
108
|
-
`find . -maxdepth 1 -name '*.json' -mmin +1440 -delete 2>/dev/null; ` +
|
|
109
|
-
`rm -f '${tmp}' '${name}' && cat > '${tmp}' && chmod 0644 '${tmp}' && mv '${tmp}' '${name}'`
|
|
110
|
-
);
|
|
98
|
+
return operatorCommandWriteScriptAt(commandDirFor(cfg), name);
|
|
111
99
|
}
|
|
112
100
|
|
|
113
101
|
/** argv for the atomic command write. The payload rides STDIN, never the
|
package/src/verdicts.ts
CHANGED
|
@@ -15,6 +15,11 @@ import {
|
|
|
15
15
|
// write the companion inbox. There is no path here that touches an existing review:
|
|
16
16
|
// the invariant against retracting an approval is structural, not prompted
|
|
17
17
|
// (AGENTS.md invariant 2).
|
|
18
|
+
import {
|
|
19
|
+
botAccountLogin,
|
|
20
|
+
isPersonMode,
|
|
21
|
+
ownLogins,
|
|
22
|
+
} from "@quickchatai/pi-quickreview/src/identity.js";
|
|
18
23
|
import {
|
|
19
24
|
normalizePassPhase,
|
|
20
25
|
normalizeSweepClaim,
|
|
@@ -406,6 +411,15 @@ export function decidedVerdictFromReceipt(receipt: PostingReceipt): ReceiptDecis
|
|
|
406
411
|
return { verdict: "REQUEST_CHANGES" };
|
|
407
412
|
if (receipt.intended_event === "APPROVE" && receipt.actual_event === "APPROVE")
|
|
408
413
|
return { verdict: "APPROVE" };
|
|
414
|
+
// Person mode posts every review as a COMMENT: the approval the session
|
|
415
|
+
// asked for is the decision, and it was not withheld.
|
|
416
|
+
if (
|
|
417
|
+
receipt.person_mode === true &&
|
|
418
|
+
receipt.intended_event === "APPROVE" &&
|
|
419
|
+
receipt.actual_event === "COMMENT" &&
|
|
420
|
+
receipt.reconciled !== true
|
|
421
|
+
)
|
|
422
|
+
return { verdict: "APPROVE" };
|
|
409
423
|
if (
|
|
410
424
|
receipt.intended_event === "COMMENT" &&
|
|
411
425
|
receipt.actual_event === "COMMENT" &&
|
|
@@ -1537,12 +1551,11 @@ export class VerdictWatcher {
|
|
|
1537
1551
|
if (!this.deps.cfg.dry_run && this.deps.adapter.listReviewsFull) {
|
|
1538
1552
|
try {
|
|
1539
1553
|
const marker = postingMarker(round.posting_key);
|
|
1540
|
-
const bot = this.deps.cfg.github.bot_login;
|
|
1541
1554
|
const reviews = await this.deps.adapter.listReviewsFull(round.pr);
|
|
1542
1555
|
reconciled = true;
|
|
1543
1556
|
const matches = reviews.filter(
|
|
1544
1557
|
(review) =>
|
|
1545
|
-
(
|
|
1558
|
+
ownLogins(this.deps.cfg.github).includes(review.user) &&
|
|
1546
1559
|
review.commit_id?.toLowerCase() === expectedHead.toLowerCase() &&
|
|
1547
1560
|
review.body.includes(marker),
|
|
1548
1561
|
);
|
|
@@ -1596,6 +1609,7 @@ export class VerdictWatcher {
|
|
|
1596
1609
|
gh_commit_id: match.commit_id,
|
|
1597
1610
|
gh_state: match.state,
|
|
1598
1611
|
...(verifiedIntent ? {} : { reconciled: true }),
|
|
1612
|
+
...(isPersonMode(this.deps.cfg.github) ? { person_mode: true as const } : {}),
|
|
1599
1613
|
};
|
|
1600
1614
|
if (verdict && !receiptMatchesVerdict(synthetic, verdict)) {
|
|
1601
1615
|
return {
|
|
@@ -1804,7 +1818,7 @@ export class VerdictWatcher {
|
|
|
1804
1818
|
);
|
|
1805
1819
|
}
|
|
1806
1820
|
const restored = restoreStandingLifecycle(store, round.pr, standing, {
|
|
1807
|
-
botLogin: cfg.github
|
|
1821
|
+
botLogin: botAccountLogin(cfg.github),
|
|
1808
1822
|
now: () => this.now(),
|
|
1809
1823
|
reason: standing
|
|
1810
1824
|
? `posting-guarded-noop:${standing}`
|
|
@@ -2339,7 +2353,11 @@ export class VerdictWatcher {
|
|
|
2339
2353
|
v.pr,
|
|
2340
2354
|
{ type: "verdict", verdict: v.verdict },
|
|
2341
2355
|
{ head_sha: v.head_sha },
|
|
2342
|
-
{
|
|
2356
|
+
{
|
|
2357
|
+
botLogin: botAccountLogin(cfg.github),
|
|
2358
|
+
now: () => this.now(),
|
|
2359
|
+
reason: `verdict:${v.verdict}`,
|
|
2360
|
+
},
|
|
2343
2361
|
);
|
|
2344
2362
|
store.logEvent("verdict_applied", v.pr, {
|
|
2345
2363
|
file: name,
|
|
@@ -3486,7 +3504,7 @@ export class VerdictWatcher {
|
|
|
3486
3504
|
pr,
|
|
3487
3505
|
{ type: "round_overdue", standing },
|
|
3488
3506
|
{},
|
|
3489
|
-
{ botLogin: cfg.github
|
|
3507
|
+
{ botLogin: botAccountLogin(cfg.github), now: () => this.now(), reason: `anomaly:${kind}` },
|
|
3490
3508
|
);
|
|
3491
3509
|
const command = round ? store.sessionCommandForRound(pr, round.round_no) : undefined;
|
|
3492
3510
|
const successor = store.successorFor(pr);
|
package/src/version.ts
CHANGED
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
SCAN_SCHEMA_VERSION,
|
|
9
9
|
VERSION_SCHEMA_VERSION,
|
|
10
10
|
} from "@quickchatai/pi-quickreview/src/contract.js";
|
|
11
|
-
import { piPinFromPackageJson } from "./onboard/
|
|
11
|
+
import { piPinFromPackageJson } from "./onboard/pipin.js";
|
|
12
12
|
|
|
13
13
|
export interface VersionInfo {
|
|
14
14
|
schema_version: number;
|
package/src/webhook.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { createHmac, timingSafeEqual } from "node:crypto";
|
|
|
9
9
|
import { createServer, type IncomingMessage, type Server, type ServerResponse } from "node:http";
|
|
10
10
|
import { captureFromEvent, recordCapturedComment } from "./capture.js";
|
|
11
11
|
import { reviewTeamSlug, type Config } from "./config.js";
|
|
12
|
+
import { botAccountLogin } from "@quickchatai/pi-quickreview/src/identity.js";
|
|
12
13
|
import { interpretReviewEvent, isFeedbackBody, ledgerFeedbackSignal } from "./feedback.js";
|
|
13
14
|
import { applyEvent as applyEventFor, mergedOverBlock } from "./statemachine.js";
|
|
14
15
|
import type { StateStore, StatusSnapshot } from "./state.js";
|
|
@@ -20,6 +21,8 @@ import { blockedPrAuthor } from "./pr-conversation.js";
|
|
|
20
21
|
* header, a wrong prefix, or a length mismatch (timingSafeEqual throws on
|
|
21
22
|
* unequal lengths — we must not let that leak as a 500). */
|
|
22
23
|
export function verifySignature(raw: Buffer, header: string | undefined, secret: string): boolean {
|
|
24
|
+
// An empty secret signs nothing: anyone can compute that HMAC.
|
|
25
|
+
if (secret === "") return false;
|
|
23
26
|
if (!header || !header.startsWith("sha256=")) return false;
|
|
24
27
|
const expected = Buffer.from(signBody(raw, secret), "utf8");
|
|
25
28
|
const given = Buffer.from(header, "utf8");
|
|
@@ -499,6 +502,10 @@ export function handleDelivery(
|
|
|
499
502
|
raw: Buffer,
|
|
500
503
|
): WebhookHandleResult {
|
|
501
504
|
const log = deps.log ?? (() => {});
|
|
505
|
+
// The polling shape (and so `[github] auth = "user"`) takes no webhooks:
|
|
506
|
+
// the daemon hands no secret, and every delivery is refused unread. The
|
|
507
|
+
// reconcile poll is that install's only trigger.
|
|
508
|
+
if (deps.secret === "") return { status: 404, reason: "webhooks-disabled" };
|
|
502
509
|
if (!verifySignature(raw, headers.signature, deps.secret)) {
|
|
503
510
|
logRejection(deps, log, headers);
|
|
504
511
|
return { status: 403, reason: "bad-signature" };
|
|
@@ -810,7 +817,9 @@ export async function applyInterpretation(
|
|
|
810
817
|
i: Interpretation,
|
|
811
818
|
): Promise<void> {
|
|
812
819
|
const { store, cfg } = deps;
|
|
813
|
-
|
|
820
|
+
// The bot ACCOUNT for PR authorship. Person mode has none; no webhook
|
|
821
|
+
// arrives there, but the state machine reads one rule everywhere.
|
|
822
|
+
const opts = { botLogin: botAccountLogin(cfg.github) };
|
|
814
823
|
const local = <T>(run: () => T): T => store.webhookLocalOnce(deps.deliveryGuid, run);
|
|
815
824
|
switch (i.kind) {
|
|
816
825
|
case "trigger":
|