quoxcli 0.0.0-stage → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (516) hide show
  1. package/LICENSE +35 -0
  2. package/README.md +144 -2
  3. package/bin/quox.js +265 -0
  4. package/bin/quoxon.js +8 -0
  5. package/lib/api/endpoints/admin.js +151 -0
  6. package/lib/api/endpoints/aee.js +38 -0
  7. package/lib/api/endpoints/agent-attach.js +126 -0
  8. package/lib/api/endpoints/agent-schedules.js +26 -0
  9. package/lib/api/endpoints/agents.js +63 -0
  10. package/lib/api/endpoints/assistants.js +62 -0
  11. package/lib/api/endpoints/audit.js +14 -0
  12. package/lib/api/endpoints/auth.js +50 -0
  13. package/lib/api/endpoints/awareness.js +34 -0
  14. package/lib/api/endpoints/away.js +15 -0
  15. package/lib/api/endpoints/backup-schedules.js +20 -0
  16. package/lib/api/endpoints/backups.js +86 -0
  17. package/lib/api/endpoints/brain.js +140 -0
  18. package/lib/api/endpoints/budgets.js +62 -0
  19. package/lib/api/endpoints/calendarq.js +71 -0
  20. package/lib/api/endpoints/catalog.js +14 -0
  21. package/lib/api/endpoints/chat.js +61 -0
  22. package/lib/api/endpoints/consent.js +26 -0
  23. package/lib/api/endpoints/contactsq.js +68 -0
  24. package/lib/api/endpoints/conversations.js +46 -0
  25. package/lib/api/endpoints/custom-roles.js +92 -0
  26. package/lib/api/endpoints/divisions.js +56 -0
  27. package/lib/api/endpoints/domains.js +38 -0
  28. package/lib/api/endpoints/engagement.js +231 -0
  29. package/lib/api/endpoints/engine.js +11 -0
  30. package/lib/api/endpoints/entity.js +50 -0
  31. package/lib/api/endpoints/executions.js +45 -0
  32. package/lib/api/endpoints/fetchAllowlist.js +27 -0
  33. package/lib/api/endpoints/file.js +20 -0
  34. package/lib/api/endpoints/files.js +40 -0
  35. package/lib/api/endpoints/github.js +21 -0
  36. package/lib/api/endpoints/governance.js +43 -0
  37. package/lib/api/endpoints/hat.js +66 -0
  38. package/lib/api/endpoints/inbox.js +52 -0
  39. package/lib/api/endpoints/inboxq.js +120 -0
  40. package/lib/api/endpoints/index.js +242 -0
  41. package/lib/api/endpoints/invites.js +19 -0
  42. package/lib/api/endpoints/llm-policy.js +43 -0
  43. package/lib/api/endpoints/lucid.js +89 -0
  44. package/lib/api/endpoints/members.js +32 -0
  45. package/lib/api/endpoints/memory.js +94 -0
  46. package/lib/api/endpoints/mfa.js +32 -0
  47. package/lib/api/endpoints/mind.js +13 -0
  48. package/lib/api/endpoints/mon.js +184 -0
  49. package/lib/api/endpoints/notesq.js +43 -0
  50. package/lib/api/endpoints/objectives.js +68 -0
  51. package/lib/api/endpoints/orgs.js +38 -0
  52. package/lib/api/endpoints/plugins.js +152 -0
  53. package/lib/api/endpoints/powers.js +75 -0
  54. package/lib/api/endpoints/presence.js +33 -0
  55. package/lib/api/endpoints/prompts.js +69 -0
  56. package/lib/api/endpoints/qlarity.js +122 -0
  57. package/lib/api/endpoints/quoxbox.js +55 -0
  58. package/lib/api/endpoints/quoxchat.js +54 -0
  59. package/lib/api/endpoints/quoxflow.js +32 -0
  60. package/lib/api/endpoints/quoxplan.js +121 -0
  61. package/lib/api/endpoints/quoxseo.js +114 -0
  62. package/lib/api/endpoints/reach.js +47 -0
  63. package/lib/api/endpoints/router.js +17 -0
  64. package/lib/api/endpoints/runs.js +38 -0
  65. package/lib/api/endpoints/safety.js +53 -0
  66. package/lib/api/endpoints/scim.js +46 -0
  67. package/lib/api/endpoints/scopes.js +14 -0
  68. package/lib/api/endpoints/security.js +69 -0
  69. package/lib/api/endpoints/setup.js +90 -0
  70. package/lib/api/endpoints/skills.js +200 -0
  71. package/lib/api/endpoints/sso.js +67 -0
  72. package/lib/api/endpoints/stepup.js +20 -0
  73. package/lib/api/endpoints/tags.js +32 -0
  74. package/lib/api/endpoints/teams.js +56 -0
  75. package/lib/api/endpoints/telephony.js +46 -0
  76. package/lib/api/endpoints/tg.js +56 -0
  77. package/lib/api/endpoints/tools.js +50 -0
  78. package/lib/api/endpoints/traces.js +157 -0
  79. package/lib/api/endpoints/triggers.js +96 -0
  80. package/lib/api/endpoints/user-library.js +41 -0
  81. package/lib/api/endpoints/users.js +20 -0
  82. package/lib/api/endpoints/v1.js +585 -0
  83. package/lib/api/endpoints/vault.js +192 -0
  84. package/lib/api/endpoints/voice.js +22 -0
  85. package/lib/api/endpoints/volt.js +84 -0
  86. package/lib/api/endpoints/ward.js +47 -0
  87. package/lib/api/endpoints/webhooks.js +46 -0
  88. package/lib/api/endpoints/workflows.js +169 -0
  89. package/lib/api/endpoints/x.js +32 -0
  90. package/lib/api/endpoints.js +15 -0
  91. package/lib/commands/actions.js +61 -0
  92. package/lib/commands/admin.js +375 -0
  93. package/lib/commands/aee.js +99 -0
  94. package/lib/commands/agent.js +337 -0
  95. package/lib/commands/agentAttach.js +1528 -0
  96. package/lib/commands/approvals.js +119 -0
  97. package/lib/commands/aside.js +24 -0
  98. package/lib/commands/ask.js +144 -0
  99. package/lib/commands/assistant.js +169 -0
  100. package/lib/commands/auth.js +464 -0
  101. package/lib/commands/awareness.js +88 -0
  102. package/lib/commands/backup.js +377 -0
  103. package/lib/commands/brain.js +1085 -0
  104. package/lib/commands/budget.js +241 -0
  105. package/lib/commands/build.js +170 -0
  106. package/lib/commands/calendar.js +216 -0
  107. package/lib/commands/capabilities.js +34 -0
  108. package/lib/commands/chat.js +295 -0
  109. package/lib/commands/claude.js +380 -0
  110. package/lib/commands/config-cmd.js +106 -0
  111. package/lib/commands/consent.js +84 -0
  112. package/lib/commands/contacts.js +1016 -0
  113. package/lib/commands/contactsSelftest.js +132 -0
  114. package/lib/commands/conversation.js +105 -0
  115. package/lib/commands/dashboard.js +43 -0
  116. package/lib/commands/department.js +96 -0
  117. package/lib/commands/dev.js +620 -0
  118. package/lib/commands/devResident.js +326 -0
  119. package/lib/commands/devSupervisor.js +1062 -0
  120. package/lib/commands/discord.js +132 -0
  121. package/lib/commands/doctor.js +181 -0
  122. package/lib/commands/domain.js +114 -0
  123. package/lib/commands/done.js +347 -0
  124. package/lib/commands/engagement.js +597 -0
  125. package/lib/commands/entity.js +119 -0
  126. package/lib/commands/facebook.js +212 -0
  127. package/lib/commands/fetchAllowlist.js +71 -0
  128. package/lib/commands/file.js +165 -0
  129. package/lib/commands/fleet.js +1189 -0
  130. package/lib/commands/governance.js +647 -0
  131. package/lib/commands/gripe.js +355 -0
  132. package/lib/commands/hat.js +374 -0
  133. package/lib/commands/helpers.js +96 -0
  134. package/lib/commands/hyperion.js +58 -0
  135. package/lib/commands/inbox.js +173 -0
  136. package/lib/commands/instagram.js +264 -0
  137. package/lib/commands/integration.js +100 -0
  138. package/lib/commands/inventory.js +115 -0
  139. package/lib/commands/invites.js +45 -0
  140. package/lib/commands/licence.js +139 -0
  141. package/lib/commands/linkedin.js +178 -0
  142. package/lib/commands/llm-policy.js +117 -0
  143. package/lib/commands/llm.js +45 -0
  144. package/lib/commands/lucid.js +597 -0
  145. package/lib/commands/mail.js +531 -0
  146. package/lib/commands/memory.js +361 -0
  147. package/lib/commands/mind.js +71 -0
  148. package/lib/commands/mon.js +581 -0
  149. package/lib/commands/monitoring.js +86 -0
  150. package/lib/commands/motion.js +22 -0
  151. package/lib/commands/notes.js +1246 -0
  152. package/lib/commands/notesBatch.js +160 -0
  153. package/lib/commands/notesBridges.js +56 -0
  154. package/lib/commands/notesLinks.js +60 -0
  155. package/lib/commands/notification.js +57 -0
  156. package/lib/commands/nucleus.js +123 -0
  157. package/lib/commands/objective.js +240 -0
  158. package/lib/commands/offload.js +89 -0
  159. package/lib/commands/onboard.js +329 -0
  160. package/lib/commands/open.js +56 -0
  161. package/lib/commands/org.js +276 -0
  162. package/lib/commands/peek.js +28 -0
  163. package/lib/commands/plan.js +944 -0
  164. package/lib/commands/plugin-doctor.js +1091 -0
  165. package/lib/commands/plugin.js +649 -0
  166. package/lib/commands/policy.js +78 -0
  167. package/lib/commands/powers.js +296 -0
  168. package/lib/commands/presence.js +89 -0
  169. package/lib/commands/prompt.js +350 -0
  170. package/lib/commands/provider.js +159 -0
  171. package/lib/commands/qlarity.js +706 -0
  172. package/lib/commands/quoxbox.js +270 -0
  173. package/lib/commands/quoxchat.js +275 -0
  174. package/lib/commands/quoxseo.js +378 -0
  175. package/lib/commands/reach.js +159 -0
  176. package/lib/commands/role.js +286 -0
  177. package/lib/commands/router.js +77 -0
  178. package/lib/commands/rows.js +358 -0
  179. package/lib/commands/run.js +187 -0
  180. package/lib/commands/safety.js +107 -0
  181. package/lib/commands/scim.js +88 -0
  182. package/lib/commands/scope.js +58 -0
  183. package/lib/commands/security.js +157 -0
  184. package/lib/commands/server.js +292 -0
  185. package/lib/commands/service.js +89 -0
  186. package/lib/commands/settings.js +99 -0
  187. package/lib/commands/setup.js +569 -0
  188. package/lib/commands/skillManifest.js +113 -0
  189. package/lib/commands/skillRightsFormat.js +38 -0
  190. package/lib/commands/skills.js +687 -0
  191. package/lib/commands/slot.js +1451 -0
  192. package/lib/commands/sso.js +229 -0
  193. package/lib/commands/status.js +137 -0
  194. package/lib/commands/tag.js +91 -0
  195. package/lib/commands/team.js +142 -0
  196. package/lib/commands/tg.js +240 -0
  197. package/lib/commands/tiktok.js +218 -0
  198. package/lib/commands/tips.js +23 -0
  199. package/lib/commands/tool.js +304 -0
  200. package/lib/commands/trace.js +530 -0
  201. package/lib/commands/trigger.js +223 -0
  202. package/lib/commands/update.js +46 -0
  203. package/lib/commands/vault.js +1447 -0
  204. package/lib/commands/vm.js +237 -0
  205. package/lib/commands/voice.js +213 -0
  206. package/lib/commands/volt.js +570 -0
  207. package/lib/commands/walkthrough.js +32 -0
  208. package/lib/commands/watch.js +167 -0
  209. package/lib/commands/webhook.js +137 -0
  210. package/lib/commands/welcome.js +14 -0
  211. package/lib/commands/workflow.js +660 -0
  212. package/lib/commands/x.js +448 -0
  213. package/lib/core/actingAs.js +136 -0
  214. package/lib/core/activeSummary.js +186 -0
  215. package/lib/core/aee.js +53 -0
  216. package/lib/core/agentAlias.js +163 -0
  217. package/lib/core/approvalContext.js +61 -0
  218. package/lib/core/aside.js +54 -0
  219. package/lib/core/askDecision.js +60 -0
  220. package/lib/core/auth.js +501 -0
  221. package/lib/core/banner.js +143 -0
  222. package/lib/core/brainPrime.js +90 -0
  223. package/lib/core/callerSource.js +129 -0
  224. package/lib/core/claudeLs.js +94 -0
  225. package/lib/core/cliBuild.js +62 -0
  226. package/lib/core/client.js +693 -0
  227. package/lib/core/commandRegistrars.js +121 -0
  228. package/lib/core/completions.js +163 -0
  229. package/lib/core/config.js +669 -0
  230. package/lib/core/confirm.js +37 -0
  231. package/lib/core/customHats.js +168 -0
  232. package/lib/core/deeplinks.js +76 -0
  233. package/lib/core/doneEngineFetch.js +227 -0
  234. package/lib/core/errors.js +441 -0
  235. package/lib/core/global-flags.js +190 -0
  236. package/lib/core/governedSession.js +402 -0
  237. package/lib/core/hyperion.js +102 -0
  238. package/lib/core/inboxResolution.js +65 -0
  239. package/lib/core/lazyCommands.js +122 -0
  240. package/lib/core/liveSessionsReport.js +112 -0
  241. package/lib/core/offload.js +504 -0
  242. package/lib/core/org-override.js +52 -0
  243. package/lib/core/orgResolve.js +70 -0
  244. package/lib/core/output.js +239 -0
  245. package/lib/core/peek.js +126 -0
  246. package/lib/core/prodEnv.js +7 -0
  247. package/lib/core/profileUsers.js +51 -0
  248. package/lib/core/promptCancel.js +18 -0
  249. package/lib/core/quoxglyph.js +158 -0
  250. package/lib/core/repoPath.js +11 -0
  251. package/lib/core/residentAlarm.js +282 -0
  252. package/lib/core/residentAuth.js +243 -0
  253. package/lib/core/residentPump.js +229 -0
  254. package/lib/core/residentSeed.js +106 -0
  255. package/lib/core/rootOptions.js +39 -0
  256. package/lib/core/router.js +168 -0
  257. package/lib/core/selfUpdate.js +188 -0
  258. package/lib/core/sessionHeartbeat.js +192 -0
  259. package/lib/core/sessionsOverview.js +224 -0
  260. package/lib/core/slotKeeper.js +527 -0
  261. package/lib/core/slotWake.js +120 -0
  262. package/lib/core/supervisorEngine.js +933 -0
  263. package/lib/core/systemdInstaller.js +256 -0
  264. package/lib/core/version.js +87 -0
  265. package/lib/core/vm.js +361 -0
  266. package/lib/core/vmTui.js +86 -0
  267. package/lib/core/walkthrough.js +35 -0
  268. package/lib/hyperion/deck.js +29 -0
  269. package/lib/lucid/LucidCard.jsx +68 -0
  270. package/lib/lucid/LucidIntro.jsx +92 -0
  271. package/lib/lucid/blind.js +71 -0
  272. package/lib/lucid/cards.js +86 -0
  273. package/lib/lucid/deck.js +32 -0
  274. package/lib/lucid/fmt.js +139 -0
  275. package/lib/lucid/markdown.js +81 -0
  276. package/lib/lucid/motion.js +56 -0
  277. package/lib/lucid/sitrep.js +173 -0
  278. package/lib/plugins/loader.js +93 -0
  279. package/lib/quoxon/activity.js +116 -0
  280. package/lib/quoxon/aocl.js +117 -0
  281. package/lib/quoxon/api.js +952 -0
  282. package/lib/quoxon/app.js +4032 -0
  283. package/lib/quoxon/approvalFacts.js +94 -0
  284. package/lib/quoxon/approvalsView.js +325 -0
  285. package/lib/quoxon/asideView.js +40 -0
  286. package/lib/quoxon/ask.js +121 -0
  287. package/lib/quoxon/away.js +52 -0
  288. package/lib/quoxon/bang.js +224 -0
  289. package/lib/quoxon/bulkRisk.js +17 -0
  290. package/lib/quoxon/capabilities.js +82 -0
  291. package/lib/quoxon/cells.js +53 -0
  292. package/lib/quoxon/commandShort.js +112 -0
  293. package/lib/quoxon/commands.js +479 -0
  294. package/lib/quoxon/connect.js +654 -0
  295. package/lib/quoxon/connectCard.js +45 -0
  296. package/lib/quoxon/consent.js +86 -0
  297. package/lib/quoxon/context.js +68 -0
  298. package/lib/quoxon/copy.js +34 -0
  299. package/lib/quoxon/countdownLine.js +11 -0
  300. package/lib/quoxon/demo.autoplay.js +29 -0
  301. package/lib/quoxon/demo.fixtures.js +24 -0
  302. package/lib/quoxon/demo.js +76 -0
  303. package/lib/quoxon/devObserver.js +20 -0
  304. package/lib/quoxon/dividerAocl.js +191 -0
  305. package/lib/quoxon/dividerRule.js +57 -0
  306. package/lib/quoxon/dragonModes.js +150 -0
  307. package/lib/quoxon/dragonView.js +41 -0
  308. package/lib/quoxon/earlyInput.js +40 -0
  309. package/lib/quoxon/echo.js +15 -0
  310. package/lib/quoxon/egg.js +36 -0
  311. package/lib/quoxon/events.js +677 -0
  312. package/lib/quoxon/feedback.js +50 -0
  313. package/lib/quoxon/gallery.js +237 -0
  314. package/lib/quoxon/handoffOffer.js +78 -0
  315. package/lib/quoxon/hatSwitch.js +37 -0
  316. package/lib/quoxon/headless.js +161 -0
  317. package/lib/quoxon/hostUrls.js +41 -0
  318. package/lib/quoxon/indicator.js +197 -0
  319. package/lib/quoxon/intent.js +52 -0
  320. package/lib/quoxon/keys.js +164 -0
  321. package/lib/quoxon/launcher.js +103 -0
  322. package/lib/quoxon/main.js +176 -0
  323. package/lib/quoxon/markdown.js +341 -0
  324. package/lib/quoxon/markdownSettle.js +19 -0
  325. package/lib/quoxon/matrixDomains.json +46 -0
  326. package/lib/quoxon/mentions.js +90 -0
  327. package/lib/quoxon/mind.js +103 -0
  328. package/lib/quoxon/mode.js +46 -0
  329. package/lib/quoxon/model.js +311 -0
  330. package/lib/quoxon/modelPrefs.js +345 -0
  331. package/lib/quoxon/motion/clock.js +70 -0
  332. package/lib/quoxon/motion/color.js +35 -0
  333. package/lib/quoxon/motion/frame.js +14 -0
  334. package/lib/quoxon/motion/level.js +91 -0
  335. package/lib/quoxon/motion/reveal.js +54 -0
  336. package/lib/quoxon/motion/ring.js +79 -0
  337. package/lib/quoxon/mouse.js +211 -0
  338. package/lib/quoxon/noai.js +57 -0
  339. package/lib/quoxon/objects/agent.js +61 -0
  340. package/lib/quoxon/objects/away.js +72 -0
  341. package/lib/quoxon/objects/crystal.js +100 -0
  342. package/lib/quoxon/objects/fetch.js +182 -0
  343. package/lib/quoxon/objects/fleet.js +149 -0
  344. package/lib/quoxon/objects/health.js +186 -0
  345. package/lib/quoxon/objects/index.js +106 -0
  346. package/lib/quoxon/objects/ladder.js +71 -0
  347. package/lib/quoxon/objects/memory.js +44 -0
  348. package/lib/quoxon/objects/mirror.js +137 -0
  349. package/lib/quoxon/objects/model.js +118 -0
  350. package/lib/quoxon/objects/peek.js +17 -0
  351. package/lib/quoxon/objects/proof.js +110 -0
  352. package/lib/quoxon/objects/prove.js +130 -0
  353. package/lib/quoxon/objects/replay.js +125 -0
  354. package/lib/quoxon/objects/sitrep.js +58 -0
  355. package/lib/quoxon/objects/skill.js +105 -0
  356. package/lib/quoxon/objects/view.js +87 -0
  357. package/lib/quoxon/objects/workflow.js +119 -0
  358. package/lib/quoxon/observers.js +42 -0
  359. package/lib/quoxon/onboarding/copy.js +263 -0
  360. package/lib/quoxon/onboarding/credential.js +108 -0
  361. package/lib/quoxon/onboarding/detect.js +22 -0
  362. package/lib/quoxon/onboarding/dockerOffer.js +45 -0
  363. package/lib/quoxon/onboarding/failureCause.js +33 -0
  364. package/lib/quoxon/onboarding/finish.js +134 -0
  365. package/lib/quoxon/onboarding/index.js +94 -0
  366. package/lib/quoxon/onboarding/installRunner.js +284 -0
  367. package/lib/quoxon/onboarding/intent.js +53 -0
  368. package/lib/quoxon/onboarding/journey.js +73 -0
  369. package/lib/quoxon/onboarding/preflight.js +156 -0
  370. package/lib/quoxon/onboarding/probe.js +65 -0
  371. package/lib/quoxon/onboarding/runner.js +58 -0
  372. package/lib/quoxon/onboarding/view.js +682 -0
  373. package/lib/quoxon/orgLabel.js +10 -0
  374. package/lib/quoxon/orgSwitch.js +33 -0
  375. package/lib/quoxon/ownBroadcast.js +22 -0
  376. package/lib/quoxon/palette.js +43 -0
  377. package/lib/quoxon/print.js +98 -0
  378. package/lib/quoxon/profileSwitch.js +53 -0
  379. package/lib/quoxon/promptHistory.js +41 -0
  380. package/lib/quoxon/providers.js +48 -0
  381. package/lib/quoxon/qart.js +348 -0
  382. package/lib/quoxon/qdragon.js +410 -0
  383. package/lib/quoxon/qdragonFly.js +61 -0
  384. package/lib/quoxon/qdragonScenes.js +282 -0
  385. package/lib/quoxon/qeyes.js +213 -0
  386. package/lib/quoxon/qglyph.js +143 -0
  387. package/lib/quoxon/qhello.js +58 -0
  388. package/lib/quoxon/qmotion.js +132 -0
  389. package/lib/quoxon/render.js +1167 -0
  390. package/lib/quoxon/resend.js +72 -0
  391. package/lib/quoxon/roster.js +307 -0
  392. package/lib/quoxon/route.js +39 -0
  393. package/lib/quoxon/rows.js +711 -0
  394. package/lib/quoxon/rowsStore.js +80 -0
  395. package/lib/quoxon/rules.js +130 -0
  396. package/lib/quoxon/screenContext.js +150 -0
  397. package/lib/quoxon/screenFrames.js +254 -0
  398. package/lib/quoxon/session.js +238 -0
  399. package/lib/quoxon/sessionDead.js +17 -0
  400. package/lib/quoxon/signin.js +125 -0
  401. package/lib/quoxon/skills.js +30 -0
  402. package/lib/quoxon/slash/agents.js +112 -0
  403. package/lib/quoxon/slash/argSources.js +65 -0
  404. package/lib/quoxon/slash/args.js +65 -0
  405. package/lib/quoxon/slash/automate.js +89 -0
  406. package/lib/quoxon/slash/broadcast.js +140 -0
  407. package/lib/quoxon/slash/broadcastRun.js +108 -0
  408. package/lib/quoxon/slash/card.js +30 -0
  409. package/lib/quoxon/slash/challenge.js +114 -0
  410. package/lib/quoxon/slash/cliCommand.js +53 -0
  411. package/lib/quoxon/slash/cliLeaves.json +948 -0
  412. package/lib/quoxon/slash/compact.js +90 -0
  413. package/lib/quoxon/slash/confirm.js +30 -0
  414. package/lib/quoxon/slash/crystallize.js +69 -0
  415. package/lib/quoxon/slash/dashboard.js +136 -0
  416. package/lib/quoxon/slash/draft.js +54 -0
  417. package/lib/quoxon/slash/hatWalk.js +206 -0
  418. package/lib/quoxon/slash/inverses.js +77 -0
  419. package/lib/quoxon/slash/killer.js +24 -0
  420. package/lib/quoxon/slash/killerApi.js +16 -0
  421. package/lib/quoxon/slash/ledger.js +88 -0
  422. package/lib/quoxon/slash/listView.js +30 -0
  423. package/lib/quoxon/slash/modelCmd.js +261 -0
  424. package/lib/quoxon/slash/palette.js +118 -0
  425. package/lib/quoxon/slash/paletteController.js +169 -0
  426. package/lib/quoxon/slash/panels.js +116 -0
  427. package/lib/quoxon/slash/picker.js +56 -0
  428. package/lib/quoxon/slash/restart.js +202 -0
  429. package/lib/quoxon/slash/resume.js +59 -0
  430. package/lib/quoxon/slash/rowCmd.js +157 -0
  431. package/lib/quoxon/slash/rowWalk.js +175 -0
  432. package/lib/quoxon/slash/settings.js +60 -0
  433. package/lib/quoxon/slash/spawn.js +177 -0
  434. package/lib/quoxon/slash/stepUp.js +274 -0
  435. package/lib/quoxon/slash/stepUpCard.js +60 -0
  436. package/lib/quoxon/slash/stepUpCmds.js +78 -0
  437. package/lib/quoxon/slash/undoApi.js +38 -0
  438. package/lib/quoxon/slash/undoCmd.js +49 -0
  439. package/lib/quoxon/slash/undoWish.js +15 -0
  440. package/lib/quoxon/slash/watch.js +164 -0
  441. package/lib/quoxon/slash/wish.js +23 -0
  442. package/lib/quoxon/slash/wishCmd.js +32 -0
  443. package/lib/quoxon/slotChat.js +300 -0
  444. package/lib/quoxon/slotFormat.js +195 -0
  445. package/lib/quoxon/slotRun.js +131 -0
  446. package/lib/quoxon/sound/index.js +186 -0
  447. package/lib/quoxon/sound/mapping.js +120 -0
  448. package/lib/quoxon/sound/output.js +52 -0
  449. package/lib/quoxon/sound/synth.js +122 -0
  450. package/lib/quoxon/stray.js +22 -0
  451. package/lib/quoxon/suggestCommand.js +59 -0
  452. package/lib/quoxon/term.js +141 -0
  453. package/lib/quoxon/textBuffer.js +416 -0
  454. package/lib/quoxon/theme.js +286 -0
  455. package/lib/quoxon/tips.js +433 -0
  456. package/lib/quoxon/toolError.js +41 -0
  457. package/lib/quoxon/transcript.js +188 -0
  458. package/lib/quoxon/web/qface.js +89 -0
  459. package/lib/quoxon/welcome.js +52 -0
  460. package/lib/quoxon/whatsnew.js +136 -0
  461. package/lib/quoxon/whatsnew.jsonl +89 -0
  462. package/lib/quoxon/windowOrg.js +113 -0
  463. package/lib/server/diag.js +129 -0
  464. package/lib/server/doctor.js +71 -0
  465. package/lib/server/install.js +149 -0
  466. package/lib/server/pluginEnable.js +161 -0
  467. package/lib/server/skew.js +56 -0
  468. package/lib/server/snapshot.js +131 -0
  469. package/lib/server/status.js +128 -0
  470. package/lib/server/sys.js +65 -0
  471. package/lib/server/uninstall.js +63 -0
  472. package/lib/server/update.js +234 -0
  473. package/lib/toolOutputShape.js +315 -0
  474. package/lib/tui/app.jsx +289 -0
  475. package/lib/tui/components/Breadcrumb.jsx +42 -0
  476. package/lib/tui/components/ChatInput.jsx +68 -0
  477. package/lib/tui/components/Confirm.jsx +32 -0
  478. package/lib/tui/components/DataTable.jsx +270 -0
  479. package/lib/tui/components/DetailPanel.jsx +241 -0
  480. package/lib/tui/components/HelpOverlay.jsx +67 -0
  481. package/lib/tui/components/Layout.jsx +129 -0
  482. package/lib/tui/components/Palette.jsx +141 -0
  483. package/lib/tui/components/SearchInput.jsx +125 -0
  484. package/lib/tui/components/Sparkline.jsx +55 -0
  485. package/lib/tui/components/StatusBar.jsx +133 -0
  486. package/lib/tui/components/Toast.jsx +52 -0
  487. package/lib/tui/hooks/useApi.js +44 -0
  488. package/lib/tui/hooks/useHealth.js +90 -0
  489. package/lib/tui/hooks/usePolledApi.js +86 -0
  490. package/lib/tui/hooks/useTerminalSize.js +46 -0
  491. package/lib/tui/index.js +56 -0
  492. package/lib/tui/intro/Intro.jsx +332 -0
  493. package/lib/tui/jsx-loader.js +32 -0
  494. package/lib/tui/keys.js +87 -0
  495. package/lib/tui/registry.js +49 -0
  496. package/lib/tui/theme.js +103 -0
  497. package/lib/tui/views/AdminView.jsx +398 -0
  498. package/lib/tui/views/AgentView.jsx +204 -0
  499. package/lib/tui/views/BackupView.jsx +225 -0
  500. package/lib/tui/views/BrainView.jsx +223 -0
  501. package/lib/tui/views/ChatView.jsx +468 -0
  502. package/lib/tui/views/FilesView.jsx +218 -0
  503. package/lib/tui/views/FleetView.jsx +234 -0
  504. package/lib/tui/views/GovernanceView.jsx +453 -0
  505. package/lib/tui/views/HealthView.jsx +216 -0
  506. package/lib/tui/views/HyperionDeck.jsx +79 -0
  507. package/lib/tui/views/InboxView.jsx +228 -0
  508. package/lib/tui/views/LucidDeck.jsx +719 -0
  509. package/lib/tui/views/MainMenu.jsx +123 -0
  510. package/lib/tui/views/MemoryView.jsx +377 -0
  511. package/lib/tui/views/ObjectivesView.jsx +230 -0
  512. package/lib/tui/views/OrgView.jsx +229 -0
  513. package/lib/tui/views/ToolView.jsx +221 -0
  514. package/lib/tui/views/VaultView.jsx +422 -0
  515. package/lib/tui/views/WorkflowView.jsx +329 -0
  516. package/package.json +75 -4
