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,514 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Monitoring commands: Docker fleet, Wazuh security monitoring, AGH-chain
|
|
3
|
+
* agent honesty leaderboard, Prometheus metrics, and monitoring config.
|
|
4
|
+
*
|
|
5
|
+
* quox mon docker fleet|containers|container|logs|stats|stacks|df|action
|
|
6
|
+
* quox mon wazuh status|agents|alerts|vulns|deploy-history|deploy-agent|deploy-bulk|active-response
|
|
7
|
+
* quox mon agh-chain leaderboard|agent|events
|
|
8
|
+
* quox mon metrics
|
|
9
|
+
* quox mon config get|cache|grafana
|
|
10
|
+
*
|
|
11
|
+
* Backend: services/collector/server.js. See endpoints/mon.js's header for
|
|
12
|
+
* the full auth/org-scoping verification per route family (WP4.3,
|
|
13
|
+
* STREAM_CLI_PARITY_PLAN.md §2.3 #10) — summary:
|
|
14
|
+
* - `/docker/*` — NO auth middleware, no org filter (fleet-wide).
|
|
15
|
+
* - `/api/v1/wazuh/*` — requireServiceOrUserAuth(); not org-filtered by
|
|
16
|
+
* the Wazuh API itself; deploy-agent/deploy-bulk/active-response are
|
|
17
|
+
* HITL-gated (queue an HITL Inbox approval, return 202, do not execute
|
|
18
|
+
* immediately).
|
|
19
|
+
* - `/api/v1/agh-chain/*` — requireServiceOrUserAuth() AND genuinely
|
|
20
|
+
* org-scoped (403s without req.user.org_id).
|
|
21
|
+
* - `/metrics` — NO auth middleware, plain text (Prometheus exposition
|
|
22
|
+
* format), not JSON.
|
|
23
|
+
* - `/api/v1/monitoring/config` — optional auth; `/api/v1/monitoring/
|
|
24
|
+
* cache/:service` and `/api/v1/grafana/config` — requireServiceOrUserAuth().
|
|
25
|
+
* None of the three are org-scoped (single shared config/cache).
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import { formatOutput, formatDetail, printSuccess, printWarning, printInfo } from '../core/output.js';
|
|
29
|
+
import { parseJSON } from './helpers.js';
|
|
30
|
+
import { resolveEndpoint } from '../api/endpoints.js';
|
|
31
|
+
|
|
32
|
+
async function confirmOrSkip(program, options, message) {
|
|
33
|
+
if (options.force || program.opts().yes) return true;
|
|
34
|
+
const { default: inquirer } = await import('inquirer');
|
|
35
|
+
const ans = await inquirer.prompt([{ type: 'confirm', name: 'confirm', message, default: false }]);
|
|
36
|
+
return ans.confirm;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function registerMonCommands(program, ctx) {
|
|
40
|
+
const { client, auth } = ctx;
|
|
41
|
+
|
|
42
|
+
const mon = program
|
|
43
|
+
.command('mon')
|
|
44
|
+
.description('Monitoring: Docker fleet, Wazuh security, AGH-chain leaderboard, metrics');
|
|
45
|
+
|
|
46
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
47
|
+
// docker
|
|
48
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
49
|
+
|
|
50
|
+
const docker = mon
|
|
51
|
+
.command('docker')
|
|
52
|
+
.description('Docker fleet: hosts, containers, stacks, disk usage');
|
|
53
|
+
|
|
54
|
+
docker
|
|
55
|
+
.command('fleet')
|
|
56
|
+
.description('Discover Docker hosts across the fleet')
|
|
57
|
+
.option('--refresh', 'force a live scan instead of returning the cache')
|
|
58
|
+
.action(async (options) => {
|
|
59
|
+
auth.requireAuth();
|
|
60
|
+
const ep = resolveEndpoint('mon.docker.fleet.list');
|
|
61
|
+
const data = await client.get(ep.path, { service: ep.service, query: options.refresh ? { refresh: 'true' } : undefined });
|
|
62
|
+
const hosts = data.hosts || [];
|
|
63
|
+
formatOutput({
|
|
64
|
+
// fleet #392: docker fleet rows are id/containers_total/docker_version; ip and status don't exist.
|
|
65
|
+
columns: ['id', 'containers_total', 'containers_running', 'docker_version'],
|
|
66
|
+
headers: ['Host', 'Containers', 'Running', 'Docker'],
|
|
67
|
+
rows: hosts,
|
|
68
|
+
}, { format: program.opts().output, emptyMessage: 'No Docker hosts found.' });
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
docker
|
|
72
|
+
.command('containers')
|
|
73
|
+
.description('List containers across the fleet (or a single host)')
|
|
74
|
+
.option('--host <hostId>', 'filter to a single host')
|
|
75
|
+
.option('--no-all', 'only running containers (default includes stopped)')
|
|
76
|
+
.option('--refresh', 'force a live fetch instead of returning the cache')
|
|
77
|
+
.action(async (options) => {
|
|
78
|
+
auth.requireAuth();
|
|
79
|
+
const ep = resolveEndpoint('mon.docker.containers.list');
|
|
80
|
+
const query = {};
|
|
81
|
+
if (options.host) query.host = options.host;
|
|
82
|
+
if (options.all === false) query.all = 'false';
|
|
83
|
+
if (options.refresh) query.refresh = 'true';
|
|
84
|
+
const data = await client.get(ep.path, { service: ep.service, query });
|
|
85
|
+
formatOutput({
|
|
86
|
+
columns: ['host_id', 'id', 'name', 'image', 'state', 'status'],
|
|
87
|
+
headers: ['Host', 'ID', 'Name', 'Image', 'State', 'Status'],
|
|
88
|
+
rows: data.containers || [],
|
|
89
|
+
}, { format: program.opts().output, emptyMessage: 'No containers found.' });
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
docker
|
|
93
|
+
.command('container')
|
|
94
|
+
.description('Show container detail (stats + recent logs)')
|
|
95
|
+
.argument('<id>', 'container ID')
|
|
96
|
+
.requiredOption('--host <hostId>', 'host the container lives on')
|
|
97
|
+
.action(async (id, options) => {
|
|
98
|
+
auth.requireAuth();
|
|
99
|
+
const ep = resolveEndpoint('mon.docker.containers.get', { id });
|
|
100
|
+
const data = await client.get(ep.path, { service: ep.service, query: { host: options.host } });
|
|
101
|
+
formatDetail(data, {
|
|
102
|
+
format: program.opts().output,
|
|
103
|
+
title: `Container: ${id} (${options.host})`,
|
|
104
|
+
fields: ['host', 'container', 'stats', 'logs'],
|
|
105
|
+
labels: { host: 'Host', container: 'Container', stats: 'Stats', logs: 'Logs' },
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
docker
|
|
110
|
+
.command('logs')
|
|
111
|
+
.description('Show container logs')
|
|
112
|
+
.argument('<id>', 'container ID')
|
|
113
|
+
.requiredOption('--host <hostId>', 'host the container lives on')
|
|
114
|
+
.option('--tail <n>', 'number of lines', '100')
|
|
115
|
+
.action(async (id, options) => {
|
|
116
|
+
auth.requireAuth();
|
|
117
|
+
const ep = resolveEndpoint('mon.docker.containers.logs', { id });
|
|
118
|
+
const data = await client.get(ep.path, { service: ep.service, query: { host: options.host, tail: options.tail } });
|
|
119
|
+
if (program.opts().output === 'json') {
|
|
120
|
+
console.log(JSON.stringify(data, null, 2));
|
|
121
|
+
} else {
|
|
122
|
+
console.log(data.logs || '');
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
docker
|
|
127
|
+
.command('stats')
|
|
128
|
+
.description('Show container resource stats')
|
|
129
|
+
.argument('<id>', 'container ID')
|
|
130
|
+
.requiredOption('--host <hostId>', 'host the container lives on')
|
|
131
|
+
.action(async (id, options) => {
|
|
132
|
+
auth.requireAuth();
|
|
133
|
+
const ep = resolveEndpoint('mon.docker.containers.stats', { id });
|
|
134
|
+
const data = await client.get(ep.path, { service: ep.service, query: { host: options.host } });
|
|
135
|
+
formatDetail(data.stats || data, { format: program.opts().output, title: `Stats: ${id} (${options.host})` });
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
docker
|
|
139
|
+
.command('stacks')
|
|
140
|
+
.description('List Docker Compose stacks')
|
|
141
|
+
.option('--host <hostId>', 'filter to a single host')
|
|
142
|
+
.action(async (options) => {
|
|
143
|
+
auth.requireAuth();
|
|
144
|
+
const ep = resolveEndpoint('mon.docker.stacks.list');
|
|
145
|
+
const data = await client.get(ep.path, { service: ep.service, query: options.host ? { host: options.host } : undefined });
|
|
146
|
+
formatOutput({
|
|
147
|
+
columns: ['host_id', 'name', 'status', 'services'],
|
|
148
|
+
headers: ['Host', 'Stack', 'Status', 'Services'],
|
|
149
|
+
rows: data.stacks || [],
|
|
150
|
+
}, { format: program.opts().output, emptyMessage: 'No stacks found.' });
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
docker
|
|
154
|
+
.command('df')
|
|
155
|
+
.description('Show Docker disk usage on a host')
|
|
156
|
+
.requiredOption('--host <hostId>', 'host to query')
|
|
157
|
+
.action(async (options) => {
|
|
158
|
+
auth.requireAuth();
|
|
159
|
+
const ep = resolveEndpoint('mon.docker.system-df.get');
|
|
160
|
+
const data = await client.get(ep.path, { service: ep.service, query: { host: options.host } });
|
|
161
|
+
formatDetail(data, { format: program.opts().output, title: `Docker disk usage: ${options.host}` });
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
docker
|
|
165
|
+
.command('action')
|
|
166
|
+
.description('Start/stop/restart a container (destructive)')
|
|
167
|
+
.argument('<id>', 'container ID')
|
|
168
|
+
.argument('<action>', 'start|stop|restart')
|
|
169
|
+
.requiredOption('--host <hostId>', 'host the container lives on')
|
|
170
|
+
.option('--force', 'skip confirmation')
|
|
171
|
+
.action(async (id, action, options) => {
|
|
172
|
+
auth.requireAuth();
|
|
173
|
+
if (!['start', 'stop', 'restart'].includes(action)) {
|
|
174
|
+
throw new Error(`Invalid action: ${action}. Must be one of: start, stop, restart.`);
|
|
175
|
+
}
|
|
176
|
+
const confirmed = await confirmOrSkip(program, options, `${action} container ${id} on ${options.host}?`);
|
|
177
|
+
if (!confirmed) {
|
|
178
|
+
printInfo('Cancelled.');
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
const ep = resolveEndpoint('mon.docker.containers.action', { id, action });
|
|
182
|
+
const data = await client.post(ep.path, { service: ep.service, body: { host: options.host } });
|
|
183
|
+
if (data.success === false) {
|
|
184
|
+
printWarning(`Action failed: ${data.error || 'unknown error'}`);
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
printSuccess(`Container ${id}: ${action} succeeded.`);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
191
|
+
// wazuh
|
|
192
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
193
|
+
|
|
194
|
+
const wazuh = mon
|
|
195
|
+
.command('wazuh')
|
|
196
|
+
.description('Wazuh security monitoring: manager status, agents, alerts, vulnerabilities');
|
|
197
|
+
|
|
198
|
+
wazuh
|
|
199
|
+
.command('status')
|
|
200
|
+
.description('Show Wazuh manager status')
|
|
201
|
+
.action(async () => {
|
|
202
|
+
auth.requireAuth();
|
|
203
|
+
const ep = resolveEndpoint('mon.wazuh.manager-status.get');
|
|
204
|
+
const data = await client.get(ep.path, { service: ep.service });
|
|
205
|
+
formatDetail(data.data || data, { format: program.opts().output, title: 'Wazuh Manager Status' });
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
wazuh
|
|
209
|
+
.command('agents')
|
|
210
|
+
.description('List Wazuh agents')
|
|
211
|
+
.option('--status <status>', 'filter by agent status')
|
|
212
|
+
.option('--limit <n>', 'max results')
|
|
213
|
+
.option('--offset <n>', 'result offset')
|
|
214
|
+
.option('--q <query>', 'query string passed through to the Wazuh API')
|
|
215
|
+
.action(async (options) => {
|
|
216
|
+
auth.requireAuth();
|
|
217
|
+
const ep = resolveEndpoint('mon.wazuh.agents.list');
|
|
218
|
+
const query = {};
|
|
219
|
+
if (options.status) query.status = options.status;
|
|
220
|
+
if (options.limit) query.limit = options.limit;
|
|
221
|
+
if (options.offset) query.offset = options.offset;
|
|
222
|
+
if (options.q) query.q = options.q;
|
|
223
|
+
const data = await client.get(ep.path, { service: ep.service, query });
|
|
224
|
+
const agents = data.data?.data?.affected_items || data.data?.affected_items || data.data || [];
|
|
225
|
+
formatOutput({
|
|
226
|
+
columns: ['id', 'name', 'ip', 'status', 'version'],
|
|
227
|
+
headers: ['ID', 'Name', 'IP', 'Status', 'Version'],
|
|
228
|
+
rows: Array.isArray(agents) ? agents : [],
|
|
229
|
+
}, { format: program.opts().output, emptyMessage: 'No Wazuh agents found.' });
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
wazuh
|
|
233
|
+
.command('alerts')
|
|
234
|
+
.description('Query recent Wazuh alerts')
|
|
235
|
+
.option('--q <query>', 'search text (rule description / agent name / rule groups)')
|
|
236
|
+
.option('--limit <n>', 'max results (default 50, capped at 500)')
|
|
237
|
+
.action(async (options) => {
|
|
238
|
+
auth.requireAuth();
|
|
239
|
+
const ep = resolveEndpoint('mon.wazuh.alerts.list');
|
|
240
|
+
const query = {};
|
|
241
|
+
if (options.q) query.q = options.q;
|
|
242
|
+
if (options.limit) query.limit = options.limit;
|
|
243
|
+
const data = await client.get(ep.path, { service: ep.service, query });
|
|
244
|
+
formatOutput({
|
|
245
|
+
columns: ['timestamp', 'rule', 'agent', 'level'],
|
|
246
|
+
headers: ['Time', 'Rule', 'Agent', 'Level'],
|
|
247
|
+
rows: (data.data || []).map(a => ({
|
|
248
|
+
timestamp: a.timestamp,
|
|
249
|
+
rule: a.rule?.description || '',
|
|
250
|
+
agent: a.agent?.name || '',
|
|
251
|
+
level: a.rule?.level ?? '',
|
|
252
|
+
})),
|
|
253
|
+
}, { format: program.opts().output, emptyMessage: 'No alerts found.' });
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
wazuh
|
|
257
|
+
.command('vulns')
|
|
258
|
+
.description('Query current vulnerability states')
|
|
259
|
+
.option('--agent-id <id>', 'filter by agent ID')
|
|
260
|
+
.option('--agent-name <name>', 'filter by agent name')
|
|
261
|
+
.option('--severity <severity>', 'filter by severity')
|
|
262
|
+
.option('--limit <n>', 'max results (default 100, capped at 1000)')
|
|
263
|
+
.action(async (options) => {
|
|
264
|
+
auth.requireAuth();
|
|
265
|
+
const ep = resolveEndpoint('mon.wazuh.vulnerabilities.list');
|
|
266
|
+
const query = {};
|
|
267
|
+
if (options.agentId) query.agent_id = options.agentId;
|
|
268
|
+
if (options.agentName) query.agent_name = options.agentName;
|
|
269
|
+
if (options.severity) query.severity = options.severity;
|
|
270
|
+
if (options.limit) query.limit = options.limit;
|
|
271
|
+
const data = await client.get(ep.path, { service: ep.service, query });
|
|
272
|
+
formatOutput({
|
|
273
|
+
columns: ['agent', 'cve', 'severity', 'score'],
|
|
274
|
+
headers: ['Agent', 'CVE', 'Severity', 'Score'],
|
|
275
|
+
rows: (data.data || []).map(v => ({
|
|
276
|
+
agent: v.agent?.name || '',
|
|
277
|
+
cve: v.vulnerability?.id || '',
|
|
278
|
+
severity: v.vulnerability?.severity || '',
|
|
279
|
+
score: v.vulnerability?.score?.base ?? '',
|
|
280
|
+
})),
|
|
281
|
+
}, { format: program.opts().output, emptyMessage: 'No vulnerabilities found.' });
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
wazuh
|
|
285
|
+
.command('deploy-history')
|
|
286
|
+
.description('Show Wazuh agent deploy history')
|
|
287
|
+
.option('--mode <mode>', 'latest|all', 'latest')
|
|
288
|
+
.option('--limit <n>', 'max results (mode=all only)', '200')
|
|
289
|
+
.action(async (options) => {
|
|
290
|
+
auth.requireAuth();
|
|
291
|
+
const ep = resolveEndpoint('mon.wazuh.deploy-history.list');
|
|
292
|
+
const data = await client.get(ep.path, { service: ep.service, query: { mode: options.mode, limit: options.limit } });
|
|
293
|
+
formatOutput({
|
|
294
|
+
// fleet #385 class sweep: the route returns
|
|
295
|
+
// { hostname, ip, ssh_user, success, message, deployed_at }, but this
|
|
296
|
+
// declared hostIp/status/deployedAt — three of four columns rendered
|
|
297
|
+
// as dashes with no error. `success` is a boolean, so it's mapped to
|
|
298
|
+
// a readable status rather than printed raw.
|
|
299
|
+
columns: ['hostname', 'ip', 'status', 'deployed_at'],
|
|
300
|
+
headers: ['Host', 'IP', 'Status', 'Deployed'],
|
|
301
|
+
rows: (data.deploys || []).map(d => ({
|
|
302
|
+
...d,
|
|
303
|
+
status: d.success ? 'ok' : (d.message ? `failed: ${String(d.message).slice(0, 40)}` : 'failed'),
|
|
304
|
+
})),
|
|
305
|
+
}, { format: program.opts().output, emptyMessage: 'No deploy history found.' });
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
wazuh
|
|
309
|
+
.command('deploy-agent')
|
|
310
|
+
.description('Queue a Wazuh agent deploy for one host (HITL-gated — requires HITL Inbox approval)')
|
|
311
|
+
.requiredOption('--hostname <hostname>', 'target hostname')
|
|
312
|
+
.requiredOption('--host-ip <ip>', 'target host IP')
|
|
313
|
+
.option('--wazuh-server <ip>', 'Wazuh manager IP (defaults to server config)')
|
|
314
|
+
.option('--bastion-host <ip>', 'bastion host for the SSH hop')
|
|
315
|
+
.option('--ssh-user <user>', 'SSH user', 'control')
|
|
316
|
+
.option('--ssh-port <n>', 'SSH port', '22')
|
|
317
|
+
.option('--force', 'skip confirmation')
|
|
318
|
+
.action(async (options) => {
|
|
319
|
+
auth.requireAuth();
|
|
320
|
+
const confirmed = await confirmOrSkip(program, options, `Queue Wazuh agent deploy to ${options.hostname} (${options.hostIp})? This will require HITL Inbox approval before it runs.`);
|
|
321
|
+
if (!confirmed) {
|
|
322
|
+
printInfo('Cancelled.');
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
const body = { hostname: options.hostname, hostIp: options.hostIp, sshUser: options.sshUser, sshPort: options.sshPort };
|
|
326
|
+
if (options.wazuhServer) body.wazuhServer = options.wazuhServer;
|
|
327
|
+
if (options.bastionHost) body.bastionHost = options.bastionHost;
|
|
328
|
+
const ep = resolveEndpoint('mon.wazuh.deploy-agent');
|
|
329
|
+
const data = await client.post(ep.path, { service: ep.service, body });
|
|
330
|
+
printSuccess(`Deploy queued — inbox item ${data.inbox_item_id} (awaiting approval).`);
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
wazuh
|
|
334
|
+
.command('deploy-bulk')
|
|
335
|
+
.description('Queue a Wazuh agent deploy for multiple hosts (HITL-gated — requires HITL Inbox approval)')
|
|
336
|
+
.requiredOption('--hosts <json>', 'JSON array of {hostname, ip} objects')
|
|
337
|
+
.option('--wazuh-server <ip>', 'Wazuh manager IP (defaults to server config)')
|
|
338
|
+
.option('--bastion-host <ip>', 'bastion host for the SSH hop')
|
|
339
|
+
.option('--force', 'skip confirmation')
|
|
340
|
+
.action(async (options) => {
|
|
341
|
+
auth.requireAuth();
|
|
342
|
+
const hosts = parseJSON(options.hosts, '--hosts');
|
|
343
|
+
if (!Array.isArray(hosts) || hosts.length === 0) {
|
|
344
|
+
throw new Error('--hosts must be a non-empty JSON array of {hostname, ip} objects.');
|
|
345
|
+
}
|
|
346
|
+
const confirmed = await confirmOrSkip(program, options, `Queue Wazuh agent deploy to ${hosts.length} host(s)? This will require HITL Inbox approval before it runs.`);
|
|
347
|
+
if (!confirmed) {
|
|
348
|
+
printInfo('Cancelled.');
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
const body = { hosts };
|
|
352
|
+
if (options.wazuhServer) body.wazuhServer = options.wazuhServer;
|
|
353
|
+
if (options.bastionHost) body.bastionHost = options.bastionHost;
|
|
354
|
+
const ep = resolveEndpoint('mon.wazuh.deploy-bulk');
|
|
355
|
+
const data = await client.post(ep.path, { service: ep.service, body });
|
|
356
|
+
printSuccess(`Bulk deploy queued for ${data.host_count ?? hosts.length} host(s) — inbox item ${data.inbox_item_id} (awaiting approval).`);
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
wazuh
|
|
360
|
+
.command('active-response')
|
|
361
|
+
.description('Trigger a Wazuh active response (HITL-gated — requires HITL Inbox approval)')
|
|
362
|
+
.requiredOption('--command <command>', 'firewall-block|host-isolate|restart-wazuh-agent')
|
|
363
|
+
.requiredOption('--agents <ids>', 'comma-separated Wazuh agent IDs')
|
|
364
|
+
.requiredOption('--reason <text>', 'justification (min 10 characters)')
|
|
365
|
+
.option('--args <args>', 'comma-separated command arguments')
|
|
366
|
+
.option('--force', 'skip confirmation')
|
|
367
|
+
.action(async (options) => {
|
|
368
|
+
auth.requireAuth();
|
|
369
|
+
const agentList = options.agents.split(',').map(a => a.trim()).filter(Boolean);
|
|
370
|
+
const args = options.args ? options.args.split(',').map(a => a.trim()).filter(Boolean) : [];
|
|
371
|
+
const confirmed = await confirmOrSkip(program, options, `Trigger active response "${options.command}" on ${agentList.length} agent(s)? This will require HITL Inbox approval before it executes.`);
|
|
372
|
+
if (!confirmed) {
|
|
373
|
+
printInfo('Cancelled.');
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
const ep = resolveEndpoint('mon.wazuh.active-response');
|
|
377
|
+
const data = await client.post(ep.path, { service: ep.service, body: { command: options.command, agentList, args, reason: options.reason } });
|
|
378
|
+
printSuccess(`Active response queued — inbox item ${data.inbox_item_id} (awaiting approval).`);
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
382
|
+
// agh-chain
|
|
383
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
384
|
+
|
|
385
|
+
const aghChain = mon
|
|
386
|
+
.command('agh-chain')
|
|
387
|
+
.description('AGH-chain: per-agent honesty leaderboard (org-scoped)');
|
|
388
|
+
|
|
389
|
+
aghChain
|
|
390
|
+
.command('leaderboard')
|
|
391
|
+
.description('Show the org honesty leaderboard')
|
|
392
|
+
.option('--window <window>', '24h|7d|30d', '24h')
|
|
393
|
+
.action(async (options) => {
|
|
394
|
+
auth.requireAuth();
|
|
395
|
+
const ep = resolveEndpoint('mon.agh-chain.leaderboard.list');
|
|
396
|
+
const data = await client.get(ep.path, { service: ep.service, query: { window: options.window } });
|
|
397
|
+
formatOutput({
|
|
398
|
+
columns: ['agent_id', 'claims_made', 'claims_verified', 'claims_stripped', 'unfulfilled_promises', 'honesty_rate'],
|
|
399
|
+
headers: ['Agent', 'Claims Made', 'Verified', 'Stripped', 'Unfulfilled', 'Honesty Rate'],
|
|
400
|
+
rows: data.agents || [],
|
|
401
|
+
}, { format: program.opts().output, emptyMessage: 'No leaderboard data found.' });
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
aghChain
|
|
405
|
+
.command('agent')
|
|
406
|
+
.description('Show honesty metrics for a single agent')
|
|
407
|
+
.argument('<agentId>', 'agent ID')
|
|
408
|
+
.option('--window <window>', '24h|7d|30d', '24h')
|
|
409
|
+
.action(async (agentId, options) => {
|
|
410
|
+
auth.requireAuth();
|
|
411
|
+
const ep = resolveEndpoint('mon.agh-chain.leaderboard.get', { agentId });
|
|
412
|
+
const data = await client.get(ep.path, { service: ep.service, query: { window: options.window } });
|
|
413
|
+
formatDetail(data, { format: program.opts().output, title: `Honesty metrics: ${agentId}` });
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
aghChain
|
|
417
|
+
.command('events')
|
|
418
|
+
.description('Show drill-through events for a guard kind')
|
|
419
|
+
.argument('<agentId>', 'agent ID')
|
|
420
|
+
.option('--window <window>', '24h|7d|30d', '24h')
|
|
421
|
+
.option('--guard <kind>', 'guard kind', 'claims_stripped')
|
|
422
|
+
.action(async (agentId, options) => {
|
|
423
|
+
auth.requireAuth();
|
|
424
|
+
const ep = resolveEndpoint('mon.agh-chain.leaderboard.events.list', { agentId });
|
|
425
|
+
const data = await client.get(ep.path, { service: ep.service, query: { window: options.window, guard: options.guard } });
|
|
426
|
+
formatOutput({
|
|
427
|
+
columns: ['event_id', 'run_id', 'ts', 'payload_preview'],
|
|
428
|
+
headers: ['Event', 'Run', 'Time', 'Preview'],
|
|
429
|
+
rows: data.events || [],
|
|
430
|
+
}, { format: program.opts().output, emptyMessage: 'No drill-through events found.' });
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
434
|
+
// metrics
|
|
435
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
436
|
+
|
|
437
|
+
mon
|
|
438
|
+
.command('metrics')
|
|
439
|
+
.description('Show Prometheus scrape metrics (collector fleet gauges)')
|
|
440
|
+
.option('--raw', 'print the raw Prometheus exposition text')
|
|
441
|
+
.action(async (options) => {
|
|
442
|
+
auth.requireAuth();
|
|
443
|
+
const ep = resolveEndpoint('mon.metrics.get');
|
|
444
|
+
const data = await client.get(ep.path, { service: ep.service });
|
|
445
|
+
const text = data.text || '';
|
|
446
|
+
|
|
447
|
+
if (options.raw || program.opts().output === 'json') {
|
|
448
|
+
if (program.opts().output === 'json') {
|
|
449
|
+
console.log(JSON.stringify({ text }, null, 2));
|
|
450
|
+
} else {
|
|
451
|
+
console.log(text);
|
|
452
|
+
}
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
const rows = text
|
|
457
|
+
.split('\n')
|
|
458
|
+
.filter(line => line && !line.startsWith('#'))
|
|
459
|
+
.map(line => {
|
|
460
|
+
const idx = line.lastIndexOf(' ');
|
|
461
|
+
if (idx === -1) return null;
|
|
462
|
+
return { metric: line.slice(0, idx), value: line.slice(idx + 1) };
|
|
463
|
+
})
|
|
464
|
+
.filter(Boolean);
|
|
465
|
+
|
|
466
|
+
formatOutput({
|
|
467
|
+
columns: ['metric', 'value'],
|
|
468
|
+
headers: ['Metric', 'Value'],
|
|
469
|
+
rows,
|
|
470
|
+
}, { format: program.opts().output, emptyMessage: 'No metrics returned.' });
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
474
|
+
// config
|
|
475
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
476
|
+
|
|
477
|
+
const config = mon
|
|
478
|
+
.command('config')
|
|
479
|
+
.description('Monitoring service config (Prometheus/Uptime Kuma/Proxmox/Grafana)');
|
|
480
|
+
|
|
481
|
+
config
|
|
482
|
+
.command('get')
|
|
483
|
+
.description('Show configured monitoring service URLs')
|
|
484
|
+
.action(async () => {
|
|
485
|
+
const ep = resolveEndpoint('mon.config.get');
|
|
486
|
+
const data = await client.get(ep.path, { service: ep.service });
|
|
487
|
+
formatDetail(data.services || data, { format: program.opts().output, title: 'Monitoring Config' });
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
config
|
|
491
|
+
.command('cache')
|
|
492
|
+
.description('Show the last cached scan for a monitoring service')
|
|
493
|
+
.argument('<service>', 'service name, e.g. prometheus|uptime-kuma|proxmox')
|
|
494
|
+
.action(async (service) => {
|
|
495
|
+
auth.requireAuth();
|
|
496
|
+
const ep = resolveEndpoint('mon.config.cache.get', { service });
|
|
497
|
+
const data = await client.get(ep.path, { service: ep.service });
|
|
498
|
+
if (data.cached === false) {
|
|
499
|
+
printInfo(`No cached data for ${service}.`);
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
formatDetail(data, { format: program.opts().output, title: `Monitoring cache: ${service}` });
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
config
|
|
506
|
+
.command('grafana')
|
|
507
|
+
.description('Show the configured Grafana base URL')
|
|
508
|
+
.action(async () => {
|
|
509
|
+
auth.requireAuth();
|
|
510
|
+
const ep = resolveEndpoint('mon.config.grafana.get');
|
|
511
|
+
const data = await client.get(ep.path, { service: ep.service });
|
|
512
|
+
formatDetail(data, { format: program.opts().output, title: 'Grafana Config' });
|
|
513
|
+
});
|
|
514
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Monitoring commands: platform health and governance incidents.
|
|
3
|
+
*
|
|
4
|
+
* `quox status` (lib/commands/status.js) and `quox service health`
|
|
5
|
+
* (lib/commands/service.js) already cover aggregate platform health and
|
|
6
|
+
* per-service reachability — this group focuses on the gap: governance
|
|
7
|
+
* incidents, which had no CLI surface at all.
|
|
8
|
+
*
|
|
9
|
+
* Endpoints wired (services/collector/server.js):
|
|
10
|
+
* GET /api/v1/health/aggregate — same endpoint `quox status` uses
|
|
11
|
+
* GET /governance/incidents — list
|
|
12
|
+
* GET /governance/incidents/:id — detail + linked AEE envelopes
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { formatOutput, formatDetail } from '../core/output.js';
|
|
16
|
+
import { resolveEndpoint } from '../api/endpoints.js';
|
|
17
|
+
|
|
18
|
+
export function registerMonitoringCommands(program, ctx) {
|
|
19
|
+
const { client, auth } = ctx;
|
|
20
|
+
|
|
21
|
+
const monitoring = program
|
|
22
|
+
.command('monitoring')
|
|
23
|
+
.description('Platform health and governance incidents');
|
|
24
|
+
|
|
25
|
+
// GET /api/v1/health/aggregate — see `quox status` for the human-readable
|
|
26
|
+
// pulse view; this subcommand exists for scripting/discoverability under
|
|
27
|
+
// the monitoring namespace.
|
|
28
|
+
monitoring
|
|
29
|
+
.command('health')
|
|
30
|
+
.description('Show aggregate platform health (services, fleet, sessions)')
|
|
31
|
+
.action(async () => {
|
|
32
|
+
// fleet #602: dropped noAuth: true, which suppressed the Authorization
|
|
33
|
+
// header and 401'd on an authenticated collector (the same bug fixed in
|
|
34
|
+
// `quox status`). Logged-out still works (client sends no header when
|
|
35
|
+
// nothing is stored); logged-in now actually sends the token.
|
|
36
|
+
const data = await client.get(resolveEndpoint('v1.health.aggregate.list').path, {
|
|
37
|
+
service: resolveEndpoint('v1.health.aggregate.list').service,
|
|
38
|
+
timeout: 5000,
|
|
39
|
+
});
|
|
40
|
+
formatDetail(data, { format: program.opts().output, title: 'Platform Health' });
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const incidents = monitoring
|
|
44
|
+
.command('incidents')
|
|
45
|
+
.description('Governance incidents');
|
|
46
|
+
|
|
47
|
+
incidents
|
|
48
|
+
.command('list')
|
|
49
|
+
.description('List governance incidents')
|
|
50
|
+
.option('--severity <severity>', 'filter by severity')
|
|
51
|
+
.option('--status <status>', 'filter by status')
|
|
52
|
+
.option('--type <type>', 'filter by incident type')
|
|
53
|
+
.option('--agent <id>', 'filter by agent ID')
|
|
54
|
+
.option('--corr <id>', 'filter by correlation ID')
|
|
55
|
+
.option('--since <date>', 'start date (ISO)')
|
|
56
|
+
.option('--until <date>', 'end date (ISO)')
|
|
57
|
+
.option('--limit <n>', 'max results', '100')
|
|
58
|
+
.action(async (options) => {
|
|
59
|
+
auth.requireAuth();
|
|
60
|
+
const query = { limit: options.limit };
|
|
61
|
+
if (options.severity) query.severity = options.severity;
|
|
62
|
+
if (options.status) query.status = options.status;
|
|
63
|
+
if (options.type) query.incident_type = options.type;
|
|
64
|
+
if (options.agent) query.agent_id = options.agent;
|
|
65
|
+
if (options.corr) query.corr = options.corr;
|
|
66
|
+
if (options.since) query.since = options.since;
|
|
67
|
+
if (options.until) query.until = options.until;
|
|
68
|
+
|
|
69
|
+
const data = await client.get(resolveEndpoint('governance.incidents.list').path, { service: resolveEndpoint('governance.incidents.list').service, query });
|
|
70
|
+
formatOutput({
|
|
71
|
+
columns: ['id', 'severity', 'status', 'incident_type', 'agent_id', 'created_at'],
|
|
72
|
+
headers: ['ID', 'Severity', 'Status', 'Type', 'Agent', 'Created'],
|
|
73
|
+
rows: data.data || data.incidents || data,
|
|
74
|
+
}, { format: program.opts().output, emptyMessage: 'No incidents found.' });
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
incidents
|
|
78
|
+
.command('get')
|
|
79
|
+
.description('Show incident details and linked AEE envelopes')
|
|
80
|
+
.argument('<id>', 'incident ID')
|
|
81
|
+
.action(async (id) => {
|
|
82
|
+
auth.requireAuth();
|
|
83
|
+
const data = await client.get(resolveEndpoint('governance.incidents.get', { id }).path, { service: resolveEndpoint('governance.incidents.get', { id }).service });
|
|
84
|
+
formatDetail(data.incident || data, { format: program.opts().output, title: `Incident: ${id}` });
|
|
85
|
+
});
|
|
86
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* quox motion: play the Quox motion gallery (every Q animation, captioned). No server needed.
|
|
3
|
+
* CLI-QUOX lab parity: the product version of ~/quoxon-work/intro/lab.
|
|
4
|
+
*/
|
|
5
|
+
export function registerMotionCommand(program) {
|
|
6
|
+
program
|
|
7
|
+
.command('motion')
|
|
8
|
+
.description('Play the Quox motion gallery: the Q intro, portal, particle, the Dragon\'s Eye, the status Q, ring and reveals (no server needed)')
|
|
9
|
+
.argument('[scenes...]', 'only these: orbit portal particle eye hello perpetual ring fx')
|
|
10
|
+
.option('--narrow', 'use the 40-column layout (the compact Q)')
|
|
11
|
+
.option('--slow', 'play three times slower')
|
|
12
|
+
.option('--mono', 'no colour (also honoured: NO_COLOR)')
|
|
13
|
+
.action(async (scenes, opts) => {
|
|
14
|
+
const { runGallery } = await import('../quoxon/gallery.js');
|
|
15
|
+
try {
|
|
16
|
+
await runGallery({ only: scenes || [], narrow: Boolean(opts.narrow), slow: Boolean(opts.slow), mono: Boolean(opts.mono) });
|
|
17
|
+
} catch (e) {
|
|
18
|
+
console.error(e.message);
|
|
19
|
+
process.exitCode = 2;
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
}
|