quoxcli 0.2.0 → 0.4.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 +1 -1
- package/README.md +2 -0
- package/bin/quox.js +99 -126
- package/lib/api/endpoints/actions.js +15 -0
- package/lib/api/endpoints/admin.js +46 -0
- package/lib/api/endpoints/agent-schedules.js +26 -0
- package/lib/api/endpoints/agents.js +7 -0
- package/lib/api/endpoints/assistants.js +12 -0
- package/lib/api/endpoints/away.js +15 -0
- package/lib/api/endpoints/blog.js +16 -0
- package/lib/api/endpoints/chat.js +14 -0
- package/lib/api/endpoints/contactsq.js +6 -0
- package/lib/api/endpoints/conversations.js +14 -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/executions.js +13 -0
- package/lib/api/endpoints/fetchAllowlist.js +27 -0
- package/lib/api/endpoints/files.js +14 -0
- package/lib/api/endpoints/hat.js +44 -0
- package/lib/api/endpoints/inbox.js +20 -0
- package/lib/api/endpoints/index.js +26 -0
- package/lib/api/endpoints/loops.js +26 -0
- package/lib/api/endpoints/memory.js +19 -0
- package/lib/api/endpoints/mind.js +13 -0
- package/lib/api/endpoints/mon.js +179 -0
- package/lib/api/endpoints/objectives.js +12 -0
- package/lib/api/endpoints/plugins.js +18 -0
- package/lib/api/endpoints/presence.js +33 -0
- package/lib/api/endpoints/quoxplan.js +7 -0
- package/lib/api/endpoints/quoxseo.js +12 -0
- package/lib/api/endpoints/router.js +17 -0
- package/lib/api/endpoints/safety.js +7 -0
- package/lib/api/endpoints/server.js +21 -0
- package/lib/api/endpoints/setup.js +6 -0
- package/lib/api/endpoints/skills.js +20 -0
- package/lib/api/endpoints/stepup.js +20 -0
- package/lib/api/endpoints/teams.js +2 -2
- package/lib/api/endpoints/triggers.js +26 -0
- package/lib/api/endpoints/v1.js +120 -0
- package/lib/api/endpoints/ward.js +7 -0
- package/lib/api/endpoints/workflows.js +7 -0
- package/lib/api/endpoints/x.js +8 -0
- package/lib/commands/actions.js +131 -0
- package/lib/commands/admin.js +16 -0
- package/lib/commands/agent.js +170 -27
- package/lib/commands/agentAttach.js +140 -13
- package/lib/commands/approvals.js +78 -1
- package/lib/commands/aside.js +24 -0
- package/lib/commands/ask.js +146 -0
- package/lib/commands/assistant.js +59 -4
- package/lib/commands/auth.js +8 -2
- package/lib/commands/backup.js +8 -4
- package/lib/commands/blog.js +110 -0
- package/lib/commands/brain.js +105 -14
- package/lib/commands/build.js +19 -5
- package/lib/commands/chat.js +180 -26
- package/lib/commands/chatCompanion.js +27 -0
- package/lib/commands/chatState.js +45 -0
- package/lib/commands/claude.js +35 -38
- package/lib/commands/contacts.js +134 -4
- package/lib/commands/contactsSendAt.js +21 -0
- package/lib/commands/conversation.js +35 -4
- package/lib/commands/dashboard.js +43 -0
- package/lib/commands/department.js +28 -7
- package/lib/commands/devResident.js +15 -3
- package/lib/commands/devSupervisor.js +13 -1
- package/lib/commands/discord.js +75 -39
- package/lib/commands/done.js +98 -28
- package/lib/commands/engagement.js +107 -6
- package/lib/commands/evidence.js +154 -0
- package/lib/commands/ext.js +23 -0
- package/lib/commands/fetchAllowlist.js +71 -0
- package/lib/commands/file.js +38 -1
- package/lib/commands/fleet.js +336 -4
- package/lib/commands/governance.js +12 -3
- package/lib/commands/gripe.js +44 -13
- package/lib/commands/hat.js +254 -9
- package/lib/commands/helpers.js +18 -1
- package/lib/commands/hyperion.js +58 -0
- package/lib/commands/image.js +68 -0
- package/lib/commands/inbox.js +74 -23
- package/lib/commands/kuma.js +1050 -0
- package/lib/commands/kumaSla.js +174 -0
- package/lib/commands/licence.js +52 -155
- package/lib/commands/loop.js +291 -0
- package/lib/commands/lucid.js +10 -4
- package/lib/commands/memory.js +85 -8
- package/lib/commands/mind.js +71 -0
- package/lib/commands/mon.js +69 -2
- package/lib/commands/monitoring.js +1 -1
- package/lib/commands/motion.js +2 -2
- package/lib/commands/nucleus.js +48 -17
- package/lib/commands/objective.js +59 -1
- package/lib/commands/offload.js +89 -0
- package/lib/commands/onboard.js +2 -1
- package/lib/commands/org.js +30 -3
- package/lib/commands/peek.js +28 -0
- package/lib/commands/plan.js +38 -0
- package/lib/commands/plugin.js +110 -0
- package/lib/commands/presence.js +89 -0
- package/lib/commands/provider.js +217 -0
- package/lib/commands/qlarity.js +19 -3
- package/lib/commands/quoxchat.js +13 -4
- package/lib/commands/quoxseo.js +69 -0
- package/lib/commands/reach.js +2 -1
- package/lib/commands/research.js +79 -0
- package/lib/commands/router.js +77 -0
- package/lib/commands/rows.js +367 -0
- package/lib/commands/run.js +82 -10
- package/lib/commands/scope.js +2 -2
- package/lib/commands/server.js +405 -0
- package/lib/commands/setup.js +33 -7
- package/lib/commands/shortcut.js +41 -0
- package/lib/commands/skills.js +68 -9
- package/lib/commands/slot.js +260 -7
- package/lib/commands/status.js +1 -1
- package/lib/commands/team.js +3 -1
- package/lib/commands/tips.js +23 -0
- package/lib/commands/tool.js +133 -45
- package/lib/commands/trace.js +12 -7
- package/lib/commands/trigger.js +95 -0
- package/lib/commands/update.js +46 -0
- package/lib/commands/vault.js +31 -8
- package/lib/commands/vm.js +237 -0
- package/lib/commands/voice.js +34 -3
- package/lib/commands/volt.js +45 -3
- package/lib/commands/walkthrough.js +32 -0
- package/lib/commands/watch.js +73 -21
- package/lib/commands/welcome.js +14 -0
- package/lib/commands/workflow.js +189 -32
- package/lib/commands/x.js +13 -4
- package/lib/commands/youtube.js +172 -0
- package/lib/core/actingAs.js +2 -2
- package/lib/core/actionItems.js +115 -0
- package/lib/core/activeSummary.js +186 -0
- package/lib/core/agentAlias.js +2 -0
- package/lib/core/agentFamilies.js +7 -0
- package/lib/core/aside.js +54 -0
- package/lib/core/askDecision.js +60 -0
- package/lib/core/auth.js +164 -13
- package/lib/core/callerSource.js +1 -1
- package/lib/core/cliBuild.js +62 -0
- package/lib/core/client.js +152 -35
- package/lib/core/commandRegistrars.js +130 -0
- package/lib/core/config.js +143 -25
- package/lib/core/customHats.js +168 -0
- package/lib/core/doneEngineFetch.js +8 -0
- package/lib/core/errors.js +329 -20
- package/lib/core/findingReconcile.js +90 -0
- package/lib/core/global-flags.js +105 -5
- package/lib/core/governedSession.js +1 -3
- package/lib/core/hyperion.js +102 -0
- package/lib/core/inboxResolution.js +65 -0
- package/lib/core/lazyCommands.js +4 -1
- package/lib/core/liveSessionsReport.js +1 -1
- package/lib/core/loopGlyph.js +97 -0
- package/lib/core/offload.js +533 -0
- package/lib/core/org-override.js +1 -1
- package/lib/core/orgResolve.js +70 -0
- package/lib/core/output.js +11 -11
- package/lib/core/peek.js +126 -0
- package/lib/core/profileUsers.js +51 -0
- package/lib/core/promptCancel.js +18 -0
- package/lib/core/repoPath.js +11 -0
- package/lib/core/residentSeed.js +3 -1
- package/lib/core/rootOptions.js +39 -0
- package/lib/core/router.js +168 -0
- package/lib/core/selfUpdate.js +188 -0
- package/lib/core/sessionHeartbeat.js +12 -8
- package/lib/core/sessionsOverview.js +64 -0
- package/lib/core/shortcut.js +83 -0
- package/lib/core/slotKeeper.js +248 -3
- package/lib/core/slotWake.js +120 -0
- package/lib/core/supervisorEngine.js +71 -2
- package/lib/core/version.js +1 -1
- package/lib/core/vm.js +366 -0
- package/lib/core/vmTui.js +86 -0
- package/lib/core/walkthrough.js +35 -0
- 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/hyperion/deck.js +29 -0
- package/lib/lucid/LucidCard.jsx +68 -0
- package/lib/lucid/LucidIntro.jsx +45 -58
- package/lib/lucid/cards.js +86 -0
- package/lib/lucid/markdown.js +81 -0
- package/lib/lucid/motion.js +56 -0
- package/lib/quoxon/EXTENSIONS.md +23 -0
- package/lib/quoxon/activity.js +14 -2
- package/lib/quoxon/aocl.js +117 -0
- package/lib/quoxon/api.js +690 -25
- package/lib/quoxon/app.js +3586 -250
- package/lib/quoxon/approvalFacts.js +96 -0
- package/lib/quoxon/approvalSentence.js +44 -0
- package/lib/quoxon/approvalsView.js +384 -0
- package/lib/quoxon/asideView.js +40 -0
- package/lib/quoxon/ask.js +121 -0
- package/lib/quoxon/away.js +52 -0
- package/lib/quoxon/bang.js +224 -0
- package/lib/quoxon/bulkRisk.js +17 -0
- package/lib/quoxon/capabilities.js +13 -6
- package/lib/quoxon/cells.js +53 -0
- package/lib/quoxon/commandShort.js +124 -0
- package/lib/quoxon/commands.js +429 -39
- package/lib/quoxon/connect.js +210 -23
- package/lib/quoxon/connectCard.js +19 -2
- package/lib/quoxon/consent.js +86 -0
- package/lib/quoxon/copy.js +4 -3
- package/lib/quoxon/countdownLine.js +20 -0
- package/lib/quoxon/deck.js +30 -0
- package/lib/quoxon/demo.autoplay.js +29 -0
- package/lib/quoxon/demo.fixtures.js +1 -1
- package/lib/quoxon/demo.js +46 -3
- package/lib/quoxon/devObserver.js +20 -0
- package/lib/quoxon/dividerAocl.js +192 -0
- package/lib/quoxon/dividerRule.js +57 -0
- package/lib/quoxon/dragonCompanion.js +255 -0
- package/lib/quoxon/dragonModes.js +151 -0
- package/lib/quoxon/dragonMoments.js +82 -0
- package/lib/quoxon/dragonReads.js +68 -0
- package/lib/quoxon/dragonView.js +41 -0
- package/lib/quoxon/earlyInput.js +40 -0
- package/lib/quoxon/echo.js +15 -0
- package/lib/quoxon/egg.js +2 -2
- package/lib/quoxon/events.js +266 -26
- package/lib/quoxon/extensions.js +276 -0
- package/lib/quoxon/feedback.js +50 -0
- package/lib/quoxon/gallery.js +21 -7
- package/lib/quoxon/handoffOffer.js +78 -0
- package/lib/quoxon/hatSwitch.js +37 -0
- package/lib/quoxon/headless.js +161 -0
- package/lib/quoxon/hostUrls.js +41 -0
- package/lib/quoxon/inboxView.js +79 -0
- package/lib/quoxon/indicator.js +6 -2
- package/lib/quoxon/installCli.js +79 -0
- package/lib/quoxon/intent.js +12 -1
- package/lib/quoxon/keys.js +51 -8
- package/lib/quoxon/launcher.js +103 -0
- package/lib/quoxon/loopCard.js +456 -0
- package/lib/quoxon/loopCards.js +180 -0
- package/lib/quoxon/loopStream.js +87 -0
- package/lib/quoxon/main.js +101 -160
- package/lib/quoxon/markdown.js +45 -21
- package/lib/quoxon/markdownSettle.js +19 -0
- package/lib/quoxon/matrixDomains.json +46 -0
- package/lib/quoxon/mentions.js +90 -0
- package/lib/quoxon/mind.js +103 -0
- package/lib/quoxon/mode.js +46 -0
- package/lib/quoxon/model.js +132 -26
- package/lib/quoxon/modelPrefs.js +345 -0
- package/lib/quoxon/motion/clock.js +3 -2
- package/lib/quoxon/motion/level.js +91 -0
- package/lib/quoxon/mouse.js +222 -0
- package/lib/quoxon/noai.js +57 -0
- package/lib/quoxon/objectiveOffer.js +82 -0
- package/lib/quoxon/objects/away.js +72 -0
- package/lib/quoxon/objects/fetch.js +17 -5
- package/lib/quoxon/objects/fleet.js +149 -0
- package/lib/quoxon/objects/health.js +186 -0
- package/lib/quoxon/objects/index.js +46 -7
- package/lib/quoxon/objects/mirror.js +137 -0
- package/lib/quoxon/objects/model.js +33 -0
- package/lib/quoxon/objects/peek.js +17 -0
- package/lib/quoxon/objects/proof.js +12 -4
- package/lib/quoxon/objects/prove.js +130 -0
- package/lib/quoxon/objects/replay.js +125 -0
- package/lib/quoxon/objects/sitrep.js +58 -0
- package/lib/quoxon/objects/view.js +11 -0
- package/lib/quoxon/onboarding/copy.js +32 -4
- package/lib/quoxon/onboarding/credential.js +17 -4
- package/lib/quoxon/onboarding/dockerOffer.js +45 -0
- package/lib/quoxon/onboarding/failureCause.js +33 -0
- package/lib/quoxon/onboarding/finish.js +15 -0
- package/lib/quoxon/onboarding/index.js +34 -7
- package/lib/quoxon/onboarding/installRunner.js +64 -5
- package/lib/quoxon/onboarding/preflight.js +17 -3
- package/lib/quoxon/onboarding/probe.js +65 -0
- package/lib/quoxon/onboarding/shortcutOffer.js +28 -0
- package/lib/quoxon/onboarding/view.js +76 -19
- package/lib/quoxon/orgLabel.js +10 -0
- package/lib/quoxon/orgSwitch.js +59 -0
- package/lib/quoxon/ownBroadcast.js +22 -0
- package/lib/quoxon/palette.js +43 -0
- package/lib/quoxon/planSteps.js +51 -0
- package/lib/quoxon/print.js +12 -5
- package/lib/quoxon/profileSwitch.js +53 -0
- package/lib/quoxon/promptHistory.js +41 -0
- package/lib/quoxon/providers.js +48 -0
- package/lib/quoxon/qart.js +20 -51
- package/lib/quoxon/qdragon.js +410 -0
- package/lib/quoxon/qdragonFly.js +62 -0
- package/lib/quoxon/qdragonScenes.js +282 -0
- package/lib/quoxon/qeyes.js +213 -0
- package/lib/quoxon/qmotion.js +75 -37
- package/lib/quoxon/render.js +740 -124
- package/lib/quoxon/resend.js +72 -0
- package/lib/quoxon/roster.js +117 -17
- package/lib/quoxon/route.js +6 -5
- package/lib/quoxon/rows.js +954 -0
- package/lib/quoxon/rowsStore.js +80 -0
- package/lib/quoxon/screenContext.js +150 -0
- package/lib/quoxon/screenFrames.js +254 -0
- package/lib/quoxon/serverChip.js +42 -0
- package/lib/quoxon/session.js +182 -2
- package/lib/quoxon/sessionDead.js +17 -0
- package/lib/quoxon/signin.js +125 -0
- package/lib/quoxon/slash/agents.js +112 -0
- package/lib/quoxon/slash/argSources.js +65 -0
- package/lib/quoxon/slash/args.js +65 -0
- package/lib/quoxon/slash/automate.js +89 -0
- package/lib/quoxon/slash/broadcast.js +140 -0
- package/lib/quoxon/slash/broadcastRun.js +108 -0
- package/lib/quoxon/slash/card.js +30 -0
- package/lib/quoxon/slash/challenge.js +114 -0
- package/lib/quoxon/slash/cliCommand.js +94 -0
- package/lib/quoxon/slash/cliLeaves.json +1023 -0
- package/lib/quoxon/slash/compact.js +1 -1
- package/lib/quoxon/slash/confirm.js +30 -0
- package/lib/quoxon/slash/crystallize.js +69 -0
- package/lib/quoxon/slash/dashboard.js +136 -0
- package/lib/quoxon/slash/draft.js +54 -0
- package/lib/quoxon/slash/evidenceCmd.js +34 -0
- package/lib/quoxon/slash/hatWalk.js +206 -0
- package/lib/quoxon/slash/inverses.js +77 -0
- package/lib/quoxon/slash/killer.js +24 -0
- package/lib/quoxon/slash/killerApi.js +16 -0
- package/lib/quoxon/slash/ledger.js +13 -8
- package/lib/quoxon/slash/listView.js +3 -1
- package/lib/quoxon/slash/modelCmd.js +261 -0
- package/lib/quoxon/slash/palette.js +22 -8
- package/lib/quoxon/slash/paletteController.js +117 -7
- package/lib/quoxon/slash/panels.js +21 -6
- package/lib/quoxon/slash/picker.js +20 -2
- package/lib/quoxon/slash/qpeCmd.js +35 -0
- package/lib/quoxon/slash/restart.js +95 -13
- package/lib/quoxon/slash/resume.js +18 -1
- package/lib/quoxon/slash/rowCmd.js +157 -0
- package/lib/quoxon/slash/rowWalk.js +175 -0
- package/lib/quoxon/slash/settings.js +16 -6
- package/lib/quoxon/slash/spawn.js +177 -0
- package/lib/quoxon/slash/stepUp.js +274 -0
- package/lib/quoxon/slash/stepUpCard.js +60 -0
- package/lib/quoxon/slash/stepUpCmds.js +78 -0
- package/lib/quoxon/slash/todoCmd.js +122 -0
- package/lib/quoxon/slash/undoApi.js +38 -0
- package/lib/quoxon/slash/undoCmd.js +49 -0
- package/lib/quoxon/slash/undoWish.js +15 -0
- package/lib/quoxon/slash/watch.js +164 -0
- package/lib/quoxon/slash/wish.js +23 -0
- package/lib/quoxon/slash/wishCmd.js +32 -0
- package/lib/quoxon/slotChat.js +300 -0
- package/lib/quoxon/slotFormat.js +195 -0
- package/lib/quoxon/slotRun.js +131 -0
- package/lib/quoxon/sound/mapping.js +2 -1
- package/lib/quoxon/stray.js +22 -0
- package/lib/quoxon/suggestCommand.js +90 -0
- package/lib/quoxon/term.js +46 -11
- package/lib/quoxon/textBuffer.js +176 -2
- package/lib/quoxon/theme.js +58 -25
- package/lib/quoxon/tips.js +433 -0
- package/lib/quoxon/toolCards.js +30 -0
- package/lib/quoxon/toolError.js +41 -0
- package/lib/quoxon/transcript.js +42 -4
- package/lib/quoxon/web/qface.js +18 -10
- package/lib/quoxon/welcome.js +1 -1
- package/lib/quoxon/whatsnew.js +136 -0
- package/lib/quoxon/whatsnew.jsonl +134 -0
- package/lib/quoxon/windowOrg.js +123 -0
- package/lib/server/devServers.js +137 -0
- package/lib/server/diag.js +129 -0
- package/lib/server/doctor.js +71 -0
- package/lib/server/install.js +149 -0
- package/lib/server/pluginEnable.js +161 -0
- package/lib/server/skew.js +56 -0
- package/lib/server/snapshot.js +131 -0
- package/lib/server/status.js +128 -0
- package/lib/server/sys.js +65 -0
- package/lib/server/uninstall.js +63 -0
- package/lib/server/update.js +234 -0
- package/lib/toolOutputShape.js +315 -0
- package/lib/tui/views/HyperionDeck.jsx +79 -0
- package/lib/tui/views/LucidDeck.jsx +209 -217
- package/package.json +5 -1
- 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/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/factory/svg-rig.mjs +63 -0
- package/vendor/dragonlab/integration/companions-v1/characters.mjs +88 -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/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/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/core/websiteSession.js +0 -148
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loop commands (QLOOP-LIVE): observe a QLoop loop as it runs.
|
|
3
|
+
*
|
|
4
|
+
* quox loop watch <id> [--spool|--quiet] [--since n] live, one QuoxGlyph line per event
|
|
5
|
+
* quox loop status <id> snapshot
|
|
6
|
+
* quox loop list running + recent loops (org)
|
|
7
|
+
*
|
|
8
|
+
* `watch` reconnects with ?since=<last event_seq> when the stream drops, and exits with the loop's
|
|
9
|
+
* outcome: 0 completed, 1 failed, 2 halted, 3 interrupted, 4 connection lost for good.
|
|
10
|
+
* Events come from GET /api/v1/loops/:id/events (design: ~/quoxon-work/qloop-live-design.md).
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { formatOutput, getOutputFormat } from '../core/output.js';
|
|
14
|
+
import { resolveEndpoint } from '../api/endpoints.js';
|
|
15
|
+
import { loopLine, renderEvent, exitCodeFor, elapsedLabel } from '../core/loopGlyph.js';
|
|
16
|
+
import { QuoxApiError, QuoxAuthError, QuoxUserError } from '../core/errors.js';
|
|
17
|
+
|
|
18
|
+
const MAX_SILENT_RETRIES = 10;
|
|
19
|
+
const BACKOFF_BASE_MS = 1000;
|
|
20
|
+
const BACKOFF_CAP_MS = 10000;
|
|
21
|
+
|
|
22
|
+
const sleepReal = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
23
|
+
|
|
24
|
+
/** SQLite "YYYY-MM-DD HH:MM:SS" (UTC, no zone) or ISO to epoch ms; null if unreadable. */
|
|
25
|
+
function toMs(ts) {
|
|
26
|
+
if (!ts) return null;
|
|
27
|
+
const iso = /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/.test(ts) ? `${ts.replace(' ', 'T')}Z` : ts;
|
|
28
|
+
const n = Date.parse(iso);
|
|
29
|
+
return Number.isNaN(n) ? null : n;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const SETTLE_MAX_MS = 90000;
|
|
33
|
+
const SETTLE_POLL_MS = 4000;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* After loop.finished: wait (bounded) for the loop's receipts to be WITNESSED by WARD, asking the snapshot
|
|
37
|
+
* (which re-checks WARD and records what it finds), printing each receipt as it flips. Returns the finished
|
|
38
|
+
* event with the final witnessed count. A snapshot that cannot be read ends the wait and says so; it never
|
|
39
|
+
* reads as "0 witnessed".
|
|
40
|
+
*/
|
|
41
|
+
async function settleReceipts({ client, id, finished, write, sleep, ctx, maxMs, signal, seen = new Set() }) {
|
|
42
|
+
const ep = resolveEndpoint('loops.get', { id: encodeURIComponent(id) });
|
|
43
|
+
let witnessed = finished.witnessed;
|
|
44
|
+
write(loopLine('receipts', `${witnessed}/${finished.receipts} witnessed, waiting for WARD to sign the covering tip`, 'busy'));
|
|
45
|
+
let waited = 0;
|
|
46
|
+
while (witnessed < finished.receipts && waited < maxMs && !(signal && signal.aborted)) {
|
|
47
|
+
await sleep(SETTLE_POLL_MS);
|
|
48
|
+
waited += SETTLE_POLL_MS;
|
|
49
|
+
let snap;
|
|
50
|
+
try { snap = await client.get(ep.path, { service: ep.service }); } catch (err) {
|
|
51
|
+
write(loopLine('receipts', `could not re-check WARD (${oneLineErr(err)}); \`quox loop status ${id}\` will`, 'warn'));
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
const items = (snap && snap.receipts && snap.receipts.items) || [];
|
|
55
|
+
for (const r of items) {
|
|
56
|
+
if (r.status === 'WITNESSED' && !seen.has(r.envelope_id)) {
|
|
57
|
+
seen.add(r.envelope_id);
|
|
58
|
+
write(loopLine('receipt', `${String(r.envelope_id)} WITNESSED`, 'ok'));
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
witnessed = snap && snap.receipts ? snap.receipts.witnessed : witnessed;
|
|
62
|
+
}
|
|
63
|
+
if (witnessed < finished.receipts) write(loopLine('receipts', `${finished.receipts - witnessed} still pending after ${Math.round(waited / 1000)}s; they are witnessed when the next tip signs (quox loop status ${id})`, 'warn'));
|
|
64
|
+
return { ...finished, witnessed };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Follow one loop to its end. Returns the process exit code. Exported for tests.
|
|
69
|
+
* @param {object} p
|
|
70
|
+
* @param {object} p.client cli client (stream)
|
|
71
|
+
* @param {string} p.id
|
|
72
|
+
* @param {object} p.snapshot loops.get result (title, max_turns, started_at)
|
|
73
|
+
* @param {{spool?: boolean, quiet?: boolean, since?: number, json?: boolean, noSettle?: boolean, settleMs?: number}} p.opts
|
|
74
|
+
* @param {(line: string) => void} p.write
|
|
75
|
+
* @param {(ms: number) => Promise<void>} [p.sleep]
|
|
76
|
+
* @param {AbortSignal} [p.signal]
|
|
77
|
+
*/
|
|
78
|
+
export async function watchLoop({ client, id, snapshot, opts = {}, write, sleep = sleepReal, signal }) {
|
|
79
|
+
const ep = resolveEndpoint('loops.events', { id: encodeURIComponent(id) });
|
|
80
|
+
let lastSeq = Number(opts.since) || 0;
|
|
81
|
+
let startedMs = toMs(snapshot && snapshot.started_at);
|
|
82
|
+
const ctx = { title: snapshot && snapshot.title, maxTurns: snapshot && snapshot.max_turns, startedMs, spool: !!opts.spool };
|
|
83
|
+
const showQuiet = new Set(['loop.started', 'loop.observe', 'loop.approval', 'loop.finished']);
|
|
84
|
+
let failures = 0;
|
|
85
|
+
let backoff = BACKOFF_BASE_MS;
|
|
86
|
+
const witnessedShown = new Set(); // receipts already printed from loop.ward events, so the settle phase does not repeat them
|
|
87
|
+
|
|
88
|
+
while (!signal || !signal.aborted) {
|
|
89
|
+
let gotEvent = false;
|
|
90
|
+
let ended = null;
|
|
91
|
+
try {
|
|
92
|
+
for await (const ev of client.stream(ep.path, { service: ep.service, query: { since: lastSeq }, signal })) {
|
|
93
|
+
// `event: end` frame (an old loop that never logged a finish): no type, a reason
|
|
94
|
+
if (ev && !ev.type && ev.reason) { ended = ev; break; }
|
|
95
|
+
if (!ev || typeof ev.type !== 'string' || !ev.type.startsWith('loop.')) continue;
|
|
96
|
+
gotEvent = true;
|
|
97
|
+
if (typeof ev.event_seq === 'number') {
|
|
98
|
+
if (ev.event_seq <= lastSeq) continue; // replay overlap after a reconnect
|
|
99
|
+
lastSeq = ev.event_seq;
|
|
100
|
+
}
|
|
101
|
+
if (ev.type === 'loop.ward' && ev.status === 'WITNESSED') witnessedShown.add(ev.envelope_id);
|
|
102
|
+
if (!ctx.startedMs && ev.type === 'loop.started') ctx.startedMs = Date.parse(ev.at);
|
|
103
|
+
if (opts.json) write(JSON.stringify(ev));
|
|
104
|
+
else if (ev.type !== 'loop.finished' && !(opts.quiet && !showQuiet.has(ev.type))) for (const l of renderEvent(ev, ctx)) write(l);
|
|
105
|
+
if (ev.type === 'loop.finished') {
|
|
106
|
+
// the loop is over; its receipts are WITNESSED when WARD's next signed tip covers them (about a minute),
|
|
107
|
+
// so say so and follow them in rather than printing a final count that is about to be stale
|
|
108
|
+
const last = (!opts.json && !opts.noSettle && ev.receipts > ev.witnessed)
|
|
109
|
+
? await settleReceipts({ client, id, finished: ev, write, sleep, ctx, maxMs: opts.settleMs ?? SETTLE_MAX_MS, signal, seen: witnessedShown })
|
|
110
|
+
: ev;
|
|
111
|
+
if (!opts.json) for (const l of renderEvent(last, ctx).slice(-1)) write(l);
|
|
112
|
+
if (!opts.json && !opts.quiet) write(` quox loop status ${id} # receipts and proof`);
|
|
113
|
+
return exitCodeFor(ev.state);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
} catch (err) {
|
|
117
|
+
// a refusal is not a dropped connection: 401/403/404 and friends end the watch now with the server's words
|
|
118
|
+
if (err instanceof QuoxAuthError || (err instanceof QuoxApiError && err.status >= 400 && err.status < 500 && err.status !== 429)) throw err;
|
|
119
|
+
if (signal && signal.aborted) return 130;
|
|
120
|
+
failures += 1;
|
|
121
|
+
if (failures > MAX_SILENT_RETRIES) {
|
|
122
|
+
write(loopLine('connection lost', `could not reach the loop stream after ${MAX_SILENT_RETRIES} tries; resume with: quox loop watch ${id} --since ${lastSeq}`, 'fail'));
|
|
123
|
+
return 4;
|
|
124
|
+
}
|
|
125
|
+
if (!opts.quiet && !opts.json) write(loopLine('reconnecting', `stream dropped (${oneLineErr(err)}); resuming after event ${lastSeq}`, 'warn'));
|
|
126
|
+
await sleep(backoff);
|
|
127
|
+
backoff = Math.min(BACKOFF_CAP_MS, backoff * 2);
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
if (ended) {
|
|
131
|
+
write(loopLine(ctx.title || 'loop', `ended without a recorded finish (${ended.status || 'unknown'}): ${ended.reason}`, 'warn'));
|
|
132
|
+
return ended.status === 'completed' ? 0 : 1;
|
|
133
|
+
}
|
|
134
|
+
// stream closed by the server (restart, proxy idle limit) before loop.finished: resume, quietly counting
|
|
135
|
+
if (gotEvent) { failures = 0; backoff = BACKOFF_BASE_MS; } else failures += 1;
|
|
136
|
+
if (failures > MAX_SILENT_RETRIES) {
|
|
137
|
+
write(loopLine('connection lost', `the loop stream keeps closing with nothing new; resume with: quox loop watch ${id} --since ${lastSeq}`, 'fail'));
|
|
138
|
+
return 4;
|
|
139
|
+
}
|
|
140
|
+
await sleep(gotEvent ? 250 : backoff);
|
|
141
|
+
if (!gotEvent) backoff = Math.min(BACKOFF_CAP_MS, backoff * 2);
|
|
142
|
+
}
|
|
143
|
+
return 130;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function oneLineErr(err) {
|
|
147
|
+
return String((err && err.message) || err).replace(/\s+/g, ' ').slice(0, 80);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function statusLines(s, { diagnostics = false } = {}) {
|
|
151
|
+
const lines = [];
|
|
152
|
+
const turns = `turn ${s.turn}${s.max_turns ? `/${s.max_turns}` : ''}`;
|
|
153
|
+
lines.push(loopLine(s.title || s.objective_id, `${s.state}, ${turns}`, s.state === 'running' ? 'busy' : (s.state === 'completed' ? 'ok' : (s.state === 'completed_with_errors' || s.state === 'halted' || s.state === 'interrupted' ? 'warn' : (s.finished ? 'fail' : 'busy')))));
|
|
154
|
+
lines.push(` id ${s.objective_id}`);
|
|
155
|
+
lines.push(` host ${s.host || '-'} backend ${s.backend || '-'} scope ${(s.scope || []).join(',') || '-'}`);
|
|
156
|
+
if (s.until) lines.push(` until ${s.until}${s.every_seconds ? ` (a turn every ${s.every_seconds}s)` : ''}`);
|
|
157
|
+
else if (s.every_seconds) lines.push(` every ${s.every_seconds}s`);
|
|
158
|
+
if (s.workspace) lines.push(` workspace ${s.workspace}`);
|
|
159
|
+
const r = s.receipts || { total: 0, witnessed: 0 };
|
|
160
|
+
lines.push(` receipts ${r.witnessed}/${r.total} witnessed${s.ward_check_error ? ` (WARD could not be asked just now: ${s.ward_check_error})` : ''}`);
|
|
161
|
+
if (s.observe) lines.push(` observe ${JSON.stringify(s.observe)}`);
|
|
162
|
+
if (s.reason && s.finished) lines.push(` reason ${s.reason}`);
|
|
163
|
+
if (diagnostics && s.diagnostics) lines.push(` diagnostics ${s.diagnostics}`);
|
|
164
|
+
if ((s.last_turns || []).length) {
|
|
165
|
+
lines.push(' last turns');
|
|
166
|
+
for (const t of s.last_turns) {
|
|
167
|
+
const tools = (t.tools || []).map((x) => `${x.tool}${x.denied ? ' denied' : (x.ok ? '' : ' FAILED')}`).join(', ');
|
|
168
|
+
lines.push(` ${t.turn} ${t.summary}${tools ? ` [${tools}]` : ''} ${t.envelope_id ? `${String(t.envelope_id)} ${t.ward}` : ''}`.trimEnd());
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
if (s.started_at) {
|
|
172
|
+
const st = toMs(s.started_at);
|
|
173
|
+
const en = toMs(s.completed_at) || Date.now();
|
|
174
|
+
if (st) lines.push(` elapsed ${elapsedLabel(en - st)}`);
|
|
175
|
+
}
|
|
176
|
+
return lines;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export function registerLoopCommands(program, ctx) {
|
|
180
|
+
const { client, auth } = ctx;
|
|
181
|
+
|
|
182
|
+
const loop = program
|
|
183
|
+
.command('loop')
|
|
184
|
+
.description('Observe QLoop loops as they run (watch, status, list)');
|
|
185
|
+
|
|
186
|
+
loop
|
|
187
|
+
.command('watch')
|
|
188
|
+
.description('Follow a loop live: one QuoxGlyph line per event, reconnecting on drop. Exits 0 completed, 1 failed, 2 halted, 3 interrupted, 4 connection lost')
|
|
189
|
+
.argument('<id>', 'loop (objective) ID')
|
|
190
|
+
.option('--spool', 'also stream the loop\'s output text under each turn line')
|
|
191
|
+
.option('--quiet', 'only the headline lines: started, observe points (milestones, errors, approvals, stalls), the final line')
|
|
192
|
+
.option('--since <n>', 'resume after this event_seq (printed by a previous watch; default: from the start)')
|
|
193
|
+
.option('--no-wait-receipts', 'print the final line as soon as the loop ends instead of waiting (up to 90s) for its receipts to be witnessed')
|
|
194
|
+
.action(async (id, options) => {
|
|
195
|
+
auth.requireAuth();
|
|
196
|
+
if (options.spool && options.quiet) throw new QuoxUserError('--spool and --quiet cancel each other out; pick one.');
|
|
197
|
+
const since = options.since !== undefined ? Number(options.since) : 0;
|
|
198
|
+
if (!Number.isInteger(since) || since < 0) throw new QuoxUserError('--since needs a whole number event_seq (0 or more).');
|
|
199
|
+
const snap = resolveEndpoint('loops.get', { id: encodeURIComponent(id) });
|
|
200
|
+
const snapshot = await client.get(snap.path, { service: snap.service });
|
|
201
|
+
const ac = new AbortController();
|
|
202
|
+
const onSigint = () => ac.abort();
|
|
203
|
+
process.once('SIGINT', onSigint);
|
|
204
|
+
let code;
|
|
205
|
+
try {
|
|
206
|
+
code = await watchLoop({
|
|
207
|
+
client, id, snapshot,
|
|
208
|
+
opts: { spool: options.spool, quiet: options.quiet, since, json: getOutputFormat() === 'json', noSettle: options.waitReceipts === false },
|
|
209
|
+
write: (l) => console.log(l),
|
|
210
|
+
signal: ac.signal,
|
|
211
|
+
});
|
|
212
|
+
} finally {
|
|
213
|
+
process.removeListener('SIGINT', onSigint);
|
|
214
|
+
}
|
|
215
|
+
if (code === 130) console.log(` stopped watching; the loop keeps running. Resume: quox loop watch ${id}`);
|
|
216
|
+
if (code) process.exitCode = code;
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
loop
|
|
220
|
+
.command('status')
|
|
221
|
+
.description('Snapshot of a loop: state, turn n/max, host, backend, scope, last turns, receipts')
|
|
222
|
+
.argument('<id>', 'loop (objective) ID')
|
|
223
|
+
.option('--diagnostics', 'also show the raw diagnostics behind the finish reason (hidden by default)')
|
|
224
|
+
.action(async (id, options = {}) => {
|
|
225
|
+
auth.requireAuth();
|
|
226
|
+
const ep = resolveEndpoint('loops.get', { id: encodeURIComponent(id) });
|
|
227
|
+
const data = await client.get(ep.path, { service: ep.service });
|
|
228
|
+
if (getOutputFormat() === 'json') {
|
|
229
|
+
const out = options.diagnostics || !data || typeof data !== 'object' ? data : { ...data, diagnostics: undefined };
|
|
230
|
+
console.log(JSON.stringify(out, null, 2)); return;
|
|
231
|
+
}
|
|
232
|
+
for (const l of statusLines(data, { diagnostics: !!options.diagnostics })) console.log(l);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
loop
|
|
236
|
+
.command('list')
|
|
237
|
+
.description('Running and recent loops in this org')
|
|
238
|
+
.option('--limit <n>', 'max results', '20')
|
|
239
|
+
.action(async (options) => {
|
|
240
|
+
auth.requireAuth();
|
|
241
|
+
const ep = resolveEndpoint('loops.list');
|
|
242
|
+
const data = await client.get(ep.path, { service: ep.service, query: { limit: options.limit } });
|
|
243
|
+
formatOutput({
|
|
244
|
+
columns: ['objective_id', 'title', 'state', 'turn', 'backend', 'created_at'],
|
|
245
|
+
headers: ['ID', 'Title', 'State', 'Turn', 'Backend', 'Created'],
|
|
246
|
+
rows: (data.loops || []).map((l) => ({ ...l, turn: l.max_turns ? `${l.turn}/${l.max_turns}` : String(l.turn) })),
|
|
247
|
+
}, { format: program.opts().output, emptyMessage: 'No loops in this org yet.' });
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
// SECURITY containment (2026-10-26): which orgs may run loops in-server (core-local) while a security review is open.
|
|
251
|
+
// Instance-admin only (the server enforces it; a non-admin gets a 403). Stored server-side in instance_config.
|
|
252
|
+
const coreLocal = loop
|
|
253
|
+
.command('core-local')
|
|
254
|
+
.description('Instance admin: which workspaces may run loops inside the server (limited while a security review is open)');
|
|
255
|
+
|
|
256
|
+
coreLocal
|
|
257
|
+
.command('list')
|
|
258
|
+
.description('Show the workspaces allowed to run loops inside the server')
|
|
259
|
+
.action(async () => {
|
|
260
|
+
auth.requireAuth();
|
|
261
|
+
const ep = resolveEndpoint('admin.instance.core_local_orgs.list');
|
|
262
|
+
const r = await client.get(ep.path, { service: ep.service });
|
|
263
|
+
if (getOutputFormat() === 'json') { console.log(JSON.stringify(r, null, 2)); return; }
|
|
264
|
+
if (!(r.orgs || []).length) { console.log('No workspace may run loops inside the server (loops need a dev server).'); return; }
|
|
265
|
+
for (const o of r.orgs) console.log(o);
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
coreLocal
|
|
269
|
+
.command('add')
|
|
270
|
+
.description('Allow a workspace to run loops inside the server (instance admin)')
|
|
271
|
+
.argument('<orgId>', 'workspace (org) id')
|
|
272
|
+
.action(async (orgId) => {
|
|
273
|
+
auth.requireAuth();
|
|
274
|
+
const ep = resolveEndpoint('admin.instance.core_local_orgs.add');
|
|
275
|
+
const r = await client.post(ep.path, { service: ep.service, body: { org_id: orgId } });
|
|
276
|
+
if (getOutputFormat() === 'json') { console.log(JSON.stringify(r, null, 2)); return; }
|
|
277
|
+
console.log(`Added ${orgId}. ${(r.orgs || []).length} workspace(s) may now run loops inside the server.`);
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
coreLocal
|
|
281
|
+
.command('remove')
|
|
282
|
+
.description('Stop a workspace running loops inside the server (instance admin)')
|
|
283
|
+
.argument('<orgId>', 'workspace (org) id')
|
|
284
|
+
.action(async (orgId) => {
|
|
285
|
+
auth.requireAuth();
|
|
286
|
+
const ep = resolveEndpoint('admin.instance.core_local_orgs.remove', { orgId: encodeURIComponent(orgId) });
|
|
287
|
+
const r = await client.delete(ep.path, { service: ep.service });
|
|
288
|
+
if (getOutputFormat() === 'json') { console.log(JSON.stringify(r, null, 2)); return; }
|
|
289
|
+
console.log(`Removed ${orgId}. ${(r.orgs || []).length} workspace(s) may still run loops inside the server.`);
|
|
290
|
+
});
|
|
291
|
+
}
|
package/lib/commands/lucid.js
CHANGED
|
@@ -102,6 +102,7 @@ export function registerLucidCommands(program, ctx) {
|
|
|
102
102
|
if (o.json) { console.log(JSON.stringify(data, null, 2)); return; }
|
|
103
103
|
if (o.agent) { console.log(data.text_agent ?? data.text ?? ''); return; }
|
|
104
104
|
if (o.spoken) { console.log(data.spoken ?? data.text ?? ''); return; }
|
|
105
|
+
if (o.markdown) { const { renderSitrepMarkdown } = await import('../lucid/markdown.js'); console.log(renderSitrepMarkdown(data)); return; }
|
|
105
106
|
const { renderSitrep } = await import('../lucid/sitrep.js');
|
|
106
107
|
const tty = Boolean(process.stdout.isTTY);
|
|
107
108
|
process.stdout.write(renderSitrep(data, {
|
|
@@ -118,14 +119,15 @@ export function registerLucidCommands(program, ctx) {
|
|
|
118
119
|
const sitrepOpts = (c) => c
|
|
119
120
|
.option('--agent', 'compact, id-rich form for agents')
|
|
120
121
|
.option('--json', 'the full structured situation')
|
|
121
|
-
.option('--spoken', 'sentences only, as read on the phone')
|
|
122
|
+
.option('--spoken', 'sentences only, as read on the phone')
|
|
123
|
+
.option('--markdown', 'chat-ready Markdown for Discord, Matrix and slots (fits one message)');
|
|
122
124
|
|
|
123
125
|
sitrepOpts(lu
|
|
124
126
|
.command('sitrep')
|
|
125
127
|
.description('One-shot formatted snapshot (areas, what needs you, what changed, what I cannot see)'))
|
|
126
128
|
.action(async (o) => showSitrep(o));
|
|
127
129
|
|
|
128
|
-
// Top-level shortcut so any human or agent can ask for a sitrep: `quox sitrep [--agent|--json|--spoken]`.
|
|
130
|
+
// Top-level shortcut so any human or agent can ask for a sitrep: `quox sitrep [--agent|--json|--spoken|--markdown]`.
|
|
129
131
|
sitrepOpts(program
|
|
130
132
|
.command('sitrep')
|
|
131
133
|
.description('LUCID situation report: what needs you, every area, what changed, what LUCID cannot see'))
|
|
@@ -393,9 +395,13 @@ export function registerLucidCommands(program, ctx) {
|
|
|
393
395
|
if (wantJson(cmd)) { console.log(JSON.stringify(data, null, 2)); return; }
|
|
394
396
|
if (data.status === 'filed') {
|
|
395
397
|
printSuccess(`Filed${data.approval_id ? ` (approval ${data.approval_id})` : ''}. Approve it in Approvals.`);
|
|
396
|
-
} else if (data.status === 'manual') {
|
|
398
|
+
} else if (data.status === 'manual' && data.command) {
|
|
397
399
|
console.log('This one is manual. Run this yourself:');
|
|
398
|
-
console.log(` ${data.command
|
|
400
|
+
console.log(` ${data.command}`);
|
|
401
|
+
} else if (data.status === 'manual') {
|
|
402
|
+
// #992: a 'say' measure has no command; print what it says and how to ask, never a blank instruction.
|
|
403
|
+
console.log(data.say || (m && m.label) || 'There is nothing to run for this one.');
|
|
404
|
+
console.log(` quox lucid ask "${((m && m.label) || 'Look into it').replace(/"/g, "'")} (${ref})"`);
|
|
399
405
|
} else if (data.status === 'opened') {
|
|
400
406
|
console.log(`Opened: ${data.url ?? ''}`);
|
|
401
407
|
} else {
|
package/lib/commands/memory.js
CHANGED
|
@@ -26,6 +26,7 @@ import { resolveEndpoint } from '../api/endpoints.js';
|
|
|
26
26
|
import { QuoxError } from '../core/errors.js';
|
|
27
27
|
import { resolveOrgId } from './helpers.js';
|
|
28
28
|
import { resolveMyAttach } from './agentAttach.js';
|
|
29
|
+
import { hasExplicitProfile } from '../core/actingAs.js';
|
|
29
30
|
|
|
30
31
|
// QUOXIDENTITY P9: which agent this session acts as is resolved session-scoped
|
|
31
32
|
// (QUOX_T subtree / QUOX_ATTACH_AGENT override / single-attach fallback), so
|
|
@@ -46,7 +47,12 @@ async function callMemoryTool({ client, auth, program }, toolName, input) {
|
|
|
46
47
|
const body = { tool_name: toolName, input };
|
|
47
48
|
const requestOptions = { service: ep.service, body };
|
|
48
49
|
|
|
49
|
-
|
|
50
|
+
// An explicit -p/--profile (or QUOX_PROFILE) is the caller saying "as me":
|
|
51
|
+
// it wins over the session's attach lease, same precedence as
|
|
52
|
+
// core/actingAs. Before this, `quox -p moo memory search` inside an attached
|
|
53
|
+
// session went out on the lease token and 401'd (proof engine 2026-10-14).
|
|
54
|
+
const argv = (program.rawArgs || process.argv).slice(2);
|
|
55
|
+
const lease = hasExplicitProfile(argv) ? null : detectSoleAttachLease();
|
|
50
56
|
if (lease) {
|
|
51
57
|
requestOptions.noAuth = true;
|
|
52
58
|
requestOptions.headers = { Authorization: `Bearer ${lease.token}` };
|
|
@@ -260,16 +266,87 @@ export function registerMemoryCommands(program, ctx) {
|
|
|
260
266
|
}, { format: program.opts().output });
|
|
261
267
|
});
|
|
262
268
|
|
|
269
|
+
// QUOXMIND #1111: the old view counted only rows under YOUR user id, so an
|
|
270
|
+
// org holding hundreds of agent-written memories looked nearly empty. Default
|
|
271
|
+
// is now the org view (per agent, per writer), falling back honestly.
|
|
263
272
|
memory
|
|
264
273
|
.command('stats')
|
|
265
|
-
.description('Show memory statistics')
|
|
266
|
-
.
|
|
274
|
+
.description('Show memory statistics: the whole org by agent and writer (org admins), else your own')
|
|
275
|
+
.option('--mine', 'only memories written under your own login')
|
|
276
|
+
.action(async (options) => {
|
|
277
|
+
auth.requireAuth();
|
|
278
|
+
const ep = resolveEndpoint('memory.stats.list');
|
|
279
|
+
let data;
|
|
280
|
+
let note = '';
|
|
281
|
+
if (options.mine) {
|
|
282
|
+
data = await client.get(ep.path, { service: ep.service, query: {} });
|
|
283
|
+
} else {
|
|
284
|
+
try {
|
|
285
|
+
data = await client.get(ep.path, { service: ep.service, query: { scope: 'org' } });
|
|
286
|
+
} catch (err) {
|
|
287
|
+
if (err?.status !== 403) throw err;
|
|
288
|
+
data = await client.get(ep.path, { service: ep.service, query: {} });
|
|
289
|
+
note = 'Org-wide counts need an org admin or owner. Showing only memories written under your own login; agents write under their own identities, so the org holds more.';
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
const format = program.opts().output;
|
|
293
|
+
if (format === 'json') { console.log(JSON.stringify(note ? { ...data, note } : data, null, 2)); return; }
|
|
294
|
+
if (data?.scope !== 'org') {
|
|
295
|
+
formatDetail(data.stats || data, { format, title: 'Memory Stats (your login only)' });
|
|
296
|
+
console.log(note || 'Counts only memories written under your own login. `quox memory stats` without --mine shows the whole org (org admins).');
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
const m = data.memories || {};
|
|
300
|
+
console.log(`Memory Stats (org): ${m.total ?? 0} valid memories, ${m.not_valid ?? 0} superseded or deleted (kept for history), ${data.pinned ?? 0} pinned, last write ${data.last_write_at || 'never'}`);
|
|
301
|
+
if (m.by_type) console.log(`By type: ${Object.entries(m.by_type).map(([k, v]) => `${k} ${v}`).join(', ')}`);
|
|
302
|
+
formatOutput({
|
|
303
|
+
columns: ['agent', 'count'], headers: ['Agent', 'Memories'],
|
|
304
|
+
rows: Object.entries(data.by_agent || {}).map(([agent, count]) => ({ agent, count })),
|
|
305
|
+
}, { format });
|
|
306
|
+
formatOutput({
|
|
307
|
+
columns: ['user_id', 'kind', 'count', 'agents'], headers: ['Writer', 'Kind', 'Memories', 'Agents'],
|
|
308
|
+
rows: (data.by_user || []).map((u) => ({ ...u, agents: (u.agents || []).join(',') })),
|
|
309
|
+
}, { format });
|
|
310
|
+
console.log('Valid = not deleted and not superseded. A writer is the login a memory was saved under: your own, an agent identity, service/system (internal agents), or org-shared beliefs.');
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
memory
|
|
314
|
+
.command('core')
|
|
315
|
+
.description('Show the core set: memories loaded into every chat turn (pinned, plus auto-promoted decisions and facts rated high or critical)')
|
|
316
|
+
.option('--limit <n>', 'max memories', '20')
|
|
317
|
+
.action(async (options) => {
|
|
267
318
|
auth.requireAuth();
|
|
268
|
-
const
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
319
|
+
const ep = resolveEndpoint('memory.core-set');
|
|
320
|
+
const data = await client.get(ep.path, { service: ep.service, query: { limit: options.limit } });
|
|
321
|
+
formatOutput({
|
|
322
|
+
columns: ['id', 'core_reason', 'agent_id', 'memory_type', 'content'],
|
|
323
|
+
headers: ['ID', 'Why', 'Agent', 'Type', 'Content'],
|
|
324
|
+
rows: (data.memories || []).map((m) => ({ ...m, content: (m.content || '').slice(0, 80) })),
|
|
325
|
+
}, { format: program.opts().output, raw: data });
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
memory
|
|
329
|
+
.command('pin')
|
|
330
|
+
.description('Pin a memory into the core set so every chat turn carries it')
|
|
331
|
+
.argument('<id>', 'memory ID')
|
|
332
|
+
.action(async (id) => {
|
|
333
|
+
auth.requireAuth();
|
|
334
|
+
const ep = resolveEndpoint('memory.pin', { id });
|
|
335
|
+
const data = await client.post(ep.path, { service: ep.service, body: {} });
|
|
336
|
+
if (program.opts().output === 'json') { console.log(JSON.stringify(data, null, 2)); return; }
|
|
337
|
+
printSuccess(`Pinned ${id} into the core set.`);
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
memory
|
|
341
|
+
.command('unpin')
|
|
342
|
+
.description('Take a memory out of the core set (also stops it being auto-promoted)')
|
|
343
|
+
.argument('<id>', 'memory ID')
|
|
344
|
+
.action(async (id) => {
|
|
345
|
+
auth.requireAuth();
|
|
346
|
+
const ep = resolveEndpoint('memory.unpin', { id });
|
|
347
|
+
const data = await client.delete(ep.path, { service: ep.service });
|
|
348
|
+
if (program.opts().output === 'json') { console.log(JSON.stringify(data, null, 2)); return; }
|
|
349
|
+
printSuccess(`Unpinned ${id}: it stays a memory, but is no longer in the core set.`);
|
|
273
350
|
});
|
|
274
351
|
|
|
275
352
|
memory
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `quox mind implants|implant <id>|health`: QuoxMind cognitive implants (read-only).
|
|
3
|
+
* Collector routes under /api/v1/mind (org-scoped). Implants with no recorded
|
|
4
|
+
* activity say so; nothing is invented.
|
|
5
|
+
*/
|
|
6
|
+
import { formatOutput } from '../core/output.js';
|
|
7
|
+
import { resolveEndpoint } from '../api/endpoints.js';
|
|
8
|
+
import { resolveOrgId } from './helpers.js';
|
|
9
|
+
|
|
10
|
+
export function registerMindCommands(program, ctx) {
|
|
11
|
+
const { client, auth } = ctx;
|
|
12
|
+
const get = async (name, params) => {
|
|
13
|
+
auth.requireAuth();
|
|
14
|
+
const ep = resolveEndpoint(name, params);
|
|
15
|
+
return client.get(ep.path, { service: ep.service, query: { org_id: resolveOrgId(auth, program) } });
|
|
16
|
+
};
|
|
17
|
+
const isJson = () => program.opts().output === 'json';
|
|
18
|
+
|
|
19
|
+
const mind = program.command('mind').description('QuoxMind: the cognitive implants (read-only)');
|
|
20
|
+
|
|
21
|
+
mind
|
|
22
|
+
.command('implants')
|
|
23
|
+
.description('List the cognitive implants: purpose, status, last activity')
|
|
24
|
+
.action(async () => {
|
|
25
|
+
const data = await get('mind.implants');
|
|
26
|
+
const rows = data.implants || [];
|
|
27
|
+
formatOutput(rows.map((i) => ({
|
|
28
|
+
ID: i.short,
|
|
29
|
+
NAME: i.name,
|
|
30
|
+
PURPOSE: i.purpose,
|
|
31
|
+
STATUS: i.status,
|
|
32
|
+
'LAST ACTIVITY': i.last_activity || 'no activity recorded',
|
|
33
|
+
// QUOXMIND-PROOF: firing evidence from the implant's own table (null = not tracked).
|
|
34
|
+
'LAST FIRED': i.activity_error ? 'unknown' : (typeof i.fired_7d === 'number' ? (i.last_fired || 'never') : 'not tracked'),
|
|
35
|
+
'7D': typeof i.fired_7d === 'number' && !i.activity_error ? i.fired_7d : '-',
|
|
36
|
+
})), {
|
|
37
|
+
format: program.opts().output,
|
|
38
|
+
columns: ['ID', 'NAME', 'STATUS', 'LAST FIRED', '7D', 'PURPOSE'],
|
|
39
|
+
emptyMessage: 'No implants registered.',
|
|
40
|
+
raw: rows,
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
mind
|
|
45
|
+
.command('implant <id>')
|
|
46
|
+
.description('One implant in detail (e.g. predict, reflect, repobrain, qortex)')
|
|
47
|
+
.action(async (id) => {
|
|
48
|
+
const d = await get('mind.implant', { id });
|
|
49
|
+
if (isJson()) { console.log(JSON.stringify(d, null, 2)); return; }
|
|
50
|
+
console.log(`${d.name} (${d.short}) · ${d.status}${d.tier ? ` · ${d.tier}` : ''}`);
|
|
51
|
+
console.log(d.description || d.purpose);
|
|
52
|
+
if (d.inspired_by) console.log(`Inspired by: ${d.inspired_by}`);
|
|
53
|
+
if (d.summary) console.log(`Now: ${d.summary}`);
|
|
54
|
+
console.log(d.activity);
|
|
55
|
+
if (typeof d.fired_7d === 'number' && !d.activity_error) console.log(`Fired: ${d.last_fired || 'never'} · ${d.fired_7d} in the last 7 days${d.activity_source ? ` (from ${d.activity_source})` : ''}`);
|
|
56
|
+
else if (d.activity_error) console.log(`Fired: unknown (${d.activity_error})`);
|
|
57
|
+
for (const [k, v] of Object.entries(d.metrics || {})) if (v !== null && typeof v !== 'object') console.log(` ${k}: ${v}`);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
mind
|
|
61
|
+
.command('health')
|
|
62
|
+
.description('Summary of QuoxMind health across all implants')
|
|
63
|
+
.action(async () => {
|
|
64
|
+
const d = await get('mind.health');
|
|
65
|
+
if (isJson()) { console.log(JSON.stringify(d, null, 2)); return; }
|
|
66
|
+
console.log(`${d.total} implants: ${Object.entries(d.by_status).map(([k, v]) => `${v} ${k}`).join(', ')}`);
|
|
67
|
+
console.log(`${d.with_activity} with recorded activity, ${d.no_activity} with no activity recorded`);
|
|
68
|
+
if (typeof d.fired_7d_total === 'number') console.log(`Last 7 days: ${d.fired_7d_implants} implants fired (${d.fired_7d_total} firings), ${d.silent_7d} silent, ${d.activity_unknown} not tracked`);
|
|
69
|
+
console.log(d.average_health === null ? 'Average health: not enough data' : `Average health: ${d.average_health} (over ${d.scored} scored)`);
|
|
70
|
+
});
|
|
71
|
+
}
|
package/lib/commands/mon.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Monitoring commands: Docker fleet, Wazuh security monitoring, AGH-chain
|
|
3
3
|
* agent honesty leaderboard, Prometheus metrics, and monitoring config.
|
|
4
4
|
*
|
|
5
|
-
* quox mon docker fleet|containers|container|logs|stats|stacks|df|action
|
|
5
|
+
* quox mon docker fleet|containers|container|inspect|logs|stats|stacks|df|action
|
|
6
6
|
* quox mon wazuh status|agents|alerts|vulns|deploy-history|deploy-agent|deploy-bulk|active-response
|
|
7
7
|
* quox mon agh-chain leaderboard|agent|events
|
|
8
8
|
* quox mon metrics
|
|
@@ -36,6 +36,47 @@ async function confirmOrSkip(program, options, message) {
|
|
|
36
36
|
return ans.confirm;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
|
|
40
|
+
/** One readable line per fact from the inspect health block (null when the container has no healthcheck). */
|
|
41
|
+
function healthRows(h) {
|
|
42
|
+
if (!h) return null;
|
|
43
|
+
const rows = [];
|
|
44
|
+
const streak = h.failing_streak ? ` (failing ${h.failing_streak} times)` : '';
|
|
45
|
+
rows.push(`health: ${h.status || 'unknown'}${streak}`);
|
|
46
|
+
if (h.test) {
|
|
47
|
+
const parts = [h.interval && `every ${h.interval}`, h.timeout && `timeout ${h.timeout}`, h.retries != null && `retries ${h.retries}`].filter(Boolean);
|
|
48
|
+
rows.push(`probe: ${h.test}${parts.length ? ' ' + parts.join(', ') : ''}`);
|
|
49
|
+
}
|
|
50
|
+
for (const p of h.last_probes || []) {
|
|
51
|
+
const o = String(p.output || '').replace(/\s+/g, ' ').trim();
|
|
52
|
+
rows.push(`last: exit ${p.exit_code}${o ? ` '${o}'` : ''}${p.end ? ` at ${p.end}` : ''}`);
|
|
53
|
+
}
|
|
54
|
+
return rows;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* DEVFLEET-DOCKER: an error body is not data. If the collector reports a refused/failed Docker
|
|
59
|
+
* read, say so (stderr) and set a non-zero exit instead of printing an empty list. Partial fleets
|
|
60
|
+
* print the failed hosts as warnings. Returns true when the whole read failed (caller stops).
|
|
61
|
+
*/
|
|
62
|
+
function reportDockerReadFailures(data) {
|
|
63
|
+
if (!data) return false;
|
|
64
|
+
const failedHosts = Array.isArray(data.hosts_failed) ? data.hosts_failed
|
|
65
|
+
: (data.success === false && Array.isArray(data.hosts) ? data.hosts.filter((h) => h && h.ok === false) : []);
|
|
66
|
+
if (data.success === false) {
|
|
67
|
+
const tag = data.refused ? ' (refused, error_code ' + (data.error_code || 'approval_required') + ')' : '';
|
|
68
|
+
console.error('Docker read failed' + tag + ': ' + (data.error || 'unknown error'));
|
|
69
|
+
for (const h of failedHosts) console.error(' ' + h.host + ': ' + (h.error || 'failed'));
|
|
70
|
+
process.exitCode = 1;
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
if (data.partial) {
|
|
74
|
+
console.error('Partial result: ' + failedHosts.length + ' host(s) could not be read; the list below is incomplete.');
|
|
75
|
+
for (const h of failedHosts) console.error(' ' + h.host + ': ' + (h.error || 'failed'));
|
|
76
|
+
}
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
|
|
39
80
|
export function registerMonCommands(program, ctx) {
|
|
40
81
|
const { client, auth } = ctx;
|
|
41
82
|
|
|
@@ -59,6 +100,7 @@ export function registerMonCommands(program, ctx) {
|
|
|
59
100
|
auth.requireAuth();
|
|
60
101
|
const ep = resolveEndpoint('mon.docker.fleet.list');
|
|
61
102
|
const data = await client.get(ep.path, { service: ep.service, query: options.refresh ? { refresh: 'true' } : undefined });
|
|
103
|
+
if (reportDockerReadFailures(data)) return;
|
|
62
104
|
const hosts = data.hosts || [];
|
|
63
105
|
formatOutput({
|
|
64
106
|
// fleet #392: docker fleet rows are id/containers_total/docker_version; ip and status don't exist.
|
|
@@ -82,6 +124,7 @@ export function registerMonCommands(program, ctx) {
|
|
|
82
124
|
if (options.all === false) query.all = 'false';
|
|
83
125
|
if (options.refresh) query.refresh = 'true';
|
|
84
126
|
const data = await client.get(ep.path, { service: ep.service, query });
|
|
127
|
+
if (reportDockerReadFailures(data)) return;
|
|
85
128
|
formatOutput({
|
|
86
129
|
columns: ['host_id', 'id', 'name', 'image', 'state', 'status'],
|
|
87
130
|
headers: ['Host', 'ID', 'Name', 'Image', 'State', 'Status'],
|
|
@@ -98,6 +141,9 @@ export function registerMonCommands(program, ctx) {
|
|
|
98
141
|
auth.requireAuth();
|
|
99
142
|
const ep = resolveEndpoint('mon.docker.containers.get', { id });
|
|
100
143
|
const data = await client.get(ep.path, { service: ep.service, query: { host: options.host } });
|
|
144
|
+
// quox#1001: a 200 with success:false (e.g. "Bastion not configured") is a failure, not an
|
|
145
|
+
// empty container. Surface the error on stderr + exit non-zero instead of rendering a blank card.
|
|
146
|
+
if (reportDockerReadFailures(data)) return;
|
|
101
147
|
formatDetail(data, {
|
|
102
148
|
format: program.opts().output,
|
|
103
149
|
title: `Container: ${id} (${options.host})`,
|
|
@@ -106,6 +152,24 @@ export function registerMonCommands(program, ctx) {
|
|
|
106
152
|
});
|
|
107
153
|
});
|
|
108
154
|
|
|
155
|
+
docker
|
|
156
|
+
.command('inspect')
|
|
157
|
+
.description('Show container state and health (failing probe command and last results)')
|
|
158
|
+
.argument('<id>', 'container ID')
|
|
159
|
+
.requiredOption('--host <hostId>', 'host the container lives on')
|
|
160
|
+
.action(async (id, options) => {
|
|
161
|
+
auth.requireAuth();
|
|
162
|
+
const ep = resolveEndpoint('mon.docker.containers.inspect', { id });
|
|
163
|
+
const data = await client.get(ep.path, { service: ep.service, query: { host: options.host } });
|
|
164
|
+
if (reportDockerReadFailures(data)) return;
|
|
165
|
+
if (program.opts().output === 'json') { console.log(JSON.stringify(data, null, 2)); return; }
|
|
166
|
+
const d = data.details || {};
|
|
167
|
+
console.log(`Container: ${id} (${options.host})`);
|
|
168
|
+
console.log(`state: ${d.status || 'unknown'} · image: ${d.image || 'unknown'}`);
|
|
169
|
+
const rows = healthRows(d.health);
|
|
170
|
+
console.log(rows ? rows.join('\n') : 'health: no healthcheck configured');
|
|
171
|
+
});
|
|
172
|
+
|
|
109
173
|
docker
|
|
110
174
|
.command('logs')
|
|
111
175
|
.description('Show container logs')
|
|
@@ -143,6 +207,7 @@ export function registerMonCommands(program, ctx) {
|
|
|
143
207
|
auth.requireAuth();
|
|
144
208
|
const ep = resolveEndpoint('mon.docker.stacks.list');
|
|
145
209
|
const data = await client.get(ep.path, { service: ep.service, query: options.host ? { host: options.host } : undefined });
|
|
210
|
+
if (reportDockerReadFailures(data)) return;
|
|
146
211
|
formatOutput({
|
|
147
212
|
columns: ['host_id', 'name', 'status', 'services'],
|
|
148
213
|
headers: ['Host', 'Stack', 'Status', 'Services'],
|
|
@@ -181,7 +246,9 @@ export function registerMonCommands(program, ctx) {
|
|
|
181
246
|
const ep = resolveEndpoint('mon.docker.containers.action', { id, action });
|
|
182
247
|
const data = await client.post(ep.path, { service: ep.service, body: { host: options.host } });
|
|
183
248
|
if (data.success === false) {
|
|
184
|
-
|
|
249
|
+
// quox#1001: surface the real server error and exit non-zero (was exit 0 = success on failure).
|
|
250
|
+
printWarning(`Action failed: ${data.error || data.message || 'unknown error'}`);
|
|
251
|
+
process.exitCode = 1;
|
|
185
252
|
return;
|
|
186
253
|
}
|
|
187
254
|
printSuccess(`Container ${id}: ${action} succeeded.`);
|
|
@@ -35,7 +35,7 @@ export function registerMonitoringCommands(program, ctx) {
|
|
|
35
35
|
// nothing is stored); logged-in now actually sends the token.
|
|
36
36
|
const data = await client.get(resolveEndpoint('v1.health.aggregate.list').path, {
|
|
37
37
|
service: resolveEndpoint('v1.health.aggregate.list').service,
|
|
38
|
-
timeout:
|
|
38
|
+
timeout: 15000, // #930: aggregate fan-out, 5s too tight under load
|
|
39
39
|
});
|
|
40
40
|
formatDetail(data, { format: program.opts().output, title: 'Platform Health' });
|
|
41
41
|
});
|