@quickchatai/quickreview 1.1.0 → 1.2.0
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 +35 -19
- package/config.example.toml +41 -9
- package/dist/extensions/cli/login-route.js +5 -0
- package/dist/extensions/cli/login-route.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 +29 -6
- package/dist/extensions/onboarding.js.map +1 -1
- package/dist/src/ciretry.js +2 -1
- package/dist/src/ciretry.js.map +1 -1
- package/dist/src/cli.js +43 -12
- 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 +16 -2
- package/dist/src/config.js.map +1 -1
- package/dist/src/daemon.js +24 -8
- package/dist/src/daemon.js.map +1 -1
- package/dist/src/debugroundpi.js +5 -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/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/loginhint.js +8 -3
- package/dist/src/loginhint.js.map +1 -1
- package/dist/src/loginrefresh/pirefresh.js +3 -0
- package/dist/src/loginrefresh/pirefresh.js.map +1 -1
- package/dist/src/npmverbs.js +4 -2
- package/dist/src/npmverbs.js.map +1 -1
- package/dist/src/onboard/choices.js +29 -1
- package/dist/src/onboard/choices.js.map +1 -1
- package/dist/src/onboard/engine.js +8 -23
- package/dist/src/onboard/engine.js.map +1 -1
- package/dist/src/onboard/launch.js +12 -20
- package/dist/src/onboard/launch.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/compose.js +29 -0
- package/dist/src/onboard/steps/compose.js.map +1 -1
- package/dist/src/onboard/steps/configfill.js +102 -32
- 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/memory.js +53 -1
- 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/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/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/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/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/state.js.map +1 -1
- package/dist/src/statemachine.js +4 -1
- package/dist/src/statemachine.js.map +1 -1
- package/dist/src/takeoverpi.js +13 -0
- 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 +74 -10
- 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/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 +106 -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/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 +29 -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 +28 -6
- package/dist/src/wire.js.map +1 -1
- package/dist/src/workerworld.js +7 -1
- package/dist/src/workerworld.js.map +1 -1
- package/docker/companion/entrypoint.sh +20 -1
- package/docker/entrypoint.sh +17 -0
- package/extensions/cli/login-route.ts +10 -0
- package/extensions/login-route.ts +220 -0
- package/extensions/onboarding.ts +35 -6
- package/package.json +2 -2
- package/prompts/onboarding.md +57 -18
- package/scripts/mint-token.sh +25 -4
- package/src/ciretry.ts +2 -1
- package/src/cli.ts +49 -14
- package/src/cliops.ts +3 -2
- package/src/commands.ts +28 -2
- package/src/config.ts +24 -4
- package/src/daemon.ts +32 -7
- package/src/debugroundpi.ts +11 -1
- package/src/dispatch.ts +5 -4
- 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/loginhint.ts +8 -3
- package/src/loginrefresh/pirefresh.ts +3 -0
- package/src/npmverbs.ts +4 -2
- package/src/onboard/choices.ts +34 -1
- package/src/onboard/engine.ts +22 -3
- package/src/onboard/launch.ts +17 -29
- 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/compose.ts +31 -0
- package/src/onboard/steps/configfill.ts +121 -33
- 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/memory.ts +58 -1
- package/src/onboard/steps/proof.ts +473 -55
- 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/posting-journal.ts +6 -1
- package/src/posting-outbox.ts +10 -2
- package/src/posting-receipt.ts +17 -0
- package/src/recheck.ts +2 -1
- package/src/reconcile.ts +29 -3
- package/src/reviewhistory.ts +20 -6
- package/src/reviewnow.ts +76 -0
- package/src/state.ts +18 -0
- package/src/statemachine.ts +4 -1
- package/src/takeoverpi.ts +13 -0
- 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 +78 -10
- package/src/tui/connect.ts +7 -1
- package/src/tui/filters.ts +7 -3
- package/src/tui/ghread.ts +74 -7
- package/src/tui/index.ts +114 -23
- package/src/tui/memforward.ts +52 -3
- 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 +52 -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 +36 -7
- package/src/workerworld.ts +7 -1
|
@@ -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 =
|
|
@@ -700,6 +700,15 @@ function serverUnreachableDetail(ctx: StepContext, base: string, why: string): s
|
|
|
700
700
|
const flags = forwardFlags(ctx);
|
|
701
701
|
const cmd = (sub: string): string =>
|
|
702
702
|
`\`cd ${root} && node scripts/companion-memory-forward.mjs ${sub} ${flags}\``;
|
|
703
|
+
if (topologyOf(ctx.choices) !== "vm")
|
|
704
|
+
return (
|
|
705
|
+
`no OpenViking answering at ${base} (${why}). The root key is in place, which is what the daemon needs at boot. ` +
|
|
706
|
+
"Before the bring-up that is expected, and nothing is needed from you: the bring-up step starts the memory engine, " +
|
|
707
|
+
"and the memory_user_keys step after it reaches it through a memory forward the wizard opens itself, then mints " +
|
|
708
|
+
"the two user keys. If the stack is already up, the memory container is the thing to check " +
|
|
709
|
+
"(`docker compose ps memory`, `docker compose logs memory`); a forward that stays up outside the wizard is " +
|
|
710
|
+
`${cmd("install")} (the systemd unit) or \`qcr tui\`, which starts one.`
|
|
711
|
+
);
|
|
703
712
|
return (
|
|
704
713
|
`no OpenViking answering at ${base} (${why}). The root key is in place, which is what the daemon needs at boot. ` +
|
|
705
714
|
"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 +905,7 @@ export function memoryStep(opts: MemoryStepOptions = {}): OnboardingStep {
|
|
|
896
905
|
};
|
|
897
906
|
};
|
|
898
907
|
|
|
899
|
-
|
|
908
|
+
const step: OnboardingStep = {
|
|
900
909
|
id: MEMORY_STEP_ID,
|
|
901
910
|
title: "Memory: mint, place and confine the OpenViking keys",
|
|
902
911
|
explain:
|
|
@@ -1216,6 +1225,54 @@ export function memoryStep(opts: MemoryStepOptions = {}): OnboardingStep {
|
|
|
1216
1225
|
return { ok: checks.every((c) => c.ok), checks };
|
|
1217
1226
|
},
|
|
1218
1227
|
};
|
|
1228
|
+
|
|
1229
|
+
// On the compose route the engine is reachable from this machine only
|
|
1230
|
+
// through the memory forward, and the wizard runs before any TUI has opened
|
|
1231
|
+
// one: each call holds one open for its own duration (adopting a running
|
|
1232
|
+
// forward, closing one it started). A declined memory needs no server.
|
|
1233
|
+
const withForward = async (
|
|
1234
|
+
ctx: StepContext,
|
|
1235
|
+
body: () => Promise<StepReport>,
|
|
1236
|
+
): Promise<StepReport> => {
|
|
1237
|
+
const open = ctx.adapters.memoryForward;
|
|
1238
|
+
const declined =
|
|
1239
|
+
ctx.input.choice === "declined" ||
|
|
1240
|
+
(ctx.input.choice === undefined && ctx.choices[MEMORY_CHOICE_KEY] === "declined");
|
|
1241
|
+
if (!open || declined || topologyOf(ctx.choices) === "vm") return body();
|
|
1242
|
+
const facts = remoteFactsFor(ctx.adapters, ctx.choices);
|
|
1243
|
+
let forward: Awaited<ReturnType<typeof open>> = null;
|
|
1244
|
+
try {
|
|
1245
|
+
forward = await open({
|
|
1246
|
+
baseUrl: resolvePaths(opts, ctx).base,
|
|
1247
|
+
repoRoot: rootOf(ctx.adapters),
|
|
1248
|
+
container: facts.memoryContainer,
|
|
1249
|
+
sudoDocker: facts.sudo,
|
|
1250
|
+
log: (line) => ctx.adapters.log?.(line),
|
|
1251
|
+
});
|
|
1252
|
+
} catch {
|
|
1253
|
+
forward = null;
|
|
1254
|
+
}
|
|
1255
|
+
try {
|
|
1256
|
+
const report = await body();
|
|
1257
|
+
return forward?.started
|
|
1258
|
+
? {
|
|
1259
|
+
...report,
|
|
1260
|
+
checks: [
|
|
1261
|
+
{ name: "memory-forward", ok: true, detail: forward.detail },
|
|
1262
|
+
...report.checks,
|
|
1263
|
+
],
|
|
1264
|
+
}
|
|
1265
|
+
: report;
|
|
1266
|
+
} finally {
|
|
1267
|
+
forward?.stop();
|
|
1268
|
+
}
|
|
1269
|
+
};
|
|
1270
|
+
|
|
1271
|
+
return {
|
|
1272
|
+
...step,
|
|
1273
|
+
run: (ctx) => withForward(ctx, () => step.run(ctx)),
|
|
1274
|
+
verify: (ctx) => withForward(ctx, () => step.verify(ctx)),
|
|
1275
|
+
};
|
|
1219
1276
|
}
|
|
1220
1277
|
|
|
1221
1278
|
/**
|