quoxcli 0.3.1 → 0.5.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/CHANGELOG.md +61 -0
- package/README.md +2 -2
- package/bin/quox.js +19 -1
- package/bin/quoxon.js +6 -1
- package/lib/api/endpoints/actions.js +15 -0
- package/lib/api/endpoints/admin.js +27 -0
- package/lib/api/endpoints/agent-attach.js +7 -0
- package/lib/api/endpoints/blog.js +16 -0
- package/lib/api/endpoints/chat.js +7 -0
- package/lib/api/endpoints/compliance.js +25 -0
- package/lib/api/endpoints/contactsq.js +4 -0
- package/lib/api/endpoints/devServerRegistry.js +25 -0
- package/lib/api/endpoints/engagement.js +26 -0
- package/lib/api/endpoints/evidence.js +20 -0
- package/lib/api/endpoints/hat.js +14 -0
- package/lib/api/endpoints/inbox.js +6 -0
- package/lib/api/endpoints/inboxq.js +19 -0
- package/lib/api/endpoints/index.js +14 -0
- package/lib/api/endpoints/loops.js +26 -0
- package/lib/api/endpoints/mon.js +295 -0
- package/lib/api/endpoints/objectives.js +6 -0
- package/lib/api/endpoints/quoxseo.js +6 -0
- package/lib/api/endpoints/server.js +21 -0
- package/lib/api/endpoints/setup.js +6 -0
- package/lib/api/endpoints/tools.js +13 -0
- package/lib/api/endpoints/v1.js +47 -0
- package/lib/commands/actions.js +81 -11
- package/lib/commands/admin.js +35 -0
- package/lib/commands/agent.js +11 -4
- package/lib/commands/agentAttach.js +61 -7
- package/lib/commands/approvals.js +198 -3
- package/lib/commands/ask.js +4 -2
- package/lib/commands/auth.js +64 -11
- package/lib/commands/backup.js +11 -4
- package/lib/commands/blog.js +110 -0
- package/lib/commands/capabilities.js +3 -2
- package/lib/commands/chat.js +142 -28
- package/lib/commands/chatCompanion.js +27 -0
- package/lib/commands/chatState.js +45 -0
- package/lib/commands/claude.js +7 -33
- package/lib/commands/compliance.js +109 -0
- package/lib/commands/connect.js +69 -0
- package/lib/commands/contacts.js +85 -4
- package/lib/commands/contactsSendAt.js +21 -0
- package/lib/commands/dev.js +6 -8
- package/lib/commands/doctor.js +22 -3
- package/lib/commands/engagement.js +90 -0
- package/lib/commands/evidence.js +155 -0
- package/lib/commands/ext.js +23 -0
- package/lib/commands/fleet.js +25 -0
- package/lib/commands/gripe.js +24 -13
- package/lib/commands/hat.js +76 -0
- package/lib/commands/image.js +68 -0
- package/lib/commands/inbox.js +37 -3
- package/lib/commands/inboxq.js +147 -0
- package/lib/commands/kuma.js +1123 -0
- package/lib/commands/kumaSla.js +174 -0
- package/lib/commands/licence.js +39 -1
- package/lib/commands/licenceLinkView.js +311 -0
- package/lib/commands/linkedin.js +19 -0
- package/lib/commands/loop.js +318 -0
- package/lib/commands/lucid.js +1 -0
- package/lib/commands/mail.js +161 -20
- package/lib/commands/memory.js +68 -4
- package/lib/commands/memorySync.js +158 -0
- package/lib/commands/mon.js +55 -144
- package/lib/commands/notes.js +29 -7
- package/lib/commands/notesSeal.js +259 -0
- package/lib/commands/objective.js +53 -3
- package/lib/commands/offload.js +2 -3
- package/lib/commands/org.js +21 -4
- package/lib/commands/plan.js +17 -0
- package/lib/commands/provider.js +87 -8
- package/lib/commands/qlarity.js +32 -1
- package/lib/commands/quoxseo.js +31 -0
- package/lib/commands/research.js +79 -0
- package/lib/commands/rows.js +9 -0
- package/lib/commands/run.js +20 -2
- package/lib/commands/scope.js +2 -2
- package/lib/commands/server.js +150 -0
- package/lib/commands/setup.js +127 -75
- package/lib/commands/shortcut.js +41 -0
- package/lib/commands/skills.js +24 -5
- package/lib/commands/slot.js +49 -9
- package/lib/commands/status.js +92 -44
- package/lib/commands/tool.js +30 -0
- package/lib/commands/vault.js +6 -3
- package/lib/commands/vm.js +2 -2
- package/lib/commands/walkthrough.js +14 -2
- package/lib/commands/watch.js +11 -2
- package/lib/commands/wazuh.js +824 -0
- package/lib/commands/wazuhIntake.js +317 -0
- package/lib/commands/wazuhReport.js +192 -0
- package/lib/commands/workflow.js +47 -4
- package/lib/commands/x.js +50 -0
- package/lib/commands/youtube.js +172 -0
- package/lib/core/actionItems.js +115 -0
- package/lib/core/agentAlias.js +2 -0
- package/lib/core/agentFamilies.js +7 -0
- package/lib/core/announce.js +86 -0
- package/lib/core/auth.js +20 -2
- package/lib/core/callMe.js +118 -0
- package/lib/core/client.js +37 -3
- package/lib/core/commandRegistrars.js +17 -0
- package/lib/core/config.js +29 -3
- package/lib/core/confirm.js +68 -7
- package/lib/core/crossOrgApprovals.js +69 -0
- package/lib/core/errors.js +52 -1
- package/lib/core/findingReconcile.js +90 -0
- package/lib/core/global-flags.js +1 -1
- package/lib/core/governedSession.js +136 -8
- package/lib/core/inboxResolution.js +3 -2
- package/lib/core/liveSessionsReport.js +1 -1
- package/lib/core/loginGuard.js +73 -0
- package/lib/core/loopGlyph.js +97 -0
- package/lib/core/offload.js +73 -7
- package/lib/core/org-override.js +5 -1
- package/lib/core/output.js +8 -8
- package/lib/core/profileTrust.js +182 -0
- package/lib/core/quoxglyph.js +22 -1
- package/lib/core/router.js +14 -0
- package/lib/core/sessionHeartbeat.js +12 -8
- package/lib/core/shortcut.js +83 -0
- package/lib/core/stepUpPrompt.js +99 -0
- package/lib/core/version.js +1 -1
- package/lib/core/vm.js +5 -0
- package/lib/core/walkthrough.js +9 -5
- package/lib/dragon/companionView.js +34 -0
- package/lib/dragon/frames.js +118 -0
- package/lib/dragon/jobs.js +123 -0
- package/lib/dragon/renderWorker.js +15 -0
- package/lib/dragon/select.js +72 -0
- package/lib/evidence/bundleExport.js +47 -0
- package/lib/evidence/bundleVerify.js +77 -0
- package/lib/quoxon/EXTENSIONS.md +146 -0
- package/lib/quoxon/api.js +364 -27
- package/lib/quoxon/app.js +1629 -183
- package/lib/quoxon/approvalBatch.js +117 -0
- package/lib/quoxon/approvalFacts.js +64 -8
- package/lib/quoxon/approvalSentence.js +48 -0
- package/lib/quoxon/approvalsView.js +124 -7
- package/lib/quoxon/autoOpen.js +94 -0
- package/lib/quoxon/backgroundWork.js +59 -0
- package/lib/quoxon/bang.js +1 -1
- package/lib/quoxon/capabilities.js +7 -2
- package/lib/quoxon/commandShort.js +22 -3
- package/lib/quoxon/commands.js +110 -15
- package/lib/quoxon/connect.js +5 -3
- package/lib/quoxon/countdownLine.js +10 -1
- package/lib/quoxon/deck.js +30 -0
- package/lib/quoxon/deckLazy.js +58 -0
- package/lib/quoxon/demo.autoplay.js +1 -1
- package/lib/quoxon/demo.js +3 -1
- package/lib/quoxon/destructive.js +35 -0
- package/lib/quoxon/dividerAocl.js +4 -3
- package/lib/quoxon/dividerRule.js +3 -3
- package/lib/quoxon/dnd.js +71 -0
- package/lib/quoxon/dragonCompanion.js +285 -0
- package/lib/quoxon/dragonModes.js +1 -0
- package/lib/quoxon/dragonMoments.js +160 -0
- package/lib/quoxon/dragonReads.js +68 -0
- package/lib/quoxon/dragonView.js +2 -2
- package/lib/quoxon/echo.js +1 -0
- package/lib/quoxon/events.js +67 -10
- package/lib/quoxon/extensions.js +636 -0
- package/lib/quoxon/gripeQueue.js +135 -0
- package/lib/quoxon/inboxView.js +182 -0
- package/lib/quoxon/installCli.js +79 -0
- package/lib/quoxon/keys.js +4 -4
- package/lib/quoxon/loopCard.js +460 -0
- package/lib/quoxon/loopCards.js +189 -0
- package/lib/quoxon/loopStream.js +98 -0
- package/lib/quoxon/mailView.js +71 -0
- package/lib/quoxon/main.js +29 -5
- package/lib/quoxon/markdown.js +1 -1
- package/lib/quoxon/model.js +15 -0
- package/lib/quoxon/mouse.js +12 -1
- package/lib/quoxon/neverAnswered.js +20 -0
- package/lib/quoxon/objectiveOffer.js +82 -0
- package/lib/quoxon/objects/away.js +43 -5
- package/lib/quoxon/objects/index.js +22 -4
- package/lib/quoxon/objects/model.js +33 -0
- package/lib/quoxon/objects/proof.js +12 -4
- package/lib/quoxon/onboarding/connectServer.js +162 -0
- package/lib/quoxon/onboarding/copy.js +6 -0
- package/lib/quoxon/onboarding/index.js +5 -43
- package/lib/quoxon/onboarding/probe.js +20 -1
- package/lib/quoxon/onboarding/shortcutOffer.js +28 -0
- package/lib/quoxon/onboarding/view.js +16 -3
- package/lib/quoxon/orgSwitch.js +49 -1
- package/lib/quoxon/planSteps.js +51 -0
- package/lib/quoxon/print.js +8 -3
- package/lib/quoxon/profileSwitch.js +7 -0
- package/lib/quoxon/promptHistory.js +3 -1
- package/lib/quoxon/qdragonFly.js +7 -6
- package/lib/quoxon/qmotion.js +17 -0
- package/lib/quoxon/render.js +187 -41
- package/lib/quoxon/roster.js +36 -17
- package/lib/quoxon/rosterSessions.js +44 -0
- package/lib/quoxon/rows.js +244 -1
- package/lib/quoxon/screenContext.js +1 -1
- package/lib/quoxon/secretShape.js +86 -0
- package/lib/quoxon/serverChip.js +42 -0
- package/lib/quoxon/session.js +10 -0
- package/lib/quoxon/slash/agents.js +2 -1
- package/lib/quoxon/slash/argSources.js +9 -2
- package/lib/quoxon/slash/args.js +4 -2
- package/lib/quoxon/slash/broadcast.js +18 -5
- package/lib/quoxon/slash/broadcastRun.js +13 -2
- package/lib/quoxon/slash/cliCommand.js +42 -1
- package/lib/quoxon/slash/cliLeaves.json +139 -2
- package/lib/quoxon/slash/evidenceCmd.js +34 -0
- package/lib/quoxon/slash/hatWalk.js +1 -1
- package/lib/quoxon/slash/palette.js +23 -2
- package/lib/quoxon/slash/paletteController.js +10 -4
- package/lib/quoxon/slash/qpeCmd.js +35 -0
- package/lib/quoxon/slash/restart.js +31 -4
- package/lib/quoxon/slash/resume.js +18 -1
- package/lib/quoxon/slash/stepUp.js +5 -5
- package/lib/quoxon/slash/stepUpCmds.js +74 -1
- package/lib/quoxon/slash/todoCmd.js +122 -0
- package/lib/quoxon/slashSpinner.js +35 -0
- package/lib/quoxon/slotAttach.js +8 -0
- package/lib/quoxon/slotChat.js +17 -0
- package/lib/quoxon/slotFormat.js +1 -1
- package/lib/quoxon/slotOutbox.js +113 -0
- package/lib/quoxon/suggestCommand.js +34 -3
- package/lib/quoxon/term.js +25 -1
- package/lib/quoxon/toolCards.js +30 -0
- package/lib/quoxon/transcript.js +2 -1
- package/lib/quoxon/welcome.js +1 -1
- package/lib/quoxon/whatsnew.js +1 -1
- package/lib/quoxon/whatsnew.jsonl +81 -0
- package/lib/quoxon/windowOrg.js +11 -1
- package/lib/server/devServers.js +137 -0
- package/lib/server/installFlow.js +168 -0
- package/lib/server/status.js +15 -1
- package/lib/toolOutputShape.js +55 -0
- package/package.json +4 -2
- package/vendor/dragonlab/SOURCE +4 -0
- package/vendor/dragonlab/design/cipher/v1/cipher-master.svg +46 -0
- package/vendor/dragonlab/design/cipher/v2/character.mjs +49 -0
- package/vendor/dragonlab/design/cipher/v2/review.mjs +33 -0
- package/vendor/dragonlab/design/cipher/v2/watch.mjs +56 -0
- package/vendor/dragonlab/design/drago/v1/character.mjs +19 -0
- package/vendor/dragonlab/design/drago/v1/drago-master.svg +56 -0
- package/vendor/dragonlab/design/drago/v2/character.mjs +87 -0
- package/vendor/dragonlab/design/drago/v4/character.mjs +67 -0
- package/vendor/dragonlab/design/drago/v4/profile.mjs +35 -0
- package/vendor/dragonlab/design/drago/v4/review.mjs +33 -0
- package/vendor/dragonlab/design/drago/v4/watch.mjs +56 -0
- package/vendor/dragonlab/design/hyperion/v1/hyperion-master.svg +56 -0
- package/vendor/dragonlab/design/hyperion/v2/character.mjs +79 -0
- package/vendor/dragonlab/design/hyperion/v2/review.mjs +33 -0
- package/vendor/dragonlab/design/hyperion/v2/watch.mjs +56 -0
- package/vendor/dragonlab/design/lucid/recovery/arrival.mjs +22 -0
- package/vendor/dragonlab/design/lucid/recovery/character.mjs +106 -0
- package/vendor/dragonlab/design/lucid/recovery/layout.mjs +50 -0
- package/vendor/dragonlab/design/lucid/recovery/lucid-master.svg +78 -0
- package/vendor/dragonlab/design/lucid/recovery/motion.mjs +42 -0
- package/vendor/dragonlab/design/lucid/recovery/render.mjs +121 -0
- package/vendor/dragonlab/design/lucid/recovery/review.mjs +68 -0
- package/vendor/dragonlab/design/lucid/recovery/verify.mjs +84 -0
- package/vendor/dragonlab/design/lucid/recovery/watch.mjs +56 -0
- package/vendor/dragonlab/design/quoxperticus/v2/quoxperticus-master.svg +59 -0
- package/vendor/dragonlab/design/quoxperticus/v3/character.mjs +43 -0
- package/vendor/dragonlab/design/quoxperticus/v4/character.mjs +57 -0
- package/vendor/dragonlab/design/quoxperticus/v4/review.mjs +35 -0
- package/vendor/dragonlab/design/quoxperticus/v4/watch.mjs +56 -0
- package/vendor/dragonlab/design/seer/v1/seer-master.svg +53 -0
- package/vendor/dragonlab/design/seer/v2/character.mjs +88 -0
- package/vendor/dragonlab/design/seer/v3/character.mjs +33 -0
- package/vendor/dragonlab/design/seer/v4/character.mjs +66 -0
- package/vendor/dragonlab/design/seer/v4/review.mjs +33 -0
- package/vendor/dragonlab/design/seer/v4/watch.mjs +56 -0
- package/vendor/dragonlab/design/stage3/companion-review.mjs +28 -0
- package/vendor/dragonlab/design/stage3/sentinel-art.js +39 -0
- package/vendor/dragonlab/design/stage3/sentinel-braille.js +216 -0
- package/vendor/dragonlab/design/stage3/sentinel-character.js +257 -0
- package/vendor/dragonlab/design/stage3/sentinel-icon.js +103 -0
- package/vendor/dragonlab/design/stage3/sentinel-warden-eyes.js +95 -0
- package/vendor/dragonlab/design/stage3/svg-coverage.js +148 -0
- package/vendor/dragonlab/design/vulcan/v1/character.mjs +34 -0
- package/vendor/dragonlab/design/vulcan/v1/vulcan-master.svg +57 -0
- package/vendor/dragonlab/design/vulcan/v2/character.mjs +39 -0
- package/vendor/dragonlab/design/vulcan/v3/character.mjs +41 -0
- package/vendor/dragonlab/design/vulcan/v3/review.mjs +35 -0
- package/vendor/dragonlab/design/vulcan/v3/watch.mjs +56 -0
- package/vendor/dragonlab/factory/svg-rig-contour.mjs +64 -0
- package/vendor/dragonlab/factory/svg-rig.mjs +63 -0
- package/vendor/dragonlab/integration/companions-v1/characters.mjs +108 -0
- package/vendor/dragonlab/integration/companions-v1/cipher.mjs +2 -0
- package/vendor/dragonlab/integration/companions-v1/companion.mjs +63 -0
- package/vendor/dragonlab/integration/companions-v1/drago.mjs +2 -0
- package/vendor/dragonlab/integration/companions-v1/hyperion.mjs +2 -0
- package/vendor/dragonlab/integration/companions-v1/lucid.mjs +2 -0
- package/vendor/dragonlab/integration/companions-v1/quoxperticus.mjs +2 -0
- package/vendor/dragonlab/integration/companions-v1/seer.mjs +2 -0
- package/vendor/dragonlab/integration/companions-v1/sentinel.mjs +2 -0
- package/vendor/dragonlab/integration/companions-v1/view.mjs +28 -0
- package/vendor/dragonlab/integration/companions-v1/vulcan.mjs +2 -0
- package/vendor/dragonlab/integration/hyperion-v3/activity.mjs +76 -0
- package/vendor/dragonlab/lib/power-effects.js +294 -0
- package/vendor/dragonlab/lib/power-registry.js +74 -0
- package/vendor/dragonlab/vendor/quox-dragon/qart.js +348 -0
- package/vendor/dragonlab/vendor/quox-dragon/qeyes.js +213 -0
package/lib/commands/status.js
CHANGED
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
import chalk from 'chalk';
|
|
11
11
|
import { emitCliEvent } from '../core/aee.js';
|
|
12
12
|
import { resolveEndpoint } from '../api/endpoints.js';
|
|
13
|
+
import { probeService } from './watch.js';
|
|
14
|
+
import { joinServiceUrl } from '../core/config.js';
|
|
13
15
|
|
|
14
16
|
const STATUS_ICON = {
|
|
15
17
|
ok: chalk.green('●'),
|
|
@@ -21,13 +23,66 @@ function statusDot(status) {
|
|
|
21
23
|
return STATUS_ICON[status] || chalk.dim('○');
|
|
22
24
|
}
|
|
23
25
|
|
|
26
|
+
const OK_STATES = new Set(['ok', 'healthy', 'up']);
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Health percentage computed from the same data the screen shows (services plus
|
|
30
|
+
* fleet agents), so it can never say 100% beside "1 degraded, 1 down".
|
|
31
|
+
* Returns null when there is nothing to compute from.
|
|
32
|
+
*/
|
|
33
|
+
export function computeHealth(data) {
|
|
34
|
+
let total = 0;
|
|
35
|
+
let good = 0;
|
|
36
|
+
for (const s of Object.values(data?.services || {})) {
|
|
37
|
+
total += 1;
|
|
38
|
+
if (OK_STATES.has(s?.status)) good += 1;
|
|
39
|
+
}
|
|
40
|
+
const f = data?.fleet;
|
|
41
|
+
if (f && f.total) {
|
|
42
|
+
total += f.total;
|
|
43
|
+
good += Math.min(f.total, f.healthy || 0);
|
|
44
|
+
}
|
|
45
|
+
if (!total) return typeof data?.score === 'number' ? data.score : null;
|
|
46
|
+
return Math.round((100 * good) / total);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Get status without letting one slow source sink the verb: the aggregate and a
|
|
51
|
+
* set of quick direct service probes run in parallel. Aggregate answers -> full
|
|
52
|
+
* result. Aggregate fails but probes answer -> partial result (exit 0). Nothing
|
|
53
|
+
* answers -> error object (exit 1).
|
|
54
|
+
*/
|
|
55
|
+
export async function resolveStatus({ aggregate, probes }) {
|
|
56
|
+
const [agg, prb] = await Promise.allSettled([aggregate(), probes()]);
|
|
57
|
+
const probeMap = prb.status === 'fulfilled' ? prb.value : {};
|
|
58
|
+
if (agg.status === 'fulfilled' && agg.value) {
|
|
59
|
+
return { exitCode: 0, data: { ...agg.value, partial: false, sources: { aggregate: { status: 'ok' } } } };
|
|
60
|
+
}
|
|
61
|
+
const message = agg.reason?.message || 'no answer';
|
|
62
|
+
const sources = {
|
|
63
|
+
aggregate: { status: 'failed', error: message },
|
|
64
|
+
probes: { status: Object.values(probeMap).some((p) => p.status === 'healthy') ? 'ok' : 'failed' },
|
|
65
|
+
};
|
|
66
|
+
const services = {};
|
|
67
|
+
for (const [name, p] of Object.entries(probeMap)) {
|
|
68
|
+
services[name] = { status: p.status === 'healthy' ? 'ok' : p.status === 'down' ? 'down' : 'degraded', latency_ms: p.latency_ms ?? null };
|
|
69
|
+
}
|
|
70
|
+
if (sources.probes.status !== 'ok') {
|
|
71
|
+
return { exitCode: 1, data: { error: 'collector_unavailable', message, sources } };
|
|
72
|
+
}
|
|
73
|
+
return { exitCode: 0, data: { partial: true, degraded: true, score: null, timestamp: new Date().toISOString(), services, sources } };
|
|
74
|
+
}
|
|
75
|
+
|
|
24
76
|
function renderPulse(data) {
|
|
25
77
|
const lines = [];
|
|
26
78
|
|
|
27
79
|
// Header
|
|
28
|
-
const
|
|
80
|
+
const score = computeHealth(data);
|
|
81
|
+
const scoreColor = score >= 90 ? chalk.green : score >= 70 ? chalk.yellow : chalk.red;
|
|
82
|
+
const scoreText = score == null ? chalk.yellow('Health: partial') : scoreColor(`Health: ${score}%`);
|
|
29
83
|
lines.push('');
|
|
30
|
-
lines.push(chalk.bold(` QUOX PLATFORM STATUS`) + chalk.dim(` ${
|
|
84
|
+
lines.push(chalk.bold(` QUOX PLATFORM STATUS`) + chalk.dim(` ${scoreText} ${data.timestamp || ''}`));
|
|
85
|
+
if (data.partial) lines.push(chalk.yellow(` Partial: the full status did not answer in time (${data.sources?.aggregate?.error || 'no answer'}); showing live service checks only.`));
|
|
31
86
|
lines.push(chalk.dim(' ' + '─'.repeat(60)));
|
|
32
87
|
|
|
33
88
|
// Services
|
|
@@ -43,7 +98,7 @@ function renderPulse(data) {
|
|
|
43
98
|
// Sessions
|
|
44
99
|
const s = data.sessions || {};
|
|
45
100
|
const providers = Object.entries(s.providers || {}).map(([k, v]) => `${v} ${k}`).join(', ');
|
|
46
|
-
lines.push(` Sessions ${s.active || 0} active ${providers ? `(${providers})` : ''} ${chalk.dim(`$${(s.cost_today_usd || 0).toFixed(2)} today`)}`);
|
|
101
|
+
lines.push(` Sessions ${s.active || 0} active (usage sessions; "quox active" lists workers) ${providers ? `(${providers})` : ''} ${chalk.dim(`$${(s.cost_today_usd || 0).toFixed(2)} today`)}`);
|
|
47
102
|
|
|
48
103
|
// Workflows
|
|
49
104
|
const w = data.workflows || {};
|
|
@@ -78,60 +133,53 @@ function renderPulse(data) {
|
|
|
78
133
|
}
|
|
79
134
|
|
|
80
135
|
export function registerStatusCommand(program, ctx) {
|
|
81
|
-
const { client } = ctx;
|
|
136
|
+
const { client, config } = ctx;
|
|
82
137
|
|
|
83
138
|
program
|
|
84
139
|
.command('status')
|
|
85
140
|
.description('Show platform health and status overview')
|
|
141
|
+
.addHelpText('after', '\nAdd --json for machine output. If the full status is slow, a partial answer (live service checks, partial: true) is returned with exit 0; only a total failure exits 1 with an error object.')
|
|
86
142
|
.action(async (opts, cmd) => {
|
|
87
143
|
const outputFormat = cmd.parent?.opts()?.output || 'table';
|
|
88
144
|
const isJson = outputFormat === 'json' || cmd.parent?.opts()?.json;
|
|
89
145
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
//
|
|
94
|
-
//
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
146
|
+
const ep = resolveEndpoint('v1.health.aggregate.list');
|
|
147
|
+
const names = ['auth', 'memory', 'collector', 'quoxflow', 'files'];
|
|
148
|
+
const { exitCode, data } = await resolveStatus({
|
|
149
|
+
// fleet #602: the normal client auth applies (no noAuth). #930: the aggregate fans out to
|
|
150
|
+
// several services; it gets 8s here because direct probes cover a slow answer.
|
|
151
|
+
aggregate: () => client.get(ep.path, { service: ep.service, timeout: 8000 }),
|
|
152
|
+
probes: async () => {
|
|
153
|
+
const out = await Promise.all(names.map(async (n) => {
|
|
154
|
+
try {
|
|
155
|
+
return [n, await probeService(joinServiceUrl(config.getServiceUrl(n), '/health').href, fetch, 3000)];
|
|
156
|
+
} catch (err) {
|
|
157
|
+
return [n, { status: 'down', reason: err.message }];
|
|
158
|
+
}
|
|
159
|
+
}));
|
|
160
|
+
return Object.fromEntries(out);
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
if (isJson) {
|
|
165
|
+
console.log(JSON.stringify(data, null, 2));
|
|
166
|
+
} else if (data.error) {
|
|
167
|
+
console.log('');
|
|
168
|
+
console.log(chalk.red(' Platform status unavailable'));
|
|
169
|
+
console.log(chalk.dim(` Collector not reachable: ${data.message}`));
|
|
170
|
+
console.log('');
|
|
171
|
+
} else {
|
|
172
|
+
console.log(renderPulse(data));
|
|
173
|
+
}
|
|
111
174
|
|
|
175
|
+
if (!data.error) {
|
|
112
176
|
emitCliEvent(client, {
|
|
113
177
|
command: 'status',
|
|
114
178
|
intent: 'quox.cli.status',
|
|
115
|
-
payload: { score: data
|
|
179
|
+
payload: { score: computeHealth(data), services: Object.keys(data.services || {}).length, partial: Boolean(data.partial) },
|
|
116
180
|
});
|
|
117
|
-
} catch (err) {
|
|
118
|
-
if (isJson) {
|
|
119
|
-
console.log(JSON.stringify({ error: 'collector_unavailable', message: err.message }));
|
|
120
|
-
} else {
|
|
121
|
-
console.log('');
|
|
122
|
-
console.log(chalk.red(' Platform status unavailable'));
|
|
123
|
-
console.log(chalk.dim(` Collector not reachable: ${err.message}`));
|
|
124
|
-
console.log('');
|
|
125
|
-
}
|
|
126
|
-
// fleet #602: a real request failure (network error, non-2xx from
|
|
127
|
-
// the collector) was printing the message above and still exiting
|
|
128
|
-
// 0, hiding the failure from scripts/CI that check the exit code.
|
|
129
|
-
// client.get only reaches this catch on an actual error (thrown
|
|
130
|
-
// QuoxApiError / QuoxNetworkError / QuoxAuthError, see
|
|
131
|
-
// lib/core/client.js) — never on a successful 2xx — so it's safe to
|
|
132
|
-
// fail the process here without risking a false failure on a
|
|
133
|
-
// legitimately-empty-but-200 status payload.
|
|
134
|
-
process.exitCode = 1;
|
|
135
181
|
}
|
|
182
|
+
// Only a total failure exits non-zero; a partial answer is still an answer.
|
|
183
|
+
if (exitCode) process.exitCode = exitCode;
|
|
136
184
|
});
|
|
137
185
|
}
|
package/lib/commands/tool.js
CHANGED
|
@@ -199,6 +199,8 @@ export function registerToolCommands(program, ctx) {
|
|
|
199
199
|
// success on failure. A HELD (pending) call awaits a human, so it stays 0.
|
|
200
200
|
const failed = failedOf(data);
|
|
201
201
|
if (failed && !(data && data.pending)) process.exitCode = 1;
|
|
202
|
+
// A held call exits 0 (chat relies on it) but must not read as a success: say so on stderr.
|
|
203
|
+
{ const heldId = data && data.pending && (data.approvalId || (data._approval && data._approval.id)); if (heldId) console.error(`HELD, not run: approval ${heldId}`); }
|
|
202
204
|
|
|
203
205
|
if (program.opts().output === 'json') {
|
|
204
206
|
console.log(JSON.stringify(shown, null, 2));
|
|
@@ -225,6 +227,34 @@ export function registerToolCommands(program, ctx) {
|
|
|
225
227
|
});
|
|
226
228
|
printSuccess('Tool installed.');
|
|
227
229
|
});
|
|
230
|
+
|
|
231
|
+
// CLI-COV 2026-11-05: `tool list` shows the org's own tool definitions, never catalog installations, so
|
|
232
|
+
// an install had no CLI read-back and no undo. These read and soft-disable the tool_installations row.
|
|
233
|
+
tool
|
|
234
|
+
.command('installed')
|
|
235
|
+
.description('List catalog tools installed in this organization (what `tool install` added)')
|
|
236
|
+
.action(async () => {
|
|
237
|
+
auth.requireAuth();
|
|
238
|
+
const ep = resolveEndpoint('tools.installed');
|
|
239
|
+
const data = await client.get(ep.path, { service: ep.service });
|
|
240
|
+
formatOutput({
|
|
241
|
+
columns: ['tool_id', 'name', 'category', 'enabled', 'installed_at'],
|
|
242
|
+
headers: ['Tool ID', 'Name', 'Category', 'Enabled', 'Installed'],
|
|
243
|
+
rows: (data && data.installations) || [],
|
|
244
|
+
}, { format: program.opts().output });
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
tool
|
|
248
|
+
.command('uninstall')
|
|
249
|
+
.description('Disable a catalog tool installed with `tool install` (soft: the row is kept, marked disabled)')
|
|
250
|
+
.argument('<toolId>', 'catalog tool_id, as `tool installed` lists it')
|
|
251
|
+
.action(async (toolId) => {
|
|
252
|
+
auth.requireAuth();
|
|
253
|
+
const ep = resolveEndpoint('tools.uninstall-from-scope', { toolId });
|
|
254
|
+
const data = await client.delete(ep.path, { service: ep.service });
|
|
255
|
+
if (data && data.removed) printSuccess(`Tool ${toolId} uninstalled (disabled for this organization).`);
|
|
256
|
+
else console.log(`Tool ${toolId} was not installed in this organization; nothing changed.`);
|
|
257
|
+
});
|
|
228
258
|
}
|
|
229
259
|
|
|
230
260
|
/**
|
package/lib/commands/vault.js
CHANGED
|
@@ -4,11 +4,13 @@
|
|
|
4
4
|
* quox vault <subcommand> [options]
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
import { withStepUp } from '../core/stepUpPrompt.js';
|
|
7
8
|
import chalk from 'chalk';
|
|
8
9
|
import Table from 'cli-table3';
|
|
9
10
|
import ora from 'ora';
|
|
10
11
|
import { formatOutput, formatDetail, printSuccess, printWarning, printInfo } from '../core/output.js';
|
|
11
12
|
import { events, printEvent, line as glyphLine } from '../core/quoxglyph.js';
|
|
13
|
+
import { announce, announceOnFail } from '../core/announce.js';
|
|
12
14
|
import { resolveOrgId } from './helpers.js';
|
|
13
15
|
import { resolveEndpoint } from '../api/endpoints.js';
|
|
14
16
|
|
|
@@ -177,7 +179,7 @@ export function makeVaultGetAction({ client, auth }, program) {
|
|
|
177
179
|
}
|
|
178
180
|
|
|
179
181
|
export function makeVaultCreateAction({ client, auth }, program) {
|
|
180
|
-
return async (options) => {
|
|
182
|
+
return announceOnFail('vault', (options) => `create credential ${options?.label || ''}`.trim(), async (options) => {
|
|
181
183
|
auth.requireAuth();
|
|
182
184
|
const orgId = resolveOrgId(auth, program);
|
|
183
185
|
|
|
@@ -397,9 +399,10 @@ export function makeVaultCreateAction({ client, auth }, program) {
|
|
|
397
399
|
printInfo(`note: not set as default — run "quox vault set-default ${result.id}" to make it the active ${result.integration_type || integrationType} credential`);
|
|
398
400
|
}
|
|
399
401
|
|
|
402
|
+
announce('vault', `credential created ${result.label || ''}`.trim(), { result: `${result.id} · ${result.integration_type || integrationType}` });
|
|
400
403
|
formatDetail(result, { format: program.opts().output });
|
|
401
404
|
return result;
|
|
402
|
-
};
|
|
405
|
+
});
|
|
403
406
|
}
|
|
404
407
|
|
|
405
408
|
export function makeVaultUpdateAction({ client, auth }, program) {
|
|
@@ -1305,7 +1308,7 @@ export function registerVaultCommands(program, ctx) {
|
|
|
1305
1308
|
throw new Error('--duration must be a positive number of minutes.');
|
|
1306
1309
|
}
|
|
1307
1310
|
const ep = resolveEndpoint('vault.leases.approve', { orgId, leaseId });
|
|
1308
|
-
const result = await client.post(ep.path, { service: ep.service, body: { duration_minutes: duration } });
|
|
1311
|
+
const result = await withStepUp((headers) => client.post(ep.path, { service: ep.service, headers, body: { duration_minutes: duration } }), { client, orgId, io: ctx.stepUpIO, what: 'This lease approval' });
|
|
1309
1312
|
printEvent(events.updated('quox', 'credential lease', leaseId, `approved for ${duration}m`));
|
|
1310
1313
|
formatDetail(result, { format: program.opts().output });
|
|
1311
1314
|
});
|
package/lib/commands/vm.js
CHANGED
|
@@ -19,7 +19,7 @@ import { resolveOrgId } from './helpers.js';
|
|
|
19
19
|
import { formatOutput, printInfo, printWarning } from '../core/output.js';
|
|
20
20
|
import {
|
|
21
21
|
VM_ACTIONS, VmPendingError, pendingHint, makeExec, makePoolRest,
|
|
22
|
-
listVms, vmInfo, vmDoctor, doctorLines, vmAction, vmSnapshot, vmRehearse, waitRehearsal, poolStatus, poolLease, poolDone, poolConfig, poolHostKeyLines,
|
|
22
|
+
listVms, vmListRows, vmInfo, vmDoctor, doctorLines, vmAction, vmSnapshot, vmRehearse, waitRehearsal, poolStatus, poolLease, poolDone, poolConfig, poolHostKeyLines,
|
|
23
23
|
readPubkey, textOf, payloadOf,
|
|
24
24
|
} from '../core/vm.js';
|
|
25
25
|
|
|
@@ -92,7 +92,7 @@ export function registerVmCommands(program, ctx) {
|
|
|
92
92
|
const vms = await listVms(exec, { node: options.node, type: options.type });
|
|
93
93
|
if (json()) { console.log(JSON.stringify(vms.map((v) => v.raw), null, 2)); return; }
|
|
94
94
|
formatOutput(
|
|
95
|
-
{ columns: ['vmid', 'name', 'node', 'type', 'status'], rows: vms
|
|
95
|
+
{ columns: ['vmid', 'name', 'node', 'type', 'status', 'tags'], rows: vmListRows(vms) },
|
|
96
96
|
{ emptyMessage: 'No VMs found.' },
|
|
97
97
|
);
|
|
98
98
|
}));
|
|
@@ -3,14 +3,17 @@ import { createInterface } from 'node:readline';
|
|
|
3
3
|
import { resolveEndpoint } from '../api/endpoints.js';
|
|
4
4
|
import { resolveOrgId } from './helpers.js';
|
|
5
5
|
import { runWalkthrough } from '../core/walkthrough.js';
|
|
6
|
+
import { otherOrgsWaiting, crossOrgLines, clientForTarget } from '../core/crossOrgApprovals.js';
|
|
6
7
|
|
|
7
|
-
export function attachWalkthrough(parent, { client, auth }, program) {
|
|
8
|
+
export function attachWalkthrough(parent, { client: baseClient, auth, clientForOrg }, program) {
|
|
8
9
|
parent
|
|
9
10
|
.command('walkthrough')
|
|
10
11
|
.description('Walk through the questions waiting for you one at a time: what it does, why, my advice, then your choice')
|
|
11
12
|
.action(async () => {
|
|
12
13
|
auth.requireAuth();
|
|
13
14
|
const orgId = resolveOrgId(auth, program);
|
|
15
|
+
const sessionOrg = auth.getOrgId();
|
|
16
|
+
const client = await clientForTarget({ client: baseClient, clientForOrg, sessionOrgId: sessionOrg, orgId });
|
|
14
17
|
const lp = resolveEndpoint('inbox.list', { orgId });
|
|
15
18
|
const data = await client.get(lp.path, { service: lp.service, query: { status: 'open', limit: 200 } });
|
|
16
19
|
const items = (data && (data.items || data)) || [];
|
|
@@ -24,9 +27,18 @@ export function attachWalkthrough(parent, { client, auth }, program) {
|
|
|
24
27
|
readLine, write: (l) => console.log(l),
|
|
25
28
|
answer: async (it, key, note) => {
|
|
26
29
|
const ep = resolveEndpoint('inbox.respond', { orgId, itemId: it.id });
|
|
27
|
-
|
|
30
|
+
const comment = note ? { comment: String(note).slice(0, 4000) } : {};
|
|
31
|
+
if (it.__approval) { // a policy-held approval: the same respond path as `quox approvals approve|deny`
|
|
32
|
+
// No PIN prompt here (the walk owns stdin): a server step-up refusal leaves it open, `quox approvals approve <id>` takes the PIN.
|
|
33
|
+
await client.post(ep.path, { service: ep.service, body: { action: key === 'y' ? 'approve' : 'reject', ...comment } });
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
await client.post(ep.path, { service: ep.service, body: { action: 'answer', option: key, ...comment } });
|
|
28
37
|
},
|
|
29
38
|
});
|
|
30
39
|
} finally { rl.close(); }
|
|
40
|
+
// Approvals waiting in the user's OTHER orgs: a count and the command, never the cards.
|
|
41
|
+
const more = crossOrgLines(await otherOrgsWaiting({ client: baseClient, currentOrgId: orgId }), 'walkthrough');
|
|
42
|
+
for (const l of more) console.log(l);
|
|
31
43
|
});
|
|
32
44
|
}
|
package/lib/commands/watch.js
CHANGED
|
@@ -52,6 +52,14 @@ export async function probeService(url, fetchImpl = fetch, timeoutMs = 8000) {
|
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
/** Parse --interval: honour it down to `min`, report when it was raised. */
|
|
56
|
+
export function clampInterval(raw, min, def) {
|
|
57
|
+
const n = parseInt(raw, 10);
|
|
58
|
+
if (!Number.isFinite(n) || n <= 0) return { seconds: def, clamped: false };
|
|
59
|
+
if (n < min) return { seconds: min, clamped: true };
|
|
60
|
+
return { seconds: n, clamped: false };
|
|
61
|
+
}
|
|
62
|
+
|
|
55
63
|
export function registerWatchCommands(program, { client, auth, config }) {
|
|
56
64
|
const watch = program
|
|
57
65
|
.command('watch')
|
|
@@ -126,7 +134,8 @@ export function registerWatchCommands(program, { client, auth, config }) {
|
|
|
126
134
|
.description('Live service health dashboard')
|
|
127
135
|
.option('--interval <seconds>', 'Refresh interval', '10')
|
|
128
136
|
.action(async (opts) => {
|
|
129
|
-
const
|
|
137
|
+
const iv = clampInterval(opts.interval, 1, 10);
|
|
138
|
+
const interval = iv.seconds * 1000;
|
|
130
139
|
const services = [
|
|
131
140
|
{ name: 'auth', port: 3101, path: '/health' },
|
|
132
141
|
{ name: 'collector', port: 9848, path: '/health' },
|
|
@@ -157,7 +166,7 @@ export function registerWatchCommands(program, { client, auth, config }) {
|
|
|
157
166
|
else if (r.status === 'timeout') console.log(` ${chalk.yellow('●')} ${r.name.padEnd(14)} ${chalk.yellow('slow (no answer in ' + Math.round(r.latency_ms / 1000) + 's)')}`);
|
|
158
167
|
else console.log(` ${chalk.red('●')} ${r.name.padEnd(14)} ${chalk.red('down')} ${chalk.dim(r.reason || '')}`);
|
|
159
168
|
}
|
|
160
|
-
console.log(chalk.dim('\n refreshing every ' + (interval / 1000) + 's'));
|
|
169
|
+
console.log(chalk.dim('\n refreshing every ' + (interval / 1000) + 's' + (iv.clamped ? ' (minimum is 1s)' : '')));
|
|
161
170
|
};
|
|
162
171
|
|
|
163
172
|
await tick();
|