quoxcli 0.2.0 → 0.4.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 (442) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +2 -0
  3. package/bin/quox.js +99 -126
  4. package/lib/api/endpoints/actions.js +15 -0
  5. package/lib/api/endpoints/admin.js +46 -0
  6. package/lib/api/endpoints/agent-schedules.js +26 -0
  7. package/lib/api/endpoints/agents.js +7 -0
  8. package/lib/api/endpoints/assistants.js +12 -0
  9. package/lib/api/endpoints/away.js +15 -0
  10. package/lib/api/endpoints/blog.js +16 -0
  11. package/lib/api/endpoints/chat.js +14 -0
  12. package/lib/api/endpoints/contactsq.js +6 -0
  13. package/lib/api/endpoints/conversations.js +14 -0
  14. package/lib/api/endpoints/devServerRegistry.js +25 -0
  15. package/lib/api/endpoints/engagement.js +26 -0
  16. package/lib/api/endpoints/evidence.js +20 -0
  17. package/lib/api/endpoints/executions.js +13 -0
  18. package/lib/api/endpoints/fetchAllowlist.js +27 -0
  19. package/lib/api/endpoints/files.js +14 -0
  20. package/lib/api/endpoints/hat.js +44 -0
  21. package/lib/api/endpoints/inbox.js +20 -0
  22. package/lib/api/endpoints/index.js +26 -0
  23. package/lib/api/endpoints/loops.js +26 -0
  24. package/lib/api/endpoints/memory.js +19 -0
  25. package/lib/api/endpoints/mind.js +13 -0
  26. package/lib/api/endpoints/mon.js +179 -0
  27. package/lib/api/endpoints/objectives.js +12 -0
  28. package/lib/api/endpoints/plugins.js +18 -0
  29. package/lib/api/endpoints/presence.js +33 -0
  30. package/lib/api/endpoints/quoxplan.js +7 -0
  31. package/lib/api/endpoints/quoxseo.js +12 -0
  32. package/lib/api/endpoints/router.js +17 -0
  33. package/lib/api/endpoints/safety.js +7 -0
  34. package/lib/api/endpoints/server.js +21 -0
  35. package/lib/api/endpoints/setup.js +6 -0
  36. package/lib/api/endpoints/skills.js +20 -0
  37. package/lib/api/endpoints/stepup.js +20 -0
  38. package/lib/api/endpoints/teams.js +2 -2
  39. package/lib/api/endpoints/triggers.js +26 -0
  40. package/lib/api/endpoints/v1.js +120 -0
  41. package/lib/api/endpoints/ward.js +7 -0
  42. package/lib/api/endpoints/workflows.js +7 -0
  43. package/lib/api/endpoints/x.js +8 -0
  44. package/lib/commands/actions.js +131 -0
  45. package/lib/commands/admin.js +16 -0
  46. package/lib/commands/agent.js +170 -27
  47. package/lib/commands/agentAttach.js +140 -13
  48. package/lib/commands/approvals.js +78 -1
  49. package/lib/commands/aside.js +24 -0
  50. package/lib/commands/ask.js +146 -0
  51. package/lib/commands/assistant.js +59 -4
  52. package/lib/commands/auth.js +8 -2
  53. package/lib/commands/backup.js +8 -4
  54. package/lib/commands/blog.js +110 -0
  55. package/lib/commands/brain.js +105 -14
  56. package/lib/commands/build.js +19 -5
  57. package/lib/commands/chat.js +180 -26
  58. package/lib/commands/chatCompanion.js +27 -0
  59. package/lib/commands/chatState.js +45 -0
  60. package/lib/commands/claude.js +35 -38
  61. package/lib/commands/contacts.js +134 -4
  62. package/lib/commands/contactsSendAt.js +21 -0
  63. package/lib/commands/conversation.js +35 -4
  64. package/lib/commands/dashboard.js +43 -0
  65. package/lib/commands/department.js +28 -7
  66. package/lib/commands/devResident.js +15 -3
  67. package/lib/commands/devSupervisor.js +13 -1
  68. package/lib/commands/discord.js +75 -39
  69. package/lib/commands/done.js +98 -28
  70. package/lib/commands/engagement.js +107 -6
  71. package/lib/commands/evidence.js +154 -0
  72. package/lib/commands/ext.js +23 -0
  73. package/lib/commands/fetchAllowlist.js +71 -0
  74. package/lib/commands/file.js +38 -1
  75. package/lib/commands/fleet.js +336 -4
  76. package/lib/commands/governance.js +12 -3
  77. package/lib/commands/gripe.js +44 -13
  78. package/lib/commands/hat.js +254 -9
  79. package/lib/commands/helpers.js +18 -1
  80. package/lib/commands/hyperion.js +58 -0
  81. package/lib/commands/image.js +68 -0
  82. package/lib/commands/inbox.js +74 -23
  83. package/lib/commands/kuma.js +1050 -0
  84. package/lib/commands/kumaSla.js +174 -0
  85. package/lib/commands/licence.js +52 -155
  86. package/lib/commands/loop.js +291 -0
  87. package/lib/commands/lucid.js +10 -4
  88. package/lib/commands/memory.js +85 -8
  89. package/lib/commands/mind.js +71 -0
  90. package/lib/commands/mon.js +69 -2
  91. package/lib/commands/monitoring.js +1 -1
  92. package/lib/commands/motion.js +2 -2
  93. package/lib/commands/nucleus.js +48 -17
  94. package/lib/commands/objective.js +59 -1
  95. package/lib/commands/offload.js +89 -0
  96. package/lib/commands/onboard.js +2 -1
  97. package/lib/commands/org.js +30 -3
  98. package/lib/commands/peek.js +28 -0
  99. package/lib/commands/plan.js +38 -0
  100. package/lib/commands/plugin.js +110 -0
  101. package/lib/commands/presence.js +89 -0
  102. package/lib/commands/provider.js +217 -0
  103. package/lib/commands/qlarity.js +19 -3
  104. package/lib/commands/quoxchat.js +13 -4
  105. package/lib/commands/quoxseo.js +69 -0
  106. package/lib/commands/reach.js +2 -1
  107. package/lib/commands/research.js +79 -0
  108. package/lib/commands/router.js +77 -0
  109. package/lib/commands/rows.js +367 -0
  110. package/lib/commands/run.js +82 -10
  111. package/lib/commands/scope.js +2 -2
  112. package/lib/commands/server.js +405 -0
  113. package/lib/commands/setup.js +33 -7
  114. package/lib/commands/shortcut.js +41 -0
  115. package/lib/commands/skills.js +68 -9
  116. package/lib/commands/slot.js +260 -7
  117. package/lib/commands/status.js +1 -1
  118. package/lib/commands/team.js +3 -1
  119. package/lib/commands/tips.js +23 -0
  120. package/lib/commands/tool.js +133 -45
  121. package/lib/commands/trace.js +12 -7
  122. package/lib/commands/trigger.js +95 -0
  123. package/lib/commands/update.js +46 -0
  124. package/lib/commands/vault.js +31 -8
  125. package/lib/commands/vm.js +237 -0
  126. package/lib/commands/voice.js +34 -3
  127. package/lib/commands/volt.js +45 -3
  128. package/lib/commands/walkthrough.js +32 -0
  129. package/lib/commands/watch.js +73 -21
  130. package/lib/commands/welcome.js +14 -0
  131. package/lib/commands/workflow.js +189 -32
  132. package/lib/commands/x.js +13 -4
  133. package/lib/commands/youtube.js +172 -0
  134. package/lib/core/actingAs.js +2 -2
  135. package/lib/core/actionItems.js +115 -0
  136. package/lib/core/activeSummary.js +186 -0
  137. package/lib/core/agentAlias.js +2 -0
  138. package/lib/core/agentFamilies.js +7 -0
  139. package/lib/core/aside.js +54 -0
  140. package/lib/core/askDecision.js +60 -0
  141. package/lib/core/auth.js +164 -13
  142. package/lib/core/callerSource.js +1 -1
  143. package/lib/core/cliBuild.js +62 -0
  144. package/lib/core/client.js +152 -35
  145. package/lib/core/commandRegistrars.js +130 -0
  146. package/lib/core/config.js +143 -25
  147. package/lib/core/customHats.js +168 -0
  148. package/lib/core/doneEngineFetch.js +8 -0
  149. package/lib/core/errors.js +329 -20
  150. package/lib/core/findingReconcile.js +90 -0
  151. package/lib/core/global-flags.js +105 -5
  152. package/lib/core/governedSession.js +1 -3
  153. package/lib/core/hyperion.js +102 -0
  154. package/lib/core/inboxResolution.js +65 -0
  155. package/lib/core/lazyCommands.js +4 -1
  156. package/lib/core/liveSessionsReport.js +1 -1
  157. package/lib/core/loopGlyph.js +97 -0
  158. package/lib/core/offload.js +533 -0
  159. package/lib/core/org-override.js +1 -1
  160. package/lib/core/orgResolve.js +70 -0
  161. package/lib/core/output.js +11 -11
  162. package/lib/core/peek.js +126 -0
  163. package/lib/core/profileUsers.js +51 -0
  164. package/lib/core/promptCancel.js +18 -0
  165. package/lib/core/repoPath.js +11 -0
  166. package/lib/core/residentSeed.js +3 -1
  167. package/lib/core/rootOptions.js +39 -0
  168. package/lib/core/router.js +168 -0
  169. package/lib/core/selfUpdate.js +188 -0
  170. package/lib/core/sessionHeartbeat.js +12 -8
  171. package/lib/core/sessionsOverview.js +64 -0
  172. package/lib/core/shortcut.js +83 -0
  173. package/lib/core/slotKeeper.js +248 -3
  174. package/lib/core/slotWake.js +120 -0
  175. package/lib/core/supervisorEngine.js +71 -2
  176. package/lib/core/version.js +1 -1
  177. package/lib/core/vm.js +366 -0
  178. package/lib/core/vmTui.js +86 -0
  179. package/lib/core/walkthrough.js +35 -0
  180. package/lib/dragon/companionView.js +34 -0
  181. package/lib/dragon/frames.js +118 -0
  182. package/lib/dragon/jobs.js +123 -0
  183. package/lib/dragon/renderWorker.js +15 -0
  184. package/lib/dragon/select.js +72 -0
  185. package/lib/evidence/bundleExport.js +47 -0
  186. package/lib/evidence/bundleVerify.js +77 -0
  187. package/lib/hyperion/deck.js +29 -0
  188. package/lib/lucid/LucidCard.jsx +68 -0
  189. package/lib/lucid/LucidIntro.jsx +45 -58
  190. package/lib/lucid/cards.js +86 -0
  191. package/lib/lucid/markdown.js +81 -0
  192. package/lib/lucid/motion.js +56 -0
  193. package/lib/quoxon/EXTENSIONS.md +23 -0
  194. package/lib/quoxon/activity.js +14 -2
  195. package/lib/quoxon/aocl.js +117 -0
  196. package/lib/quoxon/api.js +690 -25
  197. package/lib/quoxon/app.js +3586 -250
  198. package/lib/quoxon/approvalFacts.js +96 -0
  199. package/lib/quoxon/approvalSentence.js +44 -0
  200. package/lib/quoxon/approvalsView.js +384 -0
  201. package/lib/quoxon/asideView.js +40 -0
  202. package/lib/quoxon/ask.js +121 -0
  203. package/lib/quoxon/away.js +52 -0
  204. package/lib/quoxon/bang.js +224 -0
  205. package/lib/quoxon/bulkRisk.js +17 -0
  206. package/lib/quoxon/capabilities.js +13 -6
  207. package/lib/quoxon/cells.js +53 -0
  208. package/lib/quoxon/commandShort.js +124 -0
  209. package/lib/quoxon/commands.js +429 -39
  210. package/lib/quoxon/connect.js +210 -23
  211. package/lib/quoxon/connectCard.js +19 -2
  212. package/lib/quoxon/consent.js +86 -0
  213. package/lib/quoxon/copy.js +4 -3
  214. package/lib/quoxon/countdownLine.js +20 -0
  215. package/lib/quoxon/deck.js +30 -0
  216. package/lib/quoxon/demo.autoplay.js +29 -0
  217. package/lib/quoxon/demo.fixtures.js +1 -1
  218. package/lib/quoxon/demo.js +46 -3
  219. package/lib/quoxon/devObserver.js +20 -0
  220. package/lib/quoxon/dividerAocl.js +192 -0
  221. package/lib/quoxon/dividerRule.js +57 -0
  222. package/lib/quoxon/dragonCompanion.js +255 -0
  223. package/lib/quoxon/dragonModes.js +151 -0
  224. package/lib/quoxon/dragonMoments.js +82 -0
  225. package/lib/quoxon/dragonReads.js +68 -0
  226. package/lib/quoxon/dragonView.js +41 -0
  227. package/lib/quoxon/earlyInput.js +40 -0
  228. package/lib/quoxon/echo.js +15 -0
  229. package/lib/quoxon/egg.js +2 -2
  230. package/lib/quoxon/events.js +266 -26
  231. package/lib/quoxon/extensions.js +276 -0
  232. package/lib/quoxon/feedback.js +50 -0
  233. package/lib/quoxon/gallery.js +21 -7
  234. package/lib/quoxon/handoffOffer.js +78 -0
  235. package/lib/quoxon/hatSwitch.js +37 -0
  236. package/lib/quoxon/headless.js +161 -0
  237. package/lib/quoxon/hostUrls.js +41 -0
  238. package/lib/quoxon/inboxView.js +79 -0
  239. package/lib/quoxon/indicator.js +6 -2
  240. package/lib/quoxon/installCli.js +79 -0
  241. package/lib/quoxon/intent.js +12 -1
  242. package/lib/quoxon/keys.js +51 -8
  243. package/lib/quoxon/launcher.js +103 -0
  244. package/lib/quoxon/loopCard.js +456 -0
  245. package/lib/quoxon/loopCards.js +180 -0
  246. package/lib/quoxon/loopStream.js +87 -0
  247. package/lib/quoxon/main.js +101 -160
  248. package/lib/quoxon/markdown.js +45 -21
  249. package/lib/quoxon/markdownSettle.js +19 -0
  250. package/lib/quoxon/matrixDomains.json +46 -0
  251. package/lib/quoxon/mentions.js +90 -0
  252. package/lib/quoxon/mind.js +103 -0
  253. package/lib/quoxon/mode.js +46 -0
  254. package/lib/quoxon/model.js +132 -26
  255. package/lib/quoxon/modelPrefs.js +345 -0
  256. package/lib/quoxon/motion/clock.js +3 -2
  257. package/lib/quoxon/motion/level.js +91 -0
  258. package/lib/quoxon/mouse.js +222 -0
  259. package/lib/quoxon/noai.js +57 -0
  260. package/lib/quoxon/objectiveOffer.js +82 -0
  261. package/lib/quoxon/objects/away.js +72 -0
  262. package/lib/quoxon/objects/fetch.js +17 -5
  263. package/lib/quoxon/objects/fleet.js +149 -0
  264. package/lib/quoxon/objects/health.js +186 -0
  265. package/lib/quoxon/objects/index.js +46 -7
  266. package/lib/quoxon/objects/mirror.js +137 -0
  267. package/lib/quoxon/objects/model.js +33 -0
  268. package/lib/quoxon/objects/peek.js +17 -0
  269. package/lib/quoxon/objects/proof.js +12 -4
  270. package/lib/quoxon/objects/prove.js +130 -0
  271. package/lib/quoxon/objects/replay.js +125 -0
  272. package/lib/quoxon/objects/sitrep.js +58 -0
  273. package/lib/quoxon/objects/view.js +11 -0
  274. package/lib/quoxon/onboarding/copy.js +32 -4
  275. package/lib/quoxon/onboarding/credential.js +17 -4
  276. package/lib/quoxon/onboarding/dockerOffer.js +45 -0
  277. package/lib/quoxon/onboarding/failureCause.js +33 -0
  278. package/lib/quoxon/onboarding/finish.js +15 -0
  279. package/lib/quoxon/onboarding/index.js +34 -7
  280. package/lib/quoxon/onboarding/installRunner.js +64 -5
  281. package/lib/quoxon/onboarding/preflight.js +17 -3
  282. package/lib/quoxon/onboarding/probe.js +65 -0
  283. package/lib/quoxon/onboarding/shortcutOffer.js +28 -0
  284. package/lib/quoxon/onboarding/view.js +76 -19
  285. package/lib/quoxon/orgLabel.js +10 -0
  286. package/lib/quoxon/orgSwitch.js +59 -0
  287. package/lib/quoxon/ownBroadcast.js +22 -0
  288. package/lib/quoxon/palette.js +43 -0
  289. package/lib/quoxon/planSteps.js +51 -0
  290. package/lib/quoxon/print.js +12 -5
  291. package/lib/quoxon/profileSwitch.js +53 -0
  292. package/lib/quoxon/promptHistory.js +41 -0
  293. package/lib/quoxon/providers.js +48 -0
  294. package/lib/quoxon/qart.js +20 -51
  295. package/lib/quoxon/qdragon.js +410 -0
  296. package/lib/quoxon/qdragonFly.js +62 -0
  297. package/lib/quoxon/qdragonScenes.js +282 -0
  298. package/lib/quoxon/qeyes.js +213 -0
  299. package/lib/quoxon/qmotion.js +75 -37
  300. package/lib/quoxon/render.js +740 -124
  301. package/lib/quoxon/resend.js +72 -0
  302. package/lib/quoxon/roster.js +117 -17
  303. package/lib/quoxon/route.js +6 -5
  304. package/lib/quoxon/rows.js +954 -0
  305. package/lib/quoxon/rowsStore.js +80 -0
  306. package/lib/quoxon/screenContext.js +150 -0
  307. package/lib/quoxon/screenFrames.js +254 -0
  308. package/lib/quoxon/serverChip.js +42 -0
  309. package/lib/quoxon/session.js +182 -2
  310. package/lib/quoxon/sessionDead.js +17 -0
  311. package/lib/quoxon/signin.js +125 -0
  312. package/lib/quoxon/slash/agents.js +112 -0
  313. package/lib/quoxon/slash/argSources.js +65 -0
  314. package/lib/quoxon/slash/args.js +65 -0
  315. package/lib/quoxon/slash/automate.js +89 -0
  316. package/lib/quoxon/slash/broadcast.js +140 -0
  317. package/lib/quoxon/slash/broadcastRun.js +108 -0
  318. package/lib/quoxon/slash/card.js +30 -0
  319. package/lib/quoxon/slash/challenge.js +114 -0
  320. package/lib/quoxon/slash/cliCommand.js +94 -0
  321. package/lib/quoxon/slash/cliLeaves.json +1023 -0
  322. package/lib/quoxon/slash/compact.js +1 -1
  323. package/lib/quoxon/slash/confirm.js +30 -0
  324. package/lib/quoxon/slash/crystallize.js +69 -0
  325. package/lib/quoxon/slash/dashboard.js +136 -0
  326. package/lib/quoxon/slash/draft.js +54 -0
  327. package/lib/quoxon/slash/evidenceCmd.js +34 -0
  328. package/lib/quoxon/slash/hatWalk.js +206 -0
  329. package/lib/quoxon/slash/inverses.js +77 -0
  330. package/lib/quoxon/slash/killer.js +24 -0
  331. package/lib/quoxon/slash/killerApi.js +16 -0
  332. package/lib/quoxon/slash/ledger.js +13 -8
  333. package/lib/quoxon/slash/listView.js +3 -1
  334. package/lib/quoxon/slash/modelCmd.js +261 -0
  335. package/lib/quoxon/slash/palette.js +22 -8
  336. package/lib/quoxon/slash/paletteController.js +117 -7
  337. package/lib/quoxon/slash/panels.js +21 -6
  338. package/lib/quoxon/slash/picker.js +20 -2
  339. package/lib/quoxon/slash/qpeCmd.js +35 -0
  340. package/lib/quoxon/slash/restart.js +95 -13
  341. package/lib/quoxon/slash/resume.js +18 -1
  342. package/lib/quoxon/slash/rowCmd.js +157 -0
  343. package/lib/quoxon/slash/rowWalk.js +175 -0
  344. package/lib/quoxon/slash/settings.js +16 -6
  345. package/lib/quoxon/slash/spawn.js +177 -0
  346. package/lib/quoxon/slash/stepUp.js +274 -0
  347. package/lib/quoxon/slash/stepUpCard.js +60 -0
  348. package/lib/quoxon/slash/stepUpCmds.js +78 -0
  349. package/lib/quoxon/slash/todoCmd.js +122 -0
  350. package/lib/quoxon/slash/undoApi.js +38 -0
  351. package/lib/quoxon/slash/undoCmd.js +49 -0
  352. package/lib/quoxon/slash/undoWish.js +15 -0
  353. package/lib/quoxon/slash/watch.js +164 -0
  354. package/lib/quoxon/slash/wish.js +23 -0
  355. package/lib/quoxon/slash/wishCmd.js +32 -0
  356. package/lib/quoxon/slotChat.js +300 -0
  357. package/lib/quoxon/slotFormat.js +195 -0
  358. package/lib/quoxon/slotRun.js +131 -0
  359. package/lib/quoxon/sound/mapping.js +2 -1
  360. package/lib/quoxon/stray.js +22 -0
  361. package/lib/quoxon/suggestCommand.js +90 -0
  362. package/lib/quoxon/term.js +46 -11
  363. package/lib/quoxon/textBuffer.js +176 -2
  364. package/lib/quoxon/theme.js +58 -25
  365. package/lib/quoxon/tips.js +433 -0
  366. package/lib/quoxon/toolCards.js +30 -0
  367. package/lib/quoxon/toolError.js +41 -0
  368. package/lib/quoxon/transcript.js +42 -4
  369. package/lib/quoxon/web/qface.js +18 -10
  370. package/lib/quoxon/welcome.js +1 -1
  371. package/lib/quoxon/whatsnew.js +136 -0
  372. package/lib/quoxon/whatsnew.jsonl +134 -0
  373. package/lib/quoxon/windowOrg.js +123 -0
  374. package/lib/server/devServers.js +137 -0
  375. package/lib/server/diag.js +129 -0
  376. package/lib/server/doctor.js +71 -0
  377. package/lib/server/install.js +149 -0
  378. package/lib/server/pluginEnable.js +161 -0
  379. package/lib/server/skew.js +56 -0
  380. package/lib/server/snapshot.js +131 -0
  381. package/lib/server/status.js +128 -0
  382. package/lib/server/sys.js +65 -0
  383. package/lib/server/uninstall.js +63 -0
  384. package/lib/server/update.js +234 -0
  385. package/lib/toolOutputShape.js +315 -0
  386. package/lib/tui/views/HyperionDeck.jsx +79 -0
  387. package/lib/tui/views/LucidDeck.jsx +209 -217
  388. package/package.json +5 -1
  389. package/vendor/dragonlab/SOURCE +4 -0
  390. package/vendor/dragonlab/design/cipher/v1/cipher-master.svg +46 -0
  391. package/vendor/dragonlab/design/cipher/v2/character.mjs +49 -0
  392. package/vendor/dragonlab/design/cipher/v2/review.mjs +33 -0
  393. package/vendor/dragonlab/design/cipher/v2/watch.mjs +56 -0
  394. package/vendor/dragonlab/design/drago/v1/character.mjs +19 -0
  395. package/vendor/dragonlab/design/drago/v1/drago-master.svg +56 -0
  396. package/vendor/dragonlab/design/drago/v2/character.mjs +87 -0
  397. package/vendor/dragonlab/design/drago/v4/character.mjs +67 -0
  398. package/vendor/dragonlab/design/drago/v4/profile.mjs +35 -0
  399. package/vendor/dragonlab/design/drago/v4/review.mjs +33 -0
  400. package/vendor/dragonlab/design/drago/v4/watch.mjs +56 -0
  401. package/vendor/dragonlab/design/hyperion/v1/hyperion-master.svg +56 -0
  402. package/vendor/dragonlab/design/hyperion/v2/character.mjs +79 -0
  403. package/vendor/dragonlab/design/hyperion/v2/review.mjs +33 -0
  404. package/vendor/dragonlab/design/hyperion/v2/watch.mjs +56 -0
  405. package/vendor/dragonlab/design/lucid/recovery/arrival.mjs +22 -0
  406. package/vendor/dragonlab/design/lucid/recovery/character.mjs +106 -0
  407. package/vendor/dragonlab/design/lucid/recovery/layout.mjs +50 -0
  408. package/vendor/dragonlab/design/lucid/recovery/lucid-master.svg +78 -0
  409. package/vendor/dragonlab/design/lucid/recovery/motion.mjs +42 -0
  410. package/vendor/dragonlab/design/lucid/recovery/render.mjs +121 -0
  411. package/vendor/dragonlab/design/lucid/recovery/review.mjs +68 -0
  412. package/vendor/dragonlab/design/lucid/recovery/verify.mjs +84 -0
  413. package/vendor/dragonlab/design/lucid/recovery/watch.mjs +56 -0
  414. package/vendor/dragonlab/design/seer/v1/seer-master.svg +53 -0
  415. package/vendor/dragonlab/design/seer/v2/character.mjs +88 -0
  416. package/vendor/dragonlab/design/seer/v3/character.mjs +33 -0
  417. package/vendor/dragonlab/design/seer/v4/character.mjs +66 -0
  418. package/vendor/dragonlab/design/seer/v4/review.mjs +33 -0
  419. package/vendor/dragonlab/design/seer/v4/watch.mjs +56 -0
  420. package/vendor/dragonlab/design/stage3/companion-review.mjs +28 -0
  421. package/vendor/dragonlab/design/stage3/sentinel-art.js +39 -0
  422. package/vendor/dragonlab/design/stage3/sentinel-braille.js +216 -0
  423. package/vendor/dragonlab/design/stage3/sentinel-character.js +257 -0
  424. package/vendor/dragonlab/design/stage3/sentinel-icon.js +103 -0
  425. package/vendor/dragonlab/design/stage3/sentinel-warden-eyes.js +95 -0
  426. package/vendor/dragonlab/design/stage3/svg-coverage.js +148 -0
  427. package/vendor/dragonlab/factory/svg-rig.mjs +63 -0
  428. package/vendor/dragonlab/integration/companions-v1/characters.mjs +88 -0
  429. package/vendor/dragonlab/integration/companions-v1/cipher.mjs +2 -0
  430. package/vendor/dragonlab/integration/companions-v1/companion.mjs +63 -0
  431. package/vendor/dragonlab/integration/companions-v1/drago.mjs +2 -0
  432. package/vendor/dragonlab/integration/companions-v1/hyperion.mjs +2 -0
  433. package/vendor/dragonlab/integration/companions-v1/lucid.mjs +2 -0
  434. package/vendor/dragonlab/integration/companions-v1/seer.mjs +2 -0
  435. package/vendor/dragonlab/integration/companions-v1/sentinel.mjs +2 -0
  436. package/vendor/dragonlab/integration/companions-v1/view.mjs +28 -0
  437. package/vendor/dragonlab/integration/hyperion-v3/activity.mjs +76 -0
  438. package/vendor/dragonlab/lib/power-effects.js +294 -0
  439. package/vendor/dragonlab/lib/power-registry.js +74 -0
  440. package/vendor/dragonlab/vendor/quox-dragon/qart.js +348 -0
  441. package/vendor/dragonlab/vendor/quox-dragon/qeyes.js +213 -0
  442. package/lib/core/websiteSession.js +0 -148
