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,94 @@
1
+ /**
2
+ * Entry for Getting started (main.js calls this when no server is configured, or for --demo-onboarding).
3
+ * Runs the Ink view, then performs what it asked for: exit, or hand off to the existing `quox login` flow for
4
+ * `/connect server <url>`.
5
+ */
6
+ import React from 'react';
7
+ import { render } from 'ink';
8
+ import { spawnSync } from 'node:child_process';
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';
14
+ import { GettingStarted } from './view.js';
15
+ import { inkStdout } from '../term.js';
16
+ import { createCredentialBroker, createRequestCredential } from './credential.js';
17
+ import { createDryRunRunner } from './runner.js';
18
+ import { createInstallRunner, defaultInstallDir, probeAuthHealth } from './installRunner.js';
19
+ import { createFinisher } from './finish.js';
20
+ import { journeyPath as defaultJourneyPath, clearJourney, loadJourney } from './journey.js';
21
+ import * as T from './copy.js';
22
+ import { runDockerOffer } from './dockerOffer.js';
23
+
24
+ const QUOX_BIN = fileURLToPath(new URL('../../../bin/quox.js', import.meta.url));
25
+
26
+ /** The real install: get.quox.ai -> scripts/setup.sh, health = the new instance's auth service on loopback. */
27
+ export async function realInstall({ env = process.env } = {}) {
28
+ // The CLI's own service resolution (honours the per-service overrides); with no server configured
29
+ // yet it derives auth on loopback 127.0.0.1:3101, where the new instance's auth service listens.
30
+ const { loadConfig } = await import('../../core/config.js');
31
+ const authUrl = loadConfig().getServiceUrl('auth');
32
+ const saved = loadJourney();
33
+ return {
34
+ runner: createInstallRunner({ env, installDir: saved?.installDir || defaultInstallDir(), probeHealth: () => probeAuthHealth(authUrl) }),
35
+ finisher: await createFinisher()
36
+ };
37
+ }
38
+
39
+ /** -> { action: 'exit' } | { action: 'connected', replay? } | { action: 'connect-failed' } */
40
+ export async function runGettingStarted({ version, cwd, welcome = false, earlyInput = null, demo = false, stdout = process.stdout } = {}) {
41
+ const broker = createCredentialBroker();
42
+ const requestCredential = createRequestCredential(broker);
43
+ let result = { action: 'exit' };
44
+ const real = demo ? { runner: createDryRunRunner(), finisher: null } : await realInstall();
45
+ const props = {
46
+ version, cwd, welcome, earlyInput, broker, requestCredential,
47
+ dryRun: demo,
48
+ runner: real.runner,
49
+ finisher: real.finisher, // owner account, vault, one-time link (C2c); none in the dry run
50
+ journeyPath: demo ? null : defaultJourneyPath(), // the demo never leaves state behind
51
+ onExit: (r) => { result = r || result; app.unmount(); }
52
+ };
53
+ const app = render(React.createElement(GettingStarted, props), { exitOnCtrlC: false, stdout: inkStdout(stdout) });
54
+ await app.waitUntilExit();
55
+ broker.cancel();
56
+ if (result.action === 'connect') return connectServer(result.url, { demo });
57
+ if (result.action === 'docker' && !demo) return runDockerOffer(result.kind, { bin: QUOX_BIN }); // FIRSTRUN
58
+ return result;
59
+ }
60
+
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
+ } = {}) {
71
+ if (demo) { out(`${T.connectHandoff(url)}\n(demo: not connecting)\n`); return { action: 'exit' }; }
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();
76
+ const prev = config.getActiveProfile()?.url || null;
77
+ let bak = null;
78
+ try { bak = readFileSync(bakFile); } catch { /* no backup yet */ }
79
+ config.setProfileConnection({ url });
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' }; }
92
+ out(`${T.connectFailed(url)}\n`);
93
+ return { action: 'connect-failed' };
94
+ }
@@ -0,0 +1,284 @@
1
+ /**
2
+ * C2b: the real install runner (the runner interface in ./runner.js).
3
+ *
4
+ * Wraps the existing installer, never a second one: get.quox.ai serves install.sh (quox-website
5
+ * public/install.sh), which clones quox, quoxflow and quoxmcp into $QUOX_DIR and runs
6
+ * scripts/setup.sh --yes (non-interactive). We download it to a private temp file, show its
7
+ * SHA256, run it with `sh`, and map its own step markers onto the five honest stages:
8
+ *
9
+ * fetch start .. "[setup] Step 2/7" (install.sh clones; setup.sh step 1 preflight)
10
+ * configure Step 2/7 .. Step 6/7 (.env secrets, SSL cert, deps, stale cleanup)
11
+ * build Step 6/7 .. "Starting services" (docker compose build)
12
+ * start "Starting services" .. Step 7/7 (docker compose up -d)
13
+ * health Step 7/7 .. "full platform is ready" + our own probe of the auth service
14
+ *
15
+ * Re-running (setup.sh read, 2026-10-12): clone() pulls instead of re-cloning; an existing complete
16
+ * .env and ssl/quox.crt are kept (so the Postgres password still matches its volume); the image
17
+ * build is cached. Step 5 however REMOVES running quox-* containers (compose down, volumes kept)
18
+ * before rebuilding. So: interrupted before `start` completed -> run the whole installer again;
19
+ * `start` completed -> never re-run it, only check health.
20
+ *
21
+ * Never a fake percentage: elapsed time per stage, and the real log lines (`v` in the view).
22
+ */
23
+ import { spawn as defaultSpawn } from 'node:child_process';
24
+ import { createHash } from 'node:crypto';
25
+ import { mkdtempSync, writeFileSync, rmSync, mkdirSync, appendFileSync, openSync, readSync, fstatSync, closeSync } from 'node:fs';
26
+ import { explainInstallFailure } from './failureCause.js';
27
+ import { homedir, tmpdir } from 'node:os';
28
+ import { join } from 'node:path';
29
+ import { STAGE_IDS, stageList } from './runner.js';
30
+
31
+ import { BUILD_LABELS } from './copy.js';
32
+ export const INSTALLER_URL = 'https://get.quox.ai';
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');
36
+
37
+ const ANSI = /\x1b\[[0-9;?]*[ -/]*[@-~]/g;
38
+ const RED = /^\x1b\[(0;)?31m/;
39
+ const MARKS = [
40
+ [/^\[setup\] Step 2\/7\b/, 'configure'],
41
+ [/^\[setup\] Step 6\/7\b/, 'build'],
42
+ [/Starting services\.\.\./, 'start'],
43
+ [/^\[setup\] Step 7\/7\b/, 'health']
44
+ ];
45
+
46
+ /**
47
+ * Line-by-line stage machine over the installer's output.
48
+ * push(rawLine) -> { line: cleaned text | null, moves: [[stageId, 'ok'|'running']] }
49
+ */
50
+ export function createStageParser() {
51
+ const state = { current: 'fetch', buildMode: null, ready: false, degraded: false, dashboardUrl: null, lastError: null };
52
+ return {
53
+ state,
54
+ push(raw, { stderr = false } = {}) {
55
+ const text = String(raw ?? '').replace(ANSI, '').replace(/\s+$/, '');
56
+ const moves = [];
57
+ if (!text.trim()) return { line: null, moves };
58
+ const line = text.replace(/^\s+/, (m) => (m.length > 2 ? ' ' : m));
59
+ for (const [re, next] of MARKS) {
60
+ if (!re.test(line.trim())) continue;
61
+ const from = STAGE_IDS.indexOf(state.current); const to = STAGE_IDS.indexOf(next);
62
+ if (to <= from) break;
63
+ for (let i = from; i < to; i++) { moves.push([STAGE_IDS[i], 'ok']); if (i + 1 < to) moves.push([STAGE_IDS[i + 1], 'running']); }
64
+ moves.push([next, 'running']);
65
+ state.current = next;
66
+ break;
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';
71
+ if (/QuoxCORE full platform is ready/.test(line)) state.ready = true;
72
+ if (/core shell is up, but platform features are degraded/.test(line)) state.degraded = true;
73
+ const open = /^\s*Open:\s+(https?:\/\/\S+)/.exec(line);
74
+ if (open) state.dashboardUrl = open[1];
75
+ if (stderr || RED.test(String(raw))) state.lastError = line.trim();
76
+ return { line: line.trim(), moves };
77
+ }
78
+ };
79
+ }
80
+
81
+ /** GET <authUrl>/health once. -> { ok, error? } */
82
+ export async function probeAuthHealth(authUrl, { fetchImpl = globalThis.fetch, timeout = 3000 } = {}) {
83
+ try {
84
+ const r = await fetchImpl(`${authUrl.replace(/\/+$/, '')}/health`, { signal: AbortSignal.timeout(timeout) });
85
+ return r.ok ? { ok: true } : { ok: false, error: `auth /health answered HTTP ${r.status}` };
86
+ } catch (e) { return { ok: false, error: e?.cause?.code ? `${e.message} (${e.cause.code})` : (e?.message || 'no answer') }; }
87
+ }
88
+
89
+ const sleep = (ms, signal) => new Promise((res) => {
90
+ const t = setTimeout(res, ms);
91
+ signal?.addEventListener?.('abort', () => { clearTimeout(t); res(); }, { once: true });
92
+ });
93
+
94
+ export function createInstallRunner({
95
+ env = process.env,
96
+ url = env.QUOX_INSTALLER_URL || INSTALLER_URL,
97
+ installDir = defaultInstallDir(),
98
+ logDir = defaultInstallLogDir(),
99
+ fetchImpl = globalThis.fetch,
100
+ spawnImpl = defaultSpawn,
101
+ tmpRoot = tmpdir(),
102
+ probeHealth = async () => ({ ok: true }),
103
+ healthTries = 20,
104
+ healthIntervalMs = 3000,
105
+ resumeFrom = null,
106
+ resumeDashboardUrl = null,
107
+ onCheckpoint = () => {},
108
+ killGraceMs = 5000,
109
+ now = () => Date.now()
110
+ } = {}) {
111
+ async function checkHealth(emit, signal) {
112
+ let last = null;
113
+ for (let i = 0; i < Math.max(1, healthTries); i++) {
114
+ if (signal?.aborted) return { ok: false, interrupted: true };
115
+ last = await probeHealth();
116
+ if (last?.ok) return { ok: true };
117
+ emit({ type: 'log', line: `waiting for the auth service: ${last?.error || 'not ready'}` });
118
+ if (i + 1 < healthTries) await sleep(healthIntervalMs, signal);
119
+ }
120
+ return { ok: false, error: last?.error || 'the auth service did not answer' };
121
+ }
122
+
123
+ return {
124
+ kind: 'real',
125
+ url,
126
+ installDir,
127
+ stages: stageList(),
128
+ /** Per-run overrides (the view passes the journey's checkpoint): resumeFrom, resumeDashboardUrl, 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 } = {}) {
167
+ const resumeAfterStart = rf === 'start' || rf === 'health';
168
+ const started = {};
169
+ const stage = (id, status) => {
170
+ const at = now();
171
+ if (status === 'running') started[id] = at;
172
+ onEvent({ type: 'stage', id, status, at, ...(status !== 'running' ? { ms: started[id] != null ? at - started[id] : null } : {}) });
173
+ };
174
+ const emit = (e) => onEvent(e);
175
+
176
+ if (resumeAfterStart) {
177
+ for (const id of ['fetch', 'configure', 'build', 'start']) onEvent({ type: 'stage', id, status: 'ok', at: now(), ms: null, earlier: true });
178
+ emit({ type: 'log', line: 'the services were already started; checking their health' });
179
+ stage('health', 'running');
180
+ const h = await checkHealth(emit, signal);
181
+ if (h.interrupted) return { ok: false, interrupted: true };
182
+ if (!h.ok) { stage('health', 'fail'); return { ok: false, failedStage: 'health', error: h.error, dashboardUrl: rd }; }
183
+ stage('health', 'ok');
184
+ oc('health', { dashboardUrl: rd });
185
+ return { ok: true, dashboardUrl: rd, resumed: true };
186
+ }
187
+
188
+ // ---- fetch: download the installer, show what will run -------------------------------------
189
+ stage('fetch', 'running');
190
+ let body;
191
+ try {
192
+ const r = await fetchImpl(url, { signal });
193
+ if (!r.ok) { stage('fetch', 'fail'); return { ok: false, failedStage: 'fetch', error: `${url} answered HTTP ${r.status}` }; }
194
+ body = await r.text();
195
+ } catch (e) {
196
+ if (signal?.aborted) return { ok: false, interrupted: true };
197
+ stage('fetch', 'fail');
198
+ return { ok: false, failedStage: 'fetch', error: `could not download ${url}: ${e?.message || e}` };
199
+ }
200
+ if (!/^#!\s*\/bin\/(ba)?sh\b/.test(body)) { stage('fetch', 'fail'); return { ok: false, failedStage: 'fetch', error: `${url} did not return a shell script, so nothing was run` }; }
201
+ const sha256 = createHash('sha256').update(body).digest('hex');
202
+ emit({ type: 'script', url, sha256, bytes: Buffer.byteLength(body) });
203
+ emit({ type: 'log', line: `installer ${url} sha256 ${sha256}` });
204
+
205
+ const dir = mkdtempSync(join(tmpRoot, 'quox-installer-'));
206
+ const file = join(dir, 'install.sh');
207
+ writeFileSync(file, body, { mode: 0o600 });
208
+
209
+ const parser = createStageParser();
210
+ try {
211
+ const code = await new Promise((resolve) => {
212
+ const child = spawnImpl('sh', [file], {
213
+ cwd: homedir(),
214
+ env: { ...env, QUOX_DIR: installDir },
215
+ stdio: ['ignore', 'pipe', 'pipe'],
216
+ detached: true // own process group: abort stops docker compose too
217
+ });
218
+ let killTimer = null;
219
+ const onAbort = () => {
220
+ try { process.kill(-child.pid, 'SIGTERM'); } catch { /* already gone */ }
221
+ killTimer = setTimeout(() => { try { process.kill(-child.pid, 'SIGKILL'); } catch { /* gone */ } }, killGraceMs);
222
+ };
223
+ if (signal) { if (signal.aborted) onAbort(); else signal.addEventListener('abort', onAbort, { once: true }); }
224
+ const feed = (stderr) => {
225
+ let rest = '';
226
+ return (chunk) => {
227
+ const parts = (rest + chunk.toString('utf8')).split(/\r\n|\r|\n/);
228
+ rest = parts.pop();
229
+ for (const raw of parts) take(raw, stderr);
230
+ };
231
+ };
232
+ const take = (raw, stderr) => {
233
+ const modeBefore = parser.state.buildMode;
234
+ const { line, moves } = parser.push(raw, { stderr });
235
+ for (const [id, status] of moves) {
236
+ if (status === 'ok') { stage(id, 'ok'); if (id !== 'health') oc(id, {}); }
237
+ else stage(id, 'running');
238
+ }
239
+ if (parser.state.buildMode !== modeBefore) emit({ type: 'stage-label', id: 'build', label: BUILD_LABELS[parser.state.buildMode] });
240
+ if (line) emit({ type: 'log', line });
241
+ };
242
+ child.stdout.on('data', feed(false));
243
+ child.stderr.on('data', feed(true));
244
+ child.on('error', (e) => { parser.state.lastError = `could not run sh: ${e.message}`; resolve(127); });
245
+ child.on('close', (c, sig) => { clearTimeout(killTimer); signal?.removeEventListener?.('abort', onAbort); resolve(sig ? 128 : c); });
246
+ });
247
+
248
+ if (signal?.aborted) return { ok: false, interrupted: true };
249
+ const at = parser.state.current;
250
+ if (code !== 0) {
251
+ stage(at, 'fail');
252
+ return { ok: false, failedStage: at, error: parser.state.lastError || `the installer exited with code ${code}`, dashboardUrl: parser.state.dashboardUrl };
253
+ }
254
+ if (at !== 'health' || !parser.state.ready) {
255
+ if (at !== 'health') stage(at, 'fail'); else stage('health', 'fail');
256
+ return {
257
+ ok: false, failedStage: at === 'health' ? 'health' : at, degraded: parser.state.degraded, dashboardUrl: parser.state.dashboardUrl,
258
+ error: parser.state.degraded ? 'the core is up, but some services are not healthy yet' : 'the installer finished without saying the platform is ready'
259
+ };
260
+ }
261
+ const h = await checkHealth(emit, signal);
262
+ if (h.interrupted) return { ok: false, interrupted: true };
263
+ if (!h.ok) { stage('health', 'fail'); return { ok: false, failedStage: 'health', error: h.error, dashboardUrl: parser.state.dashboardUrl }; }
264
+ stage('health', 'ok');
265
+ oc('health', { dashboardUrl: parser.state.dashboardUrl });
266
+ return { ok: true, dashboardUrl: parser.state.dashboardUrl, sha256 };
267
+ } finally {
268
+ try { rmSync(dir, { recursive: true, force: true }); } catch { /* temp cleanup only */ }
269
+ }
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 */ } }
284
+ }
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Local intent matcher for Getting started. No model, no network: a keyword table, so the answer is instant
3
+ * and predictable. Anything it does not recognise is "offscript" and gets an honest deferral.
4
+ */
5
+
6
+ export const INTENTS = ['servers', 'automate', 'code', 'email', 'explore'];
7
+
8
+ // Order matters: the first rule whose pattern matches wins ("connect" first, so "I already have a server"
9
+ // is never read as "watch my servers").
10
+ const RULES = [
11
+ ['connect', /\b(already (have|got|run)|have (a|my|an?) (own )?(server|quox|instance)|existing (server|quox|instance)|my (own )?quox(core)?|connect (to )?(a |my )?(server|quox))\b/i],
12
+ ['servers', /\b(servers?|hosts?|fleet|monitor(ing)?|watch(ing)?|uptime|infra(structure)?|machines?|homelab|proxmox|vms?|nodes?|cluster|disk space|logs?)\b/i],
13
+ ['email', /\b(e-?mails?|mail|inbox|gmail|outlook|newsletters?|replies)\b/i],
14
+ ['code', /\b(code|coding|coder|repos?|repository|github|gitlab|pull requests?|prs?|bugs?|refactor|programm(ing|er)|develop(er|ment)?|tests?)\b/i],
15
+ ['automate', /\b(automat(e|ion|ing)|workflows?|schedul(e|ed|ing)|cron|routines?|repetitive|every (day|morning|week|hour)|n8n|zapier|tasks?|jobs?)\b/i],
16
+ ['explore', /\b(look(ing)? around|explore|just looking|curious|show me|demo|tour|not sure|no idea|anything|surprise me|what can (you|it|quox) do|browse)\b/i]
17
+ ];
18
+
19
+ /** -> { intent: one of INTENTS | 'connect' | 'offscript' | 'empty', text } */
20
+ export function matchIntent(input) {
21
+ const text = String(input ?? '').trim();
22
+ if (!text) return { intent: 'empty', text };
23
+ const n = /^[1-5]$/.exec(text);
24
+ if (n) return { intent: INTENTS[Number(n[0]) - 1], text };
25
+ for (const [intent, re] of RULES) if (re.test(text)) return { intent, text };
26
+ return { intent: 'offscript', text };
27
+ }
28
+
29
+ /** Y/n answer: Enter alone means yes. -> true | false | null (not an answer) */
30
+ export function yesNo(input) {
31
+ const t = String(input ?? '').trim().toLowerCase();
32
+ if (t === '' || /^(y|yes|yeah|yep|sure|ok|okay|go|do it|set it up|please)\b/.test(t)) return true;
33
+ if (/^(n|no|nope|not now|later|skip|cancel)\b/.test(t)) return false;
34
+ return null;
35
+ }
36
+
37
+ /** `/connect server <url>` -> { ok, url } | { ok: false, reason: 'usage' | 'url', value } | null (not /connect) */
38
+ export function parseConnect(input) {
39
+ const t = String(input ?? '').trim();
40
+ if (!/^\/connect\b/i.test(t)) return null;
41
+ const m = /^\/connect\s+server(?:\s+(\S+))?\s*$/i.exec(t);
42
+ if (!m) return { ok: false, reason: 'usage' };
43
+ if (!m[1]) return { ok: false, reason: 'usage' };
44
+ let raw = m[1];
45
+ if (!/^[a-z][a-z0-9+.-]*:\/\//i.test(raw)) raw = `https://${raw}`;
46
+ try {
47
+ const u = new URL(raw);
48
+ if (!['http:', 'https:'].includes(u.protocol) || !u.hostname) return { ok: false, reason: 'url', value: m[1] };
49
+ return { ok: true, url: u.origin + (u.pathname === '/' ? '' : u.pathname.replace(/\/+$/, '')) };
50
+ } catch {
51
+ return { ok: false, reason: 'url', value: m[1] };
52
+ }
53
+ }
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Journey state for Getting started: ~/.config/quox/onboarding.json, so an interrupted journey resumes.
3
+ * ALLOWLIST ONLY: the file holds exactly the fields named here (never a key, token or password), whatever the
4
+ * caller passes in.
5
+ */
6
+ import { readFileSync, writeFileSync, mkdirSync, rmSync, renameSync } from 'node:fs';
7
+ import { homedir } from 'node:os';
8
+ import { join, dirname } from 'node:path';
9
+
10
+ export const JOURNEY_VERSION = 1;
11
+ export const STEPS = ['ask', 'offer', 'declined', 'preflight', 'credential', 'install', 'owner', 'finishing', 'finale'];
12
+ // installStage = the last install stage that COMPLETED (installRunner.js decides what re-running means).
13
+ const FIELDS = ['step', 'intent', 'request', 'provider', 'installStage', 'installDir', 'dashboardUrl', 'ownerEmail', 'ollamaUrl', 'startedAt', 'updatedAt'];
14
+ const INSTALL_STAGES = ['fetch', 'configure', 'build', 'start', 'health'];
15
+ const MAX = { request: 500, installDir: 300, dashboardUrl: 300, ownerEmail: 254, ollamaUrl: 200 };
16
+
17
+ export const journeyPath = () => join(homedir(), '.config', 'quox', 'onboarding.json');
18
+
19
+ export function sanitize(state) {
20
+ const out = { version: JOURNEY_VERSION };
21
+ for (const k of FIELDS) {
22
+ const v = state?.[k];
23
+ if (v === undefined || v === null) continue;
24
+ if (typeof v !== 'string') continue;
25
+ out[k] = v.slice(0, MAX[k] || 64);
26
+ }
27
+ if (out.installStage && !INSTALL_STAGES.includes(out.installStage)) delete out.installStage;
28
+ if (out.dashboardUrl && !/^https?:\/\/[^\s]+$/.test(out.dashboardUrl)) delete out.dashboardUrl;
29
+ if (out.installDir && !out.installDir.startsWith('/')) delete out.installDir;
30
+ if (out.step && !STEPS.includes(out.step)) delete out.step;
31
+ if (out.provider && !['chatgpt', 'openai', 'anthropic', 'ollama', 'skip'].includes(out.provider)) delete out.provider;
32
+ if (out.ollamaUrl !== undefined && (typeof out.ollamaUrl !== 'string' || !/^https?:\/\//.test(out.ollamaUrl))) delete out.ollamaUrl; // B3: a URL, never a secret
33
+ return out;
34
+ }
35
+
36
+ export function loadJourney(path = journeyPath()) {
37
+ try {
38
+ const raw = JSON.parse(readFileSync(path, 'utf8'));
39
+ if (!raw || raw.version !== JOURNEY_VERSION) return null;
40
+ const s = sanitize(raw);
41
+ return s.step ? s : null;
42
+ } catch { return null; }
43
+ }
44
+
45
+ export function saveJourney(state, path = journeyPath(), now = () => new Date().toISOString()) {
46
+ if (!path) return false;
47
+ try {
48
+ const prev = loadJourney(path);
49
+ const t = now();
50
+ const next = sanitize({ ...state, startedAt: prev?.startedAt || state.startedAt || t, updatedAt: t });
51
+ mkdirSync(dirname(path), { recursive: true });
52
+ const tmp = `${path}.${process.pid}.tmp`;
53
+ writeFileSync(tmp, JSON.stringify(next, null, 2) + '\n', { mode: 0o600 });
54
+ renameSync(tmp, path);
55
+ return true;
56
+ } catch { return false; }
57
+ }
58
+
59
+ export function clearJourney(path = journeyPath()) {
60
+ if (!path) return;
61
+ try { rmSync(path, { force: true }); } catch { /* nothing to clear */ }
62
+ }
63
+
64
+ /**
65
+ * Where a resumed journey starts. The AI key only ever lived in memory, so anything past the credential
66
+ * step goes back through preflight (cheap, and the machine may have changed) and asks for the key again.
67
+ */
68
+ export function resumeStep(saved) {
69
+ if (!saved?.step) return null;
70
+ if (saved.step === 'ask' || saved.step === 'finale') return null;
71
+ if (saved.step === 'offer' || saved.step === 'declined') return 'offer';
72
+ return 'preflight';
73
+ }
@@ -0,0 +1,156 @@
1
+ /**
2
+ * Preflight for Getting started: real checks, each bounded by a timeout, never sudo, never a write.
3
+ * Thresholds and ports are the installer's own (read from the repo, keep in step):
4
+ * scripts/preflight.sh MIN_RAM_GB=4, MIN_DISK_GB=10
5
+ * README (public mirror) "Plan for 8 GB of RAM and 30 GB of free disk"
6
+ * scripts/setup.sh needs docker (daemon reachable), docker compose v2, openssl; get.quox.ai needs git
7
+ * docker-compose.yml host ports below
8
+ */
9
+ import { execFile } from 'node:child_process';
10
+ import { statfs } from 'node:fs/promises';
11
+ import { totalmem, homedir, userInfo } from 'node:os';
12
+ import { createConnection } from 'node:net';
13
+ import { CHECK } from './copy.js';
14
+
15
+ export const MIN_DISK_GB = 10;
16
+ export const REC_DISK_GB = 30;
17
+ export const MIN_RAM_GB = 4;
18
+ export const REC_RAM_GB = 8;
19
+ /** Every host port docker-compose.yml publishes (80, 3000 and 9848 on all interfaces, the rest on loopback). */
20
+ export const PORTS = [80, 3000, 9848, 3100, 3101, 3102, 3103, 3104, 3105, 3106, 3107, 3108, 3110, 3200, 5432, 6333, 9849];
21
+ export const CHECK_IDS = ['docker', 'compose', 'tools', 'disk', 'ram', 'ports'];
22
+
23
+ /** execFile with a hard timeout; never throws. -> { code, stdout, stderr, missing } */
24
+ export function run(cmd, args, timeout = 6000) {
25
+ return new Promise((resolve) => {
26
+ let child;
27
+ try {
28
+ child = execFile(cmd, args, { timeout, killSignal: 'SIGKILL', windowsHide: true, maxBuffer: 1 << 20 }, (err, stdout, stderr) => {
29
+ resolve({ code: err ? (typeof err.code === 'number' ? err.code : 1) : 0, stdout: String(stdout || ''), stderr: String(stderr || ''), missing: err?.code === 'ENOENT', timedOut: Boolean(err?.killed) });
30
+ });
31
+ } catch (e) { resolve({ code: 1, stdout: '', stderr: String(e?.message || e), missing: e?.code === 'ENOENT' }); }
32
+ child?.stdin?.end?.();
33
+ });
34
+ }
35
+
36
+ /** Is something listening on 127.0.0.1:port? A connect, not a bind: needs no privileges, works for port 80. */
37
+ export function portInUse(port, timeout = 400) {
38
+ return new Promise((resolve) => {
39
+ const s = createConnection({ host: '127.0.0.1', port });
40
+ const done = (v) => { s.destroy(); resolve(v); };
41
+ s.setTimeout(timeout, () => done(false));
42
+ s.once('connect', () => done(true));
43
+ s.once('error', () => done(false));
44
+ });
45
+ }
46
+
47
+ export const defaultDeps = () => ({
48
+ run,
49
+ portInUse,
50
+ statfs: (p) => statfs(p),
51
+ totalmem,
52
+ home: homedir()
53
+ });
54
+
55
+ const gb = (bytes) => Math.floor(bytes / 1024 ** 3);
56
+ const row = (id, status, body, extra = {}) => ({ id, name: CHECK[id].name, status, body, ...extra });
57
+
58
+ const safeUser = () => { try { return userInfo().username; } catch { return ''; } };
59
+
60
+ export async function checkDocker(d) {
61
+ const v = await d.run('docker', ['--version'], 5000);
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') });
65
+ const ver = (/version\s+([0-9][\w.\-+]*)/i.exec(v.stdout) || [])[1] || '';
66
+ const info = await d.run('docker', ['info', '--format', '{{.ServerVersion}}'], 10000);
67
+ if (info.code === 0 && info.stdout.trim()) return row('docker', 'ok', CHECK.docker.ok(ver || info.stdout.trim()));
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
+ }
79
+ return row('docker', 'fail', CHECK.docker.down, { fix: CHECK.docker.downFix, link: CHECK.docker.link });
80
+ }
81
+
82
+ export async function checkCompose(d) {
83
+ const r = await d.run('docker', ['compose', 'version', '--short'], 6000);
84
+ const v = r.stdout.trim();
85
+ if (r.code !== 0 || !v) return row('compose', 'fail', CHECK.compose.missing, { fix: CHECK.compose.missingFix, link: CHECK.compose.link });
86
+ const major = Number((/^v?(\d+)/.exec(v) || [])[1]);
87
+ if (major < 2) return row('compose', 'fail', CHECK.compose.old(v), { fix: CHECK.compose.missingFix, link: CHECK.compose.link });
88
+ return row('compose', 'ok', CHECK.compose.ok(v));
89
+ }
90
+
91
+ export async function checkTools(d) {
92
+ const [git, ssl] = await Promise.all([d.run('git', ['--version'], 4000), d.run('openssl', ['version'], 4000)]);
93
+ const missing = [git.code === 0 ? null : 'git', ssl.code === 0 ? null : 'openssl'].filter(Boolean);
94
+ if (missing.length) return row('tools', 'fail', CHECK.tools.missing(missing), { fix: CHECK.tools.fix(missing), link: CHECK.tools.link });
95
+ return row('tools', 'ok', CHECK.tools.ok);
96
+ }
97
+
98
+ export async function checkDisk(d) {
99
+ try {
100
+ const s = await d.statfs(d.home);
101
+ const free = gb(Number(s.bavail) * Number(s.bsize));
102
+ if (free < MIN_DISK_GB) return row('disk', 'fail', CHECK.disk.fail(free, MIN_DISK_GB), { fix: CHECK.disk.fix(MIN_DISK_GB), link: CHECK.disk.link });
103
+ if (free < REC_DISK_GB) return row('disk', 'warn', CHECK.disk.low(free, REC_DISK_GB));
104
+ return row('disk', 'ok', CHECK.disk.ok(free));
105
+ } catch (e) {
106
+ return row('disk', 'warn', `could not measure free space (${e?.code || 'error'})`);
107
+ }
108
+ }
109
+
110
+ export async function checkRam(d) {
111
+ const total = Math.round(d.totalmem() / 1024 ** 3);
112
+ if (total < MIN_RAM_GB) return row('ram', 'fail', CHECK.ram.fail(total, MIN_RAM_GB), { fix: CHECK.ram.fix(MIN_RAM_GB), link: CHECK.ram.link });
113
+ if (total < REC_RAM_GB) return row('ram', 'warn', CHECK.ram.low(total, REC_RAM_GB));
114
+ return row('ram', 'ok', CHECK.ram.ok(total));
115
+ }
116
+
117
+ export async function checkPorts(d, ports = PORTS) {
118
+ const used = [];
119
+ await Promise.all(ports.map(async (p) => { if (await d.portInUse(p)) used.push(p); }));
120
+ used.sort((a, b) => a - b);
121
+ const quox = used.includes(3000) && used.includes(9848); // the dashboard and collector ports: most likely Quox itself
122
+ // Resuming an install this guide started: Quox's own ports being busy is expected, not a blocker.
123
+ if (used.length && quox && d.ownInstall) return row('ports', 'warn', CHECK.ports.ownInstall);
124
+ if (used.length) return row('ports', 'fail', CHECK.ports.busy(used), { fix: quox ? CHECK.ports.quoxFix : CHECK.ports.fix(used[0]), link: CHECK.ports.link });
125
+ return row('ports', 'ok', CHECK.ports.ok(ports.length));
126
+ }
127
+
128
+ const CHECKS = { docker: checkDocker, compose: checkCompose, tools: checkTools, disk: checkDisk, ram: checkRam, ports: checkPorts };
129
+
130
+ /** Rows in their running state, before any result. */
131
+ export const pendingRows = () => CHECK_IDS.map(id => row(id, 'running', CHECK.running));
132
+
133
+ /** Run every check in parallel; onRow(row) fires as each finishes. -> rows in CHECK_IDS order. */
134
+ export async function runPreflight(deps = defaultDeps(), onRow = () => {}) {
135
+ const rows = await Promise.all(CHECK_IDS.map(async (id) => {
136
+ let r;
137
+ try { r = await CHECKS[id](deps); } catch (e) { r = row(id, 'fail', `check failed: ${e?.message || e}`, { fix: 'Press r to check again.', link: 'https://quox.ai/docs/setup' }); }
138
+ onRow(r);
139
+ return r;
140
+ }));
141
+ return rows;
142
+ }
143
+
144
+ export const preflightPassed = (rows) => rows.length > 0 && rows.every(r => r.status === 'ok' || r.status === 'warn');
145
+
146
+ /** Ollama on this machine? A 1 s GET of its model list; never throws. */
147
+ export async function detectOllama({ fetchImpl = globalThis.fetch, timeout = 1000 } = {}) {
148
+ if (!fetchImpl) return false;
149
+ const ac = new AbortController();
150
+ let t;
151
+ const late = new Promise((res) => { t = setTimeout(() => { ac.abort(); res(null); }, timeout); });
152
+ try {
153
+ const r = await Promise.race([fetchImpl('http://127.0.0.1:11434/api/tags', { signal: ac.signal }), late]);
154
+ return Boolean(r?.ok);
155
+ } catch { return false; } finally { clearTimeout(t); }
156
+ }