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,56 @@
1
+ /**
2
+ * C4: version skew between this CLI and the server it talks to. Warns at most once per day, and only when
3
+ * major or minor differ (a patch difference does not matter). Silent whenever either side is unknown.
4
+ *
5
+ * CLI side: the platform release the CLI was built from (the repo VERSION file beside cli/).
6
+ * Server side: the collector's public /version `release` (the image's /app/VERSION).
7
+ */
8
+ import { readFileSync, writeFileSync, mkdirSync } from 'node:fs';
9
+ import { homedir } from 'node:os';
10
+ import { join, dirname, resolve } from 'node:path';
11
+ import { fileURLToPath } from 'node:url';
12
+ import { parseSemver, cmpSemver } from './install.js';
13
+
14
+ const CLI_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..', '..');
15
+ export const DAY_MS = 86400000;
16
+
17
+ export function cliRelease(root = CLI_ROOT) {
18
+ try { const v = readFileSync(join(root, '..', 'VERSION'), 'utf8').trim(); return parseSemver(v) ? v : null; } catch { return null; }
19
+ }
20
+
21
+ export function skewCachePath(env = process.env) {
22
+ return join(env.XDG_CACHE_HOME || join(homedir(), '.cache'), 'quox', 'version-skew.json');
23
+ }
24
+
25
+ /** -> null | { cli, server, newer: 'cli'|'server', message } */
26
+ export function skewOf(cli, server) {
27
+ const a = parseSemver(cli); const b = parseSemver(server);
28
+ if (!a || !b) return null;
29
+ if (a[0] === b[0] && a[1] === b[1]) return null;
30
+ const newer = cmpSemver(cli, server) > 0 ? 'cli' : 'server';
31
+ const message = newer === 'cli'
32
+ ? `this quox CLI is ${cli} but the server runs ${server}. Update the server: quox server update (on the server; quox server update --dry-run shows the plan first).`
33
+ : `this quox CLI is ${cli} but the server runs ${server}. Update the CLI to ${server} so its commands match the server.`;
34
+ return { cli, server, newer, message };
35
+ }
36
+
37
+ /**
38
+ * Once per day: read the server release, compare, return the warning (or null). Never throws.
39
+ * The cache is stamped BEFORE the network call, so a slow or failing server costs one try per day.
40
+ */
41
+ export async function checkSkewOncePerDay({ collectorUrl, fetchImpl = globalThis.fetch, now = () => Date.now(), cachePath = skewCachePath(), cli = cliRelease(), timeoutMs = 1500 } = {}) {
42
+ try {
43
+ if (!cli || !collectorUrl) return null;
44
+ let cache = {};
45
+ try { cache = JSON.parse(readFileSync(cachePath, 'utf8')) || {}; } catch { /* first run */ }
46
+ if (cache.checkedAt && now() - cache.checkedAt < DAY_MS) return null;
47
+ mkdirSync(dirname(cachePath), { recursive: true });
48
+ writeFileSync(cachePath, JSON.stringify({ ...cache, checkedAt: now() }) + '\n');
49
+ const r = await fetchImpl(`${collectorUrl.replace(/\/+$/, '')}/version`, { signal: AbortSignal.timeout(timeoutMs) });
50
+ if (!r.ok) return null;
51
+ const body = await r.json();
52
+ const sk = skewOf(cli, body?.release);
53
+ if (sk) writeFileSync(cachePath, JSON.stringify({ checkedAt: now(), warnedAt: now(), cli, server: body.release }) + '\n');
54
+ return sk;
55
+ } catch { return null; }
56
+ }
@@ -0,0 +1,131 @@
1
+ /**
2
+ * C4: snapshots = DB + config, taken before every update, restored by rollback.
3
+ *
4
+ * <backups>/<stamp>-<release>-<commit>/
5
+ * manifest.json written LAST with complete:true; a dir without it is not a snapshot
6
+ * postgres.sql pg_dumpall from the running postgres service
7
+ * volumes/<v>.tgz every other compose volume (the SQLite stores), tarred with the stack stopped
8
+ * config/.env, config/ssl/, config/docker-compose.override.yml (mode 0600 / as found)
9
+ */
10
+ import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync, cpSync, statSync } from 'node:fs';
11
+ import { join, basename } from 'node:path';
12
+ import { readEnvKeys } from './install.js';
13
+ import { compose } from './status.js';
14
+
15
+ export const HELPER_IMAGE = 'alpine:3.20';
16
+ const CONFIG_ITEMS = ['.env', 'ssl', 'docker-compose.override.yml'];
17
+
18
+ export function listSnapshots(backups) {
19
+ let names = [];
20
+ try { names = readdirSync(backups); } catch { return []; }
21
+ return names.map((n) => {
22
+ const dir = join(backups, n);
23
+ try { const m = JSON.parse(readFileSync(join(dir, 'manifest.json'), 'utf8')); return m?.complete ? { ...m, dir } : null; } catch { return null; }
24
+ }).filter(Boolean).sort((a, b) => String(b.createdAt).localeCompare(String(a.createdAt)));
25
+ }
26
+ export const latestSnapshot = (backups) => listSnapshots(backups)[0] || null;
27
+
28
+ const stampOf = (d) => d.toISOString().replace(/[:.]/g, '-').replace(/-\d{3}Z$/, 'Z');
29
+
30
+ /**
31
+ * The stack's volumes, from the containers' own mounts (compose volumes carry fixed `name:`s whose project
32
+ * label depends on who created them first, so a label filter misses some, postgres included, on real boxes).
33
+ * -> { volumes: [names], pgVolume: name|null }
34
+ */
35
+ export async function stackVolumes(install, run) {
36
+ const ids = await run('docker', compose(install, ['ps', '-a', '-q']), { cwd: install.repoDir });
37
+ const list = ids.code === 0 ? ids.stdout.split('\n').map((s) => s.trim()).filter(Boolean) : [];
38
+ if (!list.length) return { volumes: [], pgVolume: null };
39
+ const r = await run('docker', ['inspect', '--format', '{{range .Mounts}}{{if eq .Type "volume"}}{{.Name}}>{{.Destination}};{{end}}{{end}}', ...list]);
40
+ if (r.code !== 0) return { volumes: [], pgVolume: null };
41
+ const seen = new Map();
42
+ for (const part of r.stdout.split(/[;\n]/).map((s) => s.trim()).filter(Boolean)) {
43
+ const [name, dest] = part.split('>');
44
+ if (name && !seen.has(name)) seen.set(name, dest || '');
45
+ }
46
+ const volumes = [...seen.keys()].sort();
47
+ return { volumes, pgVolume: volumes.find((v) => seen.get(v) === '/var/lib/postgresql/data') || null };
48
+ }
49
+
50
+ /**
51
+ * -> { ok, dir, manifest } | { ok:false, error }. g = gate(sys, { dryRun }).
52
+ * Order: dump postgres live, stop the stack, tar the other volumes, start the stack again.
53
+ */
54
+ export async function createSnapshot({ install, installed, backups, g, reason = 'manual', now = () => new Date(), log = () => {} }) {
55
+ const createdAt = now();
56
+ const dir = join(backups, `${stampOf(createdAt)}-${installed.release || 'unknown'}-${installed.commit || 'nocommit'}`);
57
+ const pgUser = readEnvKeys(install.repoDir, ['POSTGRES_USER']).POSTGRES_USER || 'quoxflow';
58
+ const { volumes, pgVolume } = await stackVolumes(install, g.ro);
59
+ const tarVolumes = volumes.filter((v) => v !== pgVolume);
60
+ const cfg = CONFIG_ITEMS.filter((f) => existsSync(join(install.repoDir, f)));
61
+
62
+ await g.fx(`create ${dir}`, () => mkdirSync(join(dir, 'volumes'), { recursive: true, mode: 0o700 }));
63
+ await g.fx(`copy config (${cfg.join(', ') || 'nothing found'}) to ${join(dir, 'config')}`, () => {
64
+ mkdirSync(join(dir, 'config'), { recursive: true, mode: 0o700 });
65
+ for (const f of cfg) cpSync(join(install.repoDir, f), join(dir, 'config', f), { recursive: true, preserveTimestamps: true });
66
+ });
67
+ let postgres = null;
68
+ if (pgVolume) {
69
+ const r = await g.mut('docker', compose(install, ['exec', '-T', 'postgres', 'pg_dumpall', '-U', pgUser]), { cwd: install.repoDir, stdoutFile: join(dir, 'postgres.sql') });
70
+ if (r.code !== 0) return { ok: false, error: `pg_dumpall failed (exit ${r.code}): ${(r.stderr || '').trim().split('\n').pop()}` };
71
+ postgres = 'postgres.sql';
72
+ }
73
+ if (tarVolumes.length) {
74
+ const s = await g.mut('docker', compose(install, ['stop']), { cwd: install.repoDir });
75
+ if (s.code !== 0) return { ok: false, error: `docker compose stop failed (exit ${s.code})` };
76
+ try {
77
+ for (const v of tarVolumes) {
78
+ const r = await g.mut('docker', ['run', '--rm', '-v', `${v}:/v:ro`, '-v', `${join(dir, 'volumes')}:/b`, HELPER_IMAGE, 'tar', 'czf', `/b/${v}.tgz`, '-C', '/v', '.']);
79
+ if (r.code !== 0) return { ok: false, error: `could not archive volume ${v} (exit ${r.code})` };
80
+ }
81
+ } finally {
82
+ await g.mut('docker', compose(install, ['start']), { cwd: install.repoDir });
83
+ }
84
+ }
85
+ const manifest = {
86
+ version: 1, complete: true, createdAt: createdAt.toISOString(), reason,
87
+ release: installed.release, commit: installed.commit, tag: installed.tag || null, siblings: installed.siblings || {},
88
+ project: install.project, repoDir: install.repoDir, root: install.root,
89
+ config: cfg, postgres, postgresUser: pgUser, pgVolume, volumes: tarVolumes
90
+ };
91
+ await g.fx(`write ${join(dir, 'manifest.json')} (marks the snapshot complete)`, () => writeFileSync(join(dir, 'manifest.json'), JSON.stringify(manifest, null, 2) + '\n', { mode: 0o600 }));
92
+ return { ok: true, dir, manifest };
93
+ }
94
+
95
+ /** Restore data + config from a snapshot into the install. Caller has already checked out the code and built. */
96
+ export async function restoreSnapshot({ install, snap, g, pgReadyTries = 30, sleep = (ms) => new Promise((r) => setTimeout(r, ms)) }) {
97
+ const dc = (args, opts = {}) => g.mut('docker', compose(install, args), { cwd: install.repoDir, ...opts });
98
+ await g.fx(`restore config (${(snap.config || []).join(', ') || 'none'}) from ${snap.dir}`, () => {
99
+ for (const f of snap.config || []) cpSync(join(snap.dir, 'config', f), join(install.repoDir, f), { recursive: true, force: true, preserveTimestamps: true });
100
+ });
101
+ let r = await dc(['down', '--remove-orphans']);
102
+ if (r.code !== 0) return { ok: false, error: `docker compose down failed (exit ${r.code})` };
103
+ for (const v of snap.volumes || []) {
104
+ r = await g.mut('docker', ['run', '--rm', '-v', `${v}:/v`, '-v', `${join(snap.dir, 'volumes')}:/b:ro`, HELPER_IMAGE, 'sh', '-c', `find /v -mindepth 1 -delete && tar xzf /b/${basename(v)}.tgz -C /v`]);
105
+ if (r.code !== 0) return { ok: false, error: `could not restore volume ${v} (exit ${r.code})` };
106
+ }
107
+ if (snap.postgres && snap.pgVolume) {
108
+ r = await g.mut('docker', ['volume', 'rm', snap.pgVolume]);
109
+ if (r.code !== 0) return { ok: false, error: `could not reset ${snap.pgVolume} (exit ${r.code})` };
110
+ r = await dc(['up', '-d', 'postgres']);
111
+ if (r.code !== 0) return { ok: false, error: `could not start postgres (exit ${r.code})` };
112
+ if (!g.dryRun) {
113
+ let ready = false;
114
+ for (let i = 0; i < pgReadyTries && !ready; i++) {
115
+ const p = await g.ro('docker', compose(install, ['exec', '-T', 'postgres', 'pg_isready', '-U', snap.postgresUser || 'quoxflow']), { cwd: install.repoDir });
116
+ ready = p.code === 0; if (!ready) await sleep(2000);
117
+ }
118
+ if (!ready) return { ok: false, error: 'postgres did not become ready to restore into' };
119
+ }
120
+ r = await dc(['exec', '-T', 'postgres', 'psql', '-q', '-U', snap.postgresUser || 'quoxflow', '-d', 'postgres'], { stdinFile: join(snap.dir, snap.postgres) });
121
+ if (r.code !== 0) return { ok: false, error: `psql restore failed (exit ${r.code})` };
122
+ }
123
+ return { ok: true };
124
+ }
125
+
126
+ export function snapshotSize(dir) {
127
+ let n = 0;
128
+ const walk = (d) => { for (const f of readdirSync(d)) { const p = join(d, f); const s = statSync(p); if (s.isDirectory()) walk(p); else n += s.size; } };
129
+ try { walk(dir); } catch { /* best effort */ }
130
+ return n;
131
+ }
@@ -0,0 +1,128 @@
1
+ /**
2
+ * C4: `quox server status` and /server. Read-only: version, health per service, disk, ports, last update.
3
+ * Anything that could not be read is said as such, never filled in.
4
+ */
5
+ import { readFileSync, writeFileSync, mkdirSync, appendFileSync, renameSync } from 'node:fs';
6
+ import { join, dirname } from 'node:path';
7
+ import { readInstalled } from './install.js';
8
+
9
+ const STATE_FILE = 'lifecycle-state.json';
10
+ const HISTORY_FILE = 'history.jsonl';
11
+
12
+ export function readState(root) { try { return JSON.parse(readFileSync(join(root, STATE_FILE), 'utf8')); } catch { return null; } }
13
+ export function writeState(root, state) {
14
+ mkdirSync(root, { recursive: true });
15
+ const p = join(root, STATE_FILE); const tmp = `${p}.${process.pid}.tmp`;
16
+ writeFileSync(tmp, JSON.stringify(state, null, 2) + '\n', { mode: 0o600 });
17
+ renameSync(tmp, p);
18
+ }
19
+ export function appendHistory(root, event) {
20
+ mkdirSync(root, { recursive: true });
21
+ appendFileSync(join(root, HISTORY_FILE), JSON.stringify(event) + '\n', { mode: 0o600 });
22
+ }
23
+ export function readHistory(root) {
24
+ try { return readFileSync(join(root, HISTORY_FILE), 'utf8').split('\n').filter(Boolean).map((l) => { try { return JSON.parse(l); } catch { return null; } }).filter(Boolean); } catch { return []; }
25
+ }
26
+
27
+ /** `docker compose ps --format json` is a JSON array on some versions and NDJSON on others. */
28
+ export function parseComposePs(text) {
29
+ const t = String(text || '').trim();
30
+ if (!t) return [];
31
+ try { const j = JSON.parse(t); return Array.isArray(j) ? j : [j]; } catch { /* NDJSON */ }
32
+ return t.split('\n').map((l) => { try { return JSON.parse(l); } catch { return null; } }).filter(Boolean);
33
+ }
34
+
35
+ export const compose = (install, args) => ['compose', '-p', install.project, ...args];
36
+
37
+ export async function composeServices(install, sys) {
38
+ const r = await sys.run('docker', compose(install, ['ps', '-a', '--format', 'json']), { cwd: install.repoDir });
39
+ if (r.code !== 0) return { ok: false, error: (r.stderr || '').trim().split('\n').pop() || `docker compose ps exited ${r.code}` };
40
+ return {
41
+ ok: true,
42
+ services: parseComposePs(r.stdout).map((s) => ({
43
+ service: s.Service || s.Name,
44
+ name: s.Name,
45
+ state: s.State || '?',
46
+ health: s.Health || null,
47
+ ports: [...new Set((s.Publishers || []).filter((p) => p.PublishedPort).map((p) => `${p.URL && p.URL !== '0.0.0.0' && p.URL !== '::' ? p.URL + ':' : ''}${p.PublishedPort}->${p.TargetPort}`))]
48
+ })).sort((a, b) => String(a.service).localeCompare(String(b.service)))
49
+ };
50
+ }
51
+
52
+ export async function diskFree(path, sys) {
53
+ const r = await sys.run('df', ['-Pk', path]);
54
+ if (r.code !== 0) return { ok: false, error: `df exited ${r.code}` };
55
+ const line = r.stdout.trim().split('\n').pop().split(/\s+/);
56
+ const availKb = Number(line[3]); const sizeKb = Number(line[1]);
57
+ if (!Number.isFinite(availKb)) return { ok: false, error: 'could not read df output' };
58
+ return { ok: true, path, freeGB: +(availKb / 1048576).toFixed(1), sizeGB: +(sizeKb / 1048576).toFixed(1), usedPct: line[4] };
59
+ }
60
+
61
+ async function getJson(url, fetchImpl, timeout = 3000) {
62
+ try {
63
+ const r = await fetchImpl(url, { signal: AbortSignal.timeout(timeout) });
64
+ if (!r.ok) return { ok: false, status: r.status, error: `HTTP ${r.status}` };
65
+ let body = null; try { body = await r.json(); } catch { /* health may be text */ }
66
+ return { ok: true, status: r.status, body };
67
+ } catch (e) { return { ok: false, error: e?.cause?.code || e?.message || 'no answer' }; }
68
+ }
69
+
70
+ /** The running build, from the collector's public /version. */
71
+ export async function runningVersion(collectorUrl, fetchImpl = globalThis.fetch) {
72
+ const r = await getJson(`${collectorUrl.replace(/\/+$/, '')}/version`, fetchImpl);
73
+ if (!r.ok) return { ok: false, error: r.error };
74
+ return { ok: true, commit: r.body?.version || null, release: r.body?.release || null, stamped: r.body?.stamped ?? null };
75
+ }
76
+
77
+ export async function probeHttp(urls, fetchImpl = globalThis.fetch) {
78
+ return Promise.all(Object.entries(urls).map(async ([name, base]) => {
79
+ const t0 = Date.now();
80
+ const r = await getJson(`${base.replace(/\/+$/, '')}/health`, fetchImpl);
81
+ return { name, url: base, ok: r.ok, detail: r.ok ? `HTTP ${r.status}` : r.error, ms: Date.now() - t0 };
82
+ }));
83
+ }
84
+
85
+ export function defaultUrls(config) {
86
+ const get = (s, fb) => { try { return config?.getServiceUrl?.(s) || fb; } catch { return fb; } };
87
+ return { auth: get('auth', 'http://127.0.0.1:3101'), collector: get('collector', 'http://127.0.0.1:9848') };
88
+ }
89
+
90
+ /** -> the full status object. Never throws for a missing piece. */
91
+ export async function gatherStatus({ install, sys, fetchImpl = globalThis.fetch, urls, backups }) {
92
+ const [installed, running, svc, disk, http] = await Promise.all([
93
+ readInstalled(install, sys),
94
+ runningVersion(urls.collector, fetchImpl),
95
+ composeServices(install, sys),
96
+ diskFree(install.root, sys),
97
+ probeHttp(urls, fetchImpl)
98
+ ]);
99
+ const hist = backups ? readHistory(backups) : [];
100
+ const lastUpdate = [...hist].reverse().find((e) => e.op === 'update' || e.op === 'rollback') || null;
101
+ const state = backups ? readState(backups) : null;
102
+ return { install, installed, running, services: svc, disk, http, lastUpdate, inFlight: state && state.status === 'running' ? state : null };
103
+ }
104
+
105
+ const svcIcon = (s) => (s.state === 'running' ? (s.health && s.health !== 'healthy' ? '⚠' : '✔') : '✖');
106
+
107
+ /** Rows for the terminal and for /server (kind/key/text like the other panels). */
108
+ export function statusRows(st) {
109
+ const row = (key, text) => ({ kind: 'row', key, text: String(text) });
110
+ const out = [{ kind: 'head', text: 'Server' }];
111
+ out.push(row('Install', `${st.install.repoDir} (found via ${st.install.source}, project ${st.install.project})`));
112
+ const i = st.installed;
113
+ out.push(row('Version', `${i.release || '? no VERSION file'}${i.tag ? ` (${i.tag})` : ''} · checkout ${i.commit || '?'}${i.dirty ? ' · local changes' : ''}`));
114
+ const rv = st.running;
115
+ out.push(row('Running', rv.ok ? `${rv.commit || '?'}${rv.stamped === false ? ' (unstamped build)' : ''}${rv.commit && i.commit && !String(rv.commit).startsWith(i.commit) && !String(i.commit).startsWith(rv.commit) ? ' · differs from the checkout' : ''}` : `? ${rv.error}`));
116
+ for (const h of st.http) out.push(row(`${h.ok ? '✔' : '✖'} ${h.name}`, `${h.url} · ${h.detail} · ${h.ms}ms`));
117
+ if (!st.services.ok) out.push(row('✖ Services', `? ${st.services.error}`));
118
+ else {
119
+ const up = st.services.services.filter((s) => s.state === 'running').length;
120
+ out.push(row('Services', `${up} of ${st.services.services.length} running`));
121
+ for (const s of st.services.services) out.push({ kind: s.state === 'running' && (!s.health || s.health === 'healthy') ? 'dim' : 'row', key: ` ${svcIcon(s)} ${s.service}`, text: [s.state + (s.health ? ` (${s.health})` : ''), s.ports.join(' ')].filter(Boolean).join(' · ') });
122
+ }
123
+ out.push(row('Disk', st.disk.ok ? `${st.disk.freeGB} GB free of ${st.disk.sizeGB} GB (${st.disk.usedPct} used) at ${st.disk.path}` : `? ${st.disk.error}`));
124
+ const lu = st.lastUpdate;
125
+ out.push(row('Last update', lu ? `${lu.op} ${lu.status} ${lu.at}${lu.from ? ` · ${lu.from} -> ${lu.to || '?'}` : ''}` : 'none recorded by quox server update'));
126
+ if (st.inFlight) out.push(row('⚠ In flight', `an update to ${st.inFlight.target} stopped after ${st.inFlight.done?.slice(-1)[0] || 'preflight'}; quox server update resumes it`));
127
+ return out;
128
+ }
@@ -0,0 +1,65 @@
1
+ /**
2
+ * C4 lifecycle: the one place that runs host commands (docker, docker compose, git, df, tar, openssl).
3
+ *
4
+ * Every lifecycle module takes a `sys` and never spawns on its own, so tests pass a stub and a dry run
5
+ * can prove it never reached a mutating command. A call marked { ro: true } is read-only and still runs
6
+ * in a dry run (it is how the plan learns what is there); anything else is recorded, never run, when
7
+ * dry. That rule lives here, once, in `gate()`.
8
+ */
9
+ import { spawn as defaultSpawn } from 'node:child_process';
10
+ import { createWriteStream, createReadStream } from 'node:fs';
11
+
12
+ /** -> { run(cmd, args, opts) -> Promise<{ code, stdout, stderr }> } */
13
+ export function createSys({ spawnImpl = defaultSpawn } = {}) {
14
+ return {
15
+ kind: 'real',
16
+ run(cmd, args = [], { cwd, env, stdoutFile, stdinFile, stdinText, timeoutMs = 0 } = {}) {
17
+ return new Promise((resolve) => {
18
+ let child;
19
+ try {
20
+ child = spawnImpl(cmd, args, { cwd, env: env ? { ...process.env, ...env } : process.env, stdio: [stdinFile || typeof stdinText === 'string' ? 'pipe' : 'ignore', 'pipe', 'pipe'] });
21
+ } catch (e) { resolve({ code: 127, stdout: '', stderr: e.message }); return; }
22
+ let stdout = ''; let stderr = '';
23
+ const sink = stdoutFile ? createWriteStream(stdoutFile, { mode: 0o600 }) : null;
24
+ if (sink) child.stdout.pipe(sink); else child.stdout.on('data', (d) => { if (stdout.length < 4e6) stdout += d; });
25
+ child.stderr.on('data', (d) => { if (stderr.length < 1e6) stderr += d; });
26
+ if (stdinFile) createReadStream(stdinFile).pipe(child.stdin);
27
+ else if (typeof stdinText === 'string') child.stdin.end(stdinText); // secrets via stdin, never argv
28
+ const timer = timeoutMs ? setTimeout(() => { try { child.kill('SIGKILL'); } catch { /* gone */ } }, timeoutMs) : null;
29
+ child.on('error', (e) => { clearTimeout(timer); resolve({ code: 127, stdout, stderr: stderr || e.message }); });
30
+ child.on('close', (code, sig) => {
31
+ clearTimeout(timer);
32
+ const done = () => resolve({ code: sig ? 128 : code, stdout, stderr });
33
+ if (sink) sink.end(done); else done();
34
+ });
35
+ });
36
+ }
37
+ };
38
+ }
39
+
40
+ export const show = (cmd, args = []) => [cmd, ...args].map((a) => (/^[\w./:=@%+,-]+$/.test(a) ? a : `'${String(a).replace(/'/g, `'\\''`)}'`)).join(' ');
41
+
42
+ /**
43
+ * A step runner over sys: ro calls always run; mutating calls run only when !dryRun, else they are
44
+ * recorded in `planned` and answered { code: 0, dry: true }. log(line) receives every command shown.
45
+ */
46
+ export function gate(sys, { dryRun = false, log = () => {} } = {}) {
47
+ const planned = [];
48
+ return {
49
+ dryRun,
50
+ planned,
51
+ async ro(cmd, args, opts) { return sys.run(cmd, args, opts); },
52
+ async mut(cmd, args, opts = {}) {
53
+ const line = show(cmd, args) + (opts.stdoutFile ? ` > ${opts.stdoutFile}` : '') + (opts.stdinFile ? ` < ${opts.stdinFile}` : '');
54
+ if (dryRun) { planned.push(line); log(`[dry run] ${line}`); return { code: 0, stdout: '', stderr: '', dry: true }; }
55
+ log(`$ ${line}`);
56
+ return sys.run(cmd, args, opts);
57
+ },
58
+ /** A filesystem change (or any non-command effect): described, and only performed when !dryRun. */
59
+ async fx(desc, fn) {
60
+ if (dryRun) { planned.push(desc); log(`[dry run] ${desc}`); return undefined; }
61
+ log(desc);
62
+ return fn();
63
+ }
64
+ };
65
+ }
@@ -0,0 +1,63 @@
1
+ /**
2
+ * C4: `quox server uninstall`. Lists exactly what goes first; two-step confirmation (the command layer);
3
+ * keeps backups unless --purge, and takes a final snapshot first unless --no-backup or --purge.
4
+ */
5
+ import { existsSync, readdirSync, rmSync } from 'node:fs';
6
+ import { homedir } from 'node:os';
7
+ import { join, resolve } from 'node:path';
8
+ import { SIBLINGS } from './install.js';
9
+ import { compose, composeServices } from './status.js';
10
+ import { createSnapshot, listSnapshots, stackVolumes } from './snapshot.js';
11
+ import { readInstalled } from './install.js';
12
+ import { gate } from './sys.js';
13
+
14
+ const DANGEROUS = (p, home) => {
15
+ const r = resolve(p);
16
+ return r === '/' || r === resolve(home) || r.split('/').filter(Boolean).length < 2;
17
+ };
18
+
19
+ /** Everything that will be removed and kept. Read-only. */
20
+ export async function planUninstall({ install, sys, backups, purge = false, journeyFile = join(homedir(), '.config', 'quox', 'onboarding.json'), home = homedir() }) {
21
+ const svc = await composeServices(install, sys);
22
+ const vols = await stackVolumes(install, (c, a, o) => sys.run(c, a, o));
23
+ const imgs = await sys.run('docker', compose(install, ['config', '--images']), { cwd: install.repoDir });
24
+ const dirs = [install.repoDir, ...SIBLINGS.map((s) => join(install.root, s)).filter((d) => existsSync(d))];
25
+ const refused = dirs.filter((d) => DANGEROUS(d, home));
26
+ let rootToo = false;
27
+ try { rootToo = !DANGEROUS(install.root, home) && readdirSync(install.root).every((f) => dirs.includes(join(install.root, f))); } catch { /* unreadable */ }
28
+ const snaps = listSnapshots(backups);
29
+ const backupsInside = dirs.some((d) => resolve(backups).startsWith(resolve(d) + '/')) || (rootToo && resolve(backups).startsWith(resolve(install.root) + '/'));
30
+ return {
31
+ backupsInside,
32
+ containers: svc.ok ? svc.services.map((s) => s.name) : [],
33
+ containersError: svc.ok ? null : svc.error,
34
+ volumes: vols.volumes,
35
+ images: imgs.code === 0 ? [...new Set(imgs.stdout.split('\n').map((s) => s.trim()).filter(Boolean))] : [],
36
+ networks: `the ${install.project} compose networks`,
37
+ dirs: dirs.filter((d) => !refused.includes(d)),
38
+ root: rootToo ? install.root : null,
39
+ refused,
40
+ journeyFile: existsSync(journeyFile) ? journeyFile : null,
41
+ backups: { dir: backups, count: snaps.length, removed: purge }
42
+ };
43
+ }
44
+
45
+ /** Executes the plan. Confirmation is the caller's job and must have happened already. */
46
+ export async function runUninstall({ install, sys, backups, purge = false, noBackup = false, dryRun = false, log = () => {}, plan, now }) {
47
+ const g = gate(sys, { dryRun, log });
48
+ if (plan.backupsInside && !purge) return { ok: false, step: 'plan', error: `the backups directory ${backups} sits inside the install, so removing the install would delete them. Set QUOX_SERVER_BACKUPS to a path outside it, or pass --purge.` };
49
+ let finalSnapshot = null;
50
+ if (!noBackup && !purge) {
51
+ const installed = await readInstalled(install, sys);
52
+ const snap = await createSnapshot({ install, installed, backups, g, reason: 'before uninstall', now, log });
53
+ if (!snap.ok) return { ok: false, step: 'backup', error: `${snap.error}. Nothing was removed; pass --no-backup to uninstall without a final snapshot.` };
54
+ finalSnapshot = snap.dir;
55
+ }
56
+ const r = await g.mut('docker', compose(install, ['down', '--volumes', '--rmi', 'local', '--remove-orphans']), { cwd: install.repoDir });
57
+ if (r.code !== 0) return { ok: false, step: 'down', error: `docker compose down failed (exit ${r.code}); the install directory was left in place` };
58
+ for (const d of plan.dirs) await g.fx(`remove ${d}`, () => rmSync(d, { recursive: true, force: true }));
59
+ if (plan.root) await g.fx(`remove ${plan.root} (now empty)`, () => rmSync(plan.root, { recursive: true, force: true }));
60
+ if (plan.journeyFile) await g.fx(`remove ${plan.journeyFile}`, () => rmSync(plan.journeyFile, { force: true }));
61
+ if (purge) await g.fx(`remove every backup in ${backups}`, () => rmSync(backups, { recursive: true, force: true }));
62
+ return { ok: true, dryRun, finalSnapshot, planned: g.planned };
63
+ }