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,660 @@
1
+ /**
2
+ * Workflow commands: CRUD, run, steps, versions, declarative apply/validate/import (WP2.5)
3
+ */
4
+
5
+ import { readFileSync } from 'node:fs';
6
+ import { extname } from 'node:path';
7
+ import yaml from 'js-yaml';
8
+ import { formatOutput, formatDetail, printWarning, printInfo, getOutputFormat } from '../core/output.js';
9
+ import { events, printEvent, line as glyphLine } from '../core/quoxglyph.js';
10
+ import { resolveOrgId, parseJSON } from './helpers.js';
11
+ import { resolveEndpoint } from '../api/endpoints.js';
12
+ import { confirmDestructive } from '../core/confirm.js';
13
+
14
+ // Top-level Workflow fields compared for `workflow apply`'s diff-preview
15
+ // (mirrors core/workflow.ts's WorkflowSchema field set, minus `id`).
16
+ const APPLY_DIFF_FIELDS = ['name', 'description', 'notes', 'version', 'nodes', 'edges', 'variables', 'credentials', 'settings', 'metadata'];
17
+
18
+ /**
19
+ * Read a declarative workflow/template file (.yaml/.yml/.json) and parse it
20
+ * to a plain object. Falls back to trying both parsers when the extension
21
+ * is unrecognized (YAML is a superset of JSON, so this also covers a
22
+ * misnamed .json-shaped file).
23
+ */
24
+ function readDeclarativeFile(filePath) {
25
+ let raw;
26
+ try {
27
+ raw = readFileSync(filePath, 'utf-8');
28
+ } catch (err) {
29
+ throw new Error(`Could not read ${filePath}: ${err.message}`);
30
+ }
31
+
32
+ const ext = extname(filePath).toLowerCase();
33
+ try {
34
+ if (ext === '.json') return JSON.parse(raw);
35
+ if (ext === '.yaml' || ext === '.yml') return yaml.load(raw);
36
+ try {
37
+ return JSON.parse(raw);
38
+ } catch {
39
+ return yaml.load(raw);
40
+ }
41
+ } catch (err) {
42
+ throw new Error(`Could not parse ${filePath} (expected YAML or JSON): ${err.message}`);
43
+ }
44
+ }
45
+
46
+ /** Short human-readable summary of a field value for the diff-preview. */
47
+ function summarizeValue(value) {
48
+ if (value === undefined) return '(unset)';
49
+ if (Array.isArray(value)) return `${value.length} item(s)`;
50
+ if (value !== null && typeof value === 'object') return `${Object.keys(value).length} key(s)`;
51
+ return JSON.stringify(value);
52
+ }
53
+
54
+ /**
55
+ * Build a field-level diff between the currently-stored workflow (from
56
+ * `workflows.applyGet`) and the local file, restricted to fields present in
57
+ * the local file — used by `workflow apply` to preview a PUT before firing it.
58
+ */
59
+ function buildApplyDiff(existing, incoming) {
60
+ const existingDef = existing.definition || existing;
61
+ const changes = [];
62
+ for (const key of APPLY_DIFF_FIELDS) {
63
+ if (!(key in incoming)) continue;
64
+ const before = existingDef[key];
65
+ const after = incoming[key];
66
+ if (JSON.stringify(before) === JSON.stringify(after)) continue;
67
+ changes.push({ key, before, after });
68
+ }
69
+ return changes;
70
+ }
71
+
72
+ function printApplyDiff(workflowId, changes) {
73
+ console.log(`\nChanges to workflow ${workflowId}:`);
74
+ for (const { key, before, after } of changes) {
75
+ console.log(` ${key}: ${summarizeValue(before)} -> ${summarizeValue(after)}`);
76
+ }
77
+ console.log('');
78
+ }
79
+
80
+ // Read one engine workflow by id, or by its exact name when no id matches (proof cases and people know
81
+ // names; engine ids are wf-<ms>-<6>). An ambiguous name is refused with the matching ids, never guessed.
82
+ async function getEngineWorkflow(client, ref) {
83
+ const ep = resolveEndpoint('workflows.applyGet', { workflowId: ref });
84
+ try {
85
+ const res = await client.get(ep.path, { service: ep.service });
86
+ return res.data || res;
87
+ } catch (err) {
88
+ if (err.status !== 404) throw err;
89
+ const listEp = resolveEndpoint('workflows.applyList');
90
+ const matches = [];
91
+ for (let offset = 0, guard = 0; guard < 200; guard++) {
92
+ const data = await client.get(listEp.path, { service: listEp.service, query: { limit: 100, offset } });
93
+ const items = data.data || data.items || data.workflows || (Array.isArray(data) ? data : []);
94
+ for (const w of items) if (w && w.name === ref) matches.push(w);
95
+ offset += items.length;
96
+ const pg = data.pagination;
97
+ if (!pg || !items.length || !(offset < pg.total)) break;
98
+ }
99
+ if (matches.length > 1) throw new Error(`Workflow name "${ref}" matches ${matches.length} workflows (${matches.map((m) => m.id).join(', ')}); give the id instead.`);
100
+ if (!matches.length) throw err;
101
+ const byId = resolveEndpoint('workflows.applyGet', { workflowId: matches[0].id });
102
+ const res = await client.get(byId.path, { service: byId.service });
103
+ return res.data || res;
104
+ }
105
+ }
106
+
107
+ export function registerWorkflowCommands(program, ctx) {
108
+ const { client, auth } = ctx;
109
+
110
+ const workflow = program
111
+ .command('workflow')
112
+ .description('Manage workflows');
113
+
114
+ // fleet #465: workflows live in two stores. The engine (quoxflow) is the
115
+ // WRITE path for `workflow apply`/`activate`, but the auth-service store
116
+ // is a completely separate model quoxflow never writes to. `list` and
117
+ // `get` below read BOTH (or try engine-first with a fallback) and label
118
+ // which store answered, instead of silently missing engine-created
119
+ // workflows the way the auth-only read used to.
120
+
121
+ workflow
122
+ .command('list')
123
+ .description('List workflows')
124
+ .option('--status <status>', 'filter by status')
125
+ .action(async (options) => {
126
+ auth.requireAuth();
127
+ const orgId = resolveOrgId(auth, program);
128
+ const query = {};
129
+ if (options.status) query.status = options.status;
130
+
131
+ const rows = [];
132
+
133
+ try {
134
+ const ep = resolveEndpoint('workflows.applyList');
135
+ // The engine pages (default 20, max 100). Walk every page so the list is the
136
+ // whole store, not the first page (121 workflows read as 20 before).
137
+ const PAGE = 100;
138
+ for (let offset = 0, guard = 0; guard < 200; guard++) {
139
+ const data = await client.get(ep.path, { service: ep.service, query: { limit: PAGE, offset } });
140
+ const items = data.data || data.items || data.workflows || (Array.isArray(data) ? data : []);
141
+ for (const item of items) rows.push({ ...item, source: 'engine' });
142
+ const pg = data.pagination;
143
+ offset += items.length;
144
+ if (!pg || !items.length || !(offset < pg.total)) break;
145
+ }
146
+ } catch (err) {
147
+ printWarning(`Engine (quoxflow) workflow list failed: ${err.message}`);
148
+ }
149
+
150
+ try {
151
+ const ep = resolveEndpoint('workflows.list', { orgId });
152
+ const data = await client.get(ep.path, { service: ep.service, query });
153
+ const items = data.items || data.workflows || (Array.isArray(data) ? data : []);
154
+ for (const item of items) rows.push({ ...item, source: 'auth' });
155
+ } catch (err) {
156
+ printWarning(`Auth-service workflow list failed: ${err.message}`);
157
+ }
158
+
159
+ formatOutput({
160
+ columns: ['id', 'name', 'status', 'source', 'step_count', 'last_run', 'created_at'],
161
+ headers: ['ID', 'Name', 'Status', 'Source', 'Steps', 'Last Run', 'Created'],
162
+ rows,
163
+ }, { format: program.opts().output });
164
+ });
165
+
166
+ workflow
167
+ .command('get')
168
+ .description('Get workflow details')
169
+ .argument('<workflowId>', 'workflow ID or exact name')
170
+ .action(async (workflowId) => {
171
+ auth.requireAuth();
172
+ const orgId = resolveOrgId(auth, program);
173
+
174
+ let data, source;
175
+ try {
176
+ data = await getEngineWorkflow(client, workflowId);
177
+ source = 'engine';
178
+ } catch (err) {
179
+ if (err.status !== 404) throw err;
180
+ try {
181
+ const ep = resolveEndpoint('workflows.get', { orgId, workflowId });
182
+ const res = await client.get(ep.path, { service: ep.service });
183
+ data = res.workflow || res;
184
+ source = 'auth';
185
+ } catch (err2) {
186
+ if (err2.status !== 404) throw err2;
187
+ throw new Error(
188
+ `Workflow "${workflowId}" was not found in either the engine (quoxflow) or auth-service workflow store.`
189
+ );
190
+ }
191
+ }
192
+
193
+ formatDetail({ ...data, source }, { format: program.opts().output, title: data.name });
194
+ });
195
+
196
+ // ── workflow schedule <id> ──────────────────────────────────────────────────
197
+ // Is the schedule REALLY registered, and when does it fire next? Read from the live scheduler
198
+ // (GET /api/triggers/schedules), never inferred from the saved config (SITE-CLI 2026-10-17).
199
+ workflow
200
+ .command('schedule')
201
+ .description('Show whether a workflow\'s schedule trigger is registered with the live scheduler, and its next fire time')
202
+ .argument('<workflowId>', 'workflow ID or exact name')
203
+ .action(async (workflowId) => {
204
+ auth.requireAuth();
205
+ const wf = await getEngineWorkflow(client, workflowId);
206
+ const id = wf.id || workflowId;
207
+ const nodes = (wf.definition && wf.definition.nodes) || wf.nodes || [];
208
+ const schedNodes = nodes.filter((n) => n && n.type === 'trigger:schedule');
209
+
210
+ const liveEp = resolveEndpoint('triggers.schedules');
211
+ const liveRes = await client.get(liveEp.path, { service: liveEp.service });
212
+ const live = liveRes.data || liveRes;
213
+ const ownedByTasks = live.owned_by === 'tasks';
214
+ const rows = (live.schedules || []).filter((r) => r.workflowId === id);
215
+
216
+ const schedules = schedNodes.map((n) => {
217
+ const cfg = n.config || {};
218
+ const saved = cfg.expression || cfg.cron || null;
219
+ const reg = rows.find((r) => r.nodeId === n.id);
220
+ if (ownedByTasks) {
221
+ return { nodeId: n.id, registered: null, state: 'unknown', saved, expression: null, timezone: cfg.timezone || 'UTC', nextFireAt: null,
222
+ reason: 'Scheduling is owned by the tasks service on this instance; the workflow engine cannot see registrations.' };
223
+ }
224
+ if (!reg) {
225
+ return { nodeId: n.id, registered: false, state: 'not registered', saved, expression: null, timezone: cfg.timezone || 'UTC', nextFireAt: null,
226
+ reason: wf.active === false ? 'The workflow is not active, so its schedule is not registered. Activate it to register.' : 'Not registered with the scheduler.' };
227
+ }
228
+ return { nodeId: n.id, registered: true, state: reg.active ? 'active' : 'paused', saved, expression: reg.expression, timezone: reg.timezone || 'UTC', nextFireAt: reg.nextFireAt || null };
229
+ });
230
+
231
+ const result = { workflowId: id, name: wf.name, active: wf.active, ...(live.owned_by ? { owned_by: live.owned_by } : {}), schedules,
232
+ ...(schedNodes.length ? {} : { note: 'This workflow has no schedule trigger.' }) };
233
+ if (getOutputFormat() === 'json' || program.opts().output === 'json') { formatDetail(result, { format: 'json' }); return; }
234
+ if (!schedNodes.length) { printInfo(`${id}: ${result.note}`); return; }
235
+ if (ownedByTasks) printInfo(live.note || 'Schedules are managed by the tasks service.');
236
+ formatOutput({
237
+ columns: ['nodeId', 'registeredLabel', 'state', 'expression', 'timezone', 'nextFireAt', 'reason'],
238
+ headers: ['Node', 'Registered', 'State', 'Cron (live)', 'Timezone', 'Next Fire', 'Note'],
239
+ rows: schedules.map((r) => ({ ...r, registeredLabel: r.registered === true ? 'yes' : r.registered === false ? 'no' : 'unknown', expression: r.expression || '-', nextFireAt: r.nextFireAt || '-', reason: r.reason || '' })),
240
+ }, { format: program.opts().output });
241
+ });
242
+
243
+ workflow
244
+ .command('create')
245
+ .description('Create a new workflow in the workflow engine (quoxflow). --steps takes a JSON array of nodes (chained in order) or an object with nodes and optional edges')
246
+ .requiredOption('--name <name>', 'workflow name')
247
+ .option('--description <desc>', 'description')
248
+ .option('--steps <json>', 'nodes as a JSON array (chained sequentially), or {"nodes":[...],"edges":[...]}')
249
+ .action(async (options) => {
250
+ auth.requireAuth();
251
+ // #1190: the engine is the one workflow store. Same id shape as `workflow apply`.
252
+ const id = `wf-${Date.now()}-${Math.random().toString(36).slice(2, 8).padEnd(6, '0')}`;
253
+ let nodes = [];
254
+ let edges = [];
255
+ if (options.steps) {
256
+ const steps = parseJSON(options.steps, '--steps');
257
+ if (Array.isArray(steps)) {
258
+ nodes = steps;
259
+ edges = steps.slice(1).map((n, i) => ({ id: `e${i + 1}`, source: steps[i].id ?? `n${i}`, target: n.id ?? `n${i + 1}` }));
260
+ } else if (steps && typeof steps === 'object' && Array.isArray(steps.nodes)) {
261
+ nodes = steps.nodes;
262
+ edges = Array.isArray(steps.edges) ? steps.edges : [];
263
+ } else {
264
+ throw new Error('--steps must be a JSON array of nodes, or an object with a "nodes" array (and optional "edges").');
265
+ }
266
+ }
267
+ const body = { id, name: options.name, nodes, edges };
268
+ if (options.description) body.description = options.description;
269
+ const ep = resolveEndpoint('workflows.applyCreate');
270
+ const res = await client.post(ep.path, { service: ep.service, body });
271
+ const created = res?.data || res;
272
+ printEvent(events.created('quox', 'workflow', `${options.name} (quoxflow) id:${created.id || id}`));
273
+ // #1000: --json gets the saved workflow (the event line is suppressed in json mode, which left nothing)
274
+ if (getOutputFormat() === 'json') formatDetail(created, { format: 'json' });
275
+ });
276
+
277
+ workflow
278
+ .command('update')
279
+ .description('Update a workflow')
280
+ .argument('<workflowId>', 'workflow ID')
281
+ .option('--name <name>', 'new name')
282
+ .option('--description <desc>', 'new description')
283
+ .option('--status <status>', 'new status')
284
+ .action(async (workflowId, options) => {
285
+ auth.requireAuth();
286
+ const orgId = resolveOrgId(auth, program);
287
+ const body = {};
288
+ if (options.name) body.name = options.name;
289
+ if (options.description) body.description = options.description;
290
+ if (options.status) body.status = options.status;
291
+ const data = await client.patch(resolveEndpoint('workflows.update', { orgId, workflowId }).path, { service: resolveEndpoint('workflows.update', { orgId, workflowId }).service, body });
292
+ printEvent(events.updated('quox', 'workflow', workflowId));
293
+ if (getOutputFormat() === 'json') formatDetail(data?.workflow || data, { format: 'json' });
294
+ });
295
+
296
+ workflow
297
+ .command('delete')
298
+ .description('Delete a workflow')
299
+ .argument('<workflowId>', 'workflow ID')
300
+ .action(async (workflowId) => {
301
+ auth.requireAuth();
302
+ const orgId = resolveOrgId(auth, program);
303
+ // #997: engine (quoxflow) first, the auth store only on an engine 404 (same order as `workflow get`)
304
+ let source = 'engine';
305
+ try {
306
+ const ep = resolveEndpoint('workflows.applyDelete', { workflowId });
307
+ await client.delete(ep.path, { service: ep.service });
308
+ } catch (err) {
309
+ if (err.status !== 404) throw err;
310
+ source = 'auth';
311
+ await client.delete(resolveEndpoint('workflows.delete', { orgId, workflowId }).path, { service: resolveEndpoint('workflows.delete', { orgId, workflowId }).service });
312
+ }
313
+ printEvent(glyphLine('quox', 'workflow deleted', { result: `${workflowId} (${source})`, status: 'ok' }));
314
+ });
315
+
316
+ workflow
317
+ .command('run')
318
+ .description('Execute a workflow (must be published/active — see `workflow publish`)')
319
+ .argument('<workflowId>', 'workflow ID')
320
+ .option('--input <json>', 'input data as JSON (passed as run context)')
321
+ .action(async (workflowId, options) => {
322
+ auth.requireAuth();
323
+ const orgId = resolveOrgId(auth, program);
324
+ const body = { workflow_id: workflowId };
325
+ if (options.input) body.context = parseJSON(options.input, '--input');
326
+ try {
327
+ const data = await client.post(resolveEndpoint('runs.create', { orgId }).path, { service: resolveEndpoint('runs.create', { orgId }).service, body });
328
+ printEvent(glyphLine('quox', 'workflow run', { result: `${workflowId} -> run ${data.run?.id || data.run_id || data.id || 'ok'}`, status: 'ok' }));
329
+ } catch (err) {
330
+ if (err.status === 404) {
331
+ // quoxflow#7: the auth runs route only knows auth-store workflows. If
332
+ // the engine store has it, execute it there instead.
333
+ let engineExists = false;
334
+ try {
335
+ const getEp = resolveEndpoint('workflows.applyGet', { workflowId });
336
+ await client.get(getEp.path, { service: getEp.service });
337
+ engineExists = true;
338
+ } catch (engineErr) {
339
+ if (engineErr.status !== 404) throw engineErr;
340
+ }
341
+ if (engineExists) {
342
+ const exEp = resolveEndpoint('workflows.applyExecute', { workflowId });
343
+ // #1029: the engine reads triggerData (a bare body lost --input), and async returns the run id at once
344
+ const run = await client.post(exEp.path, { service: exEp.service, body: { triggerData: body.context || {}, async: true } });
345
+ const runId = run.executionId || run.execution_id || run.runId || run.run_id || run.id || run.execution?.id || 'ok';
346
+ const st = run.status || run.execution?.status;
347
+ printEvent(glyphLine('quox', 'workflow run', { result: `${workflowId} (quoxflow) -> run ${runId}${st ? ` [${st}]` : ''}`, status: 'ok' }));
348
+ return;
349
+ }
350
+ throw new Error(
351
+ `Workflow not found or not active. Only published workflows can be run — try \`quox workflow publish ${workflowId}\` first.`
352
+ );
353
+ }
354
+ throw err;
355
+ }
356
+ });
357
+
358
+ workflow
359
+ .command('publish')
360
+ .description('Publish a workflow')
361
+ .argument('<workflowId>', 'workflow ID')
362
+ .action(async (workflowId) => {
363
+ auth.requireAuth();
364
+ const orgId = resolveOrgId(auth, program);
365
+ try {
366
+ const ep = resolveEndpoint('workflows.publish', { orgId, workflowId });
367
+ await client.post(ep.path, { service: ep.service });
368
+ printEvent(glyphLine('quox', 'workflow published', { result: workflowId, status: 'ok' }));
369
+ } catch (err) {
370
+ if (err.status !== 404) throw err;
371
+
372
+ // fleet #465: publish operates on the auth-service model only. A
373
+ // workflow that exists solely in the engine (e.g. via `workflow
374
+ // apply`) 404s here even though it exists — check the engine before
375
+ // reporting a bare "not found".
376
+ let engineExists = false;
377
+ try {
378
+ const getEp = resolveEndpoint('workflows.applyGet', { workflowId });
379
+ await client.get(getEp.path, { service: getEp.service });
380
+ engineExists = true;
381
+ } catch (engineErr) {
382
+ if (engineErr.status !== 404) throw engineErr;
383
+ }
384
+
385
+ if (engineExists) {
386
+ throw new Error(
387
+ `Workflow "${workflowId}" lives in the engine (quoxflow) store, not the auth-service store that `
388
+ + `\`workflow publish\` operates on. Use \`quox trigger workflow-activate ${workflowId}\` to make it live — `
389
+ + '`workflow publish` only applies to workflows created via `workflow create`.'
390
+ );
391
+ }
392
+
393
+ throw new Error(
394
+ `Workflow "${workflowId}" was not found in either the engine (quoxflow) or auth-service workflow store.`
395
+ );
396
+ }
397
+ });
398
+
399
+ // Steps subcommand
400
+ const steps = workflow
401
+ .command('steps')
402
+ .description('Manage workflow steps');
403
+
404
+ steps
405
+ .command('list')
406
+ .description('List steps of a workflow')
407
+ .argument('<workflowId>', 'workflow ID')
408
+ .action(async (workflowId) => {
409
+ auth.requireAuth();
410
+ const orgId = resolveOrgId(auth, program);
411
+ // No dedicated GET .../steps route exists: steps are a field on the
412
+ // workflow object itself. quox#928: look in the same stores `workflow get`
413
+ // does, engine (quoxflow) first, so engine workflows (wf-obj-*) resolve too.
414
+ let rows;
415
+ try {
416
+ const ep = resolveEndpoint('workflows.applyGet', { workflowId });
417
+ const res = await client.get(ep.path, { service: ep.service });
418
+ const wf = res.data || res;
419
+ const nodes = wf?.definition?.nodes || wf?.nodes;
420
+ if (!Array.isArray(nodes)) throw Object.assign(new Error('not an engine workflow'), { status: 404 });
421
+ rows = nodes.map((n, idx) => ({ idx, label: n.name || n.id, step_type: n.type, ref_id: n.config?.tool || n.config?.toolName || '-' }));
422
+ } catch (err) {
423
+ if (err.status !== 404) throw err;
424
+ const data = await client.get(resolveEndpoint('workflows.get', { orgId, workflowId }).path, { service: resolveEndpoint('workflows.get', { orgId, workflowId }).service });
425
+ rows = (data.workflow || data).steps || [];
426
+ }
427
+ formatOutput({
428
+ // fleet #392: workflow_steps columns are idx/label/step_type/ref_id.
429
+ columns: ['idx', 'label', 'step_type', 'ref_id'],
430
+ headers: ['#', 'Name', 'Type', 'Tool'],
431
+ rows,
432
+ }, { format: program.opts().output });
433
+ });
434
+
435
+ steps
436
+ .command('set')
437
+ .description('Set workflow steps (replaces all)')
438
+ .argument('<workflowId>', 'workflow ID')
439
+ .requiredOption('--steps <json>', 'steps as JSON array')
440
+ .action(async (workflowId, options) => {
441
+ auth.requireAuth();
442
+ const orgId = resolveOrgId(auth, program);
443
+ const stepList = parseJSON(options.steps, '--steps');
444
+ try {
445
+ await client.put(resolveEndpoint('workflows.steps', { orgId, workflowId }).path, {
446
+ service: resolveEndpoint('workflows.steps', { orgId, workflowId }).service,
447
+ body: { steps: stepList },
448
+ });
449
+ } catch (err) {
450
+ if (err.status !== 404) throw err;
451
+ // #997: `steps list` reads engine workflows too, so an engine id 404'd here with a bare 'Workflow not found'.
452
+ // An engine workflow is a node/edge graph, not a step list: refuse it by name, like `workflow publish`.
453
+ let engineExists = false;
454
+ try {
455
+ const getEp = resolveEndpoint('workflows.applyGet', { workflowId });
456
+ await client.get(getEp.path, { service: getEp.service });
457
+ engineExists = true;
458
+ } catch (engineErr) {
459
+ if (engineErr.status !== 404) throw engineErr;
460
+ }
461
+ if (engineExists) {
462
+ throw new Error(
463
+ `Workflow "${workflowId}" lives in the engine (quoxflow) store, where it is a graph of nodes and edges, `
464
+ + 'not the step list `workflow steps set` replaces. Edit it with `quox workflow apply -f <file>` '
465
+ + `(start from \`quox -o json workflow get ${workflowId}\`).`
466
+ );
467
+ }
468
+ throw new Error(
469
+ `Workflow "${workflowId}" was not found in either the engine (quoxflow) or auth-service workflow store.`
470
+ );
471
+ }
472
+ printEvent(events.updated('quox', 'workflow steps', workflowId));
473
+ });
474
+
475
+ // ── workflow apply / validate / import-n8n / import-objective (WP2.5) ───────
476
+ //
477
+ // Declarative, file-based create/edit against quoxflow's own workflow
478
+ // engine — DIRECT to quoxflow (service: 'quoxflow'), NOT the auth-service
479
+ // 'workflows.create'/'workflows.update' used by the commands above. See
480
+ // the header comment in lib/api/endpoints/workflows.js for the full
481
+ // access-path + naming-collision rationale.
482
+
483
+ workflow
484
+ .command('apply')
485
+ .description('Create or update a workflow declaratively from a YAML/JSON file (quoxflow-direct: POST if the file has no id, PUT with a diff-preview if it does)')
486
+ .requiredOption('-f, --file <path>', 'path to a workflow definition file (.yaml/.yml/.json)')
487
+ .option('--force', 'skip the diff-preview confirmation on update')
488
+ .action(async (options) => {
489
+ auth.requireAuth();
490
+ const wf = readDeclarativeFile(options.file);
491
+ if (!wf || typeof wf !== 'object') {
492
+ throw new Error(`${options.file} did not parse to a workflow object.`);
493
+ }
494
+
495
+ if (wf.id) {
496
+ let existing = null;
497
+ try {
498
+ const getEp = resolveEndpoint('workflows.applyGet', { workflowId: wf.id });
499
+ const data = await client.get(getEp.path, { service: getEp.service });
500
+ existing = data.data || data;
501
+ } catch (err) {
502
+ if (err.status !== 404) throw err;
503
+ }
504
+
505
+ if (existing) {
506
+ const changes = buildApplyDiff(existing, wf);
507
+ if (changes.length === 0) {
508
+ printInfo(`No field-level changes detected for workflow ${wf.id}. Nothing to apply.`);
509
+ return;
510
+ }
511
+ printApplyDiff(wf.id, changes);
512
+
513
+ // fleet #468 item 3: on a non-TTY shell (no --force/-y) the old
514
+ // unconditional inquirer.prompt() call printed "User force closed
515
+ // the prompt with 0 null" and returned silently WITHOUT applying,
516
+ // which looked like success but wasn't. confirmDestructive() checks
517
+ // process.stdin.isTTY before ever importing inquirer, so a
518
+ // non-interactive shell without --force now fails loudly (exit
519
+ // code 1) naming --force, instead of no-op-ing.
520
+ const confirmed = await confirmDestructive(program, options, `Apply these changes to workflow ${wf.id}?`);
521
+ if (!confirmed) {
522
+ printInfo('Cancelled.');
523
+ return;
524
+ }
525
+
526
+ const putEp = resolveEndpoint('workflows.applyUpdate', { workflowId: wf.id });
527
+ const data = await client.put(putEp.path, { service: putEp.service, body: wf });
528
+ printEvent(glyphLine('quox', 'workflow updated', { result: `${wf.id} (quoxflow)`, status: 'ok' }));
529
+ formatDetail(data.data || data, { format: program.opts().output });
530
+ return;
531
+ }
532
+
533
+ // An id was given but nothing is stored under it yet — falls
534
+ // through to create below (POST also accepts a caller-supplied id).
535
+ printInfo(`No existing workflow found for id "${wf.id}" — creating.`);
536
+ }
537
+
538
+ const createEp = resolveEndpoint('workflows.applyCreate');
539
+ // #1000: quoxflow's WorkflowSchema requires an id, so "no id = create" 400'd. Mint one in the
540
+ // engine's own wf-<ms>-<6> shape; the created id is printed, so the file can be given it to update later.
541
+ const body = wf.id ? wf : { ...wf, id: `wf-${Date.now()}-${Math.random().toString(36).slice(2, 8).padEnd(6, '0')}` };
542
+ const data = await client.post(createEp.path, { service: createEp.service, body });
543
+ const created = data.data || data;
544
+ printEvent(events.created('quox', 'workflow', `${created.name || wf.name || created.id} (quoxflow) id:${created.id}`));
545
+ formatDetail(created, { format: program.opts().output });
546
+ });
547
+
548
+ workflow
549
+ .command('validate')
550
+ .description('Validate a workflow (quoxflow-direct). Give a stored workflow id to fetch and validate it (the same read path as `workflow get`), or -f <file> to validate the workflow stored under that file\'s "id" field. NOTE: this validates the STORED workflow, not local file content; run `workflow apply -f <file>` first to validate your latest edits.')
551
+ .argument('[workflowId]', 'stored workflow ID (fetched the same way `quox workflow get` does, then validated). Omit if using -f instead.')
552
+ .option('-f, --file <path>', 'workflow file — only its "id" field is used to look up the stored workflow')
553
+ .action(async (workflowId, options) => {
554
+ auth.requireAuth();
555
+
556
+ let targetId;
557
+ if (options.file) {
558
+ const wf = readDeclarativeFile(options.file);
559
+ if (!wf?.id) {
560
+ throw new Error(`${options.file} has no "id" field. \`workflow validate\` checks the STORED workflow by id, not local file content — apply the file first with \`quox workflow apply -f ${options.file}\`, or add an "id" matching an existing workflow.`);
561
+ }
562
+ targetId = wf.id;
563
+ } else if (workflowId) {
564
+ // fleet #468 item 1: `quox workflow validate <id>` previously required
565
+ // -f/--file even when the caller already had a stored workflow id.
566
+ // Fetch it through the same read path `quox workflow get` uses, so a
567
+ // missing id fails with the same "not found" signal that command gives.
568
+ const orgId = resolveOrgId(auth, program);
569
+ const getEp = resolveEndpoint('workflows.get', { orgId, workflowId });
570
+ try {
571
+ await client.get(getEp.path, { service: getEp.service });
572
+ } catch (err) {
573
+ if (err.status !== 404) throw err;
574
+ // `workflow get` also reads the engine (quoxflow) store; engine-only workflows
575
+ // (most of them) are not in the auth store, so fall back the same way.
576
+ try {
577
+ const engEp = resolveEndpoint('workflows.applyGet', { workflowId });
578
+ await client.get(engEp.path, { service: engEp.service });
579
+ } catch (err2) {
580
+ if (err2.status === 404) {
581
+ throw new Error(`Workflow "${workflowId}" was not found on the read path \`quox workflow get\` uses. Use -f <file> to validate a local file instead.`);
582
+ }
583
+ throw err2;
584
+ }
585
+ }
586
+ targetId = workflowId;
587
+ } else {
588
+ throw new Error('Provide either a workflow id argument or -f <file>. Example: `quox workflow validate <id>`, or `quox workflow validate -f workflow.yaml`.');
589
+ }
590
+
591
+ const ep = resolveEndpoint('workflows.validate', { workflowId: targetId });
592
+ const data = await client.post(ep.path, { service: ep.service });
593
+ const result = data.data || data;
594
+ formatDetail(result, { format: program.opts().output, title: `Validation: ${targetId}` });
595
+ });
596
+
597
+ workflow
598
+ .command('import-n8n')
599
+ .description('Import an n8n workflow export as a quoxflow workflow')
600
+ .requiredOption('-f, --file <path>', 'path to an n8n workflow export (.yaml/.yml/.json)')
601
+ .option('--generate-ids', 'generate new node/workflow ids instead of reusing the n8n ones')
602
+ .option('--include-positions', 'preserve n8n canvas node positions')
603
+ .action(async (options) => {
604
+ auth.requireAuth();
605
+ const parsed = readDeclarativeFile(options.file);
606
+ // Accept either a raw n8n export, or an already-wrapped { workflow, options } body.
607
+ const n8nWorkflow = parsed && typeof parsed === 'object' && 'workflow' in parsed ? parsed.workflow : parsed;
608
+ const importOptions = { ...(parsed?.options || {}) };
609
+ if (options.generateIds) importOptions.generateIds = true;
610
+ if (options.includePositions) importOptions.includePositions = true;
611
+
612
+ const ep = resolveEndpoint('workflows.importN8n');
613
+ const data = await client.post(ep.path, {
614
+ service: ep.service,
615
+ body: { workflow: n8nWorkflow, options: Object.keys(importOptions).length ? importOptions : undefined },
616
+ });
617
+ const result = data.data || data;
618
+ printEvent(glyphLine('quox', 'n8n workflow imported', { result: result.workflow?.id || result.id, status: 'ok' }));
619
+ if (result.warnings?.length) {
620
+ for (const w of result.warnings) printWarning(w);
621
+ }
622
+ formatDetail(result.workflow || result, { format: program.opts().output });
623
+ });
624
+
625
+ workflow
626
+ .command('import-objective')
627
+ .description('Import an objective template as a quoxflow workflow')
628
+ .requiredOption('-f, --file <path>', 'path to an objective template (.yaml/.yml/.json) — a bare template object, or { template, schedule, options }')
629
+ .option('--cron <expr>', 'cron schedule expression (adds/overrides schedule.cron)')
630
+ .option('--timezone <tz>', 'schedule timezone (adds/overrides schedule.timezone)')
631
+ .option('--activate', 'activate the imported workflow (registers triggers)')
632
+ .option('--include-approval-gate', 'insert an approval gate node')
633
+ .action(async (options) => {
634
+ auth.requireAuth();
635
+ const parsed = readDeclarativeFile(options.file);
636
+ if (!parsed || typeof parsed !== 'object') {
637
+ throw new Error(`${options.file} did not parse to an object.`);
638
+ }
639
+ const template = parsed.template && typeof parsed.template === 'object' ? parsed.template : parsed;
640
+ const schedule = { ...(parsed.schedule || {}) };
641
+ if (options.cron) schedule.cron = options.cron;
642
+ if (options.timezone) schedule.timezone = options.timezone;
643
+ const importOptions = { ...(parsed.options || {}) };
644
+ if (options.activate) importOptions.activate = true;
645
+ if (options.includeApprovalGate) importOptions.includeApprovalGate = true;
646
+
647
+ const body = { template };
648
+ if (schedule.cron) body.schedule = schedule;
649
+ if (Object.keys(importOptions).length) body.options = importOptions;
650
+
651
+ const ep = resolveEndpoint('workflows.importObjective');
652
+ const data = await client.post(ep.path, { service: ep.service, body });
653
+ const result = data.data || data;
654
+ printEvent(glyphLine('quox', 'objective template imported', { result: `${result.workflow?.id || result.id}${result.activated ? ' (activated)' : ''}`, status: 'ok' }));
655
+ if (result.warnings?.length) {
656
+ for (const w of result.warnings) printWarning(w);
657
+ }
658
+ formatDetail(result.workflow || result, { format: program.opts().output });
659
+ });
660
+ }