quoxcli 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (348) hide show
  1. package/LICENSE +35 -0
  2. package/README.md +142 -2
  3. package/bin/quox.js +292 -0
  4. package/bin/quoxon.js +8 -0
  5. package/lib/api/endpoints/admin.js +125 -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/agents.js +56 -0
  9. package/lib/api/endpoints/assistants.js +50 -0
  10. package/lib/api/endpoints/audit.js +14 -0
  11. package/lib/api/endpoints/auth.js +50 -0
  12. package/lib/api/endpoints/awareness.js +34 -0
  13. package/lib/api/endpoints/backup-schedules.js +20 -0
  14. package/lib/api/endpoints/backups.js +86 -0
  15. package/lib/api/endpoints/brain.js +140 -0
  16. package/lib/api/endpoints/budgets.js +62 -0
  17. package/lib/api/endpoints/calendarq.js +71 -0
  18. package/lib/api/endpoints/catalog.js +14 -0
  19. package/lib/api/endpoints/chat.js +54 -0
  20. package/lib/api/endpoints/consent.js +26 -0
  21. package/lib/api/endpoints/contactsq.js +66 -0
  22. package/lib/api/endpoints/conversations.js +32 -0
  23. package/lib/api/endpoints/custom-roles.js +92 -0
  24. package/lib/api/endpoints/divisions.js +56 -0
  25. package/lib/api/endpoints/domains.js +38 -0
  26. package/lib/api/endpoints/engagement.js +231 -0
  27. package/lib/api/endpoints/engine.js +11 -0
  28. package/lib/api/endpoints/entity.js +50 -0
  29. package/lib/api/endpoints/executions.js +32 -0
  30. package/lib/api/endpoints/file.js +20 -0
  31. package/lib/api/endpoints/files.js +26 -0
  32. package/lib/api/endpoints/github.js +21 -0
  33. package/lib/api/endpoints/governance.js +43 -0
  34. package/lib/api/endpoints/hat.js +36 -0
  35. package/lib/api/endpoints/inbox.js +32 -0
  36. package/lib/api/endpoints/inboxq.js +120 -0
  37. package/lib/api/endpoints/index.js +228 -0
  38. package/lib/api/endpoints/invites.js +19 -0
  39. package/lib/api/endpoints/llm-policy.js +43 -0
  40. package/lib/api/endpoints/lucid.js +89 -0
  41. package/lib/api/endpoints/members.js +32 -0
  42. package/lib/api/endpoints/memory.js +75 -0
  43. package/lib/api/endpoints/mfa.js +32 -0
  44. package/lib/api/endpoints/mon.js +178 -0
  45. package/lib/api/endpoints/notesq.js +43 -0
  46. package/lib/api/endpoints/objectives.js +62 -0
  47. package/lib/api/endpoints/orgs.js +38 -0
  48. package/lib/api/endpoints/plugins.js +134 -0
  49. package/lib/api/endpoints/powers.js +75 -0
  50. package/lib/api/endpoints/prompts.js +69 -0
  51. package/lib/api/endpoints/qlarity.js +122 -0
  52. package/lib/api/endpoints/quoxbox.js +55 -0
  53. package/lib/api/endpoints/quoxchat.js +54 -0
  54. package/lib/api/endpoints/quoxflow.js +32 -0
  55. package/lib/api/endpoints/quoxplan.js +114 -0
  56. package/lib/api/endpoints/quoxseo.js +108 -0
  57. package/lib/api/endpoints/reach.js +47 -0
  58. package/lib/api/endpoints/runs.js +38 -0
  59. package/lib/api/endpoints/safety.js +46 -0
  60. package/lib/api/endpoints/scim.js +46 -0
  61. package/lib/api/endpoints/scopes.js +14 -0
  62. package/lib/api/endpoints/security.js +69 -0
  63. package/lib/api/endpoints/setup.js +90 -0
  64. package/lib/api/endpoints/skills.js +180 -0
  65. package/lib/api/endpoints/sso.js +67 -0
  66. package/lib/api/endpoints/tags.js +32 -0
  67. package/lib/api/endpoints/teams.js +56 -0
  68. package/lib/api/endpoints/telephony.js +46 -0
  69. package/lib/api/endpoints/tg.js +56 -0
  70. package/lib/api/endpoints/tools.js +50 -0
  71. package/lib/api/endpoints/traces.js +157 -0
  72. package/lib/api/endpoints/triggers.js +70 -0
  73. package/lib/api/endpoints/user-library.js +41 -0
  74. package/lib/api/endpoints/users.js +20 -0
  75. package/lib/api/endpoints/v1.js +499 -0
  76. package/lib/api/endpoints/vault.js +192 -0
  77. package/lib/api/endpoints/voice.js +22 -0
  78. package/lib/api/endpoints/volt.js +84 -0
  79. package/lib/api/endpoints/ward.js +40 -0
  80. package/lib/api/endpoints/webhooks.js +46 -0
  81. package/lib/api/endpoints/workflows.js +162 -0
  82. package/lib/api/endpoints/x.js +24 -0
  83. package/lib/api/endpoints.js +15 -0
  84. package/lib/commands/admin.js +359 -0
  85. package/lib/commands/aee.js +99 -0
  86. package/lib/commands/agent.js +201 -0
  87. package/lib/commands/agentAttach.js +1440 -0
  88. package/lib/commands/approvals.js +47 -0
  89. package/lib/commands/assistant.js +114 -0
  90. package/lib/commands/auth.js +458 -0
  91. package/lib/commands/awareness.js +88 -0
  92. package/lib/commands/backup.js +373 -0
  93. package/lib/commands/brain.js +994 -0
  94. package/lib/commands/budget.js +241 -0
  95. package/lib/commands/build.js +156 -0
  96. package/lib/commands/calendar.js +216 -0
  97. package/lib/commands/capabilities.js +34 -0
  98. package/lib/commands/chat.js +240 -0
  99. package/lib/commands/claude.js +354 -0
  100. package/lib/commands/config-cmd.js +106 -0
  101. package/lib/commands/consent.js +84 -0
  102. package/lib/commands/contacts.js +967 -0
  103. package/lib/commands/contactsSelftest.js +132 -0
  104. package/lib/commands/conversation.js +74 -0
  105. package/lib/commands/department.js +75 -0
  106. package/lib/commands/dev.js +620 -0
  107. package/lib/commands/devResident.js +314 -0
  108. package/lib/commands/devSupervisor.js +1050 -0
  109. package/lib/commands/discord.js +96 -0
  110. package/lib/commands/doctor.js +181 -0
  111. package/lib/commands/domain.js +114 -0
  112. package/lib/commands/done.js +277 -0
  113. package/lib/commands/engagement.js +586 -0
  114. package/lib/commands/entity.js +119 -0
  115. package/lib/commands/facebook.js +212 -0
  116. package/lib/commands/file.js +128 -0
  117. package/lib/commands/fleet.js +882 -0
  118. package/lib/commands/governance.js +638 -0
  119. package/lib/commands/gripe.js +324 -0
  120. package/lib/commands/hat.js +205 -0
  121. package/lib/commands/helpers.js +79 -0
  122. package/lib/commands/inbox.js +122 -0
  123. package/lib/commands/instagram.js +264 -0
  124. package/lib/commands/integration.js +100 -0
  125. package/lib/commands/inventory.js +115 -0
  126. package/lib/commands/invites.js +45 -0
  127. package/lib/commands/licence.js +280 -0
  128. package/lib/commands/linkedin.js +178 -0
  129. package/lib/commands/llm-policy.js +117 -0
  130. package/lib/commands/llm.js +45 -0
  131. package/lib/commands/lucid.js +591 -0
  132. package/lib/commands/mail.js +531 -0
  133. package/lib/commands/memory.js +284 -0
  134. package/lib/commands/mon.js +514 -0
  135. package/lib/commands/monitoring.js +86 -0
  136. package/lib/commands/motion.js +22 -0
  137. package/lib/commands/notes.js +1246 -0
  138. package/lib/commands/notesBatch.js +160 -0
  139. package/lib/commands/notesBridges.js +56 -0
  140. package/lib/commands/notesLinks.js +60 -0
  141. package/lib/commands/notification.js +57 -0
  142. package/lib/commands/nucleus.js +92 -0
  143. package/lib/commands/objective.js +229 -0
  144. package/lib/commands/onboard.js +328 -0
  145. package/lib/commands/open.js +56 -0
  146. package/lib/commands/org.js +266 -0
  147. package/lib/commands/plan.js +906 -0
  148. package/lib/commands/plugin-doctor.js +1091 -0
  149. package/lib/commands/plugin.js +539 -0
  150. package/lib/commands/policy.js +78 -0
  151. package/lib/commands/powers.js +296 -0
  152. package/lib/commands/prompt.js +350 -0
  153. package/lib/commands/qlarity.js +690 -0
  154. package/lib/commands/quoxbox.js +270 -0
  155. package/lib/commands/quoxchat.js +266 -0
  156. package/lib/commands/quoxseo.js +340 -0
  157. package/lib/commands/reach.js +158 -0
  158. package/lib/commands/role.js +286 -0
  159. package/lib/commands/run.js +132 -0
  160. package/lib/commands/safety.js +107 -0
  161. package/lib/commands/scim.js +88 -0
  162. package/lib/commands/scope.js +58 -0
  163. package/lib/commands/security.js +157 -0
  164. package/lib/commands/service.js +89 -0
  165. package/lib/commands/settings.js +99 -0
  166. package/lib/commands/setup.js +543 -0
  167. package/lib/commands/skillManifest.js +113 -0
  168. package/lib/commands/skillRightsFormat.js +38 -0
  169. package/lib/commands/skills.js +644 -0
  170. package/lib/commands/slot.js +1198 -0
  171. package/lib/commands/sso.js +229 -0
  172. package/lib/commands/status.js +137 -0
  173. package/lib/commands/tag.js +91 -0
  174. package/lib/commands/team.js +140 -0
  175. package/lib/commands/tg.js +240 -0
  176. package/lib/commands/tiktok.js +218 -0
  177. package/lib/commands/tool.js +216 -0
  178. package/lib/commands/trace.js +525 -0
  179. package/lib/commands/trigger.js +128 -0
  180. package/lib/commands/vault.js +1424 -0
  181. package/lib/commands/voice.js +182 -0
  182. package/lib/commands/volt.js +528 -0
  183. package/lib/commands/watch.js +115 -0
  184. package/lib/commands/webhook.js +137 -0
  185. package/lib/commands/workflow.js +503 -0
  186. package/lib/commands/x.js +439 -0
  187. package/lib/core/actingAs.js +136 -0
  188. package/lib/core/aee.js +53 -0
  189. package/lib/core/agentAlias.js +163 -0
  190. package/lib/core/approvalContext.js +61 -0
  191. package/lib/core/auth.js +350 -0
  192. package/lib/core/banner.js +143 -0
  193. package/lib/core/brainPrime.js +90 -0
  194. package/lib/core/callerSource.js +129 -0
  195. package/lib/core/claudeLs.js +94 -0
  196. package/lib/core/client.js +584 -0
  197. package/lib/core/completions.js +163 -0
  198. package/lib/core/config.js +551 -0
  199. package/lib/core/confirm.js +37 -0
  200. package/lib/core/deeplinks.js +76 -0
  201. package/lib/core/doneEngineFetch.js +219 -0
  202. package/lib/core/errors.js +132 -0
  203. package/lib/core/global-flags.js +90 -0
  204. package/lib/core/governedSession.js +402 -0
  205. package/lib/core/lazyCommands.js +119 -0
  206. package/lib/core/liveSessionsReport.js +112 -0
  207. package/lib/core/org-override.js +52 -0
  208. package/lib/core/output.js +239 -0
  209. package/lib/core/prodEnv.js +7 -0
  210. package/lib/core/quoxglyph.js +158 -0
  211. package/lib/core/residentAlarm.js +282 -0
  212. package/lib/core/residentAuth.js +243 -0
  213. package/lib/core/residentPump.js +229 -0
  214. package/lib/core/residentSeed.js +104 -0
  215. package/lib/core/sessionHeartbeat.js +192 -0
  216. package/lib/core/sessionsOverview.js +160 -0
  217. package/lib/core/slotKeeper.js +282 -0
  218. package/lib/core/supervisorEngine.js +864 -0
  219. package/lib/core/systemdInstaller.js +256 -0
  220. package/lib/core/version.js +87 -0
  221. package/lib/core/websiteSession.js +148 -0
  222. package/lib/lucid/LucidIntro.jsx +105 -0
  223. package/lib/lucid/blind.js +71 -0
  224. package/lib/lucid/deck.js +32 -0
  225. package/lib/lucid/fmt.js +139 -0
  226. package/lib/lucid/sitrep.js +173 -0
  227. package/lib/plugins/loader.js +93 -0
  228. package/lib/quoxon/activity.js +104 -0
  229. package/lib/quoxon/api.js +473 -0
  230. package/lib/quoxon/app.js +1262 -0
  231. package/lib/quoxon/capabilities.js +75 -0
  232. package/lib/quoxon/commands.js +154 -0
  233. package/lib/quoxon/connect.js +467 -0
  234. package/lib/quoxon/connectCard.js +28 -0
  235. package/lib/quoxon/context.js +68 -0
  236. package/lib/quoxon/copy.js +33 -0
  237. package/lib/quoxon/demo.fixtures.js +24 -0
  238. package/lib/quoxon/demo.js +35 -0
  239. package/lib/quoxon/egg.js +36 -0
  240. package/lib/quoxon/events.js +458 -0
  241. package/lib/quoxon/gallery.js +223 -0
  242. package/lib/quoxon/indicator.js +193 -0
  243. package/lib/quoxon/intent.js +41 -0
  244. package/lib/quoxon/keys.js +121 -0
  245. package/lib/quoxon/main.js +244 -0
  246. package/lib/quoxon/markdown.js +317 -0
  247. package/lib/quoxon/model.js +205 -0
  248. package/lib/quoxon/motion/clock.js +69 -0
  249. package/lib/quoxon/motion/color.js +35 -0
  250. package/lib/quoxon/motion/frame.js +14 -0
  251. package/lib/quoxon/motion/reveal.js +54 -0
  252. package/lib/quoxon/motion/ring.js +79 -0
  253. package/lib/quoxon/objects/agent.js +61 -0
  254. package/lib/quoxon/objects/crystal.js +100 -0
  255. package/lib/quoxon/objects/fetch.js +170 -0
  256. package/lib/quoxon/objects/index.js +85 -0
  257. package/lib/quoxon/objects/ladder.js +71 -0
  258. package/lib/quoxon/objects/memory.js +44 -0
  259. package/lib/quoxon/objects/model.js +118 -0
  260. package/lib/quoxon/objects/proof.js +110 -0
  261. package/lib/quoxon/objects/skill.js +105 -0
  262. package/lib/quoxon/objects/view.js +76 -0
  263. package/lib/quoxon/objects/workflow.js +119 -0
  264. package/lib/quoxon/observers.js +42 -0
  265. package/lib/quoxon/onboarding/copy.js +235 -0
  266. package/lib/quoxon/onboarding/credential.js +95 -0
  267. package/lib/quoxon/onboarding/detect.js +22 -0
  268. package/lib/quoxon/onboarding/finish.js +119 -0
  269. package/lib/quoxon/onboarding/index.js +67 -0
  270. package/lib/quoxon/onboarding/installRunner.js +225 -0
  271. package/lib/quoxon/onboarding/intent.js +53 -0
  272. package/lib/quoxon/onboarding/journey.js +73 -0
  273. package/lib/quoxon/onboarding/preflight.js +142 -0
  274. package/lib/quoxon/onboarding/runner.js +58 -0
  275. package/lib/quoxon/onboarding/view.js +625 -0
  276. package/lib/quoxon/print.js +94 -0
  277. package/lib/quoxon/qart.js +379 -0
  278. package/lib/quoxon/qglyph.js +143 -0
  279. package/lib/quoxon/qhello.js +58 -0
  280. package/lib/quoxon/qmotion.js +111 -0
  281. package/lib/quoxon/render.js +648 -0
  282. package/lib/quoxon/roster.js +221 -0
  283. package/lib/quoxon/route.js +38 -0
  284. package/lib/quoxon/rules.js +130 -0
  285. package/lib/quoxon/session.js +68 -0
  286. package/lib/quoxon/skills.js +30 -0
  287. package/lib/quoxon/slash/compact.js +90 -0
  288. package/lib/quoxon/slash/ledger.js +83 -0
  289. package/lib/quoxon/slash/listView.js +28 -0
  290. package/lib/quoxon/slash/palette.js +104 -0
  291. package/lib/quoxon/slash/paletteController.js +59 -0
  292. package/lib/quoxon/slash/panels.js +101 -0
  293. package/lib/quoxon/slash/picker.js +38 -0
  294. package/lib/quoxon/slash/restart.js +144 -0
  295. package/lib/quoxon/slash/resume.js +59 -0
  296. package/lib/quoxon/slash/settings.js +50 -0
  297. package/lib/quoxon/sound/index.js +186 -0
  298. package/lib/quoxon/sound/mapping.js +119 -0
  299. package/lib/quoxon/sound/output.js +52 -0
  300. package/lib/quoxon/sound/synth.js +122 -0
  301. package/lib/quoxon/term.js +106 -0
  302. package/lib/quoxon/textBuffer.js +242 -0
  303. package/lib/quoxon/theme.js +253 -0
  304. package/lib/quoxon/transcript.js +150 -0
  305. package/lib/quoxon/web/qface.js +81 -0
  306. package/lib/quoxon/welcome.js +52 -0
  307. package/lib/tui/app.jsx +289 -0
  308. package/lib/tui/components/Breadcrumb.jsx +42 -0
  309. package/lib/tui/components/ChatInput.jsx +68 -0
  310. package/lib/tui/components/Confirm.jsx +32 -0
  311. package/lib/tui/components/DataTable.jsx +270 -0
  312. package/lib/tui/components/DetailPanel.jsx +241 -0
  313. package/lib/tui/components/HelpOverlay.jsx +67 -0
  314. package/lib/tui/components/Layout.jsx +129 -0
  315. package/lib/tui/components/Palette.jsx +141 -0
  316. package/lib/tui/components/SearchInput.jsx +125 -0
  317. package/lib/tui/components/Sparkline.jsx +55 -0
  318. package/lib/tui/components/StatusBar.jsx +133 -0
  319. package/lib/tui/components/Toast.jsx +52 -0
  320. package/lib/tui/hooks/useApi.js +44 -0
  321. package/lib/tui/hooks/useHealth.js +90 -0
  322. package/lib/tui/hooks/usePolledApi.js +86 -0
  323. package/lib/tui/hooks/useTerminalSize.js +46 -0
  324. package/lib/tui/index.js +56 -0
  325. package/lib/tui/intro/Intro.jsx +332 -0
  326. package/lib/tui/jsx-loader.js +32 -0
  327. package/lib/tui/keys.js +87 -0
  328. package/lib/tui/registry.js +49 -0
  329. package/lib/tui/theme.js +103 -0
  330. package/lib/tui/views/AdminView.jsx +398 -0
  331. package/lib/tui/views/AgentView.jsx +204 -0
  332. package/lib/tui/views/BackupView.jsx +225 -0
  333. package/lib/tui/views/BrainView.jsx +223 -0
  334. package/lib/tui/views/ChatView.jsx +468 -0
  335. package/lib/tui/views/FilesView.jsx +218 -0
  336. package/lib/tui/views/FleetView.jsx +234 -0
  337. package/lib/tui/views/GovernanceView.jsx +453 -0
  338. package/lib/tui/views/HealthView.jsx +216 -0
  339. package/lib/tui/views/InboxView.jsx +228 -0
  340. package/lib/tui/views/LucidDeck.jsx +727 -0
  341. package/lib/tui/views/MainMenu.jsx +123 -0
  342. package/lib/tui/views/MemoryView.jsx +377 -0
  343. package/lib/tui/views/ObjectivesView.jsx +230 -0
  344. package/lib/tui/views/OrgView.jsx +229 -0
  345. package/lib/tui/views/ToolView.jsx +221 -0
  346. package/lib/tui/views/VaultView.jsx +422 -0
  347. package/lib/tui/views/WorkflowView.jsx +329 -0
  348. package/package.json +72 -4
