@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.
Files changed (225) hide show
  1. package/README.md +35 -19
  2. package/config.example.toml +41 -9
  3. package/dist/extensions/cli/login-route.js +5 -0
  4. package/dist/extensions/cli/login-route.js.map +1 -0
  5. package/dist/extensions/login-route.js +180 -0
  6. package/dist/extensions/login-route.js.map +1 -0
  7. package/dist/extensions/onboarding.js +29 -6
  8. package/dist/extensions/onboarding.js.map +1 -1
  9. package/dist/src/ciretry.js +2 -1
  10. package/dist/src/ciretry.js.map +1 -1
  11. package/dist/src/cli.js +43 -12
  12. package/dist/src/cli.js.map +1 -1
  13. package/dist/src/cliops.js +3 -2
  14. package/dist/src/cliops.js.map +1 -1
  15. package/dist/src/commands.js +25 -2
  16. package/dist/src/commands.js.map +1 -1
  17. package/dist/src/config.js +16 -2
  18. package/dist/src/config.js.map +1 -1
  19. package/dist/src/daemon.js +24 -8
  20. package/dist/src/daemon.js.map +1 -1
  21. package/dist/src/debugroundpi.js +5 -1
  22. package/dist/src/debugroundpi.js.map +1 -1
  23. package/dist/src/dispatch.js +5 -4
  24. package/dist/src/dispatch.js.map +1 -1
  25. package/dist/src/engine/debugpi.js +17 -5
  26. package/dist/src/engine/debugpi.js.map +1 -1
  27. package/dist/src/execboundary.js +10 -3
  28. package/dist/src/execboundary.js.map +1 -1
  29. package/dist/src/github.js +51 -16
  30. package/dist/src/github.js.map +1 -1
  31. package/dist/src/githubtoken.js +346 -0
  32. package/dist/src/githubtoken.js.map +1 -0
  33. package/dist/src/loginhint.js +8 -3
  34. package/dist/src/loginhint.js.map +1 -1
  35. package/dist/src/loginrefresh/pirefresh.js +3 -0
  36. package/dist/src/loginrefresh/pirefresh.js.map +1 -1
  37. package/dist/src/npmverbs.js +4 -2
  38. package/dist/src/npmverbs.js.map +1 -1
  39. package/dist/src/onboard/choices.js +29 -1
  40. package/dist/src/onboard/choices.js.map +1 -1
  41. package/dist/src/onboard/engine.js +8 -23
  42. package/dist/src/onboard/engine.js.map +1 -1
  43. package/dist/src/onboard/launch.js +12 -20
  44. package/dist/src/onboard/launch.js.map +1 -1
  45. package/dist/src/onboard/pipin.js +22 -0
  46. package/dist/src/onboard/pipin.js.map +1 -0
  47. package/dist/src/onboard/stepforward.js +41 -0
  48. package/dist/src/onboard/stepforward.js.map +1 -0
  49. package/dist/src/onboard/steps/appslug.js +4 -1
  50. package/dist/src/onboard/steps/appslug.js.map +1 -1
  51. package/dist/src/onboard/steps/compose.js +29 -0
  52. package/dist/src/onboard/steps/compose.js.map +1 -1
  53. package/dist/src/onboard/steps/configfill.js +102 -32
  54. package/dist/src/onboard/steps/configfill.js.map +1 -1
  55. package/dist/src/onboard/steps/githubaccount.js +595 -0
  56. package/dist/src/onboard/steps/githubaccount.js.map +1 -0
  57. package/dist/src/onboard/steps/githubapp.js +65 -7
  58. package/dist/src/onboard/steps/githubapp.js.map +1 -1
  59. package/dist/src/onboard/steps/golive.js +2 -0
  60. package/dist/src/onboard/steps/golive.js.map +1 -1
  61. package/dist/src/onboard/steps/index.js +11 -0
  62. package/dist/src/onboard/steps/index.js.map +1 -1
  63. package/dist/src/onboard/steps/ingress.js +80 -25
  64. package/dist/src/onboard/steps/ingress.js.map +1 -1
  65. package/dist/src/onboard/steps/llmauth.js +103 -30
  66. package/dist/src/onboard/steps/llmauth.js.map +1 -1
  67. package/dist/src/onboard/steps/memory.js +53 -1
  68. package/dist/src/onboard/steps/memory.js.map +1 -1
  69. package/dist/src/onboard/steps/proof.js +366 -56
  70. package/dist/src/onboard/steps/proof.js.map +1 -1
  71. package/dist/src/onboard/steps/topology.js +6 -6
  72. package/dist/src/onboard/steps/topology.js.map +1 -1
  73. package/dist/src/onboard/steps/transport.js +9 -1
  74. package/dist/src/onboard/steps/transport.js.map +1 -1
  75. package/dist/src/onboard/steps/tuiroute.js +102 -0
  76. package/dist/src/onboard/steps/tuiroute.js.map +1 -0
  77. package/dist/src/onboard/steps/verifyup.js +17 -1
  78. package/dist/src/onboard/steps/verifyup.js.map +1 -1
  79. package/dist/src/onboard/toolnames.js +30 -0
  80. package/dist/src/onboard/toolnames.js.map +1 -0
  81. package/dist/src/operatorwrite.js +23 -0
  82. package/dist/src/operatorwrite.js.map +1 -0
  83. package/dist/src/outcomes.js +4 -1
  84. package/dist/src/outcomes.js.map +1 -1
  85. package/dist/src/posting-journal.js +2 -1
  86. package/dist/src/posting-journal.js.map +1 -1
  87. package/dist/src/posting-outbox.js +6 -2
  88. package/dist/src/posting-outbox.js.map +1 -1
  89. package/dist/src/posting-receipt.js +14 -1
  90. package/dist/src/posting-receipt.js.map +1 -1
  91. package/dist/src/recheck.js +2 -1
  92. package/dist/src/recheck.js.map +1 -1
  93. package/dist/src/reconcile.js +22 -2
  94. package/dist/src/reconcile.js.map +1 -1
  95. package/dist/src/reviewhistory.js +32 -6
  96. package/dist/src/reviewhistory.js.map +1 -1
  97. package/dist/src/reviewnow.js +45 -0
  98. package/dist/src/reviewnow.js.map +1 -0
  99. package/dist/src/state.js.map +1 -1
  100. package/dist/src/statemachine.js +4 -1
  101. package/dist/src/statemachine.js.map +1 -1
  102. package/dist/src/takeoverpi.js +13 -0
  103. package/dist/src/takeoverpi.js.map +1 -1
  104. package/dist/src/tui/app.js +49 -0
  105. package/dist/src/tui/app.js.map +1 -1
  106. package/dist/src/tui/board.js +88 -12
  107. package/dist/src/tui/board.js.map +1 -1
  108. package/dist/src/tui/cmdbar.js +4 -1
  109. package/dist/src/tui/cmdbar.js.map +1 -1
  110. package/dist/src/tui/companion.js +74 -10
  111. package/dist/src/tui/companion.js.map +1 -1
  112. package/dist/src/tui/connect.js +7 -1
  113. package/dist/src/tui/connect.js.map +1 -1
  114. package/dist/src/tui/filters.js +5 -2
  115. package/dist/src/tui/filters.js.map +1 -1
  116. package/dist/src/tui/ghread.js +61 -5
  117. package/dist/src/tui/ghread.js.map +1 -1
  118. package/dist/src/tui/index.js +106 -20
  119. package/dist/src/tui/index.js.map +1 -1
  120. package/dist/src/tui/memforward.js +39 -2
  121. package/dist/src/tui/memforward.js.map +1 -1
  122. package/dist/src/tui/model.js +25 -0
  123. package/dist/src/tui/model.js.map +1 -1
  124. package/dist/src/tui/prpause.js +7 -0
  125. package/dist/src/tui/prpause.js.map +1 -1
  126. package/dist/src/tui/remote.js +7 -1
  127. package/dist/src/tui/remote.js.map +1 -1
  128. package/dist/src/tui/route.js +143 -0
  129. package/dist/src/tui/route.js.map +1 -0
  130. package/dist/src/tui/setupstate.js +29 -0
  131. package/dist/src/tui/setupstate.js.map +1 -0
  132. package/dist/src/tui/sshtriage.js +102 -5
  133. package/dist/src/tui/sshtriage.js.map +1 -1
  134. package/dist/src/tui/theme.js +2 -1
  135. package/dist/src/tui/theme.js.map +1 -1
  136. package/dist/src/tui/unwedge.js +2 -12
  137. package/dist/src/tui/unwedge.js.map +1 -1
  138. package/dist/src/verdicts.js +17 -5
  139. package/dist/src/verdicts.js.map +1 -1
  140. package/dist/src/version.js +1 -1
  141. package/dist/src/version.js.map +1 -1
  142. package/dist/src/webhook.js +12 -1
  143. package/dist/src/webhook.js.map +1 -1
  144. package/dist/src/wire.js +28 -6
  145. package/dist/src/wire.js.map +1 -1
  146. package/dist/src/workerworld.js +7 -1
  147. package/dist/src/workerworld.js.map +1 -1
  148. package/docker/companion/entrypoint.sh +20 -1
  149. package/docker/entrypoint.sh +17 -0
  150. package/extensions/cli/login-route.ts +10 -0
  151. package/extensions/login-route.ts +220 -0
  152. package/extensions/onboarding.ts +35 -6
  153. package/package.json +2 -2
  154. package/prompts/onboarding.md +57 -18
  155. package/scripts/mint-token.sh +25 -4
  156. package/src/ciretry.ts +2 -1
  157. package/src/cli.ts +49 -14
  158. package/src/cliops.ts +3 -2
  159. package/src/commands.ts +28 -2
  160. package/src/config.ts +24 -4
  161. package/src/daemon.ts +32 -7
  162. package/src/debugroundpi.ts +11 -1
  163. package/src/dispatch.ts +5 -4
  164. package/src/engine/debugpi.ts +17 -5
  165. package/src/execboundary.ts +16 -4
  166. package/src/github.ts +57 -15
  167. package/src/githubtoken.ts +435 -0
  168. package/src/loginhint.ts +8 -3
  169. package/src/loginrefresh/pirefresh.ts +3 -0
  170. package/src/npmverbs.ts +4 -2
  171. package/src/onboard/choices.ts +34 -1
  172. package/src/onboard/engine.ts +22 -3
  173. package/src/onboard/launch.ts +17 -29
  174. package/src/onboard/pipin.ts +31 -0
  175. package/src/onboard/stepforward.ts +61 -0
  176. package/src/onboard/steps/appslug.ts +4 -1
  177. package/src/onboard/steps/compose.ts +31 -0
  178. package/src/onboard/steps/configfill.ts +121 -33
  179. package/src/onboard/steps/githubaccount.ts +716 -0
  180. package/src/onboard/steps/githubapp.ts +92 -8
  181. package/src/onboard/steps/golive.ts +2 -0
  182. package/src/onboard/steps/index.ts +11 -0
  183. package/src/onboard/steps/ingress.ts +98 -31
  184. package/src/onboard/steps/llmauth.ts +114 -29
  185. package/src/onboard/steps/memory.ts +58 -1
  186. package/src/onboard/steps/proof.ts +473 -55
  187. package/src/onboard/steps/topology.ts +7 -6
  188. package/src/onboard/steps/transport.ts +15 -0
  189. package/src/onboard/steps/tuiroute.ts +119 -0
  190. package/src/onboard/steps/verifyup.ts +20 -1
  191. package/src/onboard/toolnames.ts +34 -0
  192. package/src/operatorwrite.ts +25 -0
  193. package/src/outcomes.ts +8 -1
  194. package/src/posting-journal.ts +6 -1
  195. package/src/posting-outbox.ts +10 -2
  196. package/src/posting-receipt.ts +17 -0
  197. package/src/recheck.ts +2 -1
  198. package/src/reconcile.ts +29 -3
  199. package/src/reviewhistory.ts +20 -6
  200. package/src/reviewnow.ts +76 -0
  201. package/src/state.ts +18 -0
  202. package/src/statemachine.ts +4 -1
  203. package/src/takeoverpi.ts +13 -0
  204. package/src/tui/app.ts +47 -0
  205. package/src/tui/board.ts +111 -14
  206. package/src/tui/cmdbar.ts +4 -1
  207. package/src/tui/companion.ts +78 -10
  208. package/src/tui/connect.ts +7 -1
  209. package/src/tui/filters.ts +7 -3
  210. package/src/tui/ghread.ts +74 -7
  211. package/src/tui/index.ts +114 -23
  212. package/src/tui/memforward.ts +52 -3
  213. package/src/tui/model.ts +36 -2
  214. package/src/tui/prpause.ts +11 -2
  215. package/src/tui/remote.ts +9 -1
  216. package/src/tui/route.ts +173 -0
  217. package/src/tui/setupstate.ts +52 -0
  218. package/src/tui/sshtriage.ts +127 -6
  219. package/src/tui/theme.ts +2 -1
  220. package/src/tui/unwedge.ts +2 -14
  221. package/src/verdicts.ts +23 -5
  222. package/src/version.ts +1 -1
  223. package/src/webhook.ts +10 -1
  224. package/src/wire.ts +36 -7
  225. package/src/workerworld.ts +7 -1
