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.
- package/LICENSE +35 -0
- package/README.md +142 -2
- package/bin/quox.js +292 -0
- package/bin/quoxon.js +8 -0
- package/lib/api/endpoints/admin.js +125 -0
- package/lib/api/endpoints/aee.js +38 -0
- package/lib/api/endpoints/agent-attach.js +126 -0
- package/lib/api/endpoints/agents.js +56 -0
- package/lib/api/endpoints/assistants.js +50 -0
- package/lib/api/endpoints/audit.js +14 -0
- package/lib/api/endpoints/auth.js +50 -0
- package/lib/api/endpoints/awareness.js +34 -0
- package/lib/api/endpoints/backup-schedules.js +20 -0
- package/lib/api/endpoints/backups.js +86 -0
- package/lib/api/endpoints/brain.js +140 -0
- package/lib/api/endpoints/budgets.js +62 -0
- package/lib/api/endpoints/calendarq.js +71 -0
- package/lib/api/endpoints/catalog.js +14 -0
- package/lib/api/endpoints/chat.js +54 -0
- package/lib/api/endpoints/consent.js +26 -0
- package/lib/api/endpoints/contactsq.js +66 -0
- package/lib/api/endpoints/conversations.js +32 -0
- package/lib/api/endpoints/custom-roles.js +92 -0
- package/lib/api/endpoints/divisions.js +56 -0
- package/lib/api/endpoints/domains.js +38 -0
- package/lib/api/endpoints/engagement.js +231 -0
- package/lib/api/endpoints/engine.js +11 -0
- package/lib/api/endpoints/entity.js +50 -0
- package/lib/api/endpoints/executions.js +32 -0
- package/lib/api/endpoints/file.js +20 -0
- package/lib/api/endpoints/files.js +26 -0
- package/lib/api/endpoints/github.js +21 -0
- package/lib/api/endpoints/governance.js +43 -0
- package/lib/api/endpoints/hat.js +36 -0
- package/lib/api/endpoints/inbox.js +32 -0
- package/lib/api/endpoints/inboxq.js +120 -0
- package/lib/api/endpoints/index.js +228 -0
- package/lib/api/endpoints/invites.js +19 -0
- package/lib/api/endpoints/llm-policy.js +43 -0
- package/lib/api/endpoints/lucid.js +89 -0
- package/lib/api/endpoints/members.js +32 -0
- package/lib/api/endpoints/memory.js +75 -0
- package/lib/api/endpoints/mfa.js +32 -0
- package/lib/api/endpoints/mon.js +178 -0
- package/lib/api/endpoints/notesq.js +43 -0
- package/lib/api/endpoints/objectives.js +62 -0
- package/lib/api/endpoints/orgs.js +38 -0
- package/lib/api/endpoints/plugins.js +134 -0
- package/lib/api/endpoints/powers.js +75 -0
- package/lib/api/endpoints/prompts.js +69 -0
- package/lib/api/endpoints/qlarity.js +122 -0
- package/lib/api/endpoints/quoxbox.js +55 -0
- package/lib/api/endpoints/quoxchat.js +54 -0
- package/lib/api/endpoints/quoxflow.js +32 -0
- package/lib/api/endpoints/quoxplan.js +114 -0
- package/lib/api/endpoints/quoxseo.js +108 -0
- package/lib/api/endpoints/reach.js +47 -0
- package/lib/api/endpoints/runs.js +38 -0
- package/lib/api/endpoints/safety.js +46 -0
- package/lib/api/endpoints/scim.js +46 -0
- package/lib/api/endpoints/scopes.js +14 -0
- package/lib/api/endpoints/security.js +69 -0
- package/lib/api/endpoints/setup.js +90 -0
- package/lib/api/endpoints/skills.js +180 -0
- package/lib/api/endpoints/sso.js +67 -0
- package/lib/api/endpoints/tags.js +32 -0
- package/lib/api/endpoints/teams.js +56 -0
- package/lib/api/endpoints/telephony.js +46 -0
- package/lib/api/endpoints/tg.js +56 -0
- package/lib/api/endpoints/tools.js +50 -0
- package/lib/api/endpoints/traces.js +157 -0
- package/lib/api/endpoints/triggers.js +70 -0
- package/lib/api/endpoints/user-library.js +41 -0
- package/lib/api/endpoints/users.js +20 -0
- package/lib/api/endpoints/v1.js +499 -0
- package/lib/api/endpoints/vault.js +192 -0
- package/lib/api/endpoints/voice.js +22 -0
- package/lib/api/endpoints/volt.js +84 -0
- package/lib/api/endpoints/ward.js +40 -0
- package/lib/api/endpoints/webhooks.js +46 -0
- package/lib/api/endpoints/workflows.js +162 -0
- package/lib/api/endpoints/x.js +24 -0
- package/lib/api/endpoints.js +15 -0
- package/lib/commands/admin.js +359 -0
- package/lib/commands/aee.js +99 -0
- package/lib/commands/agent.js +201 -0
- package/lib/commands/agentAttach.js +1440 -0
- package/lib/commands/approvals.js +47 -0
- package/lib/commands/assistant.js +114 -0
- package/lib/commands/auth.js +458 -0
- package/lib/commands/awareness.js +88 -0
- package/lib/commands/backup.js +373 -0
- package/lib/commands/brain.js +994 -0
- package/lib/commands/budget.js +241 -0
- package/lib/commands/build.js +156 -0
- package/lib/commands/calendar.js +216 -0
- package/lib/commands/capabilities.js +34 -0
- package/lib/commands/chat.js +240 -0
- package/lib/commands/claude.js +354 -0
- package/lib/commands/config-cmd.js +106 -0
- package/lib/commands/consent.js +84 -0
- package/lib/commands/contacts.js +967 -0
- package/lib/commands/contactsSelftest.js +132 -0
- package/lib/commands/conversation.js +74 -0
- package/lib/commands/department.js +75 -0
- package/lib/commands/dev.js +620 -0
- package/lib/commands/devResident.js +314 -0
- package/lib/commands/devSupervisor.js +1050 -0
- package/lib/commands/discord.js +96 -0
- package/lib/commands/doctor.js +181 -0
- package/lib/commands/domain.js +114 -0
- package/lib/commands/done.js +277 -0
- package/lib/commands/engagement.js +586 -0
- package/lib/commands/entity.js +119 -0
- package/lib/commands/facebook.js +212 -0
- package/lib/commands/file.js +128 -0
- package/lib/commands/fleet.js +882 -0
- package/lib/commands/governance.js +638 -0
- package/lib/commands/gripe.js +324 -0
- package/lib/commands/hat.js +205 -0
- package/lib/commands/helpers.js +79 -0
- package/lib/commands/inbox.js +122 -0
- package/lib/commands/instagram.js +264 -0
- package/lib/commands/integration.js +100 -0
- package/lib/commands/inventory.js +115 -0
- package/lib/commands/invites.js +45 -0
- package/lib/commands/licence.js +280 -0
- package/lib/commands/linkedin.js +178 -0
- package/lib/commands/llm-policy.js +117 -0
- package/lib/commands/llm.js +45 -0
- package/lib/commands/lucid.js +591 -0
- package/lib/commands/mail.js +531 -0
- package/lib/commands/memory.js +284 -0
- package/lib/commands/mon.js +514 -0
- package/lib/commands/monitoring.js +86 -0
- package/lib/commands/motion.js +22 -0
- package/lib/commands/notes.js +1246 -0
- package/lib/commands/notesBatch.js +160 -0
- package/lib/commands/notesBridges.js +56 -0
- package/lib/commands/notesLinks.js +60 -0
- package/lib/commands/notification.js +57 -0
- package/lib/commands/nucleus.js +92 -0
- package/lib/commands/objective.js +229 -0
- package/lib/commands/onboard.js +328 -0
- package/lib/commands/open.js +56 -0
- package/lib/commands/org.js +266 -0
- package/lib/commands/plan.js +906 -0
- package/lib/commands/plugin-doctor.js +1091 -0
- package/lib/commands/plugin.js +539 -0
- package/lib/commands/policy.js +78 -0
- package/lib/commands/powers.js +296 -0
- package/lib/commands/prompt.js +350 -0
- package/lib/commands/qlarity.js +690 -0
- package/lib/commands/quoxbox.js +270 -0
- package/lib/commands/quoxchat.js +266 -0
- package/lib/commands/quoxseo.js +340 -0
- package/lib/commands/reach.js +158 -0
- package/lib/commands/role.js +286 -0
- package/lib/commands/run.js +132 -0
- package/lib/commands/safety.js +107 -0
- package/lib/commands/scim.js +88 -0
- package/lib/commands/scope.js +58 -0
- package/lib/commands/security.js +157 -0
- package/lib/commands/service.js +89 -0
- package/lib/commands/settings.js +99 -0
- package/lib/commands/setup.js +543 -0
- package/lib/commands/skillManifest.js +113 -0
- package/lib/commands/skillRightsFormat.js +38 -0
- package/lib/commands/skills.js +644 -0
- package/lib/commands/slot.js +1198 -0
- package/lib/commands/sso.js +229 -0
- package/lib/commands/status.js +137 -0
- package/lib/commands/tag.js +91 -0
- package/lib/commands/team.js +140 -0
- package/lib/commands/tg.js +240 -0
- package/lib/commands/tiktok.js +218 -0
- package/lib/commands/tool.js +216 -0
- package/lib/commands/trace.js +525 -0
- package/lib/commands/trigger.js +128 -0
- package/lib/commands/vault.js +1424 -0
- package/lib/commands/voice.js +182 -0
- package/lib/commands/volt.js +528 -0
- package/lib/commands/watch.js +115 -0
- package/lib/commands/webhook.js +137 -0
- package/lib/commands/workflow.js +503 -0
- package/lib/commands/x.js +439 -0
- package/lib/core/actingAs.js +136 -0
- package/lib/core/aee.js +53 -0
- package/lib/core/agentAlias.js +163 -0
- package/lib/core/approvalContext.js +61 -0
- package/lib/core/auth.js +350 -0
- package/lib/core/banner.js +143 -0
- package/lib/core/brainPrime.js +90 -0
- package/lib/core/callerSource.js +129 -0
- package/lib/core/claudeLs.js +94 -0
- package/lib/core/client.js +584 -0
- package/lib/core/completions.js +163 -0
- package/lib/core/config.js +551 -0
- package/lib/core/confirm.js +37 -0
- package/lib/core/deeplinks.js +76 -0
- package/lib/core/doneEngineFetch.js +219 -0
- package/lib/core/errors.js +132 -0
- package/lib/core/global-flags.js +90 -0
- package/lib/core/governedSession.js +402 -0
- package/lib/core/lazyCommands.js +119 -0
- package/lib/core/liveSessionsReport.js +112 -0
- package/lib/core/org-override.js +52 -0
- package/lib/core/output.js +239 -0
- package/lib/core/prodEnv.js +7 -0
- package/lib/core/quoxglyph.js +158 -0
- package/lib/core/residentAlarm.js +282 -0
- package/lib/core/residentAuth.js +243 -0
- package/lib/core/residentPump.js +229 -0
- package/lib/core/residentSeed.js +104 -0
- package/lib/core/sessionHeartbeat.js +192 -0
- package/lib/core/sessionsOverview.js +160 -0
- package/lib/core/slotKeeper.js +282 -0
- package/lib/core/supervisorEngine.js +864 -0
- package/lib/core/systemdInstaller.js +256 -0
- package/lib/core/version.js +87 -0
- package/lib/core/websiteSession.js +148 -0
- package/lib/lucid/LucidIntro.jsx +105 -0
- package/lib/lucid/blind.js +71 -0
- package/lib/lucid/deck.js +32 -0
- package/lib/lucid/fmt.js +139 -0
- package/lib/lucid/sitrep.js +173 -0
- package/lib/plugins/loader.js +93 -0
- package/lib/quoxon/activity.js +104 -0
- package/lib/quoxon/api.js +473 -0
- package/lib/quoxon/app.js +1262 -0
- package/lib/quoxon/capabilities.js +75 -0
- package/lib/quoxon/commands.js +154 -0
- package/lib/quoxon/connect.js +467 -0
- package/lib/quoxon/connectCard.js +28 -0
- package/lib/quoxon/context.js +68 -0
- package/lib/quoxon/copy.js +33 -0
- package/lib/quoxon/demo.fixtures.js +24 -0
- package/lib/quoxon/demo.js +35 -0
- package/lib/quoxon/egg.js +36 -0
- package/lib/quoxon/events.js +458 -0
- package/lib/quoxon/gallery.js +223 -0
- package/lib/quoxon/indicator.js +193 -0
- package/lib/quoxon/intent.js +41 -0
- package/lib/quoxon/keys.js +121 -0
- package/lib/quoxon/main.js +244 -0
- package/lib/quoxon/markdown.js +317 -0
- package/lib/quoxon/model.js +205 -0
- package/lib/quoxon/motion/clock.js +69 -0
- package/lib/quoxon/motion/color.js +35 -0
- package/lib/quoxon/motion/frame.js +14 -0
- package/lib/quoxon/motion/reveal.js +54 -0
- package/lib/quoxon/motion/ring.js +79 -0
- package/lib/quoxon/objects/agent.js +61 -0
- package/lib/quoxon/objects/crystal.js +100 -0
- package/lib/quoxon/objects/fetch.js +170 -0
- package/lib/quoxon/objects/index.js +85 -0
- package/lib/quoxon/objects/ladder.js +71 -0
- package/lib/quoxon/objects/memory.js +44 -0
- package/lib/quoxon/objects/model.js +118 -0
- package/lib/quoxon/objects/proof.js +110 -0
- package/lib/quoxon/objects/skill.js +105 -0
- package/lib/quoxon/objects/view.js +76 -0
- package/lib/quoxon/objects/workflow.js +119 -0
- package/lib/quoxon/observers.js +42 -0
- package/lib/quoxon/onboarding/copy.js +235 -0
- package/lib/quoxon/onboarding/credential.js +95 -0
- package/lib/quoxon/onboarding/detect.js +22 -0
- package/lib/quoxon/onboarding/finish.js +119 -0
- package/lib/quoxon/onboarding/index.js +67 -0
- package/lib/quoxon/onboarding/installRunner.js +225 -0
- package/lib/quoxon/onboarding/intent.js +53 -0
- package/lib/quoxon/onboarding/journey.js +73 -0
- package/lib/quoxon/onboarding/preflight.js +142 -0
- package/lib/quoxon/onboarding/runner.js +58 -0
- package/lib/quoxon/onboarding/view.js +625 -0
- package/lib/quoxon/print.js +94 -0
- package/lib/quoxon/qart.js +379 -0
- package/lib/quoxon/qglyph.js +143 -0
- package/lib/quoxon/qhello.js +58 -0
- package/lib/quoxon/qmotion.js +111 -0
- package/lib/quoxon/render.js +648 -0
- package/lib/quoxon/roster.js +221 -0
- package/lib/quoxon/route.js +38 -0
- package/lib/quoxon/rules.js +130 -0
- package/lib/quoxon/session.js +68 -0
- package/lib/quoxon/skills.js +30 -0
- package/lib/quoxon/slash/compact.js +90 -0
- package/lib/quoxon/slash/ledger.js +83 -0
- package/lib/quoxon/slash/listView.js +28 -0
- package/lib/quoxon/slash/palette.js +104 -0
- package/lib/quoxon/slash/paletteController.js +59 -0
- package/lib/quoxon/slash/panels.js +101 -0
- package/lib/quoxon/slash/picker.js +38 -0
- package/lib/quoxon/slash/restart.js +144 -0
- package/lib/quoxon/slash/resume.js +59 -0
- package/lib/quoxon/slash/settings.js +50 -0
- package/lib/quoxon/sound/index.js +186 -0
- package/lib/quoxon/sound/mapping.js +119 -0
- package/lib/quoxon/sound/output.js +52 -0
- package/lib/quoxon/sound/synth.js +122 -0
- package/lib/quoxon/term.js +106 -0
- package/lib/quoxon/textBuffer.js +242 -0
- package/lib/quoxon/theme.js +253 -0
- package/lib/quoxon/transcript.js +150 -0
- package/lib/quoxon/web/qface.js +81 -0
- package/lib/quoxon/welcome.js +52 -0
- package/lib/tui/app.jsx +289 -0
- package/lib/tui/components/Breadcrumb.jsx +42 -0
- package/lib/tui/components/ChatInput.jsx +68 -0
- package/lib/tui/components/Confirm.jsx +32 -0
- package/lib/tui/components/DataTable.jsx +270 -0
- package/lib/tui/components/DetailPanel.jsx +241 -0
- package/lib/tui/components/HelpOverlay.jsx +67 -0
- package/lib/tui/components/Layout.jsx +129 -0
- package/lib/tui/components/Palette.jsx +141 -0
- package/lib/tui/components/SearchInput.jsx +125 -0
- package/lib/tui/components/Sparkline.jsx +55 -0
- package/lib/tui/components/StatusBar.jsx +133 -0
- package/lib/tui/components/Toast.jsx +52 -0
- package/lib/tui/hooks/useApi.js +44 -0
- package/lib/tui/hooks/useHealth.js +90 -0
- package/lib/tui/hooks/usePolledApi.js +86 -0
- package/lib/tui/hooks/useTerminalSize.js +46 -0
- package/lib/tui/index.js +56 -0
- package/lib/tui/intro/Intro.jsx +332 -0
- package/lib/tui/jsx-loader.js +32 -0
- package/lib/tui/keys.js +87 -0
- package/lib/tui/registry.js +49 -0
- package/lib/tui/theme.js +103 -0
- package/lib/tui/views/AdminView.jsx +398 -0
- package/lib/tui/views/AgentView.jsx +204 -0
- package/lib/tui/views/BackupView.jsx +225 -0
- package/lib/tui/views/BrainView.jsx +223 -0
- package/lib/tui/views/ChatView.jsx +468 -0
- package/lib/tui/views/FilesView.jsx +218 -0
- package/lib/tui/views/FleetView.jsx +234 -0
- package/lib/tui/views/GovernanceView.jsx +453 -0
- package/lib/tui/views/HealthView.jsx +216 -0
- package/lib/tui/views/InboxView.jsx +228 -0
- package/lib/tui/views/LucidDeck.jsx +727 -0
- package/lib/tui/views/MainMenu.jsx +123 -0
- package/lib/tui/views/MemoryView.jsx +377 -0
- package/lib/tui/views/ObjectivesView.jsx +230 -0
- package/lib/tui/views/OrgView.jsx +229 -0
- package/lib/tui/views/ToolView.jsx +221 -0
- package/lib/tui/views/VaultView.jsx +422 -0
- package/lib/tui/views/WorkflowView.jsx +329 -0
- 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
|
+
}
|