@@ -107,6 +107,21 @@ export async function createFinisher({ profileUrl = LOCAL_PROFILE_URL, fetchImpl
107
107
  }
108
108
  },
109
109
 
110
+ /** QUOXON-Z2H lane R: the owner said yes at the credential step; record it as their own act. -> { ok } | { ok: false, error } */
111
+ async grantConsent(provider) {
112
+ try { await createApi({ client, config, auth }).grantConsent(provider); return { ok: true }; }
113
+ catch (e) { return { ok: false, error: apiError(e, 'consent not recorded') }; }
114
+ },
115
+
116
+ /** Z2H-R5 E: can this server serve its host installer? -> { ok, status } | null (unknown). Reads only. */
117
+ async hostInstaller() {
118
+ try {
119
+ const api = createApi({ client, config, auth });
120
+ const base = await api.hostsBaseUrl();
121
+ return await api.hostInstallCheck(base);
122
+ } catch { return null; }
123
+ },
124
+
110
125
  /** -> { ok: true, url, expiresAt, ttlSeconds } | { ok: false, error } */
111
126
  async loginLink(dashboardUrl) {
112
127
  try {
@@ -7,6 +7,10 @@ import React from 'react';
7
7
  import { render } from 'ink';
8
8
  import { spawnSync } from 'node:child_process';
9
9
  import { fileURLToPath } from 'node:url';
10
+ import { readFileSync, writeFileSync, existsSync } from 'node:fs';
11
+ import { homedir } from 'node:os';
12
+ import { join } from 'node:path';
13
+ import { probeQuoxCore, probeMessage } from './probe.js';
10
14
  import { GettingStarted } from './view.js';
11
15
  import { inkStdout } from '../term.js';
12
16
  import { createCredentialBroker, createRequestCredential } from './credential.js';
@@ -15,6 +19,7 @@ import { createInstallRunner, defaultInstallDir, probeAuthHealth } from './insta
15
19
  import { createFinisher } from './finish.js';
16
20
  import { journeyPath as defaultJourneyPath, clearJourney, loadJourney } from './journey.js';
17
21
  import * as T from './copy.js';
22
+ import { runDockerOffer } from './dockerOffer.js';
18
23
 
19
24
  const QUOX_BIN = fileURLToPath(new URL('../../../bin/quox.js', import.meta.url));
20
25
 
@@ -49,19 +54,41 @@ export async function runGettingStarted({ version, cwd, welcome = false, earlyIn
49
54
  await app.waitUntilExit();
50
55
  broker.cancel();
51
56
  if (result.action === 'connect') return connectServer(result.url, { demo });
57
+ if (result.action === 'docker' && !demo) return runDockerOffer(result.kind, { bin: QUOX_BIN }); // FIRSTRUN
52
58
  return result;
53
59
  }
54
60
 
55
- /** /connect server <url>: point the active profile at it and run the existing login. Restores the URL on failure. */
56
- export async function connectServer(url, { demo = false, spawn = spawnSync, out = (s) => process.stdout.write(s) } = {}) {
61
+ /**
62
+ * /connect server <url>: check the URL is a real QuoxCORE (#1101), point the active profile at it and run the
63
+ * existing login. Any outcome but a completed sign-in (failure, Ctrl-C at Email or Password) puts the previous
64
+ * URL and the last-good config.json.bak back, so no half-saved profile is left behind.
65
+ */
66
+ export async function connectServer(url, {
67
+ demo = false, spawn = spawnSync, out = (s) => process.stdout.write(s),
68
+ probe = probeQuoxCore, loadConfigImpl = null, bakFile = join(homedir(), '.config', 'quox', 'config.json.bak'),
69
+ localCertPath = join(defaultInstallDir(), 'quox', 'ssl', 'quox.crt') // the guided install's own certificate, if this is the server
70
+ } = {}) {
57
71
  if (demo) { out(`${T.connectHandoff(url)}\n(demo: not connecting)\n`); return { action: 'exit' }; }
58
- const { loadConfig } = await import('../../core/config.js');
59
- const config = loadConfig();
72
+ const p = await probe(url);
73
+ if (!p?.ok) { out(`${probeMessage(url, p, { localCert: existsSync(localCertPath) ? localCertPath : null })}\n`); return { action: 'connect-failed' }; }
74
+ const load = loadConfigImpl || (await import('../../core/config.js')).loadConfig;
75
+ const config = load();
60
76
  const prev = config.getActiveProfile()?.url || null;
77
+ let bak = null;
78
+ try { bak = readFileSync(bakFile); } catch { /* no backup yet */ }
61
79
  config.setProfileConnection({ url });
62
- const r = spawn(process.execPath, [QUOX_BIN, 'login'], { stdio: 'inherit' });
63
- if (r.status === 0) { clearJourney(); out(`${T.CONNECT_DONE}\n`); return { action: 'connected' }; }
64
- if (prev) loadConfig().setProfileConnection({ url: prev });
80
+ out(`${T.connectHandoff(url)}\n`);
81
+ // Ctrl-C reaches the whole foreground group: the login child handles it, the parent must live to restore.
82
+ const hold = () => {};
83
+ process.on('SIGINT', hold);
84
+ let r;
85
+ try { r = spawn(process.execPath, [QUOX_BIN, 'login'], { stdio: 'inherit' }); } finally { process.removeListener('SIGINT', hold); }
86
+ if (r?.status === 0) { clearJourney(); out(`${T.CONNECT_DONE}\n`); return { action: 'connected' }; }
87
+ const cfg = load();
88
+ if (prev) cfg.setProfileConnection({ url: prev });
89
+ else { const prof = cfg.getActiveProfile(); if (prof) { delete prof.url; cfg.save(); } }
90
+ if (bak) { try { writeFileSync(bakFile, bak, { mode: 0o600 }); } catch { /* best effort */ } }
91
+ if (r?.status === 130 || r?.signal === 'SIGINT') { out(`\x1b[?25h${T.CONNECT_CANCELLED}\n`); return { action: 'connect-cancelled' }; }
65
92
  out(`${T.connectFailed(url)}\n`);
66
93
  return { action: 'connect-failed' };
67
94
  }
@@ -22,13 +22,17 @@
22
22
  */
23
23
  import { spawn as defaultSpawn } from 'node:child_process';
24
24
  import { createHash } from 'node:crypto';
25
- import { mkdtempSync, writeFileSync, rmSync } from 'node:fs';
25
+ import { mkdtempSync, writeFileSync, rmSync, mkdirSync, appendFileSync, openSync, readSync, fstatSync, closeSync } from 'node:fs';
26
+ import { explainInstallFailure } from './failureCause.js';
26
27
  import { homedir, tmpdir } from 'node:os';
27
28
  import { join } from 'node:path';
28
29
  import { STAGE_IDS, stageList } from './runner.js';
29
30
 
31
+ import { BUILD_LABELS } from './copy.js';
30
32
  export const INSTALLER_URL = 'https://get.quox.ai';
31
33
  export const defaultInstallDir = (home = homedir()) => join(home, 'quox-install');
34
+ /** Where the full install log lives: the user's quox state dir, ~/.local/state/quox/install-logs. */
35
+ export const defaultInstallLogDir = (home = homedir()) => join(home, '.local', 'state', 'quox', 'install-logs');
32
36
 
33
37
  const ANSI = /\x1b\[[0-9;?]*[ -/]*[@-~]/g;
34
38
  const RED = /^\x1b\[(0;)?31m/;
@@ -44,7 +48,7 @@ const MARKS = [
44
48
  * push(rawLine) -> { line: cleaned text | null, moves: [[stageId, 'ok'|'running']] }
45
49
  */
46
50
  export function createStageParser() {
47
- const state = { current: 'fetch', ready: false, degraded: false, dashboardUrl: null, lastError: null };
51
+ const state = { current: 'fetch', buildMode: null, ready: false, degraded: false, dashboardUrl: null, lastError: null };
48
52
  return {
49
53
  state,
50
54
  push(raw, { stderr = false } = {}) {
@@ -61,6 +65,9 @@ export function createStageParser() {
61
65
  state.current = next;
62
66
  break;
63
67
  }
68
+ // Z2H-IMAGES: setup.sh either downloads prebuilt images or builds them; the build stage says which.
69
+ if (/Downloading prebuilt images/.test(line)) state.buildMode = 'download';
70
+ else if (/Building images/.test(line)) state.buildMode = 'build';
64
71
  if (/QuoxCORE full platform is ready/.test(line)) state.ready = true;
65
72
  if (/core shell is up, but platform features are degraded/.test(line)) state.degraded = true;
66
73
  const open = /^\s*Open:\s+(https?:\/\/\S+)/.exec(line);
@@ -88,6 +95,7 @@ export function createInstallRunner({
88
95
  env = process.env,
89
96
  url = env.QUOX_INSTALLER_URL || INSTALLER_URL,
90
97
  installDir = defaultInstallDir(),
98
+ logDir = defaultInstallLogDir(),
91
99
  fetchImpl = globalThis.fetch,
92
100
  spawnImpl = defaultSpawn,
93
101
  tmpRoot = tmpdir(),
@@ -118,7 +126,44 @@ export function createInstallRunner({
118
126
  installDir,
119
127
  stages: stageList(),
120
128
  /** Per-run overrides (the view passes the journey's checkpoint): resumeFrom, resumeDashboardUrl, onCheckpoint. */
121
- async run({ onEvent = () => {}, signal, resumeFrom: rf = resumeFrom, resumeDashboardUrl: rd = resumeDashboardUrl, onCheckpoint: oc = onCheckpoint } = {}) {
129
+ /**
130
+ * Every run also writes the full stage log (each line the installer printed) to a file under the
131
+ * user's quox state dir; the result carries `logPath` so the UI can show it on failure and at the end.
132
+ */
133
+ async run(opts = {}) {
134
+ const { onEvent = () => {} } = opts;
135
+ let logPath = null;
136
+ try {
137
+ mkdirSync(logDir, { recursive: true, mode: 0o700 });
138
+ logPath = join(logDir, `install-${new Date(now()).toISOString().replace(/[:.]/g, '-')}.log`);
139
+ writeFileSync(logPath, `# quox install log, started ${new Date(now()).toISOString()}\n`, { mode: 0o600 });
140
+ } catch { logPath = null; } // best effort: a log that cannot be written never fails the install
141
+ const recent = []; // FIRSTRUN: the last lines, so a failure can say its cause
142
+ const tee = (e) => {
143
+ if (e.type === 'log') { recent.push(e.line); if (recent.length > 400) recent.shift(); }
144
+ if (logPath) {
145
+ try {
146
+ if (e.type === 'log') appendFileSync(logPath, e.line + '\n');
147
+ else if (e.type === 'stage') appendFileSync(logPath, `-- stage ${e.id} ${e.status}\n`);
148
+ } catch { /* best effort */ }
149
+ }
150
+ onEvent(e);
151
+ };
152
+ const res = await runCore({ ...opts, onEvent: tee });
153
+ if (!res.ok && !res.interrupted) {
154
+ const named = recent.map(l => /Full build log:\s*(\S+)/.exec(l)?.[1]).filter(Boolean).pop();
155
+ const cause = explainInstallFailure(`${recent.join('\n')}\n${res.error || ''}\n${named ? tailOf(named) : ''}`);
156
+ if (cause) res.cause = cause;
157
+ }
158
+ if (!res.ok && !res.interrupted && logPath) {
159
+ // Z2H-R2: a failed run always says where the full log is and what to do next.
160
+ res.hint = `Full log: ${logPath}. It is safe to run the installer again; finished steps are kept and the build is cached.`;
161
+ }
162
+ if (logPath) { try { appendFileSync(logPath, `-- result ${res.ok ? 'ok' : `failed at ${res.failedStage || '?'}: ${res.error || ''}`}\n`); } catch { /* best effort */ } }
163
+ return logPath ? { ...res, logPath } : res;
164
+ }
165
+ };
166
+ async function runCore({ onEvent = () => {}, signal, resumeFrom: rf = resumeFrom, resumeDashboardUrl: rd = resumeDashboardUrl, onCheckpoint: oc = onCheckpoint } = {}) {
122
167
  const resumeAfterStart = rf === 'start' || rf === 'health';
123
168
  const started = {};
124
169
  const stage = (id, status) => {
@@ -185,11 +230,13 @@ export function createInstallRunner({
185
230
  };
186
231
  };
187
232
  const take = (raw, stderr) => {
233
+ const modeBefore = parser.state.buildMode;
188
234
  const { line, moves } = parser.push(raw, { stderr });
189
235
  for (const [id, status] of moves) {
190
236
  if (status === 'ok') { stage(id, 'ok'); if (id !== 'health') oc(id, {}); }
191
237
  else stage(id, 'running');
192
238
  }
239
+ if (parser.state.buildMode !== modeBefore) emit({ type: 'stage-label', id: 'build', label: BUILD_LABELS[parser.state.buildMode] });
193
240
  if (line) emit({ type: 'log', line });
194
241
  };
195
242
  child.stdout.on('data', feed(false));
@@ -220,6 +267,18 @@ export function createInstallRunner({
220
267
  } finally {
221
268
  try { rmSync(dir, { recursive: true, force: true }); } catch { /* temp cleanup only */ }
222
269
  }
223
- }
224
- };
270
+ }
271
+ }
272
+
273
+ /** The last `max` bytes of a file, '' when it cannot be read (a cause is best effort, never a new failure). */
274
+ function tailOf(path, max = 256 * 1024) {
275
+ let fd = null;
276
+ try {
277
+ fd = openSync(path, 'r');
278
+ const size = fstatSync(fd).size;
279
+ const len = Math.min(size, max);
280
+ const buf = Buffer.alloc(len);
281
+ readSync(fd, buf, 0, len, size - len);
282
+ return buf.toString('utf8');
283
+ } catch { return ''; } finally { if (fd != null) try { closeSync(fd); } catch { /* closed */ } }
225
284
  }
@@ -8,7 +8,7 @@
8
8
  */
9
9
  import { execFile } from 'node:child_process';
10
10
  import { statfs } from 'node:fs/promises';
11
- import { totalmem, homedir } from 'node:os';
11
+ import { totalmem, homedir, userInfo } from 'node:os';
12
12
  import { createConnection } from 'node:net';
13
13
  import { CHECK } from './copy.js';
14
14
 
@@ -55,13 +55,27 @@ export const defaultDeps = () => ({
55
55
  const gb = (bytes) => Math.floor(bytes / 1024 ** 3);
56
56
  const row = (id, status, body, extra = {}) => ({ id, name: CHECK[id].name, status, body, ...extra });
57
57
 
58
+ const safeUser = () => { try { return userInfo().username; } catch { return ''; } };
59
+
58
60
  export async function checkDocker(d) {
59
61
  const v = await d.run('docker', ['--version'], 5000);
60
- if (v.missing || v.code !== 0) return row('docker', 'fail', CHECK.docker.missing, { fix: CHECK.docker.missingFix, link: CHECK.docker.missingLink });
62
+ const linux = (d.platform || process.platform) === 'linux'; // FIRSTRUN: the guide can fix these itself (dockerOffer.js)
63
+ const offer = (kind) => (linux ? { offer: kind } : {});
64
+ if (v.missing || v.code !== 0) return row('docker', 'fail', CHECK.docker.missing, { fix: CHECK.docker.missingFix, link: CHECK.docker.missingLink, ...offer('install') });
61
65
  const ver = (/version\s+([0-9][\w.\-+]*)/i.exec(v.stdout) || [])[1] || '';
62
66
  const info = await d.run('docker', ['info', '--format', '{{.ServerVersion}}'], 10000);
63
67
  if (info.code === 0 && info.stdout.trim()) return row('docker', 'ok', CHECK.docker.ok(ver || info.stdout.trim()));
64
- if (/permission denied/i.test(info.stderr)) return row('docker', 'fail', CHECK.docker.perm, { fix: CHECK.docker.permFix, link: CHECK.docker.link });
68
+ if (/permission denied/i.test(info.stderr)) {
69
+ // Z2H-R5 D: the group was added after this terminal started. `id -nG` shows the groups of THIS process,
70
+ // `id -nG <user>` the account database; in the second but not the first, "press r" can never pass.
71
+ const user = d.user || safeUser();
72
+ if (user) {
73
+ const [mine, db] = await Promise.all([d.run('id', ['-nG'], 3000), d.run('id', ['-nG', user], 3000)]);
74
+ const has = (r) => r.code === 0 && r.stdout.trim().split(/\s+/).includes('docker');
75
+ if (has(db) && !has(mine)) return row('docker', 'fail', CHECK.docker.permStale, { fix: CHECK.docker.permStaleFix, link: CHECK.docker.link, stale: true, ...offer('relaunch') });
76
+ }
77
+ return row('docker', 'fail', CHECK.docker.perm, { fix: CHECK.docker.permFix, link: CHECK.docker.link, ...offer('group') });
78
+ }
65
79
  return row('docker', 'fail', CHECK.docker.down, { fix: CHECK.docker.downFix, link: CHECK.docker.link });
66
80
  }
67
81
 
@@ -0,0 +1,65 @@
1
+ /**
2
+ * #1101: is this URL a real QuoxCORE? One cheap unauthenticated GET of the edge's /health (nginx.conf answers
3
+ * {"status":"ok","service":"dashboard"} on both the HTTP and HTTPS servers), run BEFORE /connect server asks for
4
+ * an email and password. An error body is not data: only a 2xx JSON answer of that exact shape passes.
5
+ *
6
+ * -> { ok: true } | { ok: false, kind: 'url'|'unreachable'|'tls'|'not-quoxcore', code?, status?, detail? }
7
+ */
8
+ const TLS_CODES = /^(SELF_SIGNED_CERT_IN_CHAIN|DEPTH_ZERO_SELF_SIGNED_CERT|UNABLE_TO_VERIFY_LEAF_SIGNATURE|UNABLE_TO_GET_ISSUER_CERT(_LOCALLY)?|CERT_HAS_EXPIRED|CERT_NOT_YET_VALID|ERR_TLS_.*|ERR_SSL_.*|EPROTO)$/;
9
+ const PLAINTEXT_CODES = new Set(['ERR_SSL_WRONG_VERSION_NUMBER', 'EPROTO']);
10
+
11
+ export async function probeQuoxCore(url, { fetchImpl = globalThis.fetch, timeout = 5000 } = {}) {
12
+ let base;
13
+ try {
14
+ base = new URL(String(url || ''));
15
+ if (base.protocol !== 'http:' && base.protocol !== 'https:') return { ok: false, kind: 'url', detail: 'the address must start with https:// or http://' };
16
+ } catch { return { ok: false, kind: 'url', detail: 'the address must start with https:// or http://' }; }
17
+ const target = `${base.href.replace(/\/+$/, '')}/health`;
18
+ let res;
19
+ try {
20
+ res = await fetchImpl(target, { signal: AbortSignal.timeout(timeout), headers: { accept: 'application/json' } });
21
+ } catch (e) {
22
+ const code = e?.cause?.code || e?.code || null;
23
+ if (code && TLS_CODES.test(code)) return { ok: false, kind: 'tls', code };
24
+ if (e?.name === 'TimeoutError' || e?.name === 'AbortError') return { ok: false, kind: 'unreachable', code: 'TIMEOUT', detail: `no answer within ${Math.round(timeout / 1000)} s` };
25
+ return { ok: false, kind: 'unreachable', code, detail: e?.message || 'no answer' };
26
+ }
27
+ if (res?.status === 400 && base.protocol === 'http:') {
28
+ let t = ''; try { t = await res.text(); } catch { /* no body */ }
29
+ if (/plain HTTP request was sent to HTTPS port/i.test(t)) return { ok: false, kind: 'tls', code: 'HTTP_TO_HTTPS_PORT' };
30
+ }
31
+ if (!res || !(res.status >= 200 && res.status < 300)) return { ok: false, kind: 'not-quoxcore', status: res?.status, detail: `HTTP ${res?.status} from /health` };
32
+ let body;
33
+ try { body = JSON.parse(await res.text()); } catch { return { ok: false, kind: 'not-quoxcore', status: res.status, detail: '/health did not answer JSON' }; }
34
+ if (!body || body.status !== 'ok' || body.service !== 'dashboard') return { ok: false, kind: 'not-quoxcore', status: res.status, detail: '/health answered, but not as QuoxCORE does' };
35
+ return { ok: true };
36
+ }
37
+
38
+ // A new QuoxCORE serves its own self-signed certificate (setup.sh writes ssl/quox.crt, CA:TRUE, SAN = localhost +
39
+ // the LAN IP). Trusting exactly that file is the sanctioned fix; these are the codes it fixes. An expired or
40
+ // wrong-name certificate is a different problem: trusting a file would not cure it, so it is not suggested.
41
+ const SELF_SIGNED_CODES = new Set(['DEPTH_ZERO_SELF_SIGNED_CERT', 'SELF_SIGNED_CERT_IN_CHAIN', 'UNABLE_TO_VERIFY_LEAF_SIGNATURE', 'UNABLE_TO_GET_ISSUER_CERT', 'UNABLE_TO_GET_ISSUER_CERT_LOCALLY']);
42
+ export const SERVER_CERT_DEFAULT = '~/quox-install/quox/ssl/quox.crt';
43
+
44
+ /**
45
+ * One plain message per failure kind, naming the address and the next step.
46
+ * opts.localCert: the absolute path of a QuoxCORE certificate on THIS machine (the server is here), if any.
47
+ */
48
+ export function probeMessage(url, r, { localCert = null } = {}) {
49
+ const again = 'Check the address, then try /connect server <url> again.';
50
+ switch (r?.kind) {
51
+ case 'url': return `"${url}" is not a server address (${r.detail}). Example: /connect server https://quox.example.com`;
52
+ case 'unreachable': return `No answer from ${url}${r.code ? ` (${r.code})` : ''}. Nothing was saved and no password was asked for. ${again}`;
53
+ case 'tls':
54
+ if (r.code === 'HTTP_TO_HTTPS_PORT') return `${url} is an HTTPS port spoken to in plain HTTP. Did you mean ${String(url).replace(/^http:/i, 'https:')} ? Nothing was saved.`;
55
+ if (PLAINTEXT_CODES.has(r.code)) return `${url} answered plain HTTP to an https:// request (${r.code}). Did you mean ${String(url).replace(/^https:/i, 'http:')} ? Nothing was saved.`;
56
+ if (SELF_SIGNED_CODES.has(r.code)) {
57
+ const lead = `${url} answered with its own self-signed certificate (${r.code}), which is normal for a new QuoxCORE. Trust that server's certificate, then start quox again.`;
58
+ const tail = 'Never turn TLS checks off. Nothing was saved.';
59
+ if (localCert) return `${lead} It is on this machine, so run: NODE_EXTRA_CA_CERTS=${localCert} quox ${tail}`;
60
+ return `${lead} Copy it from the server (by default ${SERVER_CERT_DEFAULT}) to this machine, then run: NODE_EXTRA_CA_CERTS=~/quox.crt quox ${tail}`;
61
+ }
62
+ return `${url} answered, but its TLS certificate is not trusted (${r.code}). For a self-signed edge, trust its CA with NODE_EXTRA_CA_CERTS=<ca.pem> and try again; never turn TLS checks off. Nothing was saved.`;
63
+ default: return `${url} answered, but it is not a QuoxCORE server (${r?.detail || 'unexpected answer'}). Nothing was saved and no password was asked for. ${again}`;
64
+ }
65
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * QUOXON-Q: after first-run onboarding succeeds, ONE offer (y/N, default No, asked once) to make `q` run quox.
3
+ * The answer is remembered in the CLI config (shortcutOffer = 'yes' | 'no'). A yes runs the same code as
4
+ * `quox shortcut add`. Nothing is offered when q already resolves to something (ours or not): never shadow.
5
+ */
6
+ import { askText } from '../ask.js';
7
+ import { addShortcut, whichOnPath, DEFAULT_NAME } from '../../core/shortcut.js';
8
+
9
+ export const SHORTCUT_QUESTION = 'Want a shorter command? Type `q` instead of `quox`.';
10
+ export const shortcutOfferLine = () => askText(SHORTCUT_QUESTION, { keys: 'y/N', who: 'Quox', action: `make \`${DEFAULT_NAME}\` a link to quox in a folder you own (no sudo, no shell edits); undo with: quox shortcut remove` });
11
+
12
+ /** -> true when the offer should be shown now. */
13
+ export function shouldOfferShortcut({ config, env = process.env, which = whichOnPath } = {}) {
14
+ if (config?.get?.('shortcutOffer')) return false; // asked once
15
+ return !which(DEFAULT_NAME, env);
16
+ }
17
+
18
+ /** Runs the offer. ask(line) -> string answer. -> { offered, accepted, result? } */
19
+ export async function offerShortcut({ config, ask, out = (s) => process.stdout.write(s), env = process.env, add = addShortcut, which = whichOnPath } = {}) {
20
+ if (!shouldOfferShortcut({ config, env, which })) return { offered: false };
21
+ const answer = String(await ask(shortcutOfferLine()) ?? '').trim();
22
+ const accepted = /^y(es)?$/i.test(answer);
23
+ config.set('shortcutOffer', accepted ? 'yes' : 'no');
24
+ if (!accepted) return { offered: true, accepted: false };
25
+ const result = add({ name: DEFAULT_NAME, env });
26
+ out(`${result.ok ? '✔' : '✖'} ${result.message}\n`);
27
+ return { offered: true, accepted: true, result };
28
+ }