@quickchatai/quickreview 1.2.0 → 1.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +116 -53
- package/bin/qcr.js +1 -1
- package/config.example.toml +25 -7
- 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/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 +1 -1
- package/dist/extensions/login-route.js.map +1 -1
- package/dist/extensions/onboarding.js +8 -7
- 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/extensions/vendor/openviking-pi/index.js +4 -0
- package/dist/extensions/vendor/openviking-pi/index.js.map +1 -1
- package/dist/extensions/vendor/openviking-pi/qcr-hooks.js +3 -1
- package/dist/extensions/vendor/openviking-pi/qcr-hooks.js.map +1 -1
- package/dist/extensions/vendor/openviking-pi/sync.js +81 -0
- package/dist/extensions/vendor/openviking-pi/sync.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/cli.js +14 -50
- package/dist/src/cli.js.map +1 -1
- package/dist/src/config.js +16 -0
- package/dist/src/config.js.map +1 -1
- package/dist/src/daemon.js +33 -4
- 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 +1 -0
- package/dist/src/debugroundpi.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/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/ledgercheck.js +39 -10
- package/dist/src/ledgercheck.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/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/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 +42 -12
- package/dist/src/npmverbs.js.map +1 -1
- package/dist/src/onboard/choices.js +6 -3
- package/dist/src/onboard/choices.js.map +1 -1
- package/dist/src/onboard/engine.js +33 -292
- package/dist/src/onboard/engine.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/configfill.js +18 -7
- package/dist/src/onboard/steps/configfill.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 +14 -2
- package/dist/src/onboard/steps/memory.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/toolnames.js +2 -2
- package/dist/src/onboard/toolnames.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/qcrbin.js +16 -11
- package/dist/src/qcrbin.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/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/session-command-supervisor.js +38 -9
- package/dist/src/session-command-supervisor.js.map +1 -1
- 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 +75 -9
- package/dist/src/state.js.map +1 -1
- package/dist/src/takeover.js +1 -0
- package/dist/src/takeover.js.map +1 -1
- package/dist/src/takeoverpi.js +2 -1
- package/dist/src/takeoverpi.js.map +1 -1
- package/dist/src/tui/alerts.js +4 -1
- package/dist/src/tui/alerts.js.map +1 -1
- package/dist/src/tui/board.js +2 -2
- package/dist/src/tui/board.js.map +1 -1
- package/dist/src/tui/companion.js +2 -1
- package/dist/src/tui/companion.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/index.js +9 -6
- package/dist/src/tui/index.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/remote.js +1 -1
- package/dist/src/tui/remote.js.map +1 -1
- package/dist/src/tui/route.js +2 -2
- package/dist/src/tui/route.js.map +1 -1
- package/dist/src/tui/setupstate.js +8 -5
- package/dist/src/tui/setupstate.js.map +1 -1
- package/dist/src/tui/sshtriage.js +1 -1
- package/dist/src/tui/sshtriage.js.map +1 -1
- package/dist/src/verdicts.js +12 -0
- package/dist/src/verdicts.js.map +1 -1
- package/dist/src/wire.js +9 -4
- package/dist/src/wire.js.map +1 -1
- package/dist/src/workerbreaker.js +32 -5
- package/dist/src/workerbreaker.js.map +1 -1
- package/dist/src/workerworld.js +4 -0
- package/dist/src/workerworld.js.map +1 -1
- package/docker/README.md +1 -1
- package/docker/entrypoint.sh +40 -6
- package/docker-compose.yml +16 -1
- package/extensions/cli/home.ts +14 -0
- package/extensions/cli/selfimprove.ts +18 -0
- package/extensions/home.ts +484 -0
- package/extensions/login-route.ts +1 -1
- package/extensions/onboarding.ts +8 -7
- package/extensions/selfimprove.ts +65 -0
- package/extensions/setup-scan.ts +79 -766
- package/extensions/setup.ts +14 -70
- package/extensions/vendor/openviking-pi/NOTICE +5 -0
- package/extensions/vendor/openviking-pi/README.md +39 -5
- package/extensions/vendor/openviking-pi/index.ts +4 -0
- package/extensions/vendor/openviking-pi/patches/0014-one-turn-sync-in-flight.patch +239 -0
- package/extensions/vendor/openviking-pi/qcr-hooks.ts +3 -1
- package/extensions/vendor/openviking-pi/sync.ts +80 -0
- package/extensions/vendor/openviking-pi/upstream.json +2 -1
- package/package.json +6 -4
- package/prompts/onboarding.md +8 -7
- package/schemas/review-result.v1.json +60 -0
- package/scripts/deploy.sh +8 -6
- 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/cli.ts +13 -54
- package/src/config.ts +38 -0
- package/src/daemon.ts +34 -4
- package/src/debuground.ts +3 -0
- package/src/debugroundpi.ts +1 -0
- package/src/engine/caserunner-sdk.ts +29 -314
- 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/ledgercheck.ts +45 -9
- package/src/live/publish.ts +4 -1
- package/src/livecli.ts +6 -1
- package/src/loginrefresh/handover.ts +138 -0
- package/src/loginrefresh/lock.ts +72 -3
- package/src/loginrefresh/refresher.ts +34 -5
- package/src/loginrefresh/supervisor.ts +25 -2
- package/src/npmverbs.ts +51 -11
- package/src/onboard/choices.ts +6 -3
- package/src/onboard/engine.ts +64 -434
- package/src/onboard/steps/bringup.ts +3 -2
- package/src/onboard/steps/configfill.ts +23 -7
- package/src/onboard/steps/loginshare.ts +258 -0
- package/src/onboard/steps/memory.ts +18 -2
- package/src/onboard/steps/repoconfig.ts +85 -0
- package/src/onboard/toolnames.ts +2 -2
- package/src/piagentdir.ts +12 -0
- package/src/pilogin.ts +32 -16
- package/src/qcrbin.ts +16 -11
- package/src/reviewargs.ts +119 -0
- package/src/reviewcli.ts +6 -167
- package/src/reviewdetach.ts +107 -13
- package/src/scancli.ts +75 -826
- package/src/selfimprove.ts +90 -0
- package/src/session-command-supervisor.ts +41 -8
- 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 +126 -11
- package/src/takeover.ts +5 -0
- package/src/takeoverpi.ts +3 -2
- package/src/tui/alerts.ts +9 -2
- package/src/tui/board.ts +2 -2
- package/src/tui/companion.ts +2 -1
- package/src/tui/deploytab.ts +2 -2
- package/src/tui/index.ts +9 -6
- package/src/tui/memory.ts +3 -15
- package/src/tui/memoryurl.ts +22 -0
- package/src/tui/remote.ts +1 -1
- package/src/tui/route.ts +2 -2
- package/src/tui/setupstate.ts +20 -16
- package/src/tui/sshtriage.ts +1 -1
- package/src/verdicts.ts +13 -0
- package/src/wire.ts +9 -4
- package/src/workerbreaker.ts +41 -5
- package/src/workerworld.ts +9 -1
- 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 -75
- 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 -103
- package/src/setup/ledger.ts +0 -324
- package/src/setup/stage2.ts +0 -926
- package/src/setup/textsniff.ts +0 -95
|
@@ -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
|
|
@@ -1009,7 +1025,7 @@ export function memoryStep(opts: MemoryStepOptions = {}): OnboardingStep {
|
|
|
1009
1025
|
const ready = await serverReady(ctx.adapters.fetch, base, prov);
|
|
1010
1026
|
if (!ready.ok) {
|
|
1011
1027
|
checks.push({
|
|
1012
|
-
name:
|
|
1028
|
+
name: MEMORY_SERVER_CHECK,
|
|
1013
1029
|
ok: false,
|
|
1014
1030
|
detail: ready.answering
|
|
1015
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.`
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// The repository the home's setup checklist picks, written into config.toml:
|
|
2
|
+
// the config fill's writer (configfill.ts) over the template or the
|
|
3
|
+
// install's config, never touching dry_run.
|
|
4
|
+
import { parse } from "smol-toml";
|
|
5
|
+
import { assertNoDryRunEdit, changedEdits, patchToml, tomlString } from "./configfill.js";
|
|
6
|
+
|
|
7
|
+
const message = (err: unknown): string => (err instanceof Error ? err.message : String(err));
|
|
8
|
+
|
|
9
|
+
/** The repository pick, as the config takes it. */
|
|
10
|
+
export interface RepoConfigChoice {
|
|
11
|
+
repo: string;
|
|
12
|
+
baseBranch: string | null;
|
|
13
|
+
timezone: string | null;
|
|
14
|
+
ownerName: string | null;
|
|
15
|
+
ownerLogin: string | null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** A config value as text, "" when absent or not a string. */
|
|
19
|
+
function valueIn(doc: Record<string, unknown> | null, section: string, key: string): string {
|
|
20
|
+
const table = doc?.[section];
|
|
21
|
+
if (typeof table !== "object" || table === null) return "";
|
|
22
|
+
const v = (table as Record<string, unknown>)[key];
|
|
23
|
+
return typeof v === "string" ? v.trim() : "";
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** The config text with the choice in it, from `existing` (null: none yet,
|
|
27
|
+
* so the template). What changed is named; a config that is not TOML is
|
|
28
|
+
* refused, since what it holds cannot be seen. */
|
|
29
|
+
export function renderRepoConfig(
|
|
30
|
+
existing: string | null,
|
|
31
|
+
template: string,
|
|
32
|
+
choice: RepoConfigChoice,
|
|
33
|
+
): { ok: true; text: string; wrote: string[] } | { ok: false; why: string } {
|
|
34
|
+
const fresh = existing === null;
|
|
35
|
+
const source = existing ?? template;
|
|
36
|
+
let doc: Record<string, unknown> | null = null;
|
|
37
|
+
try {
|
|
38
|
+
doc = parse(source);
|
|
39
|
+
} catch (err) {
|
|
40
|
+
return {
|
|
41
|
+
ok: false,
|
|
42
|
+
why: `${fresh ? "the shipped template" : "the config"} is not valid TOML (${message(err)}); nothing was written`,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
const fill = (section: string, key: string): boolean =>
|
|
46
|
+
fresh || valueIn(doc, section, key) === "";
|
|
47
|
+
const wanted = [
|
|
48
|
+
{ section: "github", key: "repo", value: tomlString(choice.repo) },
|
|
49
|
+
...(choice.baseBranch
|
|
50
|
+
? [{ section: "github", key: "base_branch", value: tomlString(choice.baseBranch) }]
|
|
51
|
+
: []),
|
|
52
|
+
...(choice.timezone && fill("ops", "timezone")
|
|
53
|
+
? [{ section: "ops", key: "timezone", value: tomlString(choice.timezone) }]
|
|
54
|
+
: []),
|
|
55
|
+
...(choice.ownerName && fill("owner", "name")
|
|
56
|
+
? [{ section: "owner", key: "name", value: tomlString(choice.ownerName) }]
|
|
57
|
+
: []),
|
|
58
|
+
...(choice.ownerLogin && fill("owner", "github_login")
|
|
59
|
+
? [{ section: "owner", key: "github_login", value: tomlString(choice.ownerLogin) }]
|
|
60
|
+
: []),
|
|
61
|
+
];
|
|
62
|
+
assertNoDryRunEdit(wanted);
|
|
63
|
+
const edits = changedEdits(source, wanted);
|
|
64
|
+
const text = edits.length > 0 ? patchToml(source, edits) : source;
|
|
65
|
+
let after: Record<string, unknown>;
|
|
66
|
+
try {
|
|
67
|
+
after = parse(text);
|
|
68
|
+
} catch (err) {
|
|
69
|
+
return {
|
|
70
|
+
ok: false,
|
|
71
|
+
why: `the patched config would not parse (${message(err)}); nothing was written`,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
if (valueIn(after, "github", "repo") !== choice.repo)
|
|
75
|
+
return {
|
|
76
|
+
ok: false,
|
|
77
|
+
why: "the patched config does not carry the repository; nothing was written",
|
|
78
|
+
};
|
|
79
|
+
if (fresh && after["dry_run"] !== true)
|
|
80
|
+
return {
|
|
81
|
+
ok: false,
|
|
82
|
+
why: "the shipped template no longer ships dry_run = true; nothing was written (invariant 6)",
|
|
83
|
+
};
|
|
84
|
+
return { ok: true, text, wrote: edits.map((e) => `[${e.section}] ${e.key}`) };
|
|
85
|
+
}
|
package/src/onboard/toolnames.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// The onboarding session's tool names: one source for the extension that
|
|
2
|
-
// registers them (extensions/onboarding.ts) and the
|
|
3
|
-
// (
|
|
2
|
+
// registers them (extensions/onboarding.ts) and the `--tools` allowlist that
|
|
3
|
+
// activates them (`onboardingToolAllowlist`).
|
|
4
4
|
//
|
|
5
5
|
// pi's `--tools <list>` is an allowlist over EVERY tool, extension tools
|
|
6
6
|
// included (pi 1.4.0: createAgentSession({ tools }) leaves an extension tool
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// pi's agent dir, the way pi itself finds it: a leaf, so the npm verbs
|
|
2
|
+
// (src/npmverbs.ts) and the home's login reader (src/home/adapters.ts) read
|
|
3
|
+
// one rule without importing each other.
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
|
|
7
|
+
/** pi's agent dir: `$PI_CODING_AGENT_DIR`, else `~/.pi/agent`, as pi reads it. */
|
|
8
|
+
export function piAgentDir(env: Readonly<Record<string, string | undefined>>): string {
|
|
9
|
+
const set = (env["PI_CODING_AGENT_DIR"] ?? "").trim();
|
|
10
|
+
if (set !== "") return set;
|
|
11
|
+
return join((env["HOME"] ?? "").trim() || homedir(), ".pi", "agent");
|
|
12
|
+
}
|
package/src/pilogin.ts
CHANGED
|
@@ -4,15 +4,24 @@
|
|
|
4
4
|
// (src/scanbox.ts) share it without one reaching the other.
|
|
5
5
|
|
|
6
6
|
/** pi itself, with `/login` (or `/login <provider>`) typed and sent for the
|
|
7
|
-
* user: pi runs in a fresh tmux session
|
|
8
|
-
* editor borders on screen
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
7
|
+
* user: pi runs in a fresh tmux session, and qcr reads pi's input box, the
|
|
8
|
+
* lines between the last two editor borders on screen, every 0.3 s (a second
|
|
9
|
+
* after a key, which counts as three polls toward the 30 s). Empty, it gets the keys and an Enter; still holding exactly
|
|
10
|
+
* the keys, one more Enter (an Enter sent while pi still starts is lost, and
|
|
11
|
+
* the first one may only accept the autocomplete). Anything else in the box is
|
|
12
|
+
* one of pi's menus (the provider list, a provider's own login methods), and
|
|
13
|
+
* no key goes in again. pi draws every selection in that box, so this holds
|
|
14
|
+
* whatever title pi or a provider gives the menu, where an Enter would pick
|
|
15
|
+
* its default: a browser login, opened again on every poll. pi starts with
|
|
16
|
+
* `--no-approve`: a login needs nothing from the folder qcr was run in, so a
|
|
17
|
+
* project folder neither stops it at pi's trust prompt nor gets trusted. A pi
|
|
18
|
+
* that has already exited ends the wait at once. After 30 s the terminal
|
|
19
|
+
* attaches regardless, so every login pi offers is there and quitting pi ends
|
|
20
|
+
* it; a failed attach (no terminal) ends the session instead of leaving pi
|
|
21
|
+
* running where nobody can reach it. `pi` is the pi to start: the
|
|
22
|
+
* companion image's CLI on its PATH by default; the release image's entrypoint
|
|
23
|
+
* passes its one pi, node_modules/.bin/pi (src/picli.ts `piCliIn`), and
|
|
24
|
+
* `qcr login` the pinned package's own bin (src/picli.ts `piCliPath`). */
|
|
16
25
|
export function loginInPi(provider?: string, pi: string = "pi"): string[] {
|
|
17
26
|
if (provider !== undefined && !/^[A-Za-z0-9._-]+$/.test(provider))
|
|
18
27
|
throw new Error(
|
|
@@ -22,16 +31,23 @@ export function loginInPi(provider?: string, pi: string = "pi"): string[] {
|
|
|
22
31
|
if (!/^[A-Za-z0-9._/@+-]+$/.test(pi))
|
|
23
32
|
throw new Error(`scan login: pi ${JSON.stringify(pi)} is not a plain path`);
|
|
24
33
|
const keys = provider ? `/login ${provider}` : "/login";
|
|
34
|
+
// The input box, its side padding (pi's editorPaddingX) trimmed and blank
|
|
35
|
+
// lines dropped; awk fails while fewer than two borders are drawn (pi still
|
|
36
|
+
// starting), and the loop then only waits.
|
|
37
|
+
const box =
|
|
38
|
+
`tmux capture-pane -p -t "$s" 2>/dev/null | awk '/──────────/ { b[++n] = NR } { l[NR] = $0 } ` +
|
|
39
|
+
`END { if (n < 2) exit 1; for (i = b[n - 1] + 1; i < b[n]; i++) ` +
|
|
40
|
+
`{ sub(/^ +/, "", l[i]); sub(/ +$/, "", l[i]); if (l[i] != "") print l[i] } }'`;
|
|
25
41
|
return [
|
|
26
42
|
"sh",
|
|
27
43
|
"-c",
|
|
28
|
-
`s=qcr-login-$$; tmux new-session -d -s "$s" ${pi} || exit 1; i=0; ` +
|
|
29
|
-
`while [ $i -lt 100 ]; do
|
|
30
|
-
`
|
|
31
|
-
`if [ "$
|
|
32
|
-
`
|
|
33
|
-
`else
|
|
44
|
+
`s=qcr-login-$$; tmux new-session -d -s "$s" ${pi} --no-approve || exit 1; i=0; ` +
|
|
45
|
+
`while [ $i -lt 100 ]; do tmux has-session -t "$s" 2>/dev/null || break; ` +
|
|
46
|
+
`if e=$(${box}); then ` +
|
|
47
|
+
`if [ -z "$e" ]; then tmux send-keys -t "$s" -l '${keys}'; tmux send-keys -t "$s" Enter; sleep 1; i=$((i+3)); ` +
|
|
48
|
+
`elif [ "$e" = '${keys}' ]; then tmux send-keys -t "$s" Enter; sleep 1; i=$((i+3)); ` +
|
|
49
|
+
`else break; fi; fi; ` +
|
|
34
50
|
`sleep 0.3; i=$((i+1)); done; ` +
|
|
35
|
-
`
|
|
51
|
+
`tmux attach -t "$s" || { tmux kill-session -t "$s" 2>/dev/null; exit 1; }`,
|
|
36
52
|
];
|
|
37
53
|
}
|
package/src/qcrbin.ts
CHANGED
|
@@ -3,11 +3,12 @@
|
|
|
3
3
|
// verbs a person runs from an npm install are routed here first and load only
|
|
4
4
|
// what they use, so they work where that native driver cannot load:
|
|
5
5
|
//
|
|
6
|
-
// qcr the
|
|
6
|
+
// qcr the home: the app's own pi (src/home/launch.ts)
|
|
7
|
+
// qcr tui the admin TUI (src/cli.ts)
|
|
7
8
|
// qcr version the install's identity (src/version.ts)
|
|
8
9
|
// qcr review … a daemonless review (src/reviewcli.ts)
|
|
9
10
|
// qcr login [p] pi's own /login in the pinned pi (src/npmverbs.ts)
|
|
10
|
-
// qcr pi [args] the pinned pi with the qcr plugin
|
|
11
|
+
// qcr pi [args] the pinned pi with the qcr plugin and /selfimprove
|
|
11
12
|
// qcr update the installing package manager updates qcr
|
|
12
13
|
//
|
|
13
14
|
// Every other verb goes to src/cli.ts `main` unchanged. The image's
|
|
@@ -22,10 +23,11 @@ import { MissingConfigError } from "./missingconfig.js";
|
|
|
22
23
|
export const BIN_HELP = [
|
|
23
24
|
"qcr — QuickReview",
|
|
24
25
|
"",
|
|
25
|
-
" qcr open the
|
|
26
|
+
" qcr open the home: pi with the plugin and your pull requests",
|
|
27
|
+
" qcr tui open the admin TUI",
|
|
26
28
|
" qcr review [target] review code here, with no daemon (`qcr review --help`)",
|
|
27
29
|
" qcr login [provider] log in with pi's own /login, for the review sessions",
|
|
28
|
-
" qcr pi [args] pi with the qcr plugin (/review, /scan, /
|
|
30
|
+
" qcr pi [args] pi with the qcr plugin (/qcr-review, /qcr-scan, /qcr-setup) and /selfimprove",
|
|
29
31
|
" qcr update update qcr with the package manager that installed it",
|
|
30
32
|
" qcr version [--json] this install's version, schemas and pi pin",
|
|
31
33
|
"",
|
|
@@ -33,19 +35,19 @@ export const BIN_HELP = [
|
|
|
33
35
|
].join("\n");
|
|
34
36
|
|
|
35
37
|
/** The verbs that never print the update notice: the ones that hand the
|
|
36
|
-
* terminal over (the TUI, pi) or are about versions themselves. */
|
|
37
|
-
const QUIET_VERBS = new Set(["tui", "pi", "update", "version", "login"]);
|
|
38
|
+
* terminal over (the home, the TUI, pi) or are about versions themselves. */
|
|
39
|
+
const QUIET_VERBS = new Set(["home", "tui", "pi", "update", "version", "login"]);
|
|
38
40
|
|
|
39
41
|
/** What `runBin` would do with `argv`, before doing it. */
|
|
40
42
|
export type BinRoute =
|
|
41
|
-
| { route: "
|
|
43
|
+
| { route: "home" }
|
|
42
44
|
| { route: "help" }
|
|
43
45
|
| { route: "pi"; args: string[] }
|
|
44
46
|
| { route: "version" | "update" | "login" | "review"; args: string[]; json: boolean }
|
|
45
47
|
| { route: "app"; argv: string[] };
|
|
46
48
|
|
|
47
49
|
export function binRoute(argv: readonly string[]): BinRoute {
|
|
48
|
-
if (argv.length === 0) return { route: "
|
|
50
|
+
if (argv.length === 0 || (argv.length === 1 && argv[0] === "home")) return { route: "home" };
|
|
49
51
|
// pi's arguments are pi's, `--json` included
|
|
50
52
|
if (argv[0] === "pi") return { route: "pi", args: argv.slice(1) };
|
|
51
53
|
// `qcr message` carries free text: src/cli.ts reads its argv as it is
|
|
@@ -115,8 +117,11 @@ async function maybeNotice(json: boolean): Promise<void> {
|
|
|
115
117
|
|
|
116
118
|
async function route(r: BinRoute): Promise<void> {
|
|
117
119
|
switch (r.route) {
|
|
118
|
-
case "
|
|
119
|
-
|
|
120
|
+
case "home": {
|
|
121
|
+
const { homeRunDeps, runHome } = await import("./home/launch.js");
|
|
122
|
+
process.exitCode = runHome(homeRunDeps(await packageRoot()));
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
120
125
|
case "help":
|
|
121
126
|
console.log(BIN_HELP);
|
|
122
127
|
try {
|
|
@@ -181,6 +186,6 @@ export async function runBin(argv: readonly string[]): Promise<void> {
|
|
|
181
186
|
return;
|
|
182
187
|
}
|
|
183
188
|
const verb = r.route === "app" ? r.argv.find((a) => a !== "--json") : r.route;
|
|
184
|
-
if (r.route !== "
|
|
189
|
+
if (r.route !== "help" && !QUIET_VERBS.has(verb ?? ""))
|
|
185
190
|
await maybeNotice("json" in r ? r.json : r.route === "app" && r.argv.includes("--json"));
|
|
186
191
|
}
|