@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,194 @@
|
|
|
1
|
+
// The home's spend line: today's and this month's review cost, summed from
|
|
2
|
+
// two sources (src/home/ports.ts `SpendPort`):
|
|
3
|
+
//
|
|
4
|
+
// the home the app's detached review runs (`<state home>/review/
|
|
5
|
+
// detached`, the registry the review button starts in): each
|
|
6
|
+
// run that has ended and whose result says what it cost, in
|
|
7
|
+
// the day and month it ended
|
|
8
|
+
// auto-review the daemon's recorded costs: its status snapshot's
|
|
9
|
+
// `today_spend_usd` and `month_spend_usd`, read from the
|
|
10
|
+
// daemon's own state on this machine's compose stack
|
|
11
|
+
//
|
|
12
|
+
// Today and this month are UTC days and months, the daemon's own (its
|
|
13
|
+
// snapshot, `qcr spend`): the two halves are summed over one calendar.
|
|
14
|
+
//
|
|
15
|
+
// When either source cannot be read, the total is unknown, with why (invariant
|
|
16
|
+
// 9): never a zero, and never the half that could be read passed off as the
|
|
17
|
+
// whole. A source that cannot hold a cost is a known zero, as a missing auth
|
|
18
|
+
// file is no login: a registry with no runs yet, a machine with no Docker, or
|
|
19
|
+
// a stack that was never brought up (no state volume) recorded nothing.
|
|
20
|
+
//
|
|
21
|
+
// A run still going is not counted until its result says what it cost; a run
|
|
22
|
+
// whose result names no cost has nothing to add.
|
|
23
|
+
import { listRunViews } from "../reviewdetach.js";
|
|
24
|
+
import type { ProcessRunner } from "../onboard/engine.js";
|
|
25
|
+
import { daemonSnapshot } from "./daemonstatus.js";
|
|
26
|
+
import { known, unknown, type Known, type SpendPort, type SpendTotals } from "./ports.js";
|
|
27
|
+
import { parseRunStatus } from "./reviews.js";
|
|
28
|
+
|
|
29
|
+
/** One cost, and when it was spent (ms since the epoch). */
|
|
30
|
+
export interface Cost {
|
|
31
|
+
at: number;
|
|
32
|
+
usd: number;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** What auto-review recorded for the UTC day and month of the reading. */
|
|
36
|
+
export interface AutoSpend {
|
|
37
|
+
today: number;
|
|
38
|
+
month: number;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface SpendSources {
|
|
42
|
+
/** The home's finished runs' costs. */
|
|
43
|
+
home(): Promise<Known<readonly Cost[]>>;
|
|
44
|
+
/** Auto-review's recorded costs for the day and month of `now`. */
|
|
45
|
+
auto(now: number): Promise<Known<AutoSpend>>;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const message = (err: unknown): string => (err instanceof Error ? err.message : String(err));
|
|
49
|
+
|
|
50
|
+
/** The UTC day (`YYYY-MM-DD`) and month (`YYYY-MM`) of `ms`. */
|
|
51
|
+
export function utcPeriod(ms: number): { day: string; month: string } {
|
|
52
|
+
const iso = new Date(ms).toISOString();
|
|
53
|
+
return { day: iso.slice(0, 10), month: iso.slice(0, 7) };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function safe<T>(read: () => Promise<Known<T>>): Promise<Known<T>> {
|
|
57
|
+
try {
|
|
58
|
+
return await read();
|
|
59
|
+
} catch (err) {
|
|
60
|
+
return unknown(message(err));
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** The spend port over its two sources: both read, or unknown. */
|
|
65
|
+
export function spendPort(src: SpendSources, now: () => number): SpendPort {
|
|
66
|
+
return {
|
|
67
|
+
totals: async (): Promise<Known<SpendTotals>> => {
|
|
68
|
+
const at = now();
|
|
69
|
+
const [home, auto] = await Promise.all([safe(() => src.home()), safe(() => src.auto(at))]);
|
|
70
|
+
if (home.kind === "unknown") return unknown(`the home's reviews: ${home.why}`);
|
|
71
|
+
if (auto.kind === "unknown") return unknown(`auto-review's: ${auto.why}`);
|
|
72
|
+
const { day, month } = utcPeriod(at);
|
|
73
|
+
let today = auto.value.today;
|
|
74
|
+
let thisMonth = auto.value.month;
|
|
75
|
+
for (const c of home.value) {
|
|
76
|
+
if (!Number.isFinite(c.at) || !Number.isFinite(c.usd)) continue;
|
|
77
|
+
const p = utcPeriod(c.at);
|
|
78
|
+
if (p.month !== month) continue;
|
|
79
|
+
thisMonth += c.usd;
|
|
80
|
+
if (p.day === day) today += c.usd;
|
|
81
|
+
}
|
|
82
|
+
return known({ today, month: thisMonth });
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// ---- the home's half: the detached registry -------------------------------
|
|
88
|
+
|
|
89
|
+
/** Is the process with this pid alive? */
|
|
90
|
+
const alive = (pid: number): boolean => {
|
|
91
|
+
try {
|
|
92
|
+
process.kill(pid, 0);
|
|
93
|
+
return true;
|
|
94
|
+
} catch {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
/** The costs of every run in the registry at `registry` that has ended with
|
|
100
|
+
* a cost, at the time it ended. */
|
|
101
|
+
export function registryCosts(
|
|
102
|
+
registry: string,
|
|
103
|
+
o: { now: () => number; alive?: (pid: number) => boolean } = { now: Date.now },
|
|
104
|
+
): SpendSources["home"] {
|
|
105
|
+
return async () => {
|
|
106
|
+
let views;
|
|
107
|
+
try {
|
|
108
|
+
views = listRunViews(registry, { alive: o.alive ?? alive, now: o.now });
|
|
109
|
+
} catch (err) {
|
|
110
|
+
return unknown(`can't read the review runs in ${registry}: ${message(err)}`);
|
|
111
|
+
}
|
|
112
|
+
const out: Cost[] = [];
|
|
113
|
+
for (const v of views) {
|
|
114
|
+
const s = parseRunStatus(v.view, v.argv);
|
|
115
|
+
const usd = s?.result?.costUsd;
|
|
116
|
+
if (s === null || s.state === "running" || usd === null || usd === undefined) continue;
|
|
117
|
+
out.push({ at: s.endedAt ?? s.startedAt, usd });
|
|
118
|
+
}
|
|
119
|
+
return known(out);
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// ---- auto-review's half: the daemon's status snapshot ----------------------
|
|
124
|
+
|
|
125
|
+
type Obj = Record<string, unknown>;
|
|
126
|
+
const isObj = (v: unknown): v is Obj => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
127
|
+
const usd = (v: unknown): number | null => (typeof v === "number" && Number.isFinite(v) ? v : null);
|
|
128
|
+
|
|
129
|
+
/** What a status snapshot's text says auto-review spent, for the UTC day and
|
|
130
|
+
* month of `now`. Pure. A snapshot written on an earlier day is the daemon's
|
|
131
|
+
* last word: it has recorded nothing since (it writes one every heartbeat
|
|
132
|
+
* while it runs), so the days after it add nothing. */
|
|
133
|
+
export function snapshotSpend(text: string, now: number): Known<AutoSpend> {
|
|
134
|
+
let s: unknown;
|
|
135
|
+
try {
|
|
136
|
+
s = JSON.parse(text);
|
|
137
|
+
} catch {
|
|
138
|
+
return unknown("its status.json is not readable JSON");
|
|
139
|
+
}
|
|
140
|
+
if (!isObj(s)) return unknown("its status.json is not an object");
|
|
141
|
+
const written = typeof s["written_at"] === "string" ? Date.parse(s["written_at"]) : NaN;
|
|
142
|
+
if (!Number.isFinite(written)) return unknown("its status has no time it was written");
|
|
143
|
+
const today = usd(s["today_spend_usd"]);
|
|
144
|
+
const month = usd(s["month_spend_usd"]);
|
|
145
|
+
if (month === null) return unknown("its status has no month's spend (an older daemon)");
|
|
146
|
+
if (today === null) return unknown("its status has no day's spend");
|
|
147
|
+
const at = utcPeriod(written);
|
|
148
|
+
const want = utcPeriod(now);
|
|
149
|
+
if (at.month !== want.month) return known({ today: 0, month: 0 });
|
|
150
|
+
return known({ today: at.day === want.day ? today : 0, month });
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export interface ComposeSpendOptions {
|
|
154
|
+
/** The app's root: the compose project directory. */
|
|
155
|
+
root: string;
|
|
156
|
+
runner: ProcessRunner;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** Auto-review's recorded costs on this machine's compose stack: read from
|
|
160
|
+
* the daemon's own status.json while it runs (src/home/daemonstatus.ts). A
|
|
161
|
+
* daemon that is off keeps its costs in its state volume, where the home
|
|
162
|
+
* cannot read them: unknown. */
|
|
163
|
+
export function composeAutoSpend(o: ComposeSpendOptions): SpendSources["auto"] {
|
|
164
|
+
return async (now) => {
|
|
165
|
+
const s = await daemonSnapshot(o.runner, o.root);
|
|
166
|
+
if (s.kind === "text") return snapshotSpend(s.text, now);
|
|
167
|
+
if (s.kind === "none") return known({ today: 0, month: 0 });
|
|
168
|
+
if (s.kind === "off")
|
|
169
|
+
return unknown("auto-review is off, and its costs are read from it while it runs");
|
|
170
|
+
return unknown(s.why);
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** The spend port as the home's env shim builds it: the install's registry
|
|
175
|
+
* in the state home, and the compose stack at `root` (null: no stack, so
|
|
176
|
+
* auto-review recorded nothing here). */
|
|
177
|
+
export function realSpendPort(o: {
|
|
178
|
+
registry: string;
|
|
179
|
+
root: string | null;
|
|
180
|
+
runner: ProcessRunner;
|
|
181
|
+
now?: () => number;
|
|
182
|
+
}): SpendPort {
|
|
183
|
+
const now = o.now ?? Date.now;
|
|
184
|
+
return spendPort(
|
|
185
|
+
{
|
|
186
|
+
home: registryCosts(o.registry, { now }),
|
|
187
|
+
auto:
|
|
188
|
+
o.root === null
|
|
189
|
+
? async () => known({ today: 0, month: 0 })
|
|
190
|
+
: composeAutoSpend({ root: o.root, runner: o.runner }),
|
|
191
|
+
},
|
|
192
|
+
now,
|
|
193
|
+
);
|
|
194
|
+
}
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
// The home's two switches, as the person meets them: the questions, in
|
|
2
|
+
// order, and what each answer does. A key and the `home_auto_review` /
|
|
3
|
+
// `home_memory` tools both start them (extensions/home.ts), and every
|
|
4
|
+
// question is asked of the person in the home: a tool can start a switch but
|
|
5
|
+
// can never answer for them, so the one confirm is always theirs.
|
|
6
|
+
//
|
|
7
|
+
// Auto-review on: machine checks → where it runs → the memory offer (when
|
|
8
|
+
// memory is off) → one confirm → the steps
|
|
9
|
+
// (src/home/switchon.ts): it comes up in dry-run
|
|
10
|
+
// Go live: the proof round (one real pull request reviewed in
|
|
11
|
+
// dry-run, its review shown) → one more confirm → the
|
|
12
|
+
// go-live step. From switch-off's question while in
|
|
13
|
+
// dry-run, or Advanced's "Dry-run or live"
|
|
14
|
+
// Auto-review off: a warning naming the running round → stop at once, data
|
|
15
|
+
// kept, memory untouched
|
|
16
|
+
// Memory: its RAM cost → on or off, auto-review untouched
|
|
17
|
+
import type {
|
|
18
|
+
AutoReviewControl,
|
|
19
|
+
AutoReviewStatus,
|
|
20
|
+
HomeAdapters,
|
|
21
|
+
Known,
|
|
22
|
+
MemoryControl,
|
|
23
|
+
MemoryState,
|
|
24
|
+
Slot,
|
|
25
|
+
} from "./ports.js";
|
|
26
|
+
import { known } from "./ports.js";
|
|
27
|
+
import { runningWarning } from "./autoreview.js";
|
|
28
|
+
|
|
29
|
+
/** How a switch asks the person: pi's own select. */
|
|
30
|
+
export interface Ask {
|
|
31
|
+
select(title: string, options: string[]): Promise<string | undefined>;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** The answers the person picks from, each its own words. */
|
|
35
|
+
export const CHOICE = {
|
|
36
|
+
cancel: "Cancel",
|
|
37
|
+
turnOn: "Turn on auto-review",
|
|
38
|
+
here: "This machine",
|
|
39
|
+
elsewhere: "Another server, over ssh (advanced)",
|
|
40
|
+
notNow: "Not now",
|
|
41
|
+
memoryOn: "Turn on memory",
|
|
42
|
+
keepOn: "Keep it on",
|
|
43
|
+
switchOff: "Switch it off",
|
|
44
|
+
goLive: "Go live",
|
|
45
|
+
proofRound: "Review it now, in dry-run",
|
|
46
|
+
} as const;
|
|
47
|
+
|
|
48
|
+
/** What the switches need of the home's model. */
|
|
49
|
+
export interface SwitchHost {
|
|
50
|
+
autoReview(): Slot<AutoReviewStatus>;
|
|
51
|
+
memory(): Slot<MemoryState>;
|
|
52
|
+
/** The trigger label, when known. */
|
|
53
|
+
trigger(): string | null;
|
|
54
|
+
repo(): string | null;
|
|
55
|
+
/** The selected pull request: the proof round's, when one is needed. */
|
|
56
|
+
selected(): number | null;
|
|
57
|
+
say(text: string, kind?: "info" | "ok" | "warn"): void;
|
|
58
|
+
/** Show this in the slot until released, whatever a refresh reads. */
|
|
59
|
+
hold(slot: "autoReview", v: Known<AutoReviewStatus>): void;
|
|
60
|
+
hold(slot: "memory", v: Known<MemoryState>): void;
|
|
61
|
+
release(slot: "autoReview" | "memory"): void;
|
|
62
|
+
refresh(): Promise<string>;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const NEEDS_PERSON =
|
|
66
|
+
"this switch asks the person in the home, and no one can answer here; nothing changed";
|
|
67
|
+
|
|
68
|
+
/** What auto-review picks up: the trigger label (unknown when it can't be
|
|
69
|
+
* read) or a review request. */
|
|
70
|
+
export function pickUpPhrase(trigger: string | null): string {
|
|
71
|
+
return trigger === null
|
|
72
|
+
? "pull requests with the trigger label (which one is unknown) or a review request"
|
|
73
|
+
: `pull requests labelled "${trigger}" or with a review request`;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** The same, as a sentence. */
|
|
77
|
+
export function pickUpLine(trigger: string | null): string {
|
|
78
|
+
return `It picks up ${pickUpPhrase(trigger)}.`;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Is auto-review, as last read, something to switch off? */
|
|
82
|
+
function isOn(s: Slot<AutoReviewStatus>): boolean {
|
|
83
|
+
return s.kind === "known" && s.value.state !== "off";
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export async function switchAutoReview(
|
|
87
|
+
host: SwitchHost,
|
|
88
|
+
adapters: HomeAdapters,
|
|
89
|
+
ask: Ask | undefined,
|
|
90
|
+
): Promise<string> {
|
|
91
|
+
const control = adapters.autoReview.control;
|
|
92
|
+
if (!control) return "auto-review can't be switched from this home";
|
|
93
|
+
if (!ask) return NEEDS_PERSON;
|
|
94
|
+
return isOn(host.autoReview())
|
|
95
|
+
? switchOff(host, control, ask)
|
|
96
|
+
: switchOn(host, adapters, control, ask);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async function switchOn(
|
|
100
|
+
host: SwitchHost,
|
|
101
|
+
adapters: HomeAdapters,
|
|
102
|
+
control: AutoReviewControl,
|
|
103
|
+
ask: Ask,
|
|
104
|
+
): Promise<string> {
|
|
105
|
+
// 1. the machine checks, before anything is asked
|
|
106
|
+
host.say("Checking this machine: Docker, disk, ports…");
|
|
107
|
+
const checks = await control.machineChecks();
|
|
108
|
+
if (!checks.ok) {
|
|
109
|
+
host.say(`Auto-review can't run on this machine yet: ${checks.detail}`, "warn");
|
|
110
|
+
return `machine checks failed: ${checks.detail}`;
|
|
111
|
+
}
|
|
112
|
+
// 2. where it runs
|
|
113
|
+
const where = await ask.select("Where should auto-review run?", [
|
|
114
|
+
`${CHOICE.here} — ${checks.detail}`,
|
|
115
|
+
CHOICE.elsewhere,
|
|
116
|
+
]);
|
|
117
|
+
if (where === undefined) return cancelled(host);
|
|
118
|
+
if (where === CHOICE.elsewhere) {
|
|
119
|
+
host.say(
|
|
120
|
+
'Auto-review on another server is set up in the advanced steps: Advanced (alt+x), "Auto-review on another server, over ssh". Nothing changed here.',
|
|
121
|
+
);
|
|
122
|
+
return "another server: the advanced steps set it up";
|
|
123
|
+
}
|
|
124
|
+
// 3. the memory offer, when memory is off
|
|
125
|
+
let memory: boolean | null = null;
|
|
126
|
+
const mem = host.memory();
|
|
127
|
+
const memoryControl = adapters.memory.control;
|
|
128
|
+
if (memoryControl && mem.kind === "known" && mem.value === "off") {
|
|
129
|
+
const m = await ask.select(
|
|
130
|
+
`Turn on memory too? Reviews learn this repository. It uses ${memoryControl.ramCost()}.`,
|
|
131
|
+
[CHOICE.notNow, CHOICE.memoryOn],
|
|
132
|
+
);
|
|
133
|
+
if (m === undefined) return cancelled(host);
|
|
134
|
+
memory = m === CHOICE.memoryOn;
|
|
135
|
+
}
|
|
136
|
+
// 4. the one confirm: it comes up in dry-run unless the config is live
|
|
137
|
+
const dry = await control.dryRun();
|
|
138
|
+
const repo = host.repo() ?? "your repository";
|
|
139
|
+
const picks = pickUpPhrase(host.trigger());
|
|
140
|
+
const posture =
|
|
141
|
+
dry.kind === "unknown"
|
|
142
|
+
? `Auto-review reviews ${picks} in ${repo}. Whether it posts to GitHub is unknown: ${dry.why}.`
|
|
143
|
+
: !dry.value
|
|
144
|
+
? `Auto-review reviews ${picks} in ${repo}, and posts each review to GitHub.`
|
|
145
|
+
: `Auto-review starts in dry-run: it reviews ${picks} in ${repo}, and records what it would post instead of posting it. Going live is its own step later: one review you read, then one more confirm.`;
|
|
146
|
+
const go = await ask.select(
|
|
147
|
+
`${posture} It keeps running, across reboots, until you switch it off.`,
|
|
148
|
+
[CHOICE.cancel, CHOICE.turnOn],
|
|
149
|
+
);
|
|
150
|
+
if (go !== CHOICE.turnOn) return cancelled(host);
|
|
151
|
+
// the steps
|
|
152
|
+
host.hold("autoReview", known({ state: "starting" }));
|
|
153
|
+
if (memory === true) host.hold("memory", known("starting"));
|
|
154
|
+
host.say("Starting auto-review…");
|
|
155
|
+
let outcome;
|
|
156
|
+
try {
|
|
157
|
+
outcome = await control.switchOn({ memory, confirmed: true }, (l) => host.say(l));
|
|
158
|
+
} catch (err) {
|
|
159
|
+
outcome = { ok: false, live: null, detail: String(err), steps: [] as string[] };
|
|
160
|
+
} finally {
|
|
161
|
+
host.release("autoReview");
|
|
162
|
+
host.release("memory");
|
|
163
|
+
}
|
|
164
|
+
await host.refresh();
|
|
165
|
+
if (outcome.ok) host.say(`${outcome.detail} ${pickUpLine(host.trigger())}`, "ok");
|
|
166
|
+
else host.say(`Auto-review did not start: ${outcome.detail}`, "warn");
|
|
167
|
+
return outcome.ok
|
|
168
|
+
? `${outcome.detail}\n${outcome.steps.join("\n")}`
|
|
169
|
+
: `auto-review did not start: ${outcome.detail}\n${outcome.steps.join("\n")}`;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
async function switchOff(host: SwitchHost, control: AutoReviewControl, ask: Ask): Promise<string> {
|
|
173
|
+
const a = host.autoReview();
|
|
174
|
+
const dry = a.kind === "known" && a.value.state === "on-dry";
|
|
175
|
+
const warning = runningWarning(await control.running());
|
|
176
|
+
const answer = await ask.select(
|
|
177
|
+
`Switch auto-review off? ${warning} All its data is kept, so switching it on again is quick.`,
|
|
178
|
+
[CHOICE.keepOn, ...(dry ? [CHOICE.goLive] : []), CHOICE.switchOff],
|
|
179
|
+
);
|
|
180
|
+
if (answer === CHOICE.goLive) return goLive(host, control, ask);
|
|
181
|
+
if (answer !== CHOICE.switchOff) return cancelled(host);
|
|
182
|
+
const r = await control.switchOff();
|
|
183
|
+
await host.refresh();
|
|
184
|
+
if (!r.ok) {
|
|
185
|
+
host.say(`Couldn't switch auto-review off: ${r.detail}`, "warn");
|
|
186
|
+
return `couldn't switch auto-review off: ${r.detail}`;
|
|
187
|
+
}
|
|
188
|
+
host.say("Auto-review is off. Its data is kept, and memory is as it was.", "ok");
|
|
189
|
+
return `auto-review is off (${r.detail}). ${warning}`;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/** What going live needs of the home. */
|
|
193
|
+
export interface GoLiveHost {
|
|
194
|
+
say(text: string, kind?: "info" | "ok" | "warn"): void;
|
|
195
|
+
refresh(): Promise<string>;
|
|
196
|
+
/** The selected pull request, for a proof round. */
|
|
197
|
+
selected(): number | null;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Go live, from dry-run: a proof round first, then one more confirm, then
|
|
202
|
+
* the go-live step (which re-checks the proof against the daemon's own
|
|
203
|
+
* state). The proof round is the wizard's: the daemon reviews one real pull
|
|
204
|
+
* request now, in dry-run, and the person reads what it would have posted.
|
|
205
|
+
* A proof already recorded stands; the go-live step judges it.
|
|
206
|
+
*/
|
|
207
|
+
export async function goLive(
|
|
208
|
+
host: GoLiveHost,
|
|
209
|
+
control: AutoReviewControl,
|
|
210
|
+
ask: Ask,
|
|
211
|
+
): Promise<string> {
|
|
212
|
+
let proven = (await control.proof())?.pr ?? null;
|
|
213
|
+
if (proven === null) {
|
|
214
|
+
const pr = host.selected();
|
|
215
|
+
if (pr === null) {
|
|
216
|
+
const said =
|
|
217
|
+
"Going live needs a proof round first: one pull request reviewed in dry-run, which you read before anything posts. Select a pull request, then choose Go live again.";
|
|
218
|
+
host.say(said, "warn");
|
|
219
|
+
return "not live: no proof round yet, and no pull request is selected";
|
|
220
|
+
}
|
|
221
|
+
const run = await ask.select(
|
|
222
|
+
`Going live needs a proof round first: auto-review reviews #${pr} now, in dry-run, and shows you what it would post. Nothing is posted.`,
|
|
223
|
+
[CHOICE.cancel, CHOICE.proofRound],
|
|
224
|
+
);
|
|
225
|
+
if (run !== CHOICE.proofRound) return cancelled(host);
|
|
226
|
+
host.say(`Reviewing #${pr} in dry-run: the proof round…`);
|
|
227
|
+
let r;
|
|
228
|
+
try {
|
|
229
|
+
r = await control.proofRound(pr, (l) => host.say(l));
|
|
230
|
+
} catch (err) {
|
|
231
|
+
r = { ok: false as const, detail: String(err) };
|
|
232
|
+
}
|
|
233
|
+
if (!r.ok) {
|
|
234
|
+
host.say(`The proof round did not finish: ${r.detail}`, "warn");
|
|
235
|
+
return `not live: the proof round did not finish: ${r.detail}`;
|
|
236
|
+
}
|
|
237
|
+
host.say(r.detail, "ok");
|
|
238
|
+
proven = pr;
|
|
239
|
+
}
|
|
240
|
+
const go = await ask.select(
|
|
241
|
+
`Go live? You have read the review of #${proven}. Auto-review then posts each review to GitHub under your name.`,
|
|
242
|
+
[CHOICE.cancel, CHOICE.goLive],
|
|
243
|
+
);
|
|
244
|
+
if (go !== CHOICE.goLive) return cancelled(host);
|
|
245
|
+
const r = await control.goLive({ confirmed: true });
|
|
246
|
+
await host.refresh();
|
|
247
|
+
host.say(r.ok ? r.detail : `Not live: ${r.detail}`, r.ok ? "ok" : "warn");
|
|
248
|
+
return r.ok ? r.detail : `not live: ${r.detail}`;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function cancelled(host: Pick<SwitchHost, "say">): string {
|
|
252
|
+
host.say("Nothing changed.");
|
|
253
|
+
return "cancelled: nothing changed";
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
export async function switchMemory(
|
|
257
|
+
host: SwitchHost,
|
|
258
|
+
adapters: HomeAdapters,
|
|
259
|
+
ask: Ask | undefined,
|
|
260
|
+
): Promise<string> {
|
|
261
|
+
const control: MemoryControl | undefined = adapters.memory.control;
|
|
262
|
+
if (!control) return "memory can't be switched from this home";
|
|
263
|
+
if (!ask) return NEEDS_PERSON;
|
|
264
|
+
const s = host.memory();
|
|
265
|
+
if (s.kind === "known" && s.value !== "off") {
|
|
266
|
+
const a = await ask.select("Switch memory off? What it learned is kept.", [
|
|
267
|
+
CHOICE.keepOn,
|
|
268
|
+
CHOICE.switchOff,
|
|
269
|
+
]);
|
|
270
|
+
if (a !== CHOICE.switchOff) return cancelled(host);
|
|
271
|
+
const r = await control.switchOff();
|
|
272
|
+
await host.refresh();
|
|
273
|
+
host.say(r.ok ? r.detail : `Couldn't switch memory off: ${r.detail}`, r.ok ? "ok" : "warn");
|
|
274
|
+
return r.ok ? "memory is off" : `couldn't switch memory off: ${r.detail}`;
|
|
275
|
+
}
|
|
276
|
+
const a = await ask.select(
|
|
277
|
+
`Turn on memory? Reviews remember this repository's conventions and past findings. It uses ${control.ramCost()}.`,
|
|
278
|
+
[CHOICE.notNow, CHOICE.memoryOn],
|
|
279
|
+
);
|
|
280
|
+
if (a !== CHOICE.memoryOn) return cancelled(host);
|
|
281
|
+
host.hold("memory", known("starting"));
|
|
282
|
+
let r;
|
|
283
|
+
try {
|
|
284
|
+
r = await control.switchOn((l) => host.say(l));
|
|
285
|
+
} catch (err) {
|
|
286
|
+
r = { ok: false as const, detail: String(err) };
|
|
287
|
+
} finally {
|
|
288
|
+
host.release("memory");
|
|
289
|
+
}
|
|
290
|
+
await host.refresh();
|
|
291
|
+
host.say(r.ok ? r.detail : `Memory did not start: ${r.detail}`, r.ok ? "ok" : "warn");
|
|
292
|
+
return r.ok ? "memory is on" : `memory did not start: ${r.detail}`;
|
|
293
|
+
}
|