@@ -63,6 +63,7 @@ import {
63
63
  type ChoiceName,
64
64
  botLoginFor,
65
65
  ingressChoiceOf,
66
+ userAuthRecorded,
66
67
  type IngressChoice,
67
68
  } from "../choices.js";
68
69
  import {
@@ -76,8 +77,8 @@ import {
76
77
  } from "../engine.js";
77
78
  import { deliverConfigChange, configDeliveryChecks } from "./configdelivery.js";
78
79
  import type { TransportOptions } from "./transport.js";
79
- import { BARE_HOSTNAME_RE, readTomlKey, secretsDirFor } from "./ingress.js";
80
- import { SSH_ALIAS_RE } from "./topology.js";
80
+ import { BARE_HOSTNAME_RE, CONTAINER_STATE_DIR, readTomlKey, secretsDirFor } from "./ingress.js";
81
+ import { SSH_ALIAS_RE, topologyOf, type Topology } from "./topology.js";
81
82
 
82
83
  export const CONFIG_FILL_STEP_ID = "config_fill";
83
84
 
@@ -228,9 +229,17 @@ export interface FillFacts {
228
229
  secrets_dir: string;
229
230
  topology: string;
230
231
  ingress: string;
232
+ /** The GitHub route the github_account step recorded ("user" | "app"), ""
233
+ * while unanswered — which is the App route's interview. */
234
+ github_auth?: string;
235
+ /** The person's own login on the "user" route (`GET /user`). */
236
+ account_login?: string;
231
237
  }
232
238
 
233
- export type AnswerKey = Exclude<keyof FillFacts, "secrets_dir" | "topology" | "ingress">;
239
+ export type AnswerKey = Exclude<
240
+ keyof FillFacts,
241
+ "secrets_dir" | "topology" | "ingress" | "github_auth" | "account_login"
242
+ >;
234
243
 
235
244
  export interface Question {
236
245
  id: AnswerKey;
@@ -282,7 +291,9 @@ export const QUESTIONS: readonly Question[] = [
282
291
  "clone directory, backfill identity — is derived from this one answer.",
283
292
  prompt: 'Which repository should the reviewer watch? ("owner/name")',
284
293
  optional: false,
285
- from: [CHOICE_KEYS.repo],
294
+ // On the own-account route the github_account step already asked, from
295
+ // the repos the person's account reaches.
296
+ from: [CHOICE_KEYS.accountRepo, CHOICE_KEYS.repo],
286
297
  read: (cfg) => cfg.github.repo,
287
298
  validate: (v) =>
288
299
  REPO_SLUG_RE.test(v)
@@ -298,8 +309,10 @@ export const QUESTIONS: readonly Question[] = [
298
309
  "The companion — the local pi that sits beside the TUI — reads the code a review is about. Your checkout is " +
299
310
  "mounted READ-ONLY into its container: it can read every file and change none of them. A missing checkout only " +
300
311
  "makes the companion less informed; it never breaks the reviewer.",
301
- prompt: "Where is your local checkout of that repository?",
302
- optional: false,
312
+ prompt:
313
+ "Where is your local checkout of that repository? (Enter if there is none here — the companion starts without it)",
314
+ // A fresh box has no checkout, and the daemon clones the repository itself.
315
+ optional: true,
303
316
  from: [CHOICE_KEYS.repoDir],
304
317
  read: (cfg) => cfg.companion.reviewed_repo_dir,
305
318
  validate: (v) => {
@@ -325,6 +338,8 @@ export const QUESTIONS: readonly Question[] = [
325
338
  prompt: "What is your GitHub App's slug?",
326
339
  optional: false,
327
340
  from: [CHOICE_KEYS.appSlug],
341
+ // No App exists on the own-account route: `[github] app_slug` stays "".
342
+ asked: (choices) => !userAuthRecorded(choices),
328
343
  read: (cfg) => cfg.github.app_slug,
329
344
  validate: (v) =>
330
345
  SLUG_RE.test(v)
@@ -390,7 +405,7 @@ export const QUESTIONS: readonly Question[] = [
390
405
  "It is never used to authenticate anything — the App credential is the only identity that acts.",
391
406
  prompt: "What is your GitHub login? (Enter to skip)",
392
407
  optional: true,
393
- from: [CHOICE_KEYS.ownerLogin],
408
+ from: [CHOICE_KEYS.accountLogin, CHOICE_KEYS.ownerLogin],
394
409
  read: (cfg) => cfg.owner.github_login,
395
410
  validate: validateGithubLogin,
396
411
  },
@@ -493,27 +508,51 @@ export interface Derivable {
493
508
  holds(cfg: Config, facts: FillFacts): string | null;
494
509
  }
495
510
 
496
- /** The TUI transport a recorded topology implies; null while it is unrecorded. */
497
- function tuiTargetFor(topology: string): "local" | "ssh" | null {
498
- return topology === "local" ? "local" : topology === "vm" ? "ssh" : null;
511
+ /** The topology a fill is for: the recorded one, and "local" while none is
512
+ * recorded — `topologyOf`'s reading, the one every other step uses. */
513
+ function fillTopology(topology: string): Topology {
514
+ return topologyOf({ [CHOICE_KEYS.topology]: topology });
515
+ }
516
+
517
+ /** The TUI transport a topology implies (an unrecorded one is local). */
518
+ function tuiTargetFor(topology: string): "local" | "ssh" {
519
+ return fillTopology(topology) === "vm" ? "ssh" : "local";
499
520
  }
500
521
 
522
+ /** The release image's state dir inside the compose daemon container (the
523
+ * `qcr-state` volume), where the local TUI reads status.json. */
524
+ const LOCAL_TUI_STATE_DIR = CONTAINER_STATE_DIR;
525
+
501
526
  export const DERIVABLES: readonly Derivable[] = [
502
527
  {
503
528
  id: "bot_login",
504
529
  path: "[github] bot_login",
505
530
  explain:
506
- "the bot login is the App slug plus the [bot] suffix — asking for it twice is how the two drift apart",
507
- write: (f) => ({
508
- section: "github",
509
- key: "bot_login",
510
- value: tomlString(botLoginFor(f.app_slug)),
511
- }),
531
+ "the bot login is the App slug plus the [bot] suffix — asking for it twice is how the two drift apart. On your own " +
532
+ "GitHub account it is your login: the reviews are posted as you",
533
+ write: (f) =>
534
+ f.github_auth === "user"
535
+ ? f.account_login
536
+ ? { section: "github", key: "bot_login", value: tomlString(f.account_login) }
537
+ : null
538
+ : {
539
+ section: "github",
540
+ key: "bot_login",
541
+ value: tomlString(botLoginFor(f.app_slug)),
542
+ },
512
543
  stable: true,
513
- holds: (cfg) =>
514
- cfg.github.bot_login === botLoginFor(cfg.github.app_slug)
544
+ holds: (cfg, f) => {
545
+ if (f.github_auth === "user") {
546
+ if (!cfg.github.bot_login.trim())
547
+ return "[github] bot_login is empty — on your own account it is your GitHub login; run the github_account step";
548
+ return f.account_login && cfg.github.bot_login !== f.account_login
549
+ ? `[github] bot_login is "${cfg.github.bot_login}" but your account is "${f.account_login}" — run the github_account step again`
550
+ : null;
551
+ }
552
+ return cfg.github.bot_login === botLoginFor(cfg.github.app_slug)
515
553
  ? null
516
- : `[github] bot_login is "${cfg.github.bot_login}" but the App slug "${cfg.github.app_slug}" implies "${botLoginFor(cfg.github.app_slug)}"`,
554
+ : `[github] bot_login is "${cfg.github.bot_login}" but the App slug "${cfg.github.app_slug}" implies "${botLoginFor(cfg.github.app_slug)}"`;
555
+ },
517
556
  },
518
557
  {
519
558
  id: "clone_dir",
@@ -547,7 +586,7 @@ export const DERIVABLES: readonly Derivable[] = [
547
586
  holds: (cfg) =>
548
587
  cfg.github.secrets_dir.trim()
549
588
  ? null
550
- : "[github] secrets_dir is empty — the App credential has nowhere to live",
589
+ : "[github] secrets_dir is empty — the GitHub credential has nowhere to live",
551
590
  },
552
591
  {
553
592
  id: "review_team",
@@ -588,10 +627,12 @@ export const DERIVABLES: readonly Derivable[] = [
588
627
  "the history importer's identity and labels cascade off [github] — never a second copy to keep in sync",
589
628
  write: () => null,
590
629
  stable: true,
591
- holds: (cfg) => {
630
+ holds: (cfg, f) => {
592
631
  if (cfg.backfill.bot_login !== cfg.github.bot_login)
593
632
  return "[backfill] bot_login no longer follows [github] bot_login";
594
- if (cfg.backfill.bot_actor !== cfg.github.app_slug)
633
+ // The actor is the App slug; the own-account route has no App, and the
634
+ // loader decides what stands in for it there.
635
+ if (f.github_auth !== "user" && cfg.backfill.bot_actor !== cfg.github.app_slug)
595
636
  return "[backfill] bot_actor no longer follows [github] app_slug";
596
637
  if (cfg.backfill.trigger_label !== cfg.github.trigger_label)
597
638
  return "[backfill] trigger_label no longer follows [github] trigger_label";
@@ -615,15 +656,12 @@ export const DERIVABLES: readonly Derivable[] = [
615
656
  path: "[tui] target",
616
657
  explain:
617
658
  'follows the recorded topology: "local" when the daemon runs on this machine (the TUI talks to local Docker), "ssh" on the VM route',
618
- write: (f) => {
619
- const target = tuiTargetFor(f.topology);
620
- return target ? { section: "tui", key: "target", value: tomlString(target) } : null;
621
- },
659
+ write: (f) => ({ section: "tui", key: "target", value: tomlString(tuiTargetFor(f.topology)) }),
622
660
  stable: true,
623
661
  holds: (cfg, f) => {
624
662
  const want = tuiTargetFor(f.topology);
625
- return want && cfg.tui.target !== want
626
- ? `[tui] target = "${cfg.tui.target}", but the recorded topology is ${f.topology} — the TUI would ` +
663
+ return cfg.tui.target !== want
664
+ ? `[tui] target = "${cfg.tui.target}", but the topology is ${f.topology || "local (none recorded)"} — the TUI would ` +
627
665
  `${want === "local" ? "look for the daemon over ssh" : "look for the daemon in local Docker"}. ` +
628
666
  `Fix: run this step again (it writes target = "${want}")`
629
667
  : null;
@@ -635,7 +673,8 @@ export const DERIVABLES: readonly Derivable[] = [
635
673
  explain:
636
674
  "the bundled local Compose project runs service daemon as quickreview-daemon-1; preserve an operator's custom container",
637
675
  write: (f, fresh, existing) =>
638
- f.topology === "local" && (fresh || existing?.tui.container === TUI_DEFAULTS.container)
676
+ fillTopology(f.topology) === "local" &&
677
+ (fresh || existing?.tui.container === TUI_DEFAULTS.container)
639
678
  ? { section: "tui", key: "container", value: tomlString("quickreview-daemon-1") }
640
679
  : null,
641
680
  stable: false,
@@ -647,12 +686,41 @@ export const DERIVABLES: readonly Derivable[] = [
647
686
  explain:
648
687
  "the bundled local Compose project runs the memory sidecar as quickreview-memory-1, where the memory forward must relay; preserve an operator's custom container",
649
688
  write: (f, fresh, existing) =>
650
- f.topology === "local" && (fresh || (existing?.tui.memory_container ?? "") === "")
689
+ fillTopology(f.topology) === "local" &&
690
+ (fresh || (existing?.tui.memory_container ?? "") === "")
651
691
  ? { section: "tui", key: "memory_container", value: tomlString("quickreview-memory-1") }
652
692
  : null,
653
693
  stable: false,
654
694
  holds: () => null,
655
695
  },
696
+ {
697
+ id: "tui_remote_state_dir",
698
+ path: "[tui] remote_state_dir",
699
+ explain:
700
+ `on this machine the TUI reads status.json inside the daemon container, where the release image keeps its state (${LOCAL_TUI_STATE_DIR}, ` +
701
+ "the compose state volume) — the shipped default is the VM host's path; preserve an operator's custom path",
702
+ write: (f, fresh, existing) =>
703
+ fillTopology(f.topology) === "local" &&
704
+ (fresh || existing?.tui.remote_state_dir === TUI_DEFAULTS.remote_state_dir)
705
+ ? { section: "tui", key: "remote_state_dir", value: tomlString(LOCAL_TUI_STATE_DIR) }
706
+ : null,
707
+ stable: false,
708
+ holds: () => null,
709
+ },
710
+ {
711
+ id: "tui_sudo_docker",
712
+ path: "[tui] sudo_docker",
713
+ explain:
714
+ "off on this machine: the wizard's own bring-up drives docker without sudo, so the TUI does too (a sudo password " +
715
+ "prompt behind the TUI's screen can only fail); on the VM route sudo stays the shipped default",
716
+ write: (f, fresh, existing) =>
717
+ fillTopology(f.topology) === "local" &&
718
+ (fresh || existing?.tui.sudo_docker === TUI_DEFAULTS.sudo_docker)
719
+ ? { section: "tui", key: "sudo_docker", value: "false" }
720
+ : null,
721
+ stable: false,
722
+ holds: () => null,
723
+ },
656
724
  {
657
725
  id: "tui_ssh_host",
658
726
  path: "[tui] ssh_host",
@@ -729,6 +797,9 @@ export const DEFAULT_SECRETS_DIR = "";
729
797
  * Read from ITS artefacts (the `deploy_shape` marker it writes) so this step
730
798
  * reports the same required set without duplicating the wizard's validators. */
731
799
  export const REQUIRED_SECRETS: readonly string[] = ["app_id", "app.pem", "webhook_secret"];
800
+ /** The own-account route's one secret: the person's token, written by
801
+ * `qcr github-token sync` (the github_account step runs it). */
802
+ export const USER_ROUTE_SECRETS: readonly string[] = ["github_token"];
732
803
  export const TUNNEL_SHAPE_SECRET = "tunnel_token";
733
804
 
734
805
  /** Unlike `readTextOrNull`, only ABSENCE is null: a file that exists and
@@ -918,6 +989,8 @@ export async function collectAnswers(
918
989
  DEFAULT_SECRETS_DIR,
919
990
  topology: str(choices, CHOICE_KEYS.topology),
920
991
  ingress: str(choices, CHOICE_KEYS.ingress),
992
+ github_auth: str(choices, CHOICE_KEYS.githubAuth),
993
+ account_login: str(choices, CHOICE_KEYS.accountLogin),
921
994
  };
922
995
  return { facts, missing, rejected, asked, ignored };
923
996
  }
@@ -992,7 +1065,17 @@ export function changedEdits(text: string, edits: readonly TomlEdit[]): TomlEdit
992
1065
  }
993
1066
 
994
1067
  /** What the secrets dir holds, at the one resolution every step uses. */
995
- function secretsReport(dir: string): CheckResult {
1068
+ function secretsReport(dir: string, userRoute: boolean): CheckResult {
1069
+ if (userRoute) {
1070
+ const missing = USER_ROUTE_SECRETS.filter((f) => !existsSync(join(dir, f)));
1071
+ return {
1072
+ name: "secrets",
1073
+ ok: true,
1074
+ detail: missing.length
1075
+ ? `${dir}: still missing ${missing.join(", ")} — the github_account step writes it from your GitHub sign-in (no App secrets on this route)`
1076
+ : `${dir}: your GitHub token is present — the own-account route needs no App secrets`,
1077
+ };
1078
+ }
996
1079
  if (!existsSync(dir)) {
997
1080
  return {
998
1081
  name: "secrets",
@@ -1221,11 +1304,14 @@ export function configFillStep(opts: { transport?: TransportOptions } = {}): Onb
1221
1304
  ok: true,
1222
1305
  detail:
1223
1306
  `topology ${facts.topology || "(unrecorded — filled for a daemon on this machine, so [tui] ssh_host stays empty)"}, ` +
1224
- `ingress ${facts.ingress || "(unrecorded — filled for an ingress that assigns its own hostname, so [server] hostname stays empty)"}` +
1307
+ `ingress ${facts.ingress || "(unrecorded — filled for an ingress that assigns its own hostname, so [server] hostname stays empty)"}, ` +
1308
+ `github ${facts.github_auth === "user" ? "own account (no App questions, no App secrets)" : "App"}` +
1225
1309
  `${str(ctx.choices, CHOICE_KEYS.authRoute) ? `, auth route ${str(ctx.choices, CHOICE_KEYS.authRoute)}` : ""}`,
1226
1310
  });
1227
1311
  // Read after the write, so the dir is the one the written config names.
1228
- checks.push(secretsReport(secretsDirFor(ctx.adapters, ctx.choices)));
1312
+ checks.push(
1313
+ secretsReport(secretsDirFor(ctx.adapters, ctx.choices), userAuthRecorded(ctx.choices)),
1314
+ );
1229
1315
 
1230
1316
  const ok = checks.every((c) => c.ok);
1231
1317
  if (ok) recordChoices(ctx, facts, configPath, parsed);
@@ -1320,6 +1406,8 @@ export function configFillStep(opts: { transport?: TransportOptions } = {}): Onb
1320
1406
  secrets_dir: cfg.github.secrets_dir,
1321
1407
  topology: str(ctx.choices, CHOICE_KEYS.topology),
1322
1408
  ingress: str(ctx.choices, CHOICE_KEYS.ingress),
1409
+ github_auth: str(ctx.choices, CHOICE_KEYS.githubAuth),
1410
+ account_login: str(ctx.choices, CHOICE_KEYS.accountLogin),
1323
1411
  };
1324
1412
  for (const d of DERIVABLES) {
1325
1413
  if (!d.stable) continue;