@@ -0,0 +1,682 @@
1
+ /**
2
+ * Getting started: the Ink view bare `quox` shows when no server is configured (FIRST_RUN_JOURNEY.md, C2a).
3
+ * A labelled, scripted setup guide: no model, no account, no network beyond local checks. It is deliberately a
4
+ * separate module from app.js; main.js picks one or the other.
5
+ *
6
+ * Steps: ask -> offer (Y/n) | declined -> preflight -> credential -> key -> install (installStop) ->
7
+ * ownerEmail -> ownerPassword -> finishing -> finale | notyet
8
+ * With no finisher (the dry run) install goes straight to the finale, as in C2a.
9
+ * Every line of copy comes from ./copy.js.
10
+ */
11
+ import React from 'react';
12
+ import { Box, Static, Text, useStdin, useStdout } from 'ink';
13
+ import { HistoryItem, Row, introAnimates, clampCols, AskCard } from '../render.js';
14
+ import { c, clip } from '../theme.js';
15
+ import { parseChunk, initialKeyState, PASTE_ON, PASTE_OFF } from '../keys.js';
16
+ import { packageVersion } from '../welcome.js';
17
+ import * as T from './copy.js';
18
+ import { matchIntent, yesNo, parseConnect } from './intent.js';
19
+ import { loadJourney, saveJourney, clearJourney, resumeStep } from './journey.js';
20
+ import { pendingRows, runPreflight, preflightPassed, detectOllama, defaultDeps } from './preflight.js';
21
+ import { credentialOptions, KEY_PROVIDERS, normaliseOllamaUrl, nextSelectable } from './credential.js';
22
+ import { needsConsent, providerLabel } from '../consent.js';
23
+ import { HelloIntro, helloIntroMs } from '../qmotion.js';
24
+ import { launchEgg } from '../egg.js';
25
+ import { passwordProblem, emailOk, generatePassword } from './finish.js';
26
+ import { dockerOffer } from './dockerOffer.js';
27
+
28
+ // FIRSTRUN: a number pick means its example ("1" is "watch my servers"); the request is replayed as the first real turn.
29
+ const requestText = (text) => { const n = /^\s*([1-9])\s*$/.exec(String(text ?? '')); return n && T.EXAMPLES[Number(n[1]) - 1] ? T.EXAMPLES[Number(n[1]) - 1] : text; };
30
+
31
+ const h = React.createElement;
32
+ const GLYPH = { ok: '✔', fail: '✖', warn: '⚠', running: '◐', pending: '·' };
33
+ const TONE = { ok: 'ok', fail: 'fail', warn: 'warn', running: 'sky', pending: 'dim' };
34
+ const fmtS = (ms) => `${(Math.max(0, ms) / 1000).toFixed(1)}s`;
35
+
36
+ /** Word wrap to `width` columns; each line is its own <Text>, so every wrapped line keeps its colour. */
37
+ export function wrapWords(text, width) {
38
+ const w = Math.max(8, width);
39
+ const out = [];
40
+ for (const para of String(text ?? '').split('\n')) {
41
+ let line = '';
42
+ for (const word of para.split(/ +/)) {
43
+ let wd = word;
44
+ while ([...wd].length > w) { if (line) { out.push(line); line = ''; } out.push([...wd].slice(0, w).join('')); wd = [...wd].slice(w).join(''); }
45
+ if (!line) line = wd;
46
+ else if ([...line].length + 1 + [...wd].length <= w) line += ' ' + wd;
47
+ else { out.push(line); line = wd; }
48
+ }
49
+ out.push(line);
50
+ }
51
+ return out;
52
+ }
53
+ const Para = ({ text, width, color, bold }) => h(Box, { flexDirection: 'column' }, ...wrapWords(text, width).map((l, i) => h(Text, { key: i, color, bold }, l)));
54
+
55
+ // ---- static items -----------------------------------------------------------------------------
56
+ function Item({ item, cols }) {
57
+ switch (item.kind) {
58
+ case 'intro': return h(HelloIntro, { item, cols, text: T.HELLO });
59
+ case 'welcome': case 'user': return h(HistoryItem, { item, cols });
60
+ case 'header': return h(Box, { flexDirection: 'column', marginTop: 1 },
61
+ h(Text, null, h(Text, { color: c('violet'), bold: true }, T.LABEL), h(Text, { color: c('dim') }, ' ' + '─'.repeat(Math.max(1, cols - T.LABEL.length - 1)))),
62
+ h(Text, { color: c('dim') }, clip(T.LABEL_SUB, cols)));
63
+ case 'ask': return h(AskCard, { text: item.text, cols }); // QUOXON-ASK: the same card as an approval
64
+ case 'guide': return h(Box, { marginTop: item.gap === false ? 0 : 1, paddingLeft: 2 }, h(Para, { width: cols - 2, color: c(item.tone || 'text'), bold: Boolean(item.bold), text: item.text }));
65
+ case 'dim': return h(Box, { paddingLeft: 2 }, h(Para, { width: cols - 2, color: c(item.tone || 'dim'), text: item.text }));
66
+ case 'cmd': return h(Box, { paddingLeft: 4 }, h(Para, { width: cols - 4, color: c('sky'), text: item.text }));
67
+ case 'long': { // a URL or hash that must never be hard-wrapped (copy, click): one line if it fits, else its own line
68
+ if ([...item.text].length <= cols - 2) return h(Box, { marginTop: item.gap === false ? 0 : 1, paddingLeft: 2 }, h(Text, { color: c(item.tone || 'text') }, item.text));
69
+ return h(Box, { flexDirection: 'column', marginTop: item.gap === false ? 0 : 1, paddingLeft: 2 },
70
+ h(Para, { width: cols - 2, color: c(item.tone || 'text'), text: item.lead }),
71
+ h(Box, { width: [...item.value].length + 2 }, h(Text, { color: c('sky'), wrap: 'truncate-end' }, item.value)));
72
+ }
73
+ case 'list': return h(Box, { flexDirection: 'column', paddingLeft: 2 }, ...item.items.map((t, i) => h(Box, { key: i, flexDirection: 'row' },
74
+ h(Text, { color: c('sky') }, item.numbered ? `${i + 1} ` : '· '), h(Para, { width: cols - 5, color: c('text'), text: t }))));
75
+ case 'rows': return h(Box, { flexDirection: 'column', marginTop: 1 }, ...item.rows.map((r, i) => h(CheckRow, { key: i, r, cols })));
76
+ default: return null;
77
+ }
78
+ }
79
+
80
+ function CheckRow({ r, cols }) {
81
+ return h(Box, { flexDirection: 'column' },
82
+ h(Row, { glyph: GLYPH[r.status] || '·', tone: TONE[r.status] || 'dim', name: r.name, body: r.body, tail: r.tail, cols }),
83
+ r.status === 'fail' && r.fix ? h(Box, { paddingLeft: 6, flexDirection: 'column' },
84
+ h(Para, { width: cols - 6, color: c('placeholder'), text: r.fix }),
85
+ r.link ? h(Text, { color: c('sky') }, clip(r.link, cols - 6)) : null) : null);
86
+ }
87
+
88
+ function InputLine({ text, placeholder, cols, masked = false }) {
89
+ const shown = masked ? '•'.repeat(Math.min([...text].length, Math.max(4, cols - 20))) : text;
90
+ const room = cols - 4;
91
+ const tail = [...shown].slice(-room).join('');
92
+ return h(Box, { marginTop: 1 },
93
+ h(Text, { color: c('sky'), bold: true }, '› '),
94
+ tail ? h(Text, { color: c('input') }, tail) : null,
95
+ h(Text, { inverse: true }, ' '),
96
+ !tail && placeholder ? h(Text, { color: c('placeholder') }, ' ' + clip(placeholder, room - 2)) : null,
97
+ masked && text ? h(Text, { color: c('dim') }, ` ${[...text].length} chars`) : null);
98
+ }
99
+
100
+ const Hint = ({ text, cols }) => h(Box, { paddingLeft: 2 }, h(Text, { color: c('dim') }, clip(text, cols - 2)));
101
+
102
+ // ---- the view ---------------------------------------------------------------------------------
103
+ export function GettingStarted({
104
+ columns, version = '0.3.1', cwd = '', welcome = false, animateIntro, earlyInput = null,
105
+ journeyPath, dryRun = false, runner = null, preflightDeps, runChecks = runPreflight, findOllama = detectOllama,
106
+ requestCredential, broker = null, finisher = null, onExit = () => {}, now = () => Date.now()
107
+ }) {
108
+ const { stdin, setRawMode, isRawModeSupported } = useStdin();
109
+ const { stdout } = useStdout();
110
+ const cols = clampCols(columns ?? ((stdout?.columns || 80) - 1));
111
+ const [, force] = React.useState(0);
112
+ const rerender = () => force(n => n + 1);
113
+ const seq = React.useRef(0);
114
+ const nid = () => `g${seq.current++}`;
115
+
116
+ const saved = React.useMemo(() => (journeyPath ? loadJourney(journeyPath) : null), []);
117
+ const resumeAt = resumeStep(saved);
118
+ const introItem = { id: 'intro', kind: 'intro', text: `${version} · Getting started · ${cwd}` };
119
+ const opening = () => {
120
+ const items = [introItem, ...(welcome ? [{ id: 'welcome', kind: 'welcome', version: packageVersion() }] : []), { id: 'hdr', kind: 'header' }];
121
+ if (resumeAt) {
122
+ items.push({ id: 'resume', kind: 'guide', text: T.RESUME });
123
+ if (saved.request) items.push({ id: 'resumeReq', kind: 'dim', text: T.resumeRequest(requestText(saved.request)) });
124
+ } else {
125
+ items.push({ id: 'greet', kind: 'guide', text: T.GREETING_REST }, { id: 'q', kind: 'guide', text: T.QUESTION }, { id: 'ex', kind: 'list', items: T.EXAMPLES, numbered: true });
126
+ }
127
+ return items;
128
+ };
129
+
130
+ const S = React.useRef({
131
+ history: [], step: 'ask', input: '', intent: saved?.intent || null, request: saved?.request ? requestText(saved.request) : null, provider: saved?.provider === 'skip' ? 'skip' : null,
132
+ rows: [], fixNote: null, options: [], sel: 1, flash: null, install: null, logs: [], showLog: false, done: false, secret: null,
133
+ installStage: saved?.installStage || null, installDir: saved?.installDir || null, dashboardUrl: saved?.dashboardUrl || null,
134
+ ownerEmail: saved?.ownerEmail || null, finRows: [], stopping: false, vault: null
135
+ });
136
+ const set = (patch) => { S.current = { ...S.current, ...(typeof patch === 'function' ? patch(S.current) : patch) }; rerender(); };
137
+ const push = (...items) => set(s => ({ history: [...s.history, ...items.map(it => ({ id: nid(), ...it }))] }));
138
+ const persist = (extra = {}) => {
139
+ if (!journeyPath) return;
140
+ const c = S.current;
141
+ saveJourney({ step: c.step, intent: c.intent, request: c.request, provider: c.provider, installStage: c.installStage, installDir: c.installDir, dashboardUrl: c.dashboardUrl, ownerEmail: c.ownerEmail, ...(c.ollamaUrl ? { ollamaUrl: c.ollamaUrl } : {}), ...extra }, journeyPath, () => new Date(now()).toISOString());
142
+ };
143
+ const live = React.useRef(true);
144
+ React.useEffect(() => () => { live.current = false; }, []);
145
+
146
+ // ---- intro (Orbit & Lock), then the opening lines move into <Static> --------------------------
147
+ const animating = animateIntro ?? introAnimates(stdout?.isTTY);
148
+ // Orbit & Lock, then the Q says hello (T.HELLO decodes in beside it); rarely the dragon's googly-eye greeting after that.
149
+ const eye = React.useMemo(() => (animating && animateIntro === undefined ? launchEgg({ tty: Boolean(stdout?.isTTY) }) : false), []);
150
+ const introMs = helloIntroMs({ eye, text: T.HELLO });
151
+ const introStart = React.useRef(Date.now());
152
+ const [, setFrame] = React.useState(0);
153
+ const introDone = React.useRef(false);
154
+ const finishIntro = () => {
155
+ if (introDone.current) return;
156
+ introDone.current = true;
157
+ S.current = { ...S.current, history: [...opening(), ...S.current.history] };
158
+ if (resumeAt === 'offer') offerLines();
159
+ else if (resumeAt === 'preflight') startPreflight();
160
+ else rerender();
161
+ };
162
+ React.useEffect(() => {
163
+ if (!animating) { finishIntro(); return undefined; }
164
+ introStart.current = Date.now();
165
+ const id = setInterval(() => {
166
+ if (Date.now() - introStart.current >= introMs) { clearInterval(id); setTimeout(finishIntro, 0); }
167
+ setFrame(f => f + 1);
168
+ }, 50);
169
+ return () => clearInterval(id);
170
+ }, []);
171
+
172
+ // ---- steps ------------------------------------------------------------------------------------
173
+ const exit = (result) => {
174
+ if (S.current.done) return;
175
+ S.current.secret?.drop?.();
176
+ set({ done: true, secret: null });
177
+ setTimeout(() => onExit(result), 0);
178
+ };
179
+
180
+ function offerLines() {
181
+ const items = [{ kind: 'guide', text: T.OFFER }];
182
+ items.push({ kind: 'ask', text: T.OFFER_ASK }, { kind: 'dim', text: T.ALREADY });
183
+ push(...items);
184
+ set({ step: 'offer' });
185
+ persist();
186
+ }
187
+
188
+ function onRequest(text) {
189
+ const m = matchIntent(text);
190
+ if (m.intent === 'empty') return;
191
+ push({ kind: 'user', text });
192
+ if (m.intent === 'connect') { push({ kind: 'guide', text: T.CONNECT_INTENT_LINE }); set({ step: 'ask' }); return; }
193
+ set({ intent: m.intent, request: requestText(text) });
194
+ push({ kind: 'guide', text: m.intent === 'offscript' ? T.DEFERRAL : T.INTENT_LINES[m.intent] });
195
+ offerLines();
196
+ }
197
+
198
+ function onSlash(text) {
199
+ const t = text.trim();
200
+ if (/^\/(exit|quit|q)$/i.test(t)) { push({ kind: 'user', text: t }, { kind: 'dim', text: T.BYE }); persist(); exit({ action: 'exit' }); return true; }
201
+ if (/^\/help$/i.test(t)) { push({ kind: 'user', text: t }, { kind: 'list', items: T.HELP.map(([k, v]) => `${k} ${v}`) }); return true; }
202
+ const conn = parseConnect(t);
203
+ if (conn) {
204
+ push({ kind: 'user', text: t });
205
+ if (!conn.ok) { push({ kind: 'dim', tone: 'warn', text: conn.reason === 'url' ? T.connectBadUrl(conn.value) : T.CONNECT_USAGE }); return true; }
206
+ push({ kind: 'guide', text: T.connectChecking(conn.url) });
207
+ exit({ action: 'connect', url: conn.url });
208
+ return true;
209
+ }
210
+ push({ kind: 'user', text: t }, { kind: 'dim', tone: 'warn', text: T.SLASH_UNKNOWN });
211
+ return true;
212
+ }
213
+
214
+ function submit() {
215
+ const s = S.current;
216
+ const text = s.input.trim();
217
+ set({ input: '' });
218
+ if (text.startsWith('/')) { onSlash(text); return; }
219
+ if (s.step === 'ask') return onRequest(text);
220
+ if (s.step === 'offer' || s.step === 'declined') {
221
+ const yn = yesNo(text);
222
+ if (yn === true) { push({ kind: 'user', text: text || 'yes' }); startPreflight(); return; }
223
+ if (yn === false) { push({ kind: 'user', text }, { kind: 'guide', text: T.DECLINED }, { kind: 'dim', text: T.DECLINED_HINT }); set({ step: 'declined' }); persist(); return; }
224
+ return onRequest(text); // a new request instead of an answer
225
+ }
226
+ }
227
+
228
+ async function startPreflight() {
229
+ set({ step: 'preflight', rows: pendingRows(), fixNote: null });
230
+ persist();
231
+ // resuming an install this guide started: its own ports being busy is expected
232
+ const deps = S.current.installStage ? { ...(preflightDeps || defaultDeps()), ownInstall: true } : preflightDeps;
233
+ const rows = await runChecks(deps, (r) => { if (live.current) set(s => ({ rows: s.rows.map(x => (x.id === r.id ? r : x)) })); });
234
+ if (!live.current || S.current.done) return;
235
+ if (!preflightPassed(rows)) { set({ step: 'preflightFail', rows, fixNote: T.PREFLIGHT_FAIL }); return; }
236
+ push({ kind: 'guide', text: T.PREFLIGHT_TITLE, bold: true }, { kind: 'rows', rows }, { kind: 'guide', tone: 'ok', text: T.PREFLIGHT_OK });
237
+ toCredential();
238
+ }
239
+
240
+ async function toCredential() {
241
+ set({ rows: [] });
242
+ if (S.current.provider === 'skip') { // Z2H-R2B: the owner already chose "Skip for now"; never re-ask on resume (/connect stays open)
243
+ push({ kind: 'guide', tone: 'warn', text: T.CRED_SKIPPED });
244
+ startInstall();
245
+ return;
246
+ }
247
+ const ollama = await findOllama();
248
+ if (!live.current || S.current.done) return;
249
+ const options = credentialOptions({ ollama });
250
+ push({ kind: 'guide', text: T.CRED_TITLE, bold: true });
251
+ set({ step: 'credential', options, sel: Math.max(0, options.findIndex(o => !o.disabled)), flash: null });
252
+ persist();
253
+ }
254
+
255
+ async function choose(opt) {
256
+ if (!opt) return;
257
+ if (opt.disabled) { set({ flash: T.CRED_CHATGPT_SOON }); return; }
258
+ set({ flash: null, provider: opt.id });
259
+ if (KEY_PROVIDERS.has(opt.id)) {
260
+ set({ step: 'key', input: '' });
261
+ const held = await requestCredential(opt.id); // THE hook: K1 secure card once it merges
262
+ if (!live.current || S.current.done) { held?.drop?.(); return; }
263
+ if (!held) { set({ step: 'credential', input: '' }); return; }
264
+ push({ kind: 'user', text: opt.label }, { kind: 'guide', tone: 'ok', text: T.credHeld(opt.label, held.fingerprint) }, ...(needsConsent(opt.id) ? [{ kind: 'guide', text: T.credConsent(opt.label) }] : []));
265
+ set({ secret: held, input: '' });
266
+ if (needsConsent(opt.id)) { set({ step: 'cloudConsent', cloudConsent: null }); return; } // ask once; the answer is the user's own
267
+
268
+ } else if (opt.id === 'ollama' && !opt.detected) {
269
+ set({ step: 'ollamaUrl', input: '' }); // B3: nothing answered locally; ask for the LAN URL
270
+ return;
271
+ } else {
272
+ push({ kind: 'user', text: opt.label }, { kind: 'guide', tone: opt.id === 'skip' ? 'warn' : 'ok', text: opt.id === 'skip' ? T.CRED_SKIPPED : T.CRED_OLLAMA });
273
+ }
274
+ startInstall();
275
+ }
276
+
277
+ function answerCloudConsent(yes) {
278
+ const p = S.current.provider; const who = providerLabel(p);
279
+ push({ kind: 'user', text: yes ? 'yes' : 'no' }, { kind: 'guide', tone: yes ? 'ok' : 'warn', text: yes ? T.credConsentYes(who) : T.credConsentNo(who, p) });
280
+ set({ cloudConsent: yes });
281
+ startInstall();
282
+ }
283
+
284
+ function submitOllamaUrl() {
285
+ const url = normaliseOllamaUrl(S.current.input);
286
+ if (!url) { set({ flash: T.OLLAMA_URL_BAD }); return; }
287
+ push({ kind: 'user', text: url }, { kind: 'guide', tone: 'ok', text: T.ollamaChosen(url) });
288
+ set({ input: '', flash: null, ollamaUrl: url });
289
+ startInstall();
290
+ }
291
+
292
+ async function startInstall() {
293
+ if (!runner) { // no runner (should not happen outside tests): say so, and give the installer command
294
+ push({ kind: 'guide', text: T.INSTALL_NOT_YET }, { kind: 'cmd', text: T.INSTALL_COMMAND }, { kind: 'dim', text: T.INSTALL_NOT_YET_AFTER });
295
+ if (S.current.secret) { S.current.secret.drop(); push({ kind: 'dim', tone: 'warn', text: T.INSTALL_KEY_GONE }); }
296
+ set({ step: 'notyet', secret: null });
297
+ persist({ step: 'install' });
298
+ return;
299
+ }
300
+ const dry = runner.kind === 'dry-run';
301
+ const healthOnly = !dry && (S.current.installStage === 'start' || S.current.installStage === 'health');
302
+ if (!dry && runner.installDir) set({ installDir: runner.installDir });
303
+ if (!dry && S.current.install == null) {
304
+ if (runner.installDir) push({ kind: 'dim', text: T.installWhere(runner.installDir) });
305
+ if (healthOnly) push({ kind: 'dim', text: T.INSTALL_HEALTH_ONLY });
306
+ }
307
+ const stages = runner.stages.map(st => ({ id: st.id, name: st.id.toUpperCase(), body: st.label, status: 'pending', startedAt: null, ms: null, earlier: false }));
308
+ set({ step: 'install', install: { stages, kind: runner.kind, failed: null, degraded: false }, logs: [], showLog: false, stopping: false });
309
+ persist();
310
+ const ac = new AbortController();
311
+ S.current.abort = ac;
312
+ const res = await runner.run({
313
+ signal: ac.signal, request: S.current.request,
314
+ resumeFrom: S.current.installStage, resumeDashboardUrl: S.current.dashboardUrl,
315
+ onCheckpoint: (id, extra = {}) => { // the last COMPLETED stage, so the next quox resumes honestly
316
+ set({ installStage: id, ...(extra.dashboardUrl ? { dashboardUrl: extra.dashboardUrl } : {}) });
317
+ persist({ step: 'install' });
318
+ },
319
+ onEvent: (ev) => {
320
+ if (!live.current) return;
321
+ if (ev.type === 'log') set(s => ({ logs: [...s.logs, ev.line].slice(-8) }));
322
+ if (ev.type === 'script') push({ kind: 'long', tone: 'dim', gap: false, text: T.installScript(ev.url, ev.sha256, ev.bytes), lead: T.installScriptLead(ev.url, ev.bytes), value: ev.sha256 });
323
+ if (ev.type === 'stage-label') set(s => ({ install: { ...s.install, stages: s.install.stages.map(x => (x.id === ev.id ? { ...x, body: ev.label } : x)) } }));
324
+ if (ev.type === 'stage') set(s => ({ install: { ...s.install, stages: s.install.stages.map(x => (x.id === ev.id ? { ...x, status: ev.status, startedAt: ev.status === 'running' ? ev.at : x.startedAt, ms: ev.ms ?? x.ms, earlier: Boolean(ev.earlier) } : x)) } }));
325
+ }
326
+ });
327
+ S.current.abort = null;
328
+ if (!live.current || S.current.done) return;
329
+ if (res.interrupted || S.current.stopping) {
330
+ push({ kind: 'dim', text: T.INSTALL_STOPPED }); persist({ step: 'install' }); exit({ action: 'exit' }); return;
331
+ }
332
+ if (res.logPath) set({ installLogPath: res.logPath });
333
+ if (res.dashboardUrl) set({ dashboardUrl: res.dashboardUrl });
334
+ const rows = S.current.install.stages.map(st => ({ ...st, tail: st.earlier ? T.INSTALL_EARLIER : st.ms != null ? fmtS(st.ms) : '' }));
335
+ if (!res.ok) { set(s => ({ step: 'installFail', install: { ...s.install, failed: res.failedStage || '?', degraded: Boolean(res.degraded), error: res.error || null, logPath: res.logPath || null, cause: res.cause?.message || null } })); persist({ step: 'install' }); return; }
336
+ push({ kind: 'guide', text: T.INSTALL_TITLE + (dry ? ` (${T.INSTALL_DRY})` : ''), bold: true }, { kind: 'rows', rows });
337
+ if (dry || !finisher) { finale({ dashboardUrl: res.dashboardUrl, dry }); return; }
338
+ toOwner();
339
+ }
340
+
341
+ // ---- owner account (C2c) ----------------------------------------------------------------------
342
+ function toOwner(note = null) {
343
+ if (note) push({ kind: 'guide', tone: 'warn', text: note });
344
+ else push({ kind: 'guide', text: T.OWNER_TITLE, bold: true }, { kind: 'dim', text: T.OWNER_WHY });
345
+ set({ step: 'ownerEmail', input: S.current.ownerEmail || '', flash: null });
346
+ persist({ step: 'owner' });
347
+ }
348
+ function onOwnerEmail() {
349
+ const email = S.current.input.trim();
350
+ if (!emailOk(email)) { set({ flash: T.OWNER_EMAIL_BAD }); return; }
351
+ push({ kind: 'user', text: email });
352
+ set({ ownerEmail: email, step: 'ownerPassword', input: '', flash: null });
353
+ persist({ step: 'owner' });
354
+ }
355
+ function onOwnerPassword() {
356
+ let pw = S.current.input;
357
+ set({ input: '' });
358
+ if (!pw) {
359
+ pw = generatePassword(); // shown once, here, then only the token is kept
360
+ push({ kind: 'guide', tone: 'ok', text: T.ownerPwShownOnce(pw) }, { kind: 'dim', text: T.OWNER_PW_SAVE });
361
+ } else {
362
+ const bad = passwordProblem(pw);
363
+ if (bad) { set({ flash: bad }); return; }
364
+ }
365
+ set({ flash: null });
366
+ runFinish(S.current.ownerEmail, pw);
367
+ }
368
+
369
+ async function runFinish(email, password) {
370
+ const F = T.FIN;
371
+ const opt = credentialOptions({ ollama: true }).find(o => o.id === S.current.provider);
372
+ const rows = [
373
+ { id: 'account', name: F.account.name, status: 'running', body: F.account.running },
374
+ { id: 'signin', name: F.signin.name, status: 'pending', body: F.signin.running },
375
+ { id: 'vault', name: F.vault.name, status: 'pending', body: S.current.secret ? F.vault.running(opt?.label || 'key') : F.vault.skipped },
376
+ ...(S.current.cloudConsent === true ? [{ id: 'consent', name: F.consent.name, status: 'pending', body: F.consent.running(providerLabel(S.current.provider)) }] : []),
377
+ { id: 'link', name: F.link.name, status: 'pending', body: F.link.running }
378
+ ];
379
+ const row = (id, patch) => set(s => ({ finRows: s.finRows.map(r => (r.id === id ? { ...r, ...patch } : r)) }));
380
+ set({ step: 'finishing', finRows: rows });
381
+ persist({ step: 'finishing' });
382
+
383
+ const made = await finisher.createOwner({ email, password });
384
+ if (!live.current || S.current.done) return;
385
+ if (!made.ok && !made.existed) { set({ finRows: [] }); toOwner(made.error); set({ step: 'ownerPassword', input: '' }); return; }
386
+ row('account', { status: 'ok', body: made.existed ? F.account.existing(email) : F.account.ok(email) });
387
+
388
+ row('signin', { status: 'running' });
389
+ const signed = await finisher.signIn({ email, password });
390
+ password = null; // the password is not needed again; only the token is kept (in the profile)
391
+ if (!live.current || S.current.done) return;
392
+ if (!signed.ok) {
393
+ set({ finRows: [] });
394
+ toOwner(made.existed ? T.OWNER_EXISTS : signed.error);
395
+ return;
396
+ }
397
+ row('signin', { status: 'ok', body: F.signin.ok(signed.org) });
398
+
399
+ let vault = null;
400
+ if (S.current.secret) {
401
+ row('vault', { status: 'running' });
402
+ vault = await finisher.storeCredential(S.current.secret); // drops the key, stored or not
403
+ set({ secret: null });
404
+ if (!live.current || S.current.done) return;
405
+ row('vault', { status: vault.ok ? 'ok' : 'warn', body: String(vault.text || '').replace(/^[✔✖]\s*/, '') }); // the row glyph already says it
406
+ } else {
407
+ row('vault', { status: 'warn', body: S.current.provider === 'ollama' ? F.vault.ollama : F.vault.skipped });
408
+ }
409
+ set({ vault });
410
+
411
+ if (S.current.cloudConsent === true && finisher.grantConsent) { // the owner said yes at the credential step
412
+ row('consent', { status: 'running' });
413
+ const g = await finisher.grantConsent(S.current.provider);
414
+ if (!live.current || S.current.done) return;
415
+ const who = providerLabel(S.current.provider);
416
+ row('consent', g?.ok ? { status: 'ok', body: F.consent.ok(who) } : { status: 'warn', body: F.consent.fail(g?.error || 'unknown error', S.current.provider) });
417
+ }
418
+
419
+ row('link', { status: 'running' });
420
+ const link = await finisher.loginLink(S.current.dashboardUrl);
421
+ if (!live.current || S.current.done) return;
422
+ row('link', link.ok ? { status: 'ok', body: F.link.ok } : { status: 'warn', body: F.link.fail(link.error) });
423
+
424
+ // Z2H-R5 E: only mention adding a host where this install can serve its host installer (unknown = as before).
425
+ let hostsAvailable = true;
426
+ try { const hi = finisher.hostInstaller ? await finisher.hostInstaller() : null; if (hi && hi.ok === false && Number(hi.status) >= 400) hostsAvailable = false; } catch { /* unknown */ }
427
+ if (!live.current || S.current.done) return;
428
+ push({ kind: 'guide', text: T.FINISH_TITLE, bold: true }, { kind: 'rows', rows: S.current.finRows });
429
+ if (vault) push({ kind: 'dim', text: vault.ok ? T.KEY_GONE_AFTER : `${T.KEY_GONE_AFTER} ${T.keyNotStored(vault.provider || S.current.provider)}` });
430
+ set({ finRows: [] });
431
+ finale({ dashboardUrl: S.current.dashboardUrl, link: link.ok ? link.url : null, dry: false, hostsAvailable });
432
+ }
433
+
434
+ function replayText() {
435
+ const { request, intent } = S.current;
436
+ if (!request || intent === 'explore') return T.REPLAY_TOUR;
437
+ return request;
438
+ }
439
+
440
+ function finale({ dashboardUrl, link = null, dry, hostsAvailable = true }) {
441
+ const items = [];
442
+ const intent = S.current.intent && T.TIPS[S.current.intent] ? S.current.intent : 'explore';
443
+ if (dry) items.push({ kind: 'guide', tone: 'warn', text: T.FINALE_DRY_BANNER });
444
+ items.push({ kind: 'guide', tone: 'ok', bold: true, text: T.FINALE_RUNNING },
445
+ { kind: 'guide', text: T.finaleReplay(S.current.request && S.current.intent !== 'explore' ? S.current.request : null) });
446
+ if (dry) items.push({ kind: 'dim', text: T.FINALE_REPLAY_DRY });
447
+ const url = dashboardUrl || 'https://localhost:3000';
448
+ if (dry || link) items.push({ kind: 'long', text: T.finaleDashboard(link || url), lead: T.FINALE_DASHBOARD_LEAD, value: link || url });
449
+ else items.push({ kind: 'long', text: T.finaleDashboardPlain(url), lead: T.FINALE_DASHBOARD_LEAD_PLAIN, value: url });
450
+ items.push({ kind: 'guide', gap: false, text: T.FINALE_PROOF });
451
+ if (!dry) items.push({ kind: 'dim', text: T.FINALE_CERT });
452
+ if (!dry && S.current.installLogPath) items.push({ kind: 'dim', text: T.INSTALL_LOG_PATH(S.current.installLogPath) });
453
+ items.push({ kind: 'guide', text: T.FINALE_TIPS_TITLE }, { kind: 'list', items: dry ? T.FINALE_TIPS : T.TIPS[intent] });
454
+ const noAi = !dry && (S.current.provider === 'skip' || (S.current.vault && !S.current.vault.ok));
455
+ if (noAi && hostsAvailable) items.push({ kind: 'guide', text: T.FINALE_HOSTS_NO_AI });
456
+ if (!dry) items.push({ kind: 'guide', text: noAi ? T.k2NoAi(S.current.provider === 'anthropic' ? 'anthropic' : 'openai') : (!hostsAvailable && intent === 'servers' ? T.K2_SERVERS_NO_INSTALLER : T.K2[intent]) });
457
+ push(...items);
458
+ S.current.secret?.drop?.();
459
+ set({ step: 'finale', secret: null, finaleDry: dry });
460
+ if (!dry && journeyPath) clearJourney(journeyPath);
461
+ }
462
+
463
+ // ---- keys -------------------------------------------------------------------------------------
464
+ const K = React.useRef({ keys: initialKeyState() });
465
+ const onKey = (ev) => {
466
+ const s = S.current;
467
+ if (s.done) return;
468
+ if (ev.type === 'key' && (ev.name === 'ctrl-c' || ev.name === 'ctrl-d') && s.step === 'install') {
469
+ set({ step: 'installStop' }); // never abort a running install on one keypress: ask first
470
+ return;
471
+ }
472
+ if (ev.type === 'key' && (ev.name === 'ctrl-c' || ev.name === 'ctrl-d') && s.step === 'installStop') { stopInstall(); return; }
473
+ if (ev.type === 'key' && (ev.name === 'ctrl-c' || ev.name === 'ctrl-d')) {
474
+ if (s.step === 'key') broker?.cancel?.();
475
+ S.current.abort?.abort?.();
476
+ push({ kind: 'dim', text: T.BYE }); persist(); exit({ action: 'exit' }); return;
477
+ }
478
+ if (!introDone.current) { if (ev.type !== 'key') set({ input: s.input + ev.text.replace(/[\r\n]+/g, ' ') }); return; }
479
+ switch (s.step) {
480
+ case 'ask': case 'offer': case 'declined': return textKey(ev, submit);
481
+ case 'key': return textKey(ev, () => {
482
+ const v = S.current.input;
483
+ if (!v.trim()) { set({ flash: T.CRED_EMPTY }); return; }
484
+ set({ input: '', flash: null });
485
+ broker?.submit?.(v.trim());
486
+ }, () => { set({ input: '', flash: null }); broker?.cancel?.(); });
487
+ case 'ollamaUrl': return textKey(ev, submitOllamaUrl, () => set({ step: 'credential', input: '', flash: null }));
488
+ case 'preflightFail':
489
+ if (ev.type === 'text' && /^r$/i.test(ev.text)) startPreflight();
490
+ else if (ev.type === 'text' && /^c$/i.test(ev.text) && dryRun) {
491
+ push({ kind: 'guide', text: T.PREFLIGHT_TITLE, bold: true }, { kind: 'rows', rows: s.rows }, { kind: 'guide', tone: 'warn', text: T.PREFLIGHT_CONTINUED });
492
+ toCredential();
493
+ }
494
+ else if (ev.type === 'text' && /^q$/i.test(ev.text)) { push({ kind: 'dim', text: T.BYE }); persist({ step: 'preflight' }); exit({ action: 'exit' }); }
495
+ else if (ev.type === 'text' && /^y$/i.test(ev.text) && !dryRun) { // FIRSTRUN: the guide fixes Docker itself
496
+ const offer = dockerOffer(s.rows);
497
+ if (offer) { push({ kind: 'ask', text: offer.ask }, { kind: 'user', text: 'yes' }); persist({ step: 'preflight' }); exit({ action: 'docker', kind: offer.kind }); }
498
+ }
499
+ return;
500
+ case 'credential': {
501
+ const n = s.options.length;
502
+ if (ev.type === 'key' && ev.name === 'up') set({ sel: nextSelectable(s.options, s.sel, -1), flash: null }); // #1288: never land on a disabled row
503
+ else if (ev.type === 'key' && (ev.name === 'down' || ev.name === 'tab')) set({ sel: nextSelectable(s.options, s.sel, 1), flash: null });
504
+ else if (ev.type === 'key' && ev.name === 'enter') choose(s.options[s.sel]);
505
+ else if (ev.type === 'text' && /^[1-9]$/.test(ev.text) && Number(ev.text) <= n) { set({ sel: Number(ev.text) - 1 }); choose(s.options[Number(ev.text) - 1]); }
506
+ return;
507
+ }
508
+ case 'cloudConsent':
509
+ if (ev.type === 'text' && /^y(es)?$/i.test(ev.text.trim())) answerCloudConsent(true);
510
+ else if ((ev.type === 'text' && /^n(o)?$/i.test(ev.text.trim())) || (ev.type === 'key' && (ev.name === 'enter' || ev.name === 'escape'))) answerCloudConsent(false);
511
+ return;
512
+ case 'install': if (ev.type === 'text' && /^v$/i.test(ev.text)) set({ showLog: !s.showLog }); return;
513
+ case 'installStop':
514
+ if (ev.type === 'text' && /^y/i.test(ev.text)) stopInstall();
515
+ else if ((ev.type === 'text' && /^n/i.test(ev.text)) || (ev.type === 'key' && (ev.name === 'enter' || ev.name === 'escape'))) set({ step: 'install' });
516
+ return;
517
+ case 'ownerEmail': return textKey(ev, onOwnerEmail);
518
+ case 'ownerPassword': return textKey(ev, onOwnerPassword, () => set({ step: 'ownerEmail', input: S.current.ownerEmail || '', flash: null }));
519
+ case 'finishing': return;
520
+ case 'installFail':
521
+ if (ev.type === 'text' && /^v$/i.test(ev.text)) set({ showLog: !s.showLog });
522
+ else if (ev.type === 'text' && /^r$/i.test(ev.text)) startInstall();
523
+ return;
524
+ case 'notyet': if (ev.type === 'key' && ev.name === 'enter') exit({ action: 'exit' }); return;
525
+ case 'finale':
526
+ if (ev.type === 'key' && ev.name === 'enter') exit(s.finaleDry || !finisher ? { action: 'exit' } : { action: 'connected', replay: replayText() });
527
+ return;
528
+ default:
529
+ }
530
+ };
531
+ function stopInstall() {
532
+ push({ kind: 'ask', text: T.INSTALL_STOP_ASK }, { kind: 'user', text: 'yes' });
533
+ set({ stopping: true, step: 'install' });
534
+ S.current.abort?.abort?.(); // the runner stops the installer's process group; startInstall then exits
535
+ }
536
+
537
+ function textKey(ev, onEnter, onEscape) {
538
+ const s = S.current;
539
+ const masked = s.step === 'key' || s.step === 'ownerPassword';
540
+ if (ev.type === 'text' || ev.type === 'paste') set({ input: s.input + ev.text.replace(/[\r\n]+/g, masked ? '' : ' '), flash: null });
541
+ else if (ev.name === 'backspace') set({ input: [...s.input].slice(0, -1).join('') });
542
+ else if (ev.name === 'kill-line') set({ input: '' });
543
+ else if (ev.name === 'enter') onEnter();
544
+ else if (ev.name === 'escape' && onEscape) onEscape();
545
+ }
546
+
547
+ React.useEffect(() => {
548
+ if (!stdin) return undefined;
549
+ if (isRawModeSupported) setRawMode(true);
550
+ if (stdout?.isTTY) stdout.write(PASTE_ON);
551
+ const handle = (chunk) => {
552
+ const parsed = parseChunk(chunk, K.current.keys);
553
+ K.current.keys = parsed.state;
554
+ for (const ev of parsed.events) onKey(ev);
555
+ };
556
+ if (earlyInput) { // typed before mount: into the input, never submitted
557
+ earlyInput.detach();
558
+ for (const chunk of earlyInput.chunks.splice(0)) {
559
+ const parsed = parseChunk(chunk, K.current.keys);
560
+ K.current.keys = parsed.state;
561
+ for (const ev of parsed.events) if (ev.type === 'text' || ev.type === 'paste') S.current.input += ev.text.replace(/[\r\n]+/g, ' ');
562
+ }
563
+ }
564
+ stdin.on('data', handle);
565
+ return () => {
566
+ stdin.off('data', handle);
567
+ if (stdout?.isTTY) stdout.write(PASTE_OFF);
568
+ if (isRawModeSupported) setRawMode(false);
569
+ };
570
+ }, [stdin]);
571
+
572
+ // a ticking clock while a stage runs (elapsed time, never a fake percentage)
573
+ React.useEffect(() => {
574
+ if (S.current.step !== 'install' && S.current.step !== 'installStop') return undefined;
575
+ const id = setInterval(rerender, 250);
576
+ return () => clearInterval(id);
577
+ }, [S.current.step]);
578
+
579
+ // the broker drives the masked card (the K1 secure card replaces it)
580
+ React.useEffect(() => broker?.subscribe?.(() => rerender()), [broker]);
581
+
582
+ // ---- render -----------------------------------------------------------------------------------
583
+ const s = S.current;
584
+ const liveParts = [];
585
+ if (!introDone.current) liveParts.push(h(HelloIntro, { key: 'intro', item: introItem, cols, text: T.HELLO, eye, t: Date.now() - introStart.current }));
586
+ else if (!s.done) {
587
+ switch (s.step) {
588
+ case 'ask':
589
+ liveParts.push(h(InputLine, { key: 'in', text: s.input, placeholder: T.INPUT_PLACEHOLDER, cols }), h(Hint, { key: 'hint', text: 'Enter to send · /help · /exit', cols }));
590
+ break;
591
+ case 'offer': case 'declined':
592
+ liveParts.push(h(InputLine, { key: 'in', text: s.input, placeholder: s.step === 'offer' ? 'Enter for yes, or n' : 'yes, /connect server <url>, or /exit', cols }), h(Hint, { key: 'hint', text: 'Enter to send · /exit', cols }));
593
+ break;
594
+ case 'preflight': case 'preflightFail':
595
+ liveParts.push(h(Box, { key: 'pf', marginTop: 1, paddingLeft: 2 }, h(Text, { bold: true, color: c('text') }, T.PREFLIGHT_TITLE)),
596
+ ...s.rows.map((r) => h(CheckRow, { key: r.id, r, cols })));
597
+ if (s.step === 'preflightFail') {
598
+ const offer = dryRun ? null : dockerOffer(s.rows); // FIRSTRUN: offer, never "go and do this"
599
+ if (offer) liveParts.push(h(Box, { key: 'pfo', marginTop: 1 }, h(AskCard, { text: offer.ask, cols })), h(Hint, { key: 'h', text: T.DOCKER_OFFER_HINT, cols }));
600
+ else liveParts.push(h(Box, { key: 'pfn', marginTop: 1, paddingLeft: 2, width: cols }, h(Para, { width: cols - 2, color: c('warn'), text: s.fixNote })), h(Hint, { key: 'h', text: dryRun ? T.PREFLIGHT_RETRY_DRY : T.PREFLIGHT_RETRY, cols }));
601
+ }
602
+ break;
603
+ case 'credential':
604
+ liveParts.push(h(Box, { key: 'opts', flexDirection: 'column', marginTop: 1 }, ...s.options.map((o, i) => {
605
+ const on = i === s.sel;
606
+ return h(Text, { key: o.id },
607
+ h(Text, { color: c(on ? 'sky' : 'dim') }, on ? ' › ' : ' '),
608
+ h(Text, { color: c(o.disabled ? 'dim' : on ? 'input' : 'text'), bold: on && !o.disabled }, `${i + 1} ${o.label}`),
609
+ o.note ? h(Text, { color: c(o.id === 'skip' ? 'warn' : 'dim') }, clip(` ${o.note}`, Math.max(4, cols - o.label.length - 9))) : null);
610
+ })));
611
+ if (s.flash) liveParts.push(h(Box, { key: 'fl', paddingLeft: 2, width: cols }, h(Para, { width: cols - 2, color: c('warn'), text: s.flash })));
612
+ liveParts.push(h(Hint, { key: 'h', text: T.CRED_HINT, cols }));
613
+ break;
614
+ case 'key': {
615
+ const opt = s.options.find(o => o.id === s.provider);
616
+ liveParts.push(h(Box, { key: 'card', flexDirection: 'column', marginTop: 1 },
617
+ h(Text, { color: c('violet') }, '─'.repeat(cols)),
618
+ h(Box, { paddingLeft: 2, width: cols }, h(Para, { width: cols - 2, color: c('text'), text: T.credPrompt(opt?.label || 'API key') })),
619
+ h(InputLine, { text: s.input, cols, masked: true }),
620
+ s.flash ? h(Box, { paddingLeft: 2, width: cols }, h(Para, { width: cols - 2, color: c('warn'), text: s.flash })) : null,
621
+ h(Hint, { text: T.CRED_INPUT_HINT, cols }),
622
+ h(Text, { color: c('violet') }, '─'.repeat(cols))));
623
+ break;
624
+ }
625
+ case 'cloudConsent':
626
+ liveParts.push(h(Box, { key: 'cc', flexDirection: 'column', marginTop: 1 },
627
+ h(AskCard, { text: T.credConsentAsk(providerLabel(s.provider)), cols }),
628
+ h(Hint, { text: T.CRED_CONSENT_HINT, cols })));
629
+ break;
630
+ case 'ollamaUrl':
631
+ liveParts.push(h(Box, { key: 'card', flexDirection: 'column', marginTop: 1 },
632
+ h(Text, { color: c('violet') }, '─'.repeat(cols)),
633
+ h(Box, { paddingLeft: 2, width: cols }, h(Para, { width: cols - 2, color: c('text'), text: T.OLLAMA_URL_PROMPT })),
634
+ h(InputLine, { text: s.input, cols }),
635
+ s.flash ? h(Box, { paddingLeft: 2, width: cols }, h(Para, { width: cols - 2, color: c('warn'), text: s.flash })) : null,
636
+ h(Hint, { text: T.OLLAMA_URL_HINT, cols }),
637
+ h(Text, { color: c('violet') }, '─'.repeat(cols))));
638
+ break;
639
+ case 'ownerEmail': case 'ownerPassword': {
640
+ const pw = s.step === 'ownerPassword';
641
+ if (pw) liveParts.push(h(Box, { key: 'pwp', paddingLeft: 2, width: cols, marginTop: 1 }, h(Para, { width: cols - 2, color: c('text'), text: T.OWNER_PW_PROMPT })));
642
+ liveParts.push(h(InputLine, { key: 'in', text: s.input, placeholder: pw ? '' : T.OWNER_EMAIL_PLACEHOLDER, cols, masked: pw }));
643
+ if (s.flash) liveParts.push(h(Box, { key: 'fl', paddingLeft: 2, width: cols }, h(Para, { width: cols - 2, color: c('warn'), text: s.flash })));
644
+ liveParts.push(h(Hint, { key: 'h', text: pw ? T.OWNER_PW_HINT : 'Enter to continue', cols }));
645
+ break;
646
+ }
647
+ case 'finishing':
648
+ liveParts.push(h(Box, { key: 'ft', marginTop: 1, paddingLeft: 2 }, h(Text, { bold: true, color: c('text') }, T.FINISH_TITLE)),
649
+ ...s.finRows.map((r) => h(CheckRow, { key: r.id, r, cols })));
650
+ break;
651
+ case 'install': case 'installFail': case 'installStop': {
652
+ const t = now();
653
+ const dry = s.install.kind === 'dry-run';
654
+ liveParts.push(h(Box, { key: 'it', marginTop: 1, paddingLeft: 2, width: cols }, h(Para, { width: cols - 2, bold: true, color: c('text'), text: T.INSTALL_TITLE + (dry ? ` (${T.INSTALL_DRY})` : '') })));
655
+ for (const st of s.install.stages) {
656
+ const tail = st.earlier ? T.INSTALL_EARLIER : st.status === 'running' && st.startedAt ? fmtS(t - st.startedAt) : st.ms != null ? fmtS(st.ms) : '';
657
+ liveParts.push(h(CheckRow, { key: st.id, r: { ...st, tail }, cols }));
658
+ }
659
+ if (s.showLog) liveParts.push(h(Box, { key: 'log', flexDirection: 'column', paddingLeft: 4, marginTop: 1 }, ...s.logs.map((l, i) => h(Text, { key: i, color: c('dim') }, clip(l, cols - 4)))));
660
+ if (s.step === 'installFail') {
661
+ liveParts.push(h(Box, { key: 'if', paddingLeft: 2, width: cols, marginTop: 1, flexDirection: 'column' },
662
+ h(Para, { width: cols - 2, color: c('fail'), text: T.INSTALL_FAILED(T.STAGES[s.install.failed] || s.install.failed) }),
663
+ s.install.cause ? h(Para, { width: cols - 2, color: c('warn'), text: s.install.cause }) : null, // FIRSTRUN: why, in one line
664
+ s.install.error ? h(Para, { width: cols - 2, color: c('dim'), text: s.install.error }) : null,
665
+ s.install.logPath ? h(Para, { width: cols - 2, color: c('dim'), text: T.INSTALL_LOG_PATH(s.install.logPath) }) : null,
666
+ s.install.logPath ? h(Para, { width: cols - 2, color: c('dim'), text: T.INSTALL_RERUN_HINT }) : null,
667
+ s.install.degraded ? h(Para, { width: cols - 2, color: c('warn'), text: T.INSTALL_DEGRADED }) : null));
668
+ }
669
+ if (s.step === 'installStop') liveParts.push(h(Box, { key: 'stop', width: cols }, h(AskCard, { text: T.INSTALL_STOP_ASK, cols })));
670
+ liveParts.push(h(Hint, { key: 'h', text: s.step === 'installFail' ? T.INSTALL_FAIL_HINT : s.step === 'installStop' ? 'y to stop · n or Enter to carry on' : T.INSTALL_HINT, cols }));
671
+ break;
672
+ }
673
+ case 'notyet': case 'finale':
674
+ liveParts.push(h(Box, { key: 'h', marginTop: 1 }, h(Hint, { text: s.step === 'finale' && !s.finaleDry && finisher ? T.FINALE_EXIT : T.FINALE_EXIT_DRY, cols })));
675
+ break;
676
+ default:
677
+ }
678
+ }
679
+ return h(Box, { flexDirection: 'column' },
680
+ h(Static, { items: introDone.current ? s.history : [] }, (it) => h(Box, { key: it.id, flexDirection: 'column' }, h(Item, { item: it, cols }))),
681
+ ...liveParts);
682
+ }