@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
|
@@ -130,19 +130,56 @@ export function hostAuthJsonPath(home: string): string {
|
|
|
130
130
|
* without its slash, and the entry pi writes into auth.json on /login. */
|
|
131
131
|
const OAUTH_PROVIDER = OAUTH_PREFIX.replace(/\/$/, "");
|
|
132
132
|
|
|
133
|
-
/**
|
|
134
|
-
*
|
|
135
|
-
|
|
136
|
-
export
|
|
133
|
+
/** pi's /login labels for the two ChatGPT subscription logins (pi 1.4.0).
|
|
134
|
+
* VERIFY-ON-PIN. */
|
|
135
|
+
export const OAUTH_LOGIN_LABEL = "OpenAI Codex (legacy)";
|
|
136
|
+
export const CHATGPT_LOGIN_LABEL = "Sign in with ChatGPT";
|
|
137
|
+
|
|
138
|
+
/** pi 1.4's newer "Sign in with ChatGPT" on the `openai` provider, which an
|
|
139
|
+
* OpenAI API key stored by /login also lands on. Every model spec reaches pi
|
|
140
|
+
* through the credential-aware route (packages/pi-plugin/src/modelroute.ts),
|
|
141
|
+
* so an `openai-codex/` model runs on this login when it is the one there:
|
|
142
|
+
* any OpenAI login serves the review. VERIFY-ON-PIN. */
|
|
143
|
+
const CHATGPT_PROVIDER = API_PREFIX.replace(/\/$/, "");
|
|
144
|
+
|
|
145
|
+
/** Where to sign in, and which logins count: what every "log in" message
|
|
146
|
+
* says, in pi 1.4.0's /login labels. */
|
|
147
|
+
export const ANY_OPENAI_LOGIN =
|
|
148
|
+
`any OpenAI login works: at pi's /login pick "Sign in with an account", then "OpenAI" (${CHATGPT_LOGIN_LABEL}) ` +
|
|
149
|
+
`or "${OAUTH_LOGIN_LABEL}"; or "Sign in with an API key", then "OpenAI"`;
|
|
150
|
+
|
|
151
|
+
/** The OpenAI logins in a pi auth.json: each ChatGPT subscription login (the
|
|
152
|
+
* shipped `openai-codex` and pi 1.4's newer `openai`), and whether an OpenAI
|
|
153
|
+
* API key was stored by /login (`{"openai":{"type":"api_key","key":…}}`).
|
|
154
|
+
* The file existing proves nothing: pi keeps every provider's login there,
|
|
155
|
+
* and a logged-out pi can leave `{}` behind. Unreadable ⇒ none. */
|
|
156
|
+
export function hostOpenAILogins(authPath: string): { oauth: string[]; apiKey: boolean } {
|
|
137
157
|
try {
|
|
138
158
|
const parsed = JSON.parse(readFileSync(authPath, "utf8")) as Record<string, unknown>;
|
|
139
|
-
const cred =
|
|
140
|
-
|
|
159
|
+
const cred = (provider: string): Record<string, unknown> | null => {
|
|
160
|
+
const c = parsed?.[provider];
|
|
161
|
+
return typeof c === "object" && c !== null ? (c as Record<string, unknown>) : null;
|
|
162
|
+
};
|
|
163
|
+
const key = cred(CHATGPT_PROVIDER);
|
|
164
|
+
return {
|
|
165
|
+
oauth: [OAUTH_PROVIDER, CHATGPT_PROVIDER].filter((p) => cred(p)?.type === "oauth"),
|
|
166
|
+
apiKey: key?.type === "api_key" && typeof key.key === "string" && key.key.trim() !== "",
|
|
167
|
+
};
|
|
141
168
|
} catch {
|
|
142
|
-
return false;
|
|
169
|
+
return { oauth: [], apiKey: false };
|
|
143
170
|
}
|
|
144
171
|
}
|
|
145
172
|
|
|
173
|
+
/** Does this host's auth.json hold a ChatGPT subscription login (either one)? */
|
|
174
|
+
export function hostHasOAuthLogin(authPath: string): boolean {
|
|
175
|
+
return hostOAuthProviders(authPath).length > 0;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** Every ChatGPT subscription login in this auth.json. Unreadable ⇒ none. */
|
|
179
|
+
export function hostOAuthProviders(authPath: string): string[] {
|
|
180
|
+
return hostOpenAILogins(authPath).oauth;
|
|
181
|
+
}
|
|
182
|
+
|
|
146
183
|
function hostHome(): string {
|
|
147
184
|
return process.env.HOME ?? homedir();
|
|
148
185
|
}
|
|
@@ -356,18 +393,24 @@ export type LoginState =
|
|
|
356
393
|
"absent" | "no-pi-volume" | "stopped" | "no-auth" | "wrong-provider" | "ok" | "undetectable";
|
|
357
394
|
|
|
358
395
|
/**
|
|
359
|
-
* The in-container login check: the same question `
|
|
360
|
-
* the host — is there an
|
|
361
|
-
*
|
|
362
|
-
*
|
|
363
|
-
*
|
|
364
|
-
*
|
|
365
|
-
*
|
|
396
|
+
* The in-container login check: the same question `hostOpenAILogins` asks of
|
|
397
|
+
* the host — is there an OpenAI login? — asked by the container's own node, so
|
|
398
|
+
* only an exit code ever leaves it (never a token, which on the VM route would
|
|
399
|
+
* otherwise cross ssh). Any OpenAI login counts, because the engine runs the
|
|
400
|
+
* same model on whichever exists (packages/pi-plugin/src/modelroute.ts): an
|
|
401
|
+
* `openai-codex` or `openai` entry of type `oauth`, or an `openai` API key.
|
|
402
|
+
* Exit 0 = logged in, 3 = no auth.json / empty, 4 = an auth.json without an
|
|
403
|
+
* OpenAI login (`{}`, or another provider picked at pi's /login menu).
|
|
404
|
+
* Anything else (no node) is undetectable, never a pass.
|
|
366
405
|
*/
|
|
367
406
|
export const CONTAINER_LOGIN_SCRIPT =
|
|
368
407
|
"const fs=require('fs');let t='';try{t=fs.readFileSync(process.argv[1],'utf8')}catch{process.exit(3)}" +
|
|
369
408
|
"if(!t.trim())process.exit(3);let a;try{a=JSON.parse(t)}catch{process.exit(4)}" +
|
|
370
|
-
|
|
409
|
+
"const c=(k)=>{const v=a&&a[k];return !!v&&typeof v==='object'?v:null};" +
|
|
410
|
+
"const o=(k)=>c(k)?.type==='oauth';" +
|
|
411
|
+
`const key=c(${JSON.stringify(CHATGPT_PROVIDER)});` +
|
|
412
|
+
"const k=!!key&&key.type==='api_key'&&typeof key.key==='string'&&key.key.trim()!=='';" +
|
|
413
|
+
`process.exit(o(${JSON.stringify(OAUTH_PROVIDER)})||o(${JSON.stringify(CHATGPT_PROVIDER)})||k?0:4)`;
|
|
371
414
|
|
|
372
415
|
export interface LoginProbe {
|
|
373
416
|
state: LoginState;
|
|
@@ -483,8 +526,8 @@ function loginCheck(
|
|
|
483
526
|
name,
|
|
484
527
|
ok: false,
|
|
485
528
|
detail:
|
|
486
|
-
`${label}: auth.json on the pi home (${probe.mount}) holds no
|
|
487
|
-
|
|
529
|
+
`${label}: auth.json on the pi home (${probe.mount}) holds no OpenAI login — ` +
|
|
530
|
+
`a /login to another provider does not count. Attach and log in (${ANY_OPENAI_LOGIN}): ` +
|
|
488
531
|
`${steps}.`,
|
|
489
532
|
};
|
|
490
533
|
case "no-pi-volume":
|
|
@@ -575,7 +618,7 @@ export function companionSessionName(configText: string): string {
|
|
|
575
618
|
|
|
576
619
|
// ---- the operator-facing text ----------------------------------------------
|
|
577
620
|
|
|
578
|
-
|
|
621
|
+
const ROUTE_CHOICES =
|
|
579
622
|
"How should the review sessions authenticate to OpenAI?\n" +
|
|
580
623
|
" 1) oauth — a ChatGPT subscription. Cost is your plan, flat. The catch: pi's OAuth tokens live in the\n" +
|
|
581
624
|
" pi home of whichever process logged in and DO NOT transfer between this host and the containers, so\n" +
|
|
@@ -583,8 +626,29 @@ export const ROUTE_QUESTION =
|
|
|
583
626
|
" companion container. Each is guided when its surface first needs it.\n" +
|
|
584
627
|
" 2) api — one OpenAI API key, metered per token. One paste covers the daemon and its review sessions (the daemon\n" +
|
|
585
628
|
" container's entrypoint exports the key file as OPENAI_API_KEY), and the wizard rewrites all five model strings\n" +
|
|
586
|
-
" for you. The companion container is not handed the key: it keeps its own login.\n"
|
|
587
|
-
|
|
629
|
+
" for you. The companion container is not handed the key: it keeps its own login.\n";
|
|
630
|
+
|
|
631
|
+
export const ROUTE_QUESTION = `${ROUTE_CHOICES}Answer 1/oauth or 2/api:`;
|
|
632
|
+
|
|
633
|
+
/** The same question where no prompter can ask it: the pi-driven wizard
|
|
634
|
+
* passes the operator's answer as this step's `route` input. */
|
|
635
|
+
export const ROUTE_INPUT_QUESTION =
|
|
636
|
+
`${ROUTE_CHOICES}Ask the operator which one, then run this step again with route: "oauth" or route: "api". ` +
|
|
637
|
+
"Never ask them to type the answer anywhere else: there is no other prompt.";
|
|
638
|
+
|
|
639
|
+
/** The step's input schema: the operator's route answer (pi-driven flow). */
|
|
640
|
+
const ROUTE_PARAMETERS = {
|
|
641
|
+
type: "object",
|
|
642
|
+
properties: {
|
|
643
|
+
route: {
|
|
644
|
+
type: "string",
|
|
645
|
+
enum: ["oauth", "api"],
|
|
646
|
+
description:
|
|
647
|
+
'The operator\'s answer: "oauth" (a ChatGPT subscription) or "api" (an OpenAI API key). ' +
|
|
648
|
+
"Omit it to keep the recorded route, or to read both options out.",
|
|
649
|
+
},
|
|
650
|
+
},
|
|
651
|
+
} as const;
|
|
588
652
|
|
|
589
653
|
export const KEY_QUESTION = `paste the OpenAI API key (it is written straight to the ${OPENAI_KEY_FILE} secrets file, 0600, never to a command line):`;
|
|
590
654
|
|
|
@@ -635,12 +699,16 @@ interface RouteDecision {
|
|
|
635
699
|
* route is what pi will really use, so the config must say so;
|
|
636
700
|
* 3. otherwise a valid key file;
|
|
637
701
|
* 4. otherwise the previously recorded route;
|
|
638
|
-
* 5. otherwise
|
|
702
|
+
* 5. otherwise an OpenAI API key this host's pi stored at /login: the
|
|
703
|
+
* wizard's own session runs on a key, so the api route is the one in use
|
|
704
|
+
* (the daemon still takes its key from the secrets file);
|
|
705
|
+
* 6. otherwise: no answer yet — the step goes red with the choice text.
|
|
639
706
|
*/
|
|
640
707
|
async function decideRoute(
|
|
641
708
|
ctx: StepContext,
|
|
642
709
|
keyFile: KeyFileState,
|
|
643
710
|
hostLogin: boolean,
|
|
711
|
+
hostApiKey = false,
|
|
644
712
|
): Promise<RouteDecision> {
|
|
645
713
|
const prompter = ctx.adapters.prompter;
|
|
646
714
|
if (prompter) {
|
|
@@ -652,15 +720,24 @@ async function decideRoute(
|
|
|
652
720
|
return { route: kept, source: "kept the recorded route (empty answer)" };
|
|
653
721
|
return { route: null, source: `did not understand the answer — reply 1/oauth or 2/api` };
|
|
654
722
|
}
|
|
723
|
+
const answered = typeof ctx.input.route === "string" ? parseRouteAnswer(ctx.input.route) : null;
|
|
724
|
+
if (answered) return { route: answered, source: "you chose it" };
|
|
655
725
|
if (hostLogin)
|
|
656
726
|
return {
|
|
657
727
|
route: "oauth",
|
|
658
|
-
source:
|
|
728
|
+
source:
|
|
729
|
+
"a completed ChatGPT login is on this host, and pi prefers auth.json over any env key",
|
|
659
730
|
};
|
|
660
731
|
if (keyFile.valid)
|
|
661
732
|
return { route: "api", source: `a valid ${OPENAI_KEY_FILE} is in the secrets dir` };
|
|
662
733
|
const kept = recordedRoute(ctx);
|
|
663
734
|
if (kept) return { route: kept, source: "the recorded route" };
|
|
735
|
+
if (hostApiKey)
|
|
736
|
+
return {
|
|
737
|
+
route: "api",
|
|
738
|
+
source:
|
|
739
|
+
"this host's pi signed in with an OpenAI API key (its /login), and holds no ChatGPT login",
|
|
740
|
+
};
|
|
664
741
|
return { route: null, source: "nothing on disk says which route this installation uses" };
|
|
665
742
|
}
|
|
666
743
|
|
|
@@ -793,20 +870,22 @@ async function oauthLoginChecks(
|
|
|
793
870
|
): Promise<CheckResult[]> {
|
|
794
871
|
const checks: CheckResult[] = [];
|
|
795
872
|
const authPath = hostAuthJsonPath(home);
|
|
796
|
-
const
|
|
873
|
+
const logins = hostOpenAILogins(authPath);
|
|
874
|
+
const hostOk = logins.oauth.length > 0 || logins.apiKey;
|
|
797
875
|
checks.push(
|
|
798
876
|
hostOk
|
|
799
877
|
? {
|
|
800
878
|
name: "wizard_login",
|
|
801
879
|
ok: true,
|
|
802
|
-
detail: `this host's pi is logged in (${authPath}) — login 1 of 3 done`,
|
|
880
|
+
detail: `this host's pi is logged in (${authPath}, ${[...logins.oauth, ...(logins.apiKey ? ["an OpenAI API key"] : [])].join(" + ")}) — login 1 of 3 done`,
|
|
803
881
|
}
|
|
804
882
|
: {
|
|
805
883
|
name: "wizard_login",
|
|
806
884
|
ok: false,
|
|
807
885
|
detail:
|
|
808
|
-
`no
|
|
809
|
-
|
|
886
|
+
`no OpenAI login in this host's pi (${authPath}). Log in from this very session — type /login at pi's input line ` +
|
|
887
|
+
`(${ANY_OPENAI_LOGIN}) and follow its flow — then run this step again. This is login 1 of 3; ` +
|
|
888
|
+
"the two container logins come later.",
|
|
810
889
|
},
|
|
811
890
|
);
|
|
812
891
|
const runner = ctx.adapters.runner;
|
|
@@ -852,6 +931,7 @@ export function llmAuthStep(opts: { transport?: TransportOptions } = {}): Onboar
|
|
|
852
931
|
// After preflight (10) and the topology question (20): the spec's second
|
|
853
932
|
// question, and everything after it assumes a decided route.
|
|
854
933
|
order: 30,
|
|
934
|
+
parameters: ROUTE_PARAMETERS,
|
|
855
935
|
|
|
856
936
|
async run(ctx): Promise<StepReport> {
|
|
857
937
|
const home = hostHome();
|
|
@@ -878,14 +958,19 @@ export function llmAuthStep(opts: { transport?: TransportOptions } = {}): Onboar
|
|
|
878
958
|
rootOf(ctx.adapters),
|
|
879
959
|
);
|
|
880
960
|
let keyFile = readKeyFileState(secretsDir);
|
|
881
|
-
const
|
|
961
|
+
const logins = hostOpenAILogins(hostAuthJsonPath(home));
|
|
962
|
+
const hostLogin = logins.oauth.length > 0;
|
|
882
963
|
|
|
883
|
-
const decision = await decideRoute(ctx, keyFile, hostLogin);
|
|
964
|
+
const decision = await decideRoute(ctx, keyFile, hostLogin, logins.apiKey);
|
|
884
965
|
if (decision.route === null) {
|
|
885
|
-
|
|
966
|
+
const question = ctx.adapters.prompter ? ROUTE_QUESTION : ROUTE_INPUT_QUESTION;
|
|
967
|
+
checks.push({ name: "route", ok: false, detail: `${decision.source}.\n${question}` });
|
|
886
968
|
return { ok: false, checks };
|
|
887
969
|
}
|
|
888
970
|
const route = decision.route;
|
|
971
|
+
// An answer is recorded at once, so a re-run (a login still owed, say)
|
|
972
|
+
// keeps it without asking again.
|
|
973
|
+
if (decision.source === "you chose it") ctx.recordChoice(CHOICE_KEYS.authRoute, route);
|
|
889
974
|
checks.push({ name: "route", ok: true, detail: `${route} route — ${decision.source}` });
|
|
890
975
|
|
|
891
976
|
let credentialChanged =
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
// Switch-on's login step: one OpenAI login per box. The daemon's
|
|
2
|
+
// container reviews on the login the home signed in to, the host pi home's
|
|
3
|
+
// auth.json, instead of a login of its own.
|
|
4
|
+
//
|
|
5
|
+
// .env QCR_PI_LOGIN_DIR = the home's pi agent dir. docker-compose.yml
|
|
6
|
+
// mounts it at /opt/qcr/login and names it to the daemon
|
|
7
|
+
// (QCR_LOGIN_DIR), so the refresher keeps that one file and every
|
|
8
|
+
// worker borrows it; pi's settings stay in the container's own
|
|
9
|
+
// pi home, never the person's.
|
|
10
|
+
// config [login_refresh] enabled = true, so the daemon runs its single
|
|
11
|
+
// refresher on that file while auto-review runs. The config must
|
|
12
|
+
// still load with it (its margin outlasts the longest worker
|
|
13
|
+
// cap), or nothing is written.
|
|
14
|
+
// restart a stack brought up before is recreated, so the daemon has the
|
|
15
|
+
// mount and reads the config at boot (its volumes, so its data,
|
|
16
|
+
// are kept). A first switch-on's bring-up starts it this way.
|
|
17
|
+
// proof a stack brought up before keeps its image pin and its extracted
|
|
18
|
+
// docker-compose.yml, which may be an older release's. So the
|
|
19
|
+
// compose file must define the mount before anything is written,
|
|
20
|
+
// and the running daemon must show it reviews on the login (its
|
|
21
|
+
// QCR_LOGIN_DIR, the login in the mount, an image whose
|
|
22
|
+
// refresher reads that dir) before the step says so. Until then
|
|
23
|
+
// the step is not done, and the next switch-on runs it again.
|
|
24
|
+
//
|
|
25
|
+
// The hand-over needs nothing more: while the refresher runs it refreshes far
|
|
26
|
+
// ahead of pi's own window, so the home's pi, a borrower now, never has a
|
|
27
|
+
// token to refresh; switching auto-review off stops the daemon, the refresher
|
|
28
|
+
// with it, and the home's pi refreshes on its own again (src/home/loginowner.ts).
|
|
29
|
+
// The single-instance lock lives beside the shared auth.json, so a second
|
|
30
|
+
// refresher on this box is refused whoever starts it.
|
|
31
|
+
//
|
|
32
|
+
// A remote server (the advanced route) never gets this step: it signs in on
|
|
33
|
+
// its own device login, and the host's login is never copied there.
|
|
34
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
35
|
+
import { join } from "node:path";
|
|
36
|
+
import { parse } from "smol-toml";
|
|
37
|
+
import { parseConfig } from "../../config.js";
|
|
38
|
+
import type {
|
|
39
|
+
CheckResult,
|
|
40
|
+
OnboardingStep,
|
|
41
|
+
ProcessRunner,
|
|
42
|
+
StepContext,
|
|
43
|
+
StepReport,
|
|
44
|
+
} from "../engine.js";
|
|
45
|
+
import { CONTAINER_LOGIN_DIR, LOGIN_DIR_ENV } from "../../loginrefresh/supervisor.js";
|
|
46
|
+
import { BROUGHT_UP_CHOICE_KEY } from "./bringup.js";
|
|
47
|
+
import {
|
|
48
|
+
DAEMON_SERVICE,
|
|
49
|
+
composeArgs,
|
|
50
|
+
daemonExec,
|
|
51
|
+
readEnvText,
|
|
52
|
+
releaseVersion,
|
|
53
|
+
rootOf,
|
|
54
|
+
writeAtomic,
|
|
55
|
+
} from "./compose.js";
|
|
56
|
+
import { assertNoDryRunEdit, patchToml, type TomlEdit } from "./configfill.js";
|
|
57
|
+
import { readEnvKey, setEnvKey } from "./ingress.js";
|
|
58
|
+
import { topologyOf } from "./topology.js";
|
|
59
|
+
|
|
60
|
+
export const LOGIN_SHARE_STEP_ID = "login_share";
|
|
61
|
+
/** The project .env key docker-compose.yml mounts the host's login from. */
|
|
62
|
+
export const PI_LOGIN_DIR_ENV = "QCR_PI_LOGIN_DIR";
|
|
63
|
+
|
|
64
|
+
export interface LoginShareOptions {
|
|
65
|
+
/** The home's pi agent dir, where its login is. Absent: nothing is shared
|
|
66
|
+
* and the daemon keeps a login of its own. */
|
|
67
|
+
loginDir?: string;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const firstLine = (s: string): string => s.trim().split("\n")[0]?.trim() || "no output";
|
|
71
|
+
const message = (err: unknown): string => (err instanceof Error ? err.message : String(err));
|
|
72
|
+
|
|
73
|
+
/** Does the config run the refresher? null when it cannot be read. */
|
|
74
|
+
export function refresherEnabled(configText: string): boolean | null {
|
|
75
|
+
try {
|
|
76
|
+
const t = (parse(configText) as Record<string, unknown>)["login_refresh"];
|
|
77
|
+
return (
|
|
78
|
+
typeof t === "object" && t !== null && (t as Record<string, unknown>)["enabled"] === true
|
|
79
|
+
);
|
|
80
|
+
} catch {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** The config with the refresher on; null when it is on already. */
|
|
86
|
+
export function refresherPatch(configText: string): string | null {
|
|
87
|
+
if (refresherEnabled(configText) === true) return null;
|
|
88
|
+
const edits: TomlEdit[] = [{ section: "login_refresh", key: "enabled", value: "true" }];
|
|
89
|
+
assertNoDryRunEdit(edits);
|
|
90
|
+
return patchToml(configText, edits);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const fail = (name: string, detail: string): StepReport => ({
|
|
94
|
+
ok: false,
|
|
95
|
+
checks: [{ name, ok: false, detail }],
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
/** The upgrade a stack too old for a shared login needs, said once. */
|
|
99
|
+
function upgradeFix(root: string): string {
|
|
100
|
+
const version = releaseVersion(root);
|
|
101
|
+
return (
|
|
102
|
+
`Fix: update the stack in ${root} to a release with the shared login` +
|
|
103
|
+
`${version ? ` (QCR_VERSION=${version} in its .env, and that release's docker-compose.yml)` : ""}, ` +
|
|
104
|
+
"then switch auto-review on again"
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** Does the project's docker-compose.yml mount a shared login? null when
|
|
109
|
+
* there is no compose file yet (the bring-up places it, this release's). */
|
|
110
|
+
export function composeMountsLogin(root: string): boolean | null {
|
|
111
|
+
let text: string;
|
|
112
|
+
try {
|
|
113
|
+
text = readFileSync(join(root, "docker-compose.yml"), "utf8");
|
|
114
|
+
} catch {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
return text.includes(PI_LOGIN_DIR_ENV);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** The running daemon, asked whether it reviews on the shared login: the
|
|
121
|
+
* compose definition hands it the dir, the mount holds the login, and its
|
|
122
|
+
* image's refresher reads that dir. Exit codes name which failed. */
|
|
123
|
+
export const DAEMON_LOGIN_PROBE = [
|
|
124
|
+
"sh",
|
|
125
|
+
"-c",
|
|
126
|
+
`test "$${LOGIN_DIR_ENV}" = ${CONTAINER_LOGIN_DIR} || exit 3; ` +
|
|
127
|
+
`test -s ${CONTAINER_LOGIN_DIR}/auth.json || exit 4; ` +
|
|
128
|
+
`grep -q ${LOGIN_DIR_ENV} dist/src/loginrefresh/supervisor.js 2>/dev/null || exit 5`,
|
|
129
|
+
] as const;
|
|
130
|
+
|
|
131
|
+
/** Ask the running daemon (DAEMON_LOGIN_PROBE). A daemon that cannot be
|
|
132
|
+
* asked is not one that reviews on the login: never claimed. */
|
|
133
|
+
export async function daemonLoginCheck(runner: ProcessRunner, root: string): Promise<CheckResult> {
|
|
134
|
+
const r = await daemonExec(runner, root, DAEMON_LOGIN_PROBE);
|
|
135
|
+
if (r.code === 0)
|
|
136
|
+
return { name: "daemon", ok: true, detail: "the daemon reviews on your login, data kept" };
|
|
137
|
+
const why =
|
|
138
|
+
r.code === 3
|
|
139
|
+
? `the running daemon is not handed the login (its ${LOGIN_DIR_ENV} is not ${CONTAINER_LOGIN_DIR}): the stack's docker-compose.yml is an older release's`
|
|
140
|
+
: r.code === 4
|
|
141
|
+
? `the running daemon sees no login at ${CONTAINER_LOGIN_DIR}: the stack's docker-compose.yml does not mount it`
|
|
142
|
+
: r.code === 5
|
|
143
|
+
? "the running daemon's image predates the shared login: it would keep reviewing on a login of its own"
|
|
144
|
+
: `can't ask the running daemon whether it reviews on your login: ${firstLine(r.stderr || r.stdout)}`;
|
|
145
|
+
return { name: "daemon", ok: false, detail: `${why}. ${upgradeFix(root)}` };
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export function loginShareStep(o: LoginShareOptions = {}): OnboardingStep {
|
|
149
|
+
const state = async (ctx: StepContext): Promise<StepReport> => {
|
|
150
|
+
if (o.loginDir === undefined)
|
|
151
|
+
return {
|
|
152
|
+
ok: true,
|
|
153
|
+
checks: [{ name: "login", ok: true, detail: "no home login to share here" }],
|
|
154
|
+
};
|
|
155
|
+
const env = readEnvKey(readEnvText(rootOf(ctx.adapters)), PI_LOGIN_DIR_ENV);
|
|
156
|
+
let config: string;
|
|
157
|
+
try {
|
|
158
|
+
config = readFileSync(ctx.adapters.paths.configPath, "utf8");
|
|
159
|
+
} catch (err) {
|
|
160
|
+
return fail("login", `can't read ${ctx.adapters.paths.configPath}: ${message(err)}`);
|
|
161
|
+
}
|
|
162
|
+
const checks: CheckResult[] = [
|
|
163
|
+
env === o.loginDir
|
|
164
|
+
? { name: "login", ok: true, detail: `auto-review reviews on your login in ${o.loginDir}` }
|
|
165
|
+
: {
|
|
166
|
+
name: "login",
|
|
167
|
+
ok: false,
|
|
168
|
+
detail: `the stack does not mount your login (${PI_LOGIN_DIR_ENV} is ${env === null ? "unset" : JSON.stringify(env)})`,
|
|
169
|
+
},
|
|
170
|
+
refresherEnabled(config) === true
|
|
171
|
+
? { name: "refresher", ok: true, detail: "its refresher keeps the login fresh" }
|
|
172
|
+
: { name: "refresher", ok: false, detail: "[login_refresh] is not enabled" },
|
|
173
|
+
];
|
|
174
|
+
return { ok: checks.every((c) => c.ok), checks };
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
return {
|
|
178
|
+
id: LOGIN_SHARE_STEP_ID,
|
|
179
|
+
title: "Share your login with auto-review",
|
|
180
|
+
explain:
|
|
181
|
+
"Auto-review reviews on the OpenAI login this home signed in to: one login per machine, which its refresher keeps fresh while it runs.",
|
|
182
|
+
// after the config steps (memory is 60), before the daemon starts (69)
|
|
183
|
+
order: 66,
|
|
184
|
+
enabled: (choices) => topologyOf(choices) === "local",
|
|
185
|
+
async run(ctx): Promise<StepReport> {
|
|
186
|
+
const dir = o.loginDir;
|
|
187
|
+
if (dir === undefined) return state(ctx);
|
|
188
|
+
const auth = join(dir, "auth.json");
|
|
189
|
+
if (!existsSync(auth))
|
|
190
|
+
return fail(
|
|
191
|
+
"login",
|
|
192
|
+
`there is no login at ${auth} yet: sign in in the home first, then switch auto-review on again`,
|
|
193
|
+
);
|
|
194
|
+
// compose expands `$` in an .env value and treats ` #` as a comment
|
|
195
|
+
if (/[$\n]|\s#/.test(dir))
|
|
196
|
+
return fail(
|
|
197
|
+
"login",
|
|
198
|
+
`${dir} can't be passed to Docker Compose through .env (a \`$\`, a newline or a space before #): set PI_CODING_AGENT_DIR to a plainer path`,
|
|
199
|
+
);
|
|
200
|
+
const configPath = ctx.adapters.paths.configPath;
|
|
201
|
+
let config: string;
|
|
202
|
+
try {
|
|
203
|
+
config = readFileSync(configPath, "utf8");
|
|
204
|
+
} catch (err) {
|
|
205
|
+
return fail("refresher", `can't read ${configPath}: ${message(err)}`);
|
|
206
|
+
}
|
|
207
|
+
const patched = refresherPatch(config);
|
|
208
|
+
if (patched !== null) {
|
|
209
|
+
try {
|
|
210
|
+
parseConfig(patched);
|
|
211
|
+
} catch (err) {
|
|
212
|
+
return fail(
|
|
213
|
+
"refresher",
|
|
214
|
+
`[login_refresh] enabled = true would not load in ${configPath}: ${message(err)} — nothing was written`,
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
const root = rootOf(ctx.adapters);
|
|
219
|
+
if (composeMountsLogin(root) === false)
|
|
220
|
+
return fail(
|
|
221
|
+
"daemon",
|
|
222
|
+
`${join(root, "docker-compose.yml")} is an older release's: it does not mount a shared login, so the daemon would keep a login of its own. Nothing was written. ${upgradeFix(root)}`,
|
|
223
|
+
);
|
|
224
|
+
const envText = readEnvText(root);
|
|
225
|
+
const nextEnv = setEnvKey(envText, PI_LOGIN_DIR_ENV, dir);
|
|
226
|
+
if (patched !== null) writeAtomic(configPath, patched);
|
|
227
|
+
if (nextEnv !== envText) writeAtomic(join(root, ".env"), nextEnv);
|
|
228
|
+
const checks: CheckResult[] = [
|
|
229
|
+
{
|
|
230
|
+
name: "login",
|
|
231
|
+
ok: true,
|
|
232
|
+
detail: `auto-review reviews on your login in ${dir}, and its refresher keeps it fresh while it runs`,
|
|
233
|
+
},
|
|
234
|
+
];
|
|
235
|
+
const changed = patched !== null || nextEnv !== envText;
|
|
236
|
+
if (typeof ctx.choices[BROUGHT_UP_CHOICE_KEY] === "string") {
|
|
237
|
+
if (changed) {
|
|
238
|
+
// the mount and the config are read when the container starts
|
|
239
|
+
const r = await ctx.adapters.runner.run(
|
|
240
|
+
"docker",
|
|
241
|
+
composeArgs(root, ["up", "-d", "--no-deps", "--force-recreate", DAEMON_SERVICE]),
|
|
242
|
+
);
|
|
243
|
+
if (r.code !== 0)
|
|
244
|
+
return fail(
|
|
245
|
+
"daemon",
|
|
246
|
+
`\`docker compose up -d --force-recreate ${DAEMON_SERVICE}\` exited ${r.code}: ${firstLine(r.stderr || r.stdout)}`,
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
// a stack brought up before says so itself, or the step is not done
|
|
250
|
+
const daemon = await daemonLoginCheck(ctx.adapters.runner, root);
|
|
251
|
+
checks.push(daemon);
|
|
252
|
+
if (!daemon.ok) return { ok: false, checks };
|
|
253
|
+
}
|
|
254
|
+
return { ok: true, checks };
|
|
255
|
+
},
|
|
256
|
+
verify: state,
|
|
257
|
+
};
|
|
258
|
+
}
|
|
@@ -77,7 +77,7 @@ import { dirname, join, resolve, sep } from "node:path";
|
|
|
77
77
|
import { pathToFileURL } from "node:url";
|
|
78
78
|
import { OPENVIKING_DEFAULTS, parseConfig } from "../../config.js";
|
|
79
79
|
import { configHome, locateKeysFile, locateSecrets } from "../../install.js";
|
|
80
|
-
import { tuiMemoryBaseUrl } from "../../tui/
|
|
80
|
+
import { tuiMemoryBaseUrl } from "../../tui/memoryurl.js";
|
|
81
81
|
import { shellQuote } from "../../tui/remote.js";
|
|
82
82
|
import { CHOICE_KEYS, MEMORY_CHOICE_IDS, type MemoryChoice } from "../choices.js";
|
|
83
83
|
import { deliverConfigChange } from "./configdelivery.js";
|
|
@@ -481,6 +481,22 @@ export const DECLINED_CHECKS: readonly CheckResult[] = [
|
|
|
481
481
|
/** The check row that reports the daemon's on/off switch. */
|
|
482
482
|
export const MEMORY_SWITCH_CHECK = "memory-switch";
|
|
483
483
|
|
|
484
|
+
/** The check row that says phase B waits on a server not answering yet. */
|
|
485
|
+
export const MEMORY_SERVER_CHECK = "memory-server";
|
|
486
|
+
|
|
487
|
+
/** A run that landed phase A (the root key) and stopped only because the
|
|
488
|
+
* server is not up yet: on a stack not running memory, the bring-up starts
|
|
489
|
+
* it and the memory_user_keys step finishes phase B. Any other failure is a
|
|
490
|
+
* failure. */
|
|
491
|
+
export function waitsOnServer(checks: readonly CheckResult[]): boolean {
|
|
492
|
+
const failed = checks.filter((c) => !c.ok);
|
|
493
|
+
return (
|
|
494
|
+
failed.length > 0 &&
|
|
495
|
+
failed.every((c) => c.name === MEMORY_SERVER_CHECK) &&
|
|
496
|
+
checks.some((c) => c.name === "root-key" && c.ok)
|
|
497
|
+
);
|
|
498
|
+
}
|
|
499
|
+
|
|
484
500
|
/**
|
|
485
501
|
* Tell the DAEMON the operator's answer: `[openviking] enabled =
|
|
486
502
|
* false` on a decline — its word for "off", under which memory health reports
|
|
@@ -700,6 +716,15 @@ function serverUnreachableDetail(ctx: StepContext, base: string, why: string): s
|
|
|
700
716
|
const flags = forwardFlags(ctx);
|
|
701
717
|
const cmd = (sub: string): string =>
|
|
702
718
|
`\`cd ${root} && node scripts/companion-memory-forward.mjs ${sub} ${flags}\``;
|
|
719
|
+
if (topologyOf(ctx.choices) !== "vm")
|
|
720
|
+
return (
|
|
721
|
+
`no OpenViking answering at ${base} (${why}). The root key is in place, which is what the daemon needs at boot. ` +
|
|
722
|
+
"Before the bring-up that is expected, and nothing is needed from you: the bring-up step starts the memory engine, " +
|
|
723
|
+
"and the memory_user_keys step after it reaches it through a memory forward the wizard opens itself, then mints " +
|
|
724
|
+
"the two user keys. If the stack is already up, the memory container is the thing to check " +
|
|
725
|
+
"(`docker compose ps memory`, `docker compose logs memory`); a forward that stays up outside the wizard is " +
|
|
726
|
+
`${cmd("install")} (the systemd unit) or \`qcr tui\`, which starts one.`
|
|
727
|
+
);
|
|
703
728
|
return (
|
|
704
729
|
`no OpenViking answering at ${base} (${why}). The root key is in place, which is what the daemon needs at boot. ` +
|
|
705
730
|
"The memory engine listens only on its own container's loopback (the memory sidecar on the laptop route), so this machine reaches it through the memory " +
|
|
@@ -896,7 +921,7 @@ export function memoryStep(opts: MemoryStepOptions = {}): OnboardingStep {
|
|
|
896
921
|
};
|
|
897
922
|
};
|
|
898
923
|
|
|
899
|
-
|
|
924
|
+
const step: OnboardingStep = {
|
|
900
925
|
id: MEMORY_STEP_ID,
|
|
901
926
|
title: "Memory: mint, place and confine the OpenViking keys",
|
|
902
927
|
explain:
|
|
@@ -1000,7 +1025,7 @@ export function memoryStep(opts: MemoryStepOptions = {}): OnboardingStep {
|
|
|
1000
1025
|
const ready = await serverReady(ctx.adapters.fetch, base, prov);
|
|
1001
1026
|
if (!ready.ok) {
|
|
1002
1027
|
checks.push({
|
|
1003
|
-
name:
|
|
1028
|
+
name: MEMORY_SERVER_CHECK,
|
|
1004
1029
|
ok: false,
|
|
1005
1030
|
detail: ready.answering
|
|
1006
1031
|
? `OpenViking answers at ${base} but is not ready (${ready.detail}). Check the engine and embedder logs, then run this step again; the memory forward already reaches it.`
|
|
@@ -1216,6 +1241,54 @@ export function memoryStep(opts: MemoryStepOptions = {}): OnboardingStep {
|
|
|
1216
1241
|
return { ok: checks.every((c) => c.ok), checks };
|
|
1217
1242
|
},
|
|
1218
1243
|
};
|
|
1244
|
+
|
|
1245
|
+
// On the compose route the engine is reachable from this machine only
|
|
1246
|
+
// through the memory forward, and the wizard runs before any TUI has opened
|
|
1247
|
+
// one: each call holds one open for its own duration (adopting a running
|
|
1248
|
+
// forward, closing one it started). A declined memory needs no server.
|
|
1249
|
+
const withForward = async (
|
|
1250
|
+
ctx: StepContext,
|
|
1251
|
+
body: () => Promise<StepReport>,
|
|
1252
|
+
): Promise<StepReport> => {
|
|
1253
|
+
const open = ctx.adapters.memoryForward;
|
|
1254
|
+
const declined =
|
|
1255
|
+
ctx.input.choice === "declined" ||
|
|
1256
|
+
(ctx.input.choice === undefined && ctx.choices[MEMORY_CHOICE_KEY] === "declined");
|
|
1257
|
+
if (!open || declined || topologyOf(ctx.choices) === "vm") return body();
|
|
1258
|
+
const facts = remoteFactsFor(ctx.adapters, ctx.choices);
|
|
1259
|
+
let forward: Awaited<ReturnType<typeof open>> = null;
|
|
1260
|
+
try {
|
|
1261
|
+
forward = await open({
|
|
1262
|
+
baseUrl: resolvePaths(opts, ctx).base,
|
|
1263
|
+
repoRoot: rootOf(ctx.adapters),
|
|
1264
|
+
container: facts.memoryContainer,
|
|
1265
|
+
sudoDocker: facts.sudo,
|
|
1266
|
+
log: (line) => ctx.adapters.log?.(line),
|
|
1267
|
+
});
|
|
1268
|
+
} catch {
|
|
1269
|
+
forward = null;
|
|
1270
|
+
}
|
|
1271
|
+
try {
|
|
1272
|
+
const report = await body();
|
|
1273
|
+
return forward?.started
|
|
1274
|
+
? {
|
|
1275
|
+
...report,
|
|
1276
|
+
checks: [
|
|
1277
|
+
{ name: "memory-forward", ok: true, detail: forward.detail },
|
|
1278
|
+
...report.checks,
|
|
1279
|
+
],
|
|
1280
|
+
}
|
|
1281
|
+
: report;
|
|
1282
|
+
} finally {
|
|
1283
|
+
forward?.stop();
|
|
1284
|
+
}
|
|
1285
|
+
};
|
|
1286
|
+
|
|
1287
|
+
return {
|
|
1288
|
+
...step,
|
|
1289
|
+
run: (ctx) => withForward(ctx, () => step.run(ctx)),
|
|
1290
|
+
verify: (ctx) => withForward(ctx, () => step.verify(ctx)),
|
|
1291
|
+
};
|
|
1219
1292
|
}
|
|
1220
1293
|
|
|
1221
1294
|
/**
|