@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/app.ts
CHANGED
|
@@ -841,6 +841,13 @@ export class App {
|
|
|
841
841
|
if (pr !== this.selectedPr) this.msgDraft = ""; // a cancelled draft belongs to the PR it was typed for
|
|
842
842
|
this.selectedPr = pr;
|
|
843
843
|
if (this.view.progress?.pr !== pr) this.view.progress = undefined;
|
|
844
|
+
// An offered row (not tracked yet) has no detail, round or session to
|
|
845
|
+
// read: its pane is the PR list's own fields and the way to review it.
|
|
846
|
+
if (pr !== undefined && this.offeredSelected()) {
|
|
847
|
+
this.syncPiPane();
|
|
848
|
+
if (!this.view.piFocus) this.selectPane(undefined);
|
|
849
|
+
return;
|
|
850
|
+
}
|
|
844
851
|
void this.refreshDetail();
|
|
845
852
|
void this.refreshProgress();
|
|
846
853
|
this.followSelection();
|
|
@@ -967,6 +974,38 @@ export class App {
|
|
|
967
974
|
return sorted[this.view.selected]?.number;
|
|
968
975
|
}
|
|
969
976
|
|
|
977
|
+
/** Is the selected row an offered one (open on GitHub, not tracked yet)? */
|
|
978
|
+
private offeredSelected(): boolean {
|
|
979
|
+
return visiblePrs(this.view)[this.view.selected]?.offered === true;
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
/** A PR verb on an offered row: there is no session or round to act on. */
|
|
983
|
+
private refuseOffered(): boolean {
|
|
984
|
+
if (!this.offeredSelected()) return false;
|
|
985
|
+
this.notice(`#${this.selectedPrNumber()} is not reviewed yet — R reviews it now`);
|
|
986
|
+
return true;
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
/** `R`: ask the daemon to review the selected PR now — the operator's
|
|
990
|
+
* `review-now` command over the sealed channel, the same as
|
|
991
|
+
* `qcr review-now <n>`. Any open PR of the repo, tracked or not. */
|
|
992
|
+
private reviewNow(): void {
|
|
993
|
+
const pr = this.selectedPrNumber();
|
|
994
|
+
if (pr === undefined) {
|
|
995
|
+
this.notice("select a pull request first (j/k), then R reviews it");
|
|
996
|
+
return;
|
|
997
|
+
}
|
|
998
|
+
if (!this.hooks.operatorCommand) {
|
|
999
|
+
this.notice("review now: not wired (no remote runner)");
|
|
1000
|
+
return;
|
|
1001
|
+
}
|
|
1002
|
+
this.notice(`asking the daemon to review #${pr} now…`);
|
|
1003
|
+
void this.hooks
|
|
1004
|
+
.operatorCommand({ command: "review-now", pr }, this)
|
|
1005
|
+
.then(() => this.poller.tick())
|
|
1006
|
+
.catch(() => {});
|
|
1007
|
+
}
|
|
1008
|
+
|
|
970
1009
|
/** Synthesize a keypress through the normal input path — the command bar's
|
|
971
1010
|
* buttons and any tab code that wants "exactly what the key would do". */
|
|
972
1011
|
pressKey(key: string): void {
|
|
@@ -1432,6 +1471,7 @@ export class App {
|
|
|
1432
1471
|
// the whole TUI (AGENTS.md invariant 10, the fixed layout). With no PR
|
|
1433
1472
|
// selected there is nothing to focus, and nothing else to open.
|
|
1434
1473
|
if (this.selectedPr !== undefined) {
|
|
1474
|
+
if (this.refuseOffered()) break;
|
|
1435
1475
|
this.focusPiPane();
|
|
1436
1476
|
this.repaint();
|
|
1437
1477
|
}
|
|
@@ -1443,6 +1483,7 @@ export class App {
|
|
|
1443
1483
|
case "message": {
|
|
1444
1484
|
const session = this.selectedSession();
|
|
1445
1485
|
if (!session) break;
|
|
1486
|
+
if (this.refuseOffered()) break;
|
|
1446
1487
|
if (this.hooks.message) this.hooks.message(session, this);
|
|
1447
1488
|
else this.openMessageBox(session);
|
|
1448
1489
|
break;
|
|
@@ -1457,6 +1498,7 @@ export class App {
|
|
|
1457
1498
|
// overseer command, where the extra deliberation is the point.
|
|
1458
1499
|
const pr = this.selectedPrNumber();
|
|
1459
1500
|
if (pr === undefined) break;
|
|
1501
|
+
if (this.refuseOffered()) break;
|
|
1460
1502
|
if (this.hooks.unwedge) this.hooks.unwedge(pr, this);
|
|
1461
1503
|
else this.notice("unwedge: not wired (no remote runner)");
|
|
1462
1504
|
break;
|
|
@@ -1482,6 +1524,11 @@ export class App {
|
|
|
1482
1524
|
this.setBoardFilter(null);
|
|
1483
1525
|
break;
|
|
1484
1526
|
}
|
|
1527
|
+
// R reviews the selected PR now — tracked or offered.
|
|
1528
|
+
if (key.ch === "R" && this.tab === "board") {
|
|
1529
|
+
this.reviewNow();
|
|
1530
|
+
break;
|
|
1531
|
+
}
|
|
1485
1532
|
// I starts setup — the companion takes it from there.
|
|
1486
1533
|
if (key.ch === "I" && this.tab === "board") {
|
|
1487
1534
|
if (this.hooks.setup) this.hooks.setup(this);
|
package/src/tui/board.ts
CHANGED
|
@@ -11,7 +11,7 @@ import { cancelCauseLabel, isLoudCancelCause, type StatusSnapshot } from "../sta
|
|
|
11
11
|
import type { AlertBadge } from "./alerts.js";
|
|
12
12
|
import { barHeight, paintCmdBar, type BarButton, type LiveKeys } from "./cmdbar.js";
|
|
13
13
|
import { filterLabel, visiblePrs, type BoardFilter } from "./filters.js";
|
|
14
|
-
import { glyphFor, type EventLine, type SnapshotPr } from "./model.js";
|
|
14
|
+
import { glyphFor, OFFERED_GLYPH, type EventLine, type SnapshotPr } from "./model.js";
|
|
15
15
|
import { windowStart } from "./panes.js";
|
|
16
16
|
import {
|
|
17
17
|
ACCENT,
|
|
@@ -47,7 +47,8 @@ import {
|
|
|
47
47
|
type LiveAgentProgress,
|
|
48
48
|
type RoundProgressSnapshot,
|
|
49
49
|
} from "@quickchatai/pi-quickreview/src/round-progress.js";
|
|
50
|
-
import type
|
|
50
|
+
import { isLocalDiagnosis, type ConnectionDiagnosis } from "./sshtriage.js";
|
|
51
|
+
import { setupLine, type SetupState } from "./setupstate.js";
|
|
51
52
|
import { FG, RESET, fmtAge, legendLine, osc8, prUrl, stateColor, verdictChip } from "./theme.js";
|
|
52
53
|
|
|
53
54
|
/** Every clickable thing the board declares. */
|
|
@@ -109,6 +110,9 @@ export interface BoardView {
|
|
|
109
110
|
/** Active board filter (companion `board_filter` verb; F clears).
|
|
110
111
|
* Consumed through `visiblePrs` — never filter a sortPrs call directly. */
|
|
111
112
|
boardFilter?: BoardFilter | null;
|
|
113
|
+
/** Set while onboarding's checklist exists and is not complete
|
|
114
|
+
* (src/tui/setupstate.ts): the board says so before anything else. */
|
|
115
|
+
setup?: SetupState;
|
|
112
116
|
}
|
|
113
117
|
|
|
114
118
|
// ---- layout ----------------------------------------------------------------
|
|
@@ -197,22 +201,26 @@ export function listWidth(prs: SnapshotPr[], cols: number, now: Date = new Date(
|
|
|
197
201
|
export function boardKeys(
|
|
198
202
|
view: Pick<BoardView, "snapshot" | "boardFilter" | "selected" | "repo">,
|
|
199
203
|
): LiveKeys {
|
|
200
|
-
const
|
|
204
|
+
const row = visiblePrs(view)[view.selected];
|
|
205
|
+
const selected = row !== undefined;
|
|
206
|
+
// An offered row (open on GitHub, not tracked yet) has no session, round or
|
|
207
|
+
// pane: `R` reviews it and `g` opens it, and nothing else addresses it.
|
|
208
|
+
const tracked = selected && row.offered !== true;
|
|
201
209
|
const bar: BarButton[] = [];
|
|
202
|
-
if (selected) bar.push({ key: "
|
|
210
|
+
if (selected) bar.push({ key: "R", label: "R review now" });
|
|
211
|
+
if (tracked) bar.push({ key: "\r", label: "⏎ pi pane" }, { key: "m", label: "m message" });
|
|
203
212
|
if (selected && view.repo) bar.push({ key: "g", label: "g github" });
|
|
204
213
|
bar.push({ key: "e", label: "e events" });
|
|
205
|
-
if (
|
|
214
|
+
if (tracked) bar.push({ key: "u", label: "u unwedge" });
|
|
206
215
|
// Setup is about the repo, not a PR — offered whatever is selected.
|
|
207
216
|
bar.push({ key: "I", label: "I setup" });
|
|
208
217
|
const inline: string[] = [];
|
|
209
|
-
if (
|
|
218
|
+
if (tracked) inline.push("a");
|
|
210
219
|
if (view.boardFilter != null) inline.push("F");
|
|
211
220
|
// A worker-hosted PR's pane is the transcript viewer: `[ ]` step its streams
|
|
212
221
|
// and `< >` its rounds. They are the pane header's keys (painted there), so
|
|
213
222
|
// they exist only while the selected PR's pane is the viewer.
|
|
214
|
-
|
|
215
|
-
if (pr !== undefined && paneSurface(pr.runtime, pr.number).kind === "viewer")
|
|
223
|
+
if (tracked && paneSurface(row.runtime, row.number).kind === "viewer")
|
|
216
224
|
inline.push("[", "]", "<", ">");
|
|
217
225
|
return { bar, inline };
|
|
218
226
|
}
|
|
@@ -287,6 +295,9 @@ const CONNECTION_LABEL: Record<ConnectionDiagnosis["kind"], string> = {
|
|
|
287
295
|
"wedged-master": "ssh master wedged",
|
|
288
296
|
"host-unreachable": "host down",
|
|
289
297
|
unknown: "cause unknown",
|
|
298
|
+
"local-down": "daemon container not running",
|
|
299
|
+
"local-docker": "local docker not answering",
|
|
300
|
+
unconfigured: "no ssh_host",
|
|
290
301
|
};
|
|
291
302
|
|
|
292
303
|
/**
|
|
@@ -297,12 +308,30 @@ const CONNECTION_LABEL: Record<ConnectionDiagnosis["kind"], string> = {
|
|
|
297
308
|
* the truth is "I could not ask"), the reason is named, and the remedy is a
|
|
298
309
|
* command the operator can paste into their own shell.
|
|
299
310
|
*/
|
|
300
|
-
export function unknownSourceLines(
|
|
311
|
+
export function unknownSourceLines(
|
|
312
|
+
conn: ConnectionDiagnosis | undefined,
|
|
313
|
+
width: number,
|
|
314
|
+
setup?: SetupState,
|
|
315
|
+
): string[] {
|
|
301
316
|
const lines = [
|
|
302
317
|
`${FG.anomaly}the daemon has not answered yet${RESET}`,
|
|
303
318
|
`${FG.dim}nothing below is a measurement — it is not known${RESET}`,
|
|
304
319
|
"",
|
|
305
320
|
];
|
|
321
|
+
if (setup) {
|
|
322
|
+
// A half-finished onboarding is the whole explanation: the stack is
|
|
323
|
+
// started by one of its later steps, so daemon or ssh troubleshooting
|
|
324
|
+
// here would send the operator after a fault that is not one.
|
|
325
|
+
for (const l of wrap(setupLine(setup), width)) lines.push(`${WARN}${l}${DRESET}`);
|
|
326
|
+
for (const l of wrap(
|
|
327
|
+
"the daemon, its memory and this board's connection are set up by the wizard's remaining steps",
|
|
328
|
+
width,
|
|
329
|
+
))
|
|
330
|
+
lines.push(`${FG.dim}${l}${RESET}`);
|
|
331
|
+
lines.push("");
|
|
332
|
+
lines.push(`${FG.dim}r refreshes${RESET}`);
|
|
333
|
+
return lines.map((l) => truncate(l, width));
|
|
334
|
+
}
|
|
306
335
|
if (conn && conn.kind !== "ok" && conn.kind !== "local") {
|
|
307
336
|
for (const l of wrap(stripAnsi(conn.cause), width)) lines.push(`${FG.dim}${l}${RESET}`);
|
|
308
337
|
if (conn.remedy !== "") {
|
|
@@ -337,7 +366,8 @@ export function unknownDetailLines(conn: ConnectionDiagnosis | undefined, width:
|
|
|
337
366
|
];
|
|
338
367
|
if (conn?.detail) {
|
|
339
368
|
lines.push("");
|
|
340
|
-
|
|
369
|
+
const who = isLocalDiagnosis(conn) ? "docker" : "ssh";
|
|
370
|
+
for (const l of wrap(`${who} said: ${stripAnsi(conn.detail)}`, width))
|
|
341
371
|
lines.push(`${FG.dim}${l}${RESET}`);
|
|
342
372
|
}
|
|
343
373
|
return lines.map((l) => truncate(l, width));
|
|
@@ -482,9 +512,17 @@ function barSegments(view: BoardView, alerts: boolean): string[] {
|
|
|
482
512
|
health = `${WARN}▲ DATA STALE (${view.health.dataAgeS}s)${DRESET}`;
|
|
483
513
|
break;
|
|
484
514
|
case "unreachable":
|
|
485
|
-
|
|
515
|
+
// Under an unfinished setup the setup segment that replaces this is the
|
|
516
|
+
// cause; a transport label would only point at a fault that is not one
|
|
517
|
+
// (the list still says, in full, that the daemon has not answered).
|
|
518
|
+
health = view.setup
|
|
519
|
+
? ""
|
|
520
|
+
: `${FG.anomaly}✖ unreachable${view.connection ? ` · ${CONNECTION_LABEL[view.connection.kind]}` : ""}${RESET}`;
|
|
486
521
|
break;
|
|
487
522
|
}
|
|
523
|
+
// Short on the bar, where every segment competes for the width; the list
|
|
524
|
+
// carries the full sentence with the next step.
|
|
525
|
+
const setupSeg = view.setup ? `${WARN}▲ setup not finished${DRESET}` : "";
|
|
488
526
|
const s = view.snapshot;
|
|
489
527
|
const rounds = s ? `rounds ${s.running_rounds}/${s.max_concurrent_rounds ?? "?"}` : "";
|
|
490
528
|
const spend = s?.today_spend_usd !== undefined ? `today $${s.today_spend_usd.toFixed(2)}` : "";
|
|
@@ -512,6 +550,7 @@ function barSegments(view: BoardView, alerts: boolean): string[] {
|
|
|
512
550
|
`${HEADING}quickreview${DRESET}`,
|
|
513
551
|
mode,
|
|
514
552
|
health,
|
|
553
|
+
setupSeg,
|
|
515
554
|
mem?.loud ? mem.text : "",
|
|
516
555
|
loop?.loud ? loop.text : "",
|
|
517
556
|
curation,
|
|
@@ -549,6 +588,51 @@ export function alertZone(view: BoardView, cols: number): { x1: number; x2: numb
|
|
|
549
588
|
return { x1, x2: Math.min(cols - 1, x1 + width - 1) };
|
|
550
589
|
}
|
|
551
590
|
|
|
591
|
+
/**
|
|
592
|
+
* The board's own guide to the first review, under the PRS header. With open
|
|
593
|
+
* PRs offered and none tracked it says what to do; with no open PR at all it
|
|
594
|
+
* says that, naming the repo; once a PR is tracked it is silent. Absent
|
|
595
|
+
* `open_prs` (a daemon that has not listed yet, or an older one) is unknown,
|
|
596
|
+
* so the guide says nothing rather than "no open PRs".
|
|
597
|
+
*/
|
|
598
|
+
export function reviewGuideLines(
|
|
599
|
+
view: Pick<BoardView, "snapshot" | "boardFilter" | "repo">,
|
|
600
|
+
tracked: number,
|
|
601
|
+
offered: number,
|
|
602
|
+
width: number,
|
|
603
|
+
): string[] {
|
|
604
|
+
if (view.boardFilter != null || tracked > 0 || view.snapshot?.open_prs === undefined) return [];
|
|
605
|
+
if (offered === 0)
|
|
606
|
+
return [
|
|
607
|
+
truncate(
|
|
608
|
+
`${FG.dim}no open pull requests in ${view.repo ?? "the repository"} — open one, and it is listed here within a few minutes${RESET}`,
|
|
609
|
+
width,
|
|
610
|
+
),
|
|
611
|
+
];
|
|
612
|
+
return [
|
|
613
|
+
truncate(`${FG.accent}select a pull request (j/k), then R reviews it now${RESET}`, width),
|
|
614
|
+
];
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
/** The detail pane of an offered row: what is known (the PR list's own
|
|
618
|
+
* fields) and the one thing to do with it. */
|
|
619
|
+
function offeredDetailLines(pr: SnapshotPr, width: number, now: Date): string[] {
|
|
620
|
+
return [
|
|
621
|
+
`${FG.dim}${OFFERED_GLYPH}${RESET} ${STRONG}#${pr.number}${RESET} ${STRONG}${truncate(sanitizeText(pr.title), width - 8 - String(pr.number).length)}${RESET}`,
|
|
622
|
+
truncate(
|
|
623
|
+
`${FG.author}${pr.author ?? ""}${RESET} ${FG.dim}· updated ${fmtAge(pr.updated_at, now)}${RESET}`,
|
|
624
|
+
width,
|
|
625
|
+
),
|
|
626
|
+
"",
|
|
627
|
+
"not reviewed yet",
|
|
628
|
+
"",
|
|
629
|
+
truncate(`${STRONG}R${RESET} reviews it now: the reviewer runs one full round`, width),
|
|
630
|
+
truncate("on this pull request, and its progress shows here.", width),
|
|
631
|
+
truncate(`${FG.dim}In dry-run (the top bar says so) the review is recorded,${RESET}`, width),
|
|
632
|
+
truncate(`${FG.dim}never posted to GitHub.${RESET}`, width),
|
|
633
|
+
];
|
|
634
|
+
}
|
|
635
|
+
|
|
552
636
|
function styledRow(
|
|
553
637
|
pr: SnapshotPr,
|
|
554
638
|
selected: boolean,
|
|
@@ -811,6 +895,7 @@ export function detailLines(
|
|
|
811
895
|
): string[] {
|
|
812
896
|
const lines: string[] = [];
|
|
813
897
|
if (!pr) return ["no PR selected"];
|
|
898
|
+
if (pr.offered) return offeredDetailLines(pr, width, now);
|
|
814
899
|
lines.push(
|
|
815
900
|
`${stateColor(pr.state)}${STRONG}#${pr.number}${RESET} ${STRONG}${truncate(sanitizeText(pr.title), width - 8 - String(pr.number).length)}${RESET}`,
|
|
816
901
|
);
|
|
@@ -1010,7 +1095,7 @@ export function detailLines(
|
|
|
1010
1095
|
export const HELP_LINES = [
|
|
1011
1096
|
"1…8 the eight surfaces: board · memory · digest · tickets · deploy · metrics · feedback · scan",
|
|
1012
1097
|
" the bar at the foot shows what THIS frame answers to; a button that is not there is a key that would do nothing",
|
|
1013
|
-
"r / s / ? / q refresh ·
|
|
1098
|
+
"r / s / ? / q refresh · re-check the connection (VM route: ssh, the one key-passphrase prompt; local route: the daemon container) · this overlay · quit (asks first)",
|
|
1014
1099
|
"Ctrl-c ×3 kill the TUI from anywhere (terminal restored) — the un-stuck hatch",
|
|
1015
1100
|
" an alert in the top bar: click it or ⏎ to open the companion",
|
|
1016
1101
|
"the companion column down the right edge is on every tab:",
|
|
@@ -1025,6 +1110,7 @@ export const HELP_LINES = [
|
|
|
1025
1110
|
"< / > past / later round in the viewer; PgUp / wheel parks it, End follows the tail again",
|
|
1026
1111
|
" its input line (focused pane): ⏎ steers the running round, or sends a message to an idle PR; Alt-⏎ queues a follow-up",
|
|
1027
1112
|
" Alt-p marks the next message may-post (shown, reset after send); Ctrl-c asks to abort the round (y confirms); [ ] < > are keys only while the line is empty",
|
|
1113
|
+
"R review the selected PR now — any open PR, tracked or not (○ not reviewed): one round, nothing posted in dry-run",
|
|
1028
1114
|
"m quick-message the selected PR's session",
|
|
1029
1115
|
"u unwedge: cancel the running round and re-queue it (nothing posted, no GitHub write)",
|
|
1030
1116
|
"e events overlay (j/k scroll, e/Esc close; click an event → its PR)",
|
|
@@ -1296,11 +1382,22 @@ export function renderBoard(view: BoardView, cols: number, rows: number): Frame
|
|
|
1296
1382
|
// detail pane says "no data" rather than "no PR selected".
|
|
1297
1383
|
const known = view.snapshot !== undefined;
|
|
1298
1384
|
const flabel = filterLabel(view.boardFilter);
|
|
1385
|
+
const offered = sorted.filter((p) => p.offered === true).length;
|
|
1299
1386
|
const listLines: string[] = known
|
|
1300
|
-
? [
|
|
1387
|
+
? [
|
|
1388
|
+
section(
|
|
1389
|
+
`PRS (${sorted.length})`,
|
|
1390
|
+
flabel !== ""
|
|
1391
|
+
? flabel
|
|
1392
|
+
: offered > 0
|
|
1393
|
+
? `${sorted.length - offered} tracked · ${offered} open, not reviewed`
|
|
1394
|
+
: "the daemon's live set",
|
|
1395
|
+
),
|
|
1396
|
+
...reviewGuideLines(view, sorted.length - offered, offered, listW - 1),
|
|
1397
|
+
]
|
|
1301
1398
|
: [
|
|
1302
1399
|
section("PRS (?)", "unknown — the daemon has not answered"),
|
|
1303
|
-
...unknownSourceLines(view.connection, listW - 1),
|
|
1400
|
+
...unknownSourceLines(view.connection, listW - 1, view.setup),
|
|
1304
1401
|
];
|
|
1305
1402
|
// The list scrolls with the selection: the verbs act on the selected PR, so
|
|
1306
1403
|
// it must never sit off-screen (`windowStart`, the tabs' shared rule). The
|
package/src/tui/cmdbar.ts
CHANGED
|
@@ -51,7 +51,9 @@ const NAV: ReadonlyArray<BarButton & { tab: Tab }> = TABS.map((tab) => ({
|
|
|
51
51
|
/** Row A right: always the same four, always in the same place. */
|
|
52
52
|
const GLOBALS: readonly BarButton[] = [
|
|
53
53
|
{ key: "r", label: "r refresh" },
|
|
54
|
-
|
|
54
|
+
// Transport-neutral: on the VM route `s` re-checks ssh (and offers the one
|
|
55
|
+
// key-passphrase prompt), on the local route the daemon container.
|
|
56
|
+
{ key: "s", label: "s check" },
|
|
55
57
|
{ key: "?", label: "? help" },
|
|
56
58
|
{ key: "q", label: "q quit" },
|
|
57
59
|
];
|
|
@@ -73,6 +75,7 @@ export const GLOBAL_KEYS: readonly string[] = [
|
|
|
73
75
|
*/
|
|
74
76
|
export const TAB_VOCABULARY: Readonly<Record<Tab, readonly BarButton[]>> = {
|
|
75
77
|
board: [
|
|
78
|
+
{ key: "R", label: "R review now" },
|
|
76
79
|
{ key: "\r", label: "⏎ pi pane" },
|
|
77
80
|
{ key: "m", label: "m message" },
|
|
78
81
|
{ key: "g", label: "g github" },
|
package/src/tui/companion.ts
CHANGED
|
@@ -41,10 +41,11 @@ import {
|
|
|
41
41
|
stateHome,
|
|
42
42
|
type InstallEnv,
|
|
43
43
|
} from "../install.js";
|
|
44
|
-
import type
|
|
44
|
+
import { COMPANION_IMAGE, type CompanionConfig } from "../config.js";
|
|
45
45
|
import { writeAtomic } from "../channelfs.js";
|
|
46
46
|
import { appStamp } from "../appstamp.js";
|
|
47
47
|
import { TOKEN_FILE } from "./ghread.js";
|
|
48
|
+
import { MEMORY_PORT } from "./memoryurl.js";
|
|
48
49
|
|
|
49
50
|
export interface DockerResult {
|
|
50
51
|
ok: boolean;
|
|
@@ -274,7 +275,7 @@ export const MEMORY = {
|
|
|
274
275
|
subnet: "198.51.100.0/24",
|
|
275
276
|
gateway: "198.51.100.1",
|
|
276
277
|
/** Same port the server listens on in the VM container ([openviking] port). */
|
|
277
|
-
port:
|
|
278
|
+
port: MEMORY_PORT,
|
|
278
279
|
/** The OpenViking user whose memory the companion owns — becomes the
|
|
279
280
|
* `X-OpenViking-User` header on every request it makes. */
|
|
280
281
|
user: "companion",
|
|
@@ -453,6 +454,15 @@ export function companionInjectDir(cfg: Pick<CompanionConfig, "repo_dir" | "sess
|
|
|
453
454
|
* `qcr scan` the companion runs) knows it is inside the companion container. */
|
|
454
455
|
export const IN_COMPANION_ENV = "QCR_IN_COMPANION";
|
|
455
456
|
|
|
457
|
+
/** The published release image's repository (src/onboard/steps/compose.ts
|
|
458
|
+
* RELEASE_IMAGE_REPO): a tag under it is pulled, never built locally. */
|
|
459
|
+
const RELEASE_IMAGE_PREFIX = "ghcr.io/quickchatai/quickreview:";
|
|
460
|
+
|
|
461
|
+
/** Is `image` a published release tag (pulled, never built over)? */
|
|
462
|
+
export function isReleaseImage(image: string): boolean {
|
|
463
|
+
return image.startsWith(RELEASE_IMAGE_PREFIX);
|
|
464
|
+
}
|
|
465
|
+
|
|
456
466
|
/** The release image's mode word for the companion container
|
|
457
467
|
* (docker/entrypoint.sh): provision pi, start the tmux server, stay alive. */
|
|
458
468
|
export const COMPANION_MODE = "companion";
|
|
@@ -560,6 +570,23 @@ export class CompanionLifecycle {
|
|
|
560
570
|
* companion's tmux server. A field so tests need not wait it out. */
|
|
561
571
|
wrapperStartWaitMs = WRAPPER_START_WAIT_MS;
|
|
562
572
|
|
|
573
|
+
/** An image already on this machine to run instead of BUILDING the default
|
|
574
|
+
* one: the release image a compose box pulled (`ghcr.io/…:<QCR_VERSION>`,
|
|
575
|
+
* the same docker/Dockerfile CI built). Consulted only on a cold create,
|
|
576
|
+
* only while `[companion] image` is the shipped default, and only when no
|
|
577
|
+
* local build of that default exists — so an explicit image and a
|
|
578
|
+
* contributor's `qcr companion rebuild` both keep winning. */
|
|
579
|
+
preferredImage: string | undefined;
|
|
580
|
+
|
|
581
|
+
/** The image this instance runs: `[companion] image`, until a cold create
|
|
582
|
+
* adopts `preferredImage`. */
|
|
583
|
+
private imageInUse: string | undefined;
|
|
584
|
+
|
|
585
|
+
/** The image a create runs (and a build tags). */
|
|
586
|
+
get image(): string {
|
|
587
|
+
return this.imageInUse ?? this.cfg.image;
|
|
588
|
+
}
|
|
589
|
+
|
|
563
590
|
/** The shared socket in `tmux-socket` mode, else undefined. */
|
|
564
591
|
get socketPath(): string | undefined {
|
|
565
592
|
return this.mode === "tmux-socket" ? tmuxSocketPath(this.cfg) : undefined;
|
|
@@ -620,7 +647,7 @@ export class CompanionLifecycle {
|
|
|
620
647
|
// The agent is reached by its mounted socket; no key is in the container.
|
|
621
648
|
if (this.sshSources().agent !== undefined) argv.push("--env", `SSH_AUTH_SOCK=${SSH.agent}`);
|
|
622
649
|
// The release image, in its companion mode (docker/entrypoint.sh).
|
|
623
|
-
argv.push(this.
|
|
650
|
+
argv.push(this.image, COMPANION_MODE);
|
|
624
651
|
return argv;
|
|
625
652
|
}
|
|
626
653
|
|
|
@@ -672,6 +699,13 @@ export class CompanionLifecycle {
|
|
|
672
699
|
"--theme",
|
|
673
700
|
`${ext}/muted-theme.json`,
|
|
674
701
|
"--extension",
|
|
702
|
+
// The login route (extensions/login-route.ts), before the inject bridge so
|
|
703
|
+
// the day-open delivery meets a routed model: a companion whose model's
|
|
704
|
+
// provider has no login runs the same model on the other OpenAI login
|
|
705
|
+
// when that one exists, and a companion with no usable login at all says
|
|
706
|
+
// which logins work and leaves `/login` on its input line.
|
|
707
|
+
`${ext}/cli/login-route.ts`,
|
|
708
|
+
"--extension",
|
|
675
709
|
// Day-open channel: the daemon's inject bridge, unmodified. It
|
|
676
710
|
// drains its directory on session_start before it starts watching, so
|
|
677
711
|
// the rollover can queue the opening message before pi is spawned and
|
|
@@ -926,10 +960,27 @@ export class CompanionLifecycle {
|
|
|
926
960
|
|
|
927
961
|
/** True when the companion image exists locally. */
|
|
928
962
|
async imageExists(): Promise<boolean> {
|
|
929
|
-
const res = await this.docker(["image", "inspect", this.
|
|
963
|
+
const res = await this.docker(["image", "inspect", this.image]);
|
|
930
964
|
return res.ok;
|
|
931
965
|
}
|
|
932
966
|
|
|
967
|
+
/**
|
|
968
|
+
* Before a cold create builds the default image: adopt the preferred
|
|
969
|
+
* (already-pulled release) image when the default is not built here and the
|
|
970
|
+
* preferred one is. True when the image to run now exists.
|
|
971
|
+
*/
|
|
972
|
+
private async adoptPreferredImage(onProgress?: (line: string) => void): Promise<boolean> {
|
|
973
|
+
if (await this.imageExists()) return true;
|
|
974
|
+
const preferred = this.preferredImage;
|
|
975
|
+
if (preferred === undefined || preferred === "" || this.cfg.image !== COMPANION_IMAGE)
|
|
976
|
+
return false;
|
|
977
|
+
const res = await this.docker(["image", "inspect", preferred]);
|
|
978
|
+
if (!res.ok) return false;
|
|
979
|
+
this.imageInUse = preferred;
|
|
980
|
+
onProgress?.(`using the release image ${preferred}, already pulled — no local build`);
|
|
981
|
+
return true;
|
|
982
|
+
}
|
|
983
|
+
|
|
933
984
|
/** Remove the container, running or not. Volumes (pihome: OAuth, the
|
|
934
985
|
* conversation) survive — this replaces the container, never its memory. */
|
|
935
986
|
async removeContainer(): Promise<void> {
|
|
@@ -945,9 +996,22 @@ export class CompanionLifecycle {
|
|
|
945
996
|
if (!res.ok) throw new Error(`docker rm ${this.cfg.container_name} failed: ${errText(res)}`);
|
|
946
997
|
}
|
|
947
998
|
|
|
999
|
+
/** `docker pull` of a configured release image the machine does not have. */
|
|
1000
|
+
private async pullImage(onProgress?: (line: string) => void): Promise<void> {
|
|
1001
|
+
onProgress?.(`pulling ${this.image} (first run — a few minutes)…`);
|
|
1002
|
+
const res = await this.docker(["pull", this.image], { timeoutMs: 1_800_000 });
|
|
1003
|
+
if (!res.ok) throw new Error(`docker pull ${this.image} failed: ${errText(res)}`);
|
|
1004
|
+
onProgress?.(`${this.image} pulled`);
|
|
1005
|
+
}
|
|
1006
|
+
|
|
948
1007
|
/** Throws when the image cannot be built (`imageBuildProblem`) — asked
|
|
949
1008
|
* before anything is removed for a rebuild. */
|
|
950
1009
|
checkBuildable(): void {
|
|
1010
|
+
if (isReleaseImage(this.image))
|
|
1011
|
+
throw new Error(
|
|
1012
|
+
`${this.image} is a published release image — it is pulled (docker pull ${this.image}), never built over; ` +
|
|
1013
|
+
`set [companion] image = "${COMPANION_IMAGE}" to build one from this checkout`,
|
|
1014
|
+
);
|
|
951
1015
|
const problem = imageBuildProblem(this.cfg.build_context, (p) => this.fs.exists(p));
|
|
952
1016
|
if (problem !== null) throw new Error(problem);
|
|
953
1017
|
}
|
|
@@ -960,21 +1024,21 @@ export class CompanionLifecycle {
|
|
|
960
1024
|
async buildImage(onProgress?: (line: string) => void): Promise<void> {
|
|
961
1025
|
this.checkBuildable();
|
|
962
1026
|
const ctx = this.cfg.build_context;
|
|
963
|
-
onProgress?.(`building ${this.
|
|
1027
|
+
onProgress?.(`building ${this.image} image (first run — takes a few minutes)…`);
|
|
964
1028
|
// The stamp is the checkout's; the build reads an assembled copy of the
|
|
965
1029
|
// release context, so it works on the legacy builder too.
|
|
966
1030
|
const stamp = this.stampOf(ctx);
|
|
967
1031
|
const built = this.assembleContext(ctx);
|
|
968
1032
|
let res;
|
|
969
1033
|
try {
|
|
970
|
-
res = await this.docker(appImageBuildArgv(this.
|
|
1034
|
+
res = await this.docker(appImageBuildArgv(this.image, built, stamp), {
|
|
971
1035
|
timeoutMs: 1_800_000,
|
|
972
1036
|
});
|
|
973
1037
|
} finally {
|
|
974
1038
|
if (built !== ctx) rmSync(built, { recursive: true, force: true });
|
|
975
1039
|
}
|
|
976
|
-
if (!res.ok) throw new Error(`docker build ${this.
|
|
977
|
-
onProgress?.(`${this.
|
|
1040
|
+
if (!res.ok) throw new Error(`docker build ${this.image} failed: ${errText(res)}`);
|
|
1041
|
+
onProgress?.(`${this.image} image built`);
|
|
978
1042
|
}
|
|
979
1043
|
|
|
980
1044
|
// ---- memory wiring --------------------------------------------------------
|
|
@@ -1266,8 +1330,13 @@ export class CompanionLifecycle {
|
|
|
1266
1330
|
onProgress?.(`companion container unstartable (${firstLine(errText(started))}) — recreating`);
|
|
1267
1331
|
await this.forceRemove();
|
|
1268
1332
|
}
|
|
1269
|
-
// Self-healing first run:
|
|
1270
|
-
|
|
1333
|
+
// Self-healing first run: the pulled release image when there is one,
|
|
1334
|
+
// else build the image rather than failing on its absence — or, for a
|
|
1335
|
+
// configured release image, pull it (a release tag is never built over).
|
|
1336
|
+
if (!(await this.adoptPreferredImage(onProgress))) {
|
|
1337
|
+
if (isReleaseImage(this.image)) await this.pullImage(onProgress);
|
|
1338
|
+
else await this.buildImage(onProgress);
|
|
1339
|
+
}
|
|
1271
1340
|
const homes = this.hostHomes();
|
|
1272
1341
|
this.ensureDirs([
|
|
1273
1342
|
this.cfg.feed_dir,
|
|
@@ -1280,7 +1349,7 @@ export class CompanionLifecycle {
|
|
|
1280
1349
|
homes.github,
|
|
1281
1350
|
]);
|
|
1282
1351
|
const created = await this.docker(this.runArgv(), { timeoutMs: 120_000 });
|
|
1283
|
-
if (!created.ok) throw new Error(`docker run ${this.
|
|
1352
|
+
if (!created.ok) throw new Error(`docker run ${this.image} failed: ${errText(created)}`);
|
|
1284
1353
|
return st.exists || recreating ? "recreated" : "created";
|
|
1285
1354
|
}
|
|
1286
1355
|
|
package/src/tui/connect.ts
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
// the exact remedy; pressing `s` is the operator asking for this prompt.
|
|
12
12
|
import { spawnSync } from "node:child_process";
|
|
13
13
|
import type { TuiConfig } from "../config.js";
|
|
14
|
-
import { transportOf } from "./remote.js";
|
|
14
|
+
import { NO_SSH_HOST_STDERR, transportOf } from "./remote.js";
|
|
15
15
|
|
|
16
16
|
export function masterCheckArgv(cfg: TuiConfig): string[] {
|
|
17
17
|
return ["-O", "check", cfg.ssh_host];
|
|
@@ -51,6 +51,12 @@ export function ensureSshMaster(
|
|
|
51
51
|
// at the call sites) so the board's `s` reconnect hook and any future caller
|
|
52
52
|
// inherit the guarantee: in local mode this function spawns nothing.
|
|
53
53
|
if (transportOf(cfg) === "local") return { kind: "local" };
|
|
54
|
+
// No host, nothing to connect to: `ssh "" true` only ever fails on name
|
|
55
|
+
// resolution, and the passphrase prompt it promises would never come.
|
|
56
|
+
if (cfg.ssh_host.trim() === "") {
|
|
57
|
+
say(NO_SSH_HOST_STDERR);
|
|
58
|
+
return { kind: "failed", detail: NO_SSH_HOST_STDERR };
|
|
59
|
+
}
|
|
54
60
|
if (ssh(masterCheckArgv(cfg), false).status === 0) return { kind: "alive" };
|
|
55
61
|
say(
|
|
56
62
|
`connecting to ${cfg.ssh_host} — enter the GCP key passphrase if asked (once; the connection is shared for 4h)`,
|
package/src/tui/deploytab.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// `[5] deploy` — "what would a deploy ship?" plus the deploy button.
|
|
2
|
-
// Every deploy is a RELEASE: the tag
|
|
3
|
-
//
|
|
2
|
+
// Every deploy is a RELEASE: the tag and the two-arch build with its smoke
|
|
3
|
+
// already happened in CI, so the tab only chooses a version.
|
|
4
4
|
// It shows the version and revision the VM runs (`qcr-update status --json`,
|
|
5
5
|
// read over ssh) against the chosen version, the commits between the two
|
|
6
6
|
// revisions (from the local checkout), and deploy.sh's own
|
package/src/tui/filters.ts
CHANGED
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
// they never touch the poller's snapshot or the memory store.
|
|
6
6
|
import { normalizeTag } from "../memory/store.js";
|
|
7
7
|
import type { MemoryEntry } from "./memory.js";
|
|
8
|
-
import {
|
|
8
|
+
import type { StatusSnapshot } from "../state.js";
|
|
9
|
+
import { offeredPrs, sortPrs, type SnapshotPr } from "./model.js";
|
|
9
10
|
|
|
10
11
|
export interface BoardFilter {
|
|
11
12
|
/** Case-insensitive substring match against the PR author login. */
|
|
@@ -76,10 +77,13 @@ export function applyMemoryFilter<T extends Pick<MemoryEntry, "tags">>(
|
|
|
76
77
|
* companion driver all derive rows from here, so a verb can never address a
|
|
77
78
|
* row the filter hid. */
|
|
78
79
|
export function visiblePrs(view: {
|
|
79
|
-
snapshot?: { prs: SnapshotPr[] };
|
|
80
|
+
snapshot?: { prs: SnapshotPr[]; open_prs?: StatusSnapshot["open_prs"] };
|
|
80
81
|
boardFilter?: BoardFilter | null;
|
|
81
82
|
}): SnapshotPr[] {
|
|
82
|
-
|
|
83
|
+
const tracked = applyBoardFilter(sortPrs(view.snapshot?.prs ?? []), view.boardFilter);
|
|
84
|
+
// The repo's other open PRs follow, offered for review — under a filter the
|
|
85
|
+
// board shows only what the filter names.
|
|
86
|
+
return view.boardFilter != null ? tracked : [...tracked, ...offeredPrs(view.snapshot)];
|
|
83
87
|
}
|
|
84
88
|
|
|
85
89
|
/** The memory view's indicator, same shape as the board's. */
|