@@ -0,0 +1,525 @@
1
+ /**
2
+ * Trace commands: TRV (Trace Replay & Visualization) — assembled QuoxTraces,
3
+ * what-if simulation, trace comparison, and persisted replay
4
+ * sessions/comparisons/correlation cache.
5
+ *
6
+ * quox trace list|get|simulate|compare
7
+ * quox trace replay list|get|create|update|delete|delete-all
8
+ * quox trace comparison list|get|create|update|delete|delete-all
9
+ * quox trace correlation list|get|create|delete
10
+ *
11
+ * Backend: services/collector/server.js (TRV-1/TRV-2/TRV-5/TRV-6), mounted
12
+ * at plain `/traces` — collector-direct, NOT org-scoped in the URL (same
13
+ * pattern as `quox aee`/`quox chat`). See endpoints/traces.js's header for
14
+ * the verified org-scoping finding on the list reads.
15
+ *
16
+ * UX note (repo lesson, project_trv_ux): don't bury the useful data behind
17
+ * an extra fetch. `trace get` prints the narrative + key facts inline, not
18
+ * just a raw JSON dump requiring a follow-up call.
19
+ */
20
+
21
+ import { randomUUID } from 'node:crypto';
22
+ import { formatOutput, formatDetail, printSuccess, printWarning, printInfo } from '../core/output.js';
23
+ import { parseJSON } from './helpers.js';
24
+ import { resolveEndpoint } from '../api/endpoints.js';
25
+
26
+ /** Flatten the nested `actor` object into a display string for table rows. */
27
+ function actorLabel(actor) {
28
+ if (!actor) return '';
29
+ return actor.display_name || actor.id || '';
30
+ }
31
+
32
+ function normalizeTraceSummary(t) {
33
+ return { ...t, actor: actorLabel(t.actor) };
34
+ }
35
+
36
+ async function confirmOrSkip(program, options, message) {
37
+ if (options.force || program.opts().yes) return true;
38
+ const { default: inquirer } = await import('inquirer');
39
+ const ans = await inquirer.prompt([{ type: 'confirm', name: 'confirm', message, default: false }]);
40
+ return ans.confirm;
41
+ }
42
+
43
+ export function registerTraceCommands(program, ctx) {
44
+ const { client, auth } = ctx;
45
+
46
+ const trace = program
47
+ .command('trace')
48
+ .description('Trace Replay & Visualization: assembled traces, simulation, comparison, replay sessions');
49
+
50
+ // ── trace list ──────────────────────────────────────────────────────────────
51
+
52
+ trace
53
+ .command('list')
54
+ .description('List assembled trace summaries')
55
+ .option('--limit <n>', 'max results', '30')
56
+ .option('--offset <n>', 'result offset', '0')
57
+ .option('--after <iso>', 'only traces starting at/after this ISO timestamp')
58
+ .option('--before <iso>', 'only traces starting before this ISO timestamp')
59
+ .option('--actor <id>', 'filter by actor ID (e.g. human.adam)')
60
+ .option('--outcome <outcome>', 'filter by outcome')
61
+ .option('--denied', 'only traces with at least one policy denial')
62
+ .option('--hitl', 'only traces with at least one HITL layer')
63
+ .option('--sort <mode>', 'newest|oldest|slowest|fastest', 'newest')
64
+ .action(async (options) => {
65
+ auth.requireAuth();
66
+ const query = { limit: options.limit, offset: options.offset, sort: options.sort };
67
+ if (options.after) query.after = options.after;
68
+ if (options.before) query.before = options.before;
69
+ if (options.actor) query.actor = options.actor;
70
+ if (options.outcome) query.outcome = options.outcome;
71
+ if (options.denied) query.has_denial = 'true';
72
+ if (options.hitl) query.has_hitl = 'true';
73
+
74
+ const data = await client.get(resolveEndpoint('traces.list').path, {
75
+ service: resolveEndpoint('traces.list').service,
76
+ query,
77
+ });
78
+ const traces = (data.traces || data || []).map(normalizeTraceSummary);
79
+
80
+ formatOutput({
81
+ columns: ['run_id', 'actor', 'outcome', 'layer_count', 'decision_count', 'has_denial', 'has_hitl', 'duration_ms', 'started_at'],
82
+ headers: ['Run ID', 'Actor', 'Outcome', 'Layers', 'Decisions', 'Denied', 'HITL', 'Duration (ms)', 'Started'],
83
+ rows: traces,
84
+ }, { format: program.opts().output, emptyMessage: 'No traces found.' });
85
+
86
+ if (program.opts().output !== 'json' && program.opts().output !== 'quiet' && typeof data.total === 'number') {
87
+ printInfo(`Showing ${traces.length} of ${data.total} trace(s).`);
88
+ }
89
+ });
90
+
91
+ // ── trace get ───────────────────────────────────────────────────────────────
92
+
93
+ trace
94
+ .command('get')
95
+ .description('Assemble and show a single trace (narrative + per-layer waterfall)')
96
+ .argument('<identifier>', 'conversation ID (corr) or RUN_ id')
97
+ .option('--include-raw', 'include raw layer payloads')
98
+ .action(async (identifier, options) => {
99
+ auth.requireAuth();
100
+ const query = {};
101
+ if (options.includeRaw) query.include_raw = 'true';
102
+
103
+ const t = await client.get(resolveEndpoint('traces.get', { id: identifier }).path, {
104
+ service: resolveEndpoint('traces.get', { id: identifier }).service,
105
+ query,
106
+ });
107
+
108
+ const format = program.opts().output;
109
+ if (format === 'json') {
110
+ console.log(JSON.stringify(t, null, 2));
111
+ return;
112
+ }
113
+ if (format === 'quiet') {
114
+ console.log(t.run_id || t.corr || '');
115
+ return;
116
+ }
117
+
118
+ formatDetail({ ...t, id: t.run_id }, {
119
+ format,
120
+ title: `Trace: ${t.run_id || identifier}`,
121
+ fields: ['id', 'corr', 'actor', 'query', 'outcome', 'narrative', 'duration_ms', 'layer_count', 'decision_count', 'has_denial', 'has_hitl', 'compliance_tags', 'most_interesting_layer', 'started_at', 'ended_at', 'volt'],
122
+ labels: {
123
+ id: 'Run ID', corr: 'Conversation', actor: 'Actor', query: 'Query', outcome: 'Outcome',
124
+ narrative: 'Narrative', duration_ms: 'Duration (ms)', layer_count: 'Layers',
125
+ decision_count: 'Decisions', has_denial: 'Denied', has_hitl: 'HITL',
126
+ compliance_tags: 'Compliance Tags', most_interesting_layer: 'Most Interesting Layer',
127
+ started_at: 'Started', ended_at: 'Ended', volt: 'VOLT',
128
+ },
129
+ });
130
+
131
+ if (Array.isArray(t.layers) && t.layers.length > 0) {
132
+ console.log('');
133
+ formatOutput({
134
+ columns: ['layer_id', 'layer_label', 'status', 'outcome', 'duration_ms', 'layer_summary'],
135
+ headers: ['Layer', 'Name', 'Status', 'Outcome', 'Duration (ms)', 'Summary'],
136
+ rows: t.layers,
137
+ }, { format, emptyMessage: 'No layers.' });
138
+ }
139
+ });
140
+
141
+ // ── trace simulate ──────────────────────────────────────────────────────────
142
+
143
+ trace
144
+ .command('simulate')
145
+ .description('Run a what-if policy simulation against a trace (TRV-5)')
146
+ .argument('<identifier>', 'conversation ID (corr) or RUN_ id')
147
+ .option('--policy-overrides <json>', 'JSON map of layer_id -> { action } overrides')
148
+ .option('--layer-toggles <json>', 'JSON map of layer_id -> enabled(bool)')
149
+ .option('--query-override <text>', 'replacement query text')
150
+ .action(async (identifier, options) => {
151
+ auth.requireAuth();
152
+ const body = {};
153
+ if (options.policyOverrides) body.policy_overrides = parseJSON(options.policyOverrides, '--policy-overrides');
154
+ if (options.layerToggles) body.layer_toggles = parseJSON(options.layerToggles, '--layer-toggles');
155
+ if (options.queryOverride !== undefined) body.query_override = options.queryOverride;
156
+
157
+ const data = await client.post(resolveEndpoint('traces.simulate', { id: identifier }).path, {
158
+ service: resolveEndpoint('traces.simulate', { id: identifier }).service,
159
+ body,
160
+ });
161
+
162
+ const format = program.opts().output;
163
+ if (format === 'json') {
164
+ console.log(JSON.stringify(data, null, 2));
165
+ return;
166
+ }
167
+ printInfo(`Confidence: ${data.confidence}${data.simulation_boundary ? ` (boundary ${data.simulation_boundary})` : ''}`);
168
+ formatOutput({
169
+ columns: ['layer_id', 'original_outcome', 'simulated_outcome'],
170
+ headers: ['Layer', 'Original', 'Simulated'],
171
+ rows: data.diffs || [],
172
+ }, { format, emptyMessage: 'No differences — simulation matches the original trace.' });
173
+ });
174
+
175
+ // ── trace compare ───────────────────────────────────────────────────────────
176
+
177
+ trace
178
+ .command('compare')
179
+ .description('Compare two traces with layer-aligned diffs (TRV-6)')
180
+ .requiredOption('--a <identifier>', 'first trace ID (corr or RUN_ id)')
181
+ .requiredOption('--b <identifier>', 'second trace ID (corr or RUN_ id)')
182
+ .action(async (options) => {
183
+ auth.requireAuth();
184
+ const data = await client.post(resolveEndpoint('traces.compare').path, {
185
+ service: resolveEndpoint('traces.compare').service,
186
+ body: { trace_a: options.a, trace_b: options.b },
187
+ });
188
+
189
+ const format = program.opts().output;
190
+ if (format === 'json') {
191
+ console.log(JSON.stringify(data, null, 2));
192
+ return;
193
+ }
194
+ printInfo(data.summary || '');
195
+ formatOutput({
196
+ // fleet #392: compare returns diff_type, not status.
197
+ columns: ['layer_id', 'diff_type'],
198
+ headers: ['Layer', 'Diff'],
199
+ rows: data.layer_diffs || [],
200
+ }, { format, emptyMessage: 'No layer diffs.' });
201
+ });
202
+
203
+ // ══ trace replay — persisted replay sessions ════════════════════════════════
204
+
205
+ const replay = trace
206
+ .command('replay')
207
+ .description('Manage persisted trace replay sessions');
208
+
209
+ replay
210
+ .command('list')
211
+ .description('List replay sessions')
212
+ .option('--limit <n>', 'max results', '30')
213
+ .option('--offset <n>', 'result offset', '0')
214
+ .action(async (options) => {
215
+ auth.requireAuth();
216
+ const data = await client.get(resolveEndpoint('traces.replaySessions.list').path, {
217
+ service: resolveEndpoint('traces.replaySessions.list').service,
218
+ query: { limit: options.limit, offset: options.offset },
219
+ });
220
+ formatOutput({
221
+ columns: ['id', 'run_id', 'status', 'created_at', 'completed_at'],
222
+ headers: ['ID', 'Run ID', 'Status', 'Created', 'Completed'],
223
+ rows: data.sessions || data || [],
224
+ }, { format: program.opts().output, emptyMessage: 'No replay sessions found.' });
225
+ });
226
+
227
+ replay
228
+ .command('get')
229
+ .description('Show a replay session')
230
+ .argument('<id>', 'replay session ID')
231
+ .action(async (id) => {
232
+ auth.requireAuth();
233
+ const data = await client.get(resolveEndpoint('traces.replaySessions.get', { id }).path, {
234
+ service: resolveEndpoint('traces.replaySessions.get', { id }).service,
235
+ });
236
+ formatDetail(data.session || data, { format: program.opts().output, title: `Replay Session: ${id}` });
237
+ });
238
+
239
+ replay
240
+ .command('create')
241
+ .description('Create a replay session for a run')
242
+ .requiredOption('--run-id <runId>', 'the RUN_ id being replayed')
243
+ .option('--id <id>', 'replay session ID (auto-generated if omitted)')
244
+ .option('--status <status>', 'initial status', 'pending')
245
+ .option('--query <text>', 'replacement query for the replay')
246
+ .option('--layer-config <json>', 'JSON layer configuration')
247
+ .option('--mock-config <json>', 'JSON mock configuration')
248
+ .option('--original-trace <json>', 'JSON snapshot of the original trace')
249
+ .action(async (options) => {
250
+ auth.requireAuth();
251
+ const body = {
252
+ id: options.id || randomUUID(),
253
+ run_id: options.runId,
254
+ status: options.status,
255
+ };
256
+ if (options.query) body.query = options.query;
257
+ if (options.layerConfig) body.layer_config = parseJSON(options.layerConfig, '--layer-config');
258
+ if (options.mockConfig) body.mock_config = parseJSON(options.mockConfig, '--mock-config');
259
+ if (options.originalTrace) body.original_trace = parseJSON(options.originalTrace, '--original-trace');
260
+
261
+ const data = await client.post(resolveEndpoint('traces.replaySessions.create').path, {
262
+ service: resolveEndpoint('traces.replaySessions.create').service,
263
+ body,
264
+ });
265
+ printSuccess(`Replay session "${body.id}" created.`);
266
+ formatDetail(data.session || data, { format: program.opts().output });
267
+ });
268
+
269
+ replay
270
+ .command('update')
271
+ .description('Update a replay session (status and/or result)')
272
+ .argument('<id>', 'replay session ID')
273
+ .option('--status <status>', 'new status')
274
+ .option('--result <json>', 'JSON replay result')
275
+ .action(async (id, options) => {
276
+ auth.requireAuth();
277
+ if (!options.status && !options.result) {
278
+ throw new Error('Specify at least one field to update (--status, --result).');
279
+ }
280
+ const body = {};
281
+ if (options.status) body.status = options.status;
282
+ if (options.result) body.replay_result = parseJSON(options.result, '--result');
283
+
284
+ const data = await client.put(resolveEndpoint('traces.replaySessions.update', { id }).path, {
285
+ service: resolveEndpoint('traces.replaySessions.update', { id }).service,
286
+ body,
287
+ });
288
+ printSuccess('Replay session updated.');
289
+ formatDetail(data.session || data, { format: program.opts().output });
290
+ });
291
+
292
+ replay
293
+ .command('delete')
294
+ .description('Delete a replay session')
295
+ .argument('<id>', 'replay session ID')
296
+ .option('--force', 'skip confirmation')
297
+ .action(async (id, options) => {
298
+ auth.requireAuth();
299
+ if (!(await confirmOrSkip(program, options, `Delete replay session ${id}? This cannot be undone.`))) {
300
+ printInfo('Cancelled.');
301
+ return;
302
+ }
303
+ await client.delete(resolveEndpoint('traces.replaySessions.delete', { id }).path, {
304
+ service: resolveEndpoint('traces.replaySessions.delete', { id }).service,
305
+ });
306
+ printSuccess('Replay session deleted.');
307
+ });
308
+
309
+ replay
310
+ .command('delete-all')
311
+ .description('Delete ALL replay sessions')
312
+ .option('--force', 'skip confirmation')
313
+ .action(async (options) => {
314
+ auth.requireAuth();
315
+ if (!(await confirmOrSkip(program, options, 'Delete ALL replay sessions? This cannot be undone.'))) {
316
+ printInfo('Cancelled.');
317
+ return;
318
+ }
319
+ await client.delete(resolveEndpoint('traces.replaySessions.deleteAll').path, {
320
+ service: resolveEndpoint('traces.replaySessions.deleteAll').service,
321
+ });
322
+ printSuccess('All replay sessions deleted.');
323
+ });
324
+
325
+ // ══ trace comparison — persisted trace comparisons ══════════════════════════
326
+
327
+ const comparison = trace
328
+ .command('comparison')
329
+ .description('Manage persisted trace comparisons');
330
+
331
+ comparison
332
+ .command('list')
333
+ .description('List trace comparisons')
334
+ .option('--limit <n>', 'max results', '30')
335
+ .option('--offset <n>', 'result offset', '0')
336
+ .action(async (options) => {
337
+ auth.requireAuth();
338
+ const data = await client.get(resolveEndpoint('traces.comparisons.list').path, {
339
+ service: resolveEndpoint('traces.comparisons.list').service,
340
+ query: { limit: options.limit, offset: options.offset },
341
+ });
342
+ const rows = (data.comparisons || data || []).map(c => ({
343
+ ...c,
344
+ corr_ids: Array.isArray(c.corr_ids) ? c.corr_ids.join(', ') : c.corr_ids,
345
+ }));
346
+ formatOutput({
347
+ columns: ['id', 'name', 'corr_ids', 'similarity_score', 'created_at'],
348
+ headers: ['ID', 'Name', 'Traces', 'Similarity', 'Created'],
349
+ rows,
350
+ }, { format: program.opts().output, emptyMessage: 'No trace comparisons found.' });
351
+ });
352
+
353
+ comparison
354
+ .command('get')
355
+ .description('Show a trace comparison')
356
+ .argument('<id>', 'comparison ID')
357
+ .action(async (id) => {
358
+ auth.requireAuth();
359
+ const data = await client.get(resolveEndpoint('traces.comparisons.get', { id }).path, {
360
+ service: resolveEndpoint('traces.comparisons.get', { id }).service,
361
+ });
362
+ formatDetail(data.comparison || data, { format: program.opts().output, title: `Trace Comparison: ${id}` });
363
+ });
364
+
365
+ comparison
366
+ .command('create')
367
+ .description('Persist a comparison across two or more traces')
368
+ .requiredOption('--corr-ids <ids>', 'comma-separated conversation IDs (corr) to compare')
369
+ .option('--id <id>', 'comparison ID (auto-generated if omitted)')
370
+ .option('--name <name>', 'comparison name')
371
+ .option('--options <json>', 'JSON comparison options')
372
+ .option('--result <json>', 'JSON precomputed comparison result')
373
+ .option('--similarity-score <n>', 'similarity score (0-1)')
374
+ .action(async (options) => {
375
+ auth.requireAuth();
376
+ const body = {
377
+ id: options.id || randomUUID(),
378
+ corr_ids: options.corrIds.split(',').map(s => s.trim()).filter(Boolean),
379
+ };
380
+ if (options.name) body.name = options.name;
381
+ if (options.options) body.options = parseJSON(options.options, '--options');
382
+ if (options.result) body.result = parseJSON(options.result, '--result');
383
+ if (options.similarityScore) body.similarity_score = parseFloat(options.similarityScore);
384
+
385
+ const data = await client.post(resolveEndpoint('traces.comparisons.create').path, {
386
+ service: resolveEndpoint('traces.comparisons.create').service,
387
+ body,
388
+ });
389
+ printSuccess(`Trace comparison "${body.id}" created.`);
390
+ formatDetail(data.comparison || data, { format: program.opts().output });
391
+ });
392
+
393
+ comparison
394
+ .command('update')
395
+ .description('Rename a trace comparison')
396
+ .argument('<id>', 'comparison ID')
397
+ .requiredOption('--name <name>', 'new name')
398
+ .action(async (id, options) => {
399
+ auth.requireAuth();
400
+ await client.put(resolveEndpoint('traces.comparisons.update', { id }).path, {
401
+ service: resolveEndpoint('traces.comparisons.update', { id }).service,
402
+ body: { name: options.name },
403
+ });
404
+ printSuccess('Trace comparison updated.');
405
+ });
406
+
407
+ comparison
408
+ .command('delete')
409
+ .description('Delete a trace comparison')
410
+ .argument('<id>', 'comparison ID')
411
+ .option('--force', 'skip confirmation')
412
+ .action(async (id, options) => {
413
+ auth.requireAuth();
414
+ if (!(await confirmOrSkip(program, options, `Delete trace comparison ${id}? This cannot be undone.`))) {
415
+ printInfo('Cancelled.');
416
+ return;
417
+ }
418
+ await client.delete(resolveEndpoint('traces.comparisons.delete', { id }).path, {
419
+ service: resolveEndpoint('traces.comparisons.delete', { id }).service,
420
+ });
421
+ printSuccess('Trace comparison deleted.');
422
+ });
423
+
424
+ comparison
425
+ .command('delete-all')
426
+ .description('Delete ALL trace comparisons')
427
+ .option('--force', 'skip confirmation')
428
+ .action(async (options) => {
429
+ auth.requireAuth();
430
+ if (!(await confirmOrSkip(program, options, 'Delete ALL trace comparisons? This cannot be undone.'))) {
431
+ printInfo('Cancelled.');
432
+ return;
433
+ }
434
+ await client.delete(resolveEndpoint('traces.comparisons.deleteAll').path, {
435
+ service: resolveEndpoint('traces.comparisons.deleteAll').service,
436
+ });
437
+ printSuccess('All trace comparisons deleted.');
438
+ });
439
+
440
+ // ══ trace correlation — cross-agent correlation cache ═══════════════════════
441
+ // NOTE: the backend route is `/traces/correlations/:runId`, but the row it
442
+ // resolves against is keyed by `root_run_id` (see trvStatements.getCorrelationByRun
443
+ // / deleteCorrelation in services/collector/server.js) — the CLI argument is
444
+ // named <rootRunId> to match that semantics, not the literal route segment name.
445
+
446
+ const correlation = trace
447
+ .command('correlation')
448
+ .description('Manage the cross-agent trace correlation cache');
449
+
450
+ correlation
451
+ .command('list')
452
+ .description('List cached correlations')
453
+ .option('--limit <n>', 'max results', '30')
454
+ .option('--offset <n>', 'result offset', '0')
455
+ .action(async (options) => {
456
+ auth.requireAuth();
457
+ const data = await client.get(resolveEndpoint('traces.correlations.list').path, {
458
+ service: resolveEndpoint('traces.correlations.list').service,
459
+ query: { limit: options.limit, offset: options.offset },
460
+ });
461
+ formatOutput({
462
+ columns: ['id', 'root_run_id', 'correlation_id', 'agent_count', 'handoff_count', 'created_at', 'expires_at'],
463
+ headers: ['ID', 'Root Run', 'Correlation ID', 'Agents', 'Handoffs', 'Created', 'Expires'],
464
+ rows: data.correlations || data || [],
465
+ }, { format: program.opts().output, emptyMessage: 'No cached correlations found.' });
466
+ });
467
+
468
+ correlation
469
+ .command('get')
470
+ .description('Show the cached correlation tree for a root run')
471
+ .argument('<rootRunId>', 'root run ID')
472
+ .action(async (rootRunId) => {
473
+ auth.requireAuth();
474
+ const data = await client.get(resolveEndpoint('traces.correlations.get', { runId: rootRunId }).path, {
475
+ service: resolveEndpoint('traces.correlations.get', { runId: rootRunId }).service,
476
+ });
477
+ formatDetail(data.correlation || data, { format: program.opts().output, title: `Correlation: ${rootRunId}` });
478
+ });
479
+
480
+ correlation
481
+ .command('create')
482
+ .description('Store a correlation tree in the cache (24h TTL)')
483
+ .requiredOption('--root-run-id <id>', 'root run ID')
484
+ .requiredOption('--correlation-id <id>', 'correlation ID')
485
+ .option('--id <id>', 'cache entry ID (auto-generated if omitted)')
486
+ .option('--tree <json>', 'JSON correlation tree', '{}')
487
+ .option('--stats <json>', 'JSON stats object')
488
+ .option('--agent-count <n>', 'agent count', '0')
489
+ .option('--handoff-count <n>', 'handoff count', '0')
490
+ .action(async (options) => {
491
+ auth.requireAuth();
492
+ const body = {
493
+ id: options.id || randomUUID(),
494
+ root_run_id: options.rootRunId,
495
+ correlation_id: options.correlationId,
496
+ tree: parseJSON(options.tree, '--tree'),
497
+ agent_count: parseInt(options.agentCount, 10),
498
+ handoff_count: parseInt(options.handoffCount, 10),
499
+ };
500
+ if (options.stats) body.stats = parseJSON(options.stats, '--stats');
501
+
502
+ const data = await client.post(resolveEndpoint('traces.correlations.create').path, {
503
+ service: resolveEndpoint('traces.correlations.create').service,
504
+ body,
505
+ });
506
+ printSuccess(`Correlation cached (expires ${data.expires_at}).`);
507
+ });
508
+
509
+ correlation
510
+ .command('delete')
511
+ .description('Delete the cached correlation for a root run')
512
+ .argument('<rootRunId>', 'root run ID')
513
+ .option('--force', 'skip confirmation')
514
+ .action(async (rootRunId, options) => {
515
+ auth.requireAuth();
516
+ if (!(await confirmOrSkip(program, options, `Delete cached correlation for ${rootRunId}?`))) {
517
+ printInfo('Cancelled.');
518
+ return;
519
+ }
520
+ await client.delete(resolveEndpoint('traces.correlations.delete', { runId: rootRunId }).path, {
521
+ service: resolveEndpoint('traces.correlations.delete', { runId: rootRunId }).service,
522
+ });
523
+ printSuccess('Cached correlation deleted.');
524
+ });
525
+ }
@@ -0,0 +1,128 @@
1
+ /**
2
+ * Trigger commands: read-only enumeration of quoxflow's active trigger
3
+ * subscriptions, plus workflow activate/deactivate (which registers or
4
+ * unregisters those triggers).
5
+ *
6
+ * quox trigger webhooks|schedules|events
7
+ * quox trigger workflow-activate|workflow-deactivate <workflow-id>
8
+ *
9
+ * Backend: quoxflow (NOT the auth service, NOT proxied by collector) — see
10
+ * cli/lib/api/endpoints/triggers.js header for the full access-path
11
+ * decision. quoxflow/src/api/routes/triggers.ts (mounted at /api/triggers)
12
+ * and quoxflow/src/api/routes/workflows-lifecycle.ts (mounted at
13
+ * /api/workflows).
14
+ *
15
+ * Every read here returns UNFILTERED results — see the BACKEND FINDING in
16
+ * the endpoints/triggers.js header: none of webhooks/schedules/events scope
17
+ * by org or workflow. Do not treat an empty `--org` filter as meaningful;
18
+ * there isn't one, by backend design (or omission) today.
19
+ */
20
+
21
+ import { formatOutput, formatDetail, printInfo } from '../core/output.js';
22
+ import { line as glyphLine, printEvent } from '../core/quoxglyph.js';
23
+ import { resolveOrgId } from './helpers.js';
24
+ import { resolveEndpoint } from '../api/endpoints.js';
25
+
26
+ export function registerTriggerCommands(program, ctx) {
27
+ const { client, auth } = ctx;
28
+
29
+ const trigger = program
30
+ .command('trigger')
31
+ .description('View quoxflow trigger subscriptions (webhooks, schedules, events) and activate/deactivate workflows');
32
+
33
+ // ── trigger webhooks ────────────────────────────────────────────────────────
34
+
35
+ trigger
36
+ .command('webhooks')
37
+ .description('List registered inbound webhook trigger paths (from active workflow nodes)')
38
+ .action(async () => {
39
+ auth.requireAuth();
40
+ const data = await client.get(resolveEndpoint('triggers.webhooks').path, {
41
+ service: resolveEndpoint('triggers.webhooks').service,
42
+ });
43
+ const webhooks = data.data?.webhooks || data.webhooks || [];
44
+
45
+ formatOutput({
46
+ columns: ['workflowId', 'nodeId', 'method', 'path', 'fireCount', 'lastFiredAt'],
47
+ headers: ['Workflow', 'Node', 'Method', 'Path', 'Fires', 'Last Fired'],
48
+ rows: webhooks,
49
+ }, { format: program.opts().output, emptyMessage: 'No registered webhook triggers.' });
50
+ });
51
+
52
+ // ── trigger schedules ───────────────────────────────────────────────────────
53
+
54
+ trigger
55
+ .command('schedules')
56
+ .description('List active cron schedule triggers owned by quoxflow (empty when tasks-service-owned)')
57
+ .action(async () => {
58
+ auth.requireAuth();
59
+ const data = await client.get(resolveEndpoint('triggers.schedules').path, {
60
+ service: resolveEndpoint('triggers.schedules').service,
61
+ });
62
+ const payload = data.data || data;
63
+ const schedules = payload.schedules || [];
64
+
65
+ if (payload.owned_by === 'tasks') {
66
+ printInfo(payload.note || 'Schedules are managed by the tasks service. View at `quox schedule` / the dashboard.');
67
+ }
68
+
69
+ formatOutput({
70
+ columns: ['workflowId', 'nodeId', 'expression', 'timezone', 'nextFireAt', 'active'],
71
+ headers: ['Workflow', 'Node', 'Cron', 'Timezone', 'Next Fire', 'Active'],
72
+ rows: schedules,
73
+ }, { format: program.opts().output, emptyMessage: 'No quoxflow-owned schedule triggers.' });
74
+ });
75
+
76
+ // ── trigger events ──────────────────────────────────────────────────────────
77
+
78
+ trigger
79
+ .command('events')
80
+ .description('List active EventBus subscriptions')
81
+ .action(async () => {
82
+ auth.requireAuth();
83
+ const data = await client.get(resolveEndpoint('triggers.events').path, {
84
+ service: resolveEndpoint('triggers.events').service,
85
+ });
86
+ const subscriptions = data.data?.subscriptions || data.subscriptions || [];
87
+
88
+ formatOutput({
89
+ columns: ['id', 'pattern', 'workflowId', 'once', 'lastMatchAt'],
90
+ headers: ['ID', 'Pattern', 'Workflow', 'Once', 'Last Match'],
91
+ rows: subscriptions,
92
+ }, { format: program.opts().output, emptyMessage: 'No active event subscriptions.' });
93
+ });
94
+
95
+ // ── trigger workflow-activate ───────────────────────────────────────────────
96
+
97
+ trigger
98
+ .command('workflow-activate')
99
+ .description('Activate a workflow — registers its webhook/schedule/event triggers')
100
+ .argument('<id>', 'workflow ID')
101
+ .action(async (id) => {
102
+ auth.requireAuth();
103
+ const orgId = resolveOrgId(auth, program);
104
+ const data = await client.post(resolveEndpoint('workflows.activate', { workflowId: id }).path, {
105
+ service: resolveEndpoint('workflows.activate', { workflowId: id }).service,
106
+ body: { org_id: orgId },
107
+ });
108
+ const result = data.data || data;
109
+ printEvent(glyphLine('quox', 'workflow activated', { result: `${id} · ${result.triggersRegistered ?? 0} trigger(s) registered`, status: 'ok' }));
110
+ formatDetail(result, { format: program.opts().output });
111
+ });
112
+
113
+ // ── trigger workflow-deactivate ─────────────────────────────────────────────
114
+
115
+ trigger
116
+ .command('workflow-deactivate')
117
+ .description('Deactivate a workflow — unregisters its webhook/schedule/event triggers')
118
+ .argument('<id>', 'workflow ID')
119
+ .action(async (id) => {
120
+ auth.requireAuth();
121
+ const data = await client.post(resolveEndpoint('workflows.deactivate', { workflowId: id }).path, {
122
+ service: resolveEndpoint('workflows.deactivate', { workflowId: id }).service,
123
+ });
124
+ const result = data.data || data;
125
+ printEvent(glyphLine('quox', 'workflow deactivated', { result: id, status: 'ok' }));
126
+ formatDetail(result, { format: program.opts().output });
127
+ });
128
+ }