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,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* quox kuma sla ... : signed, verifiable uptime reports (NQ-KUMA-7).
|
|
3
|
+
*
|
|
4
|
+
* quox kuma sla [--month YYYY-MM | --from <iso> --to <iso>] [--out <file>] [--json] generate + sign + store
|
|
5
|
+
* quox kuma sla list this org's stored reports
|
|
6
|
+
* quox kuma sla verify <id|file> [--public-key <base64>] recompute the hash, check the signature
|
|
7
|
+
*
|
|
8
|
+
* `verify <id>` asks the collector, which checks against its pinned WARD key (trust anchored).
|
|
9
|
+
* `verify <file>` is fully OFFLINE: it recomputes sha256 over the canonical report and checks the Ed25519
|
|
10
|
+
* signature with node:crypto. Against the key embedded in the file this proves integrity only (anyone can
|
|
11
|
+
* mint a keypair); pass --public-key <raw 32 byte base64 WARD key> to pin the signer yourself.
|
|
12
|
+
*
|
|
13
|
+
* Exit codes: 0 ok / verified, 1 error, 2 verification FAILED.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { readFileSync, writeFileSync, existsSync } from 'node:fs';
|
|
17
|
+
import { createHash, createPublicKey, verify as cryptoVerify } from 'node:crypto';
|
|
18
|
+
import { resolveEndpoint } from '../api/endpoints.js';
|
|
19
|
+
import { line as glyphLine } from '../core/quoxglyph.js';
|
|
20
|
+
|
|
21
|
+
// Same canonical form as services/collector/lib/bundleSigner.js canonicalize (sorted keys, no whitespace).
|
|
22
|
+
export function canonicalize(val) {
|
|
23
|
+
if (val === null || val === undefined) return JSON.stringify(val);
|
|
24
|
+
if (typeof val !== 'object') return JSON.stringify(val);
|
|
25
|
+
if (Array.isArray(val)) return `[${val.map((v) => canonicalize(v)).join(',')}]`;
|
|
26
|
+
const keys = Object.keys(val).filter((k) => val[k] !== undefined).sort();
|
|
27
|
+
return `{${keys.map((k) => `${JSON.stringify(k)}:${canonicalize(val[k])}`).join(',')}}`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const SPKI_PREFIX = Buffer.from('302a300506032b6570032100', 'hex');
|
|
31
|
+
const keyFromRaw = (b64) => {
|
|
32
|
+
try {
|
|
33
|
+
const raw = Buffer.from(String(b64 || ''), 'base64');
|
|
34
|
+
if (raw.length !== 32) return null;
|
|
35
|
+
return createPublicKey({ key: Buffer.concat([SPKI_PREFIX, raw]), format: 'der', type: 'spki' });
|
|
36
|
+
} catch { return null; }
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/** Offline verification of an export bundle. Mirrors kumaSlaReports.verifyBundle. */
|
|
40
|
+
export function verifyBundleOffline(bundle, { publicKey } = {}) {
|
|
41
|
+
const out = { valid: false, hash_ok: false, binding_ok: false, signature_ok: false, key_source: 'none', trust_anchored: false };
|
|
42
|
+
if (!bundle || typeof bundle !== 'object' || !bundle.report || typeof bundle.report !== 'object') return { ...out, reason: 'malformed_bundle' };
|
|
43
|
+
const sig = bundle.signature;
|
|
44
|
+
if (!sig || !sig.signature || !sig.message) return { ...out, reason: 'missing_signature' };
|
|
45
|
+
const recomputed = createHash('sha256').update(canonicalize(bundle.report), 'utf-8').digest('hex');
|
|
46
|
+
out.recomputed_sha256 = recomputed;
|
|
47
|
+
out.hash_ok = recomputed === bundle.report_sha256;
|
|
48
|
+
if (!out.hash_ok) return { ...out, reason: 'report_hash_mismatch' };
|
|
49
|
+
const r = bundle.report;
|
|
50
|
+
const expected = r.period ? {
|
|
51
|
+
kind: 'quox.kuma.sla.report', report_id: bundle.report_id, org_id: r.org_id,
|
|
52
|
+
period_from: r.period.from, period_to: r.period.to, generated_at: r.generated_at, report_sha256: recomputed,
|
|
53
|
+
} : null;
|
|
54
|
+
out.binding_ok = !!expected && canonicalize(expected) === canonicalize(sig.message);
|
|
55
|
+
if (!out.binding_ok) return { ...out, reason: 'signature_not_bound_to_report' };
|
|
56
|
+
if (sig.sig_type !== 'ed25519') return { ...out, reason: `unverifiable_sig_type:${sig.sig_type}` };
|
|
57
|
+
let key = null; let source = 'none';
|
|
58
|
+
if (publicKey) { key = keyFromRaw(publicKey); source = 'pinned'; if (!key) return { ...out, reason: 'bad_public_key' }; }
|
|
59
|
+
else { key = keyFromRaw(sig.public_key); source = 'embedded'; }
|
|
60
|
+
if (!key) return { ...out, reason: 'no_verification_key' };
|
|
61
|
+
out.key_source = source;
|
|
62
|
+
try {
|
|
63
|
+
out.signature_ok = cryptoVerify(null, Buffer.from(canonicalize(sig.message), 'utf-8'), key, Buffer.from(String(sig.signature), 'base64'));
|
|
64
|
+
} catch (err) { return { ...out, reason: `verify_error:${err.message}` }; }
|
|
65
|
+
if (!out.signature_ok) return { ...out, reason: 'signature_mismatch' };
|
|
66
|
+
out.valid = true;
|
|
67
|
+
out.trust_anchored = source === 'pinned' && !sig.warning;
|
|
68
|
+
if (sig.warning) out.warning = sig.warning;
|
|
69
|
+
return out;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const dur = (s) => {
|
|
73
|
+
if (s === null || s === undefined) return '-';
|
|
74
|
+
if (s < 60) return `${s}s`;
|
|
75
|
+
if (s < 3600) return `${Math.floor(s / 60)}m ${s % 60}s`;
|
|
76
|
+
return `${Math.floor(s / 3600)}h ${Math.floor((s % 3600) / 60)}m`;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
export function registerKumaSla(kuma, { client, auth, jsonOut, guard, KumaFail, pad }) {
|
|
80
|
+
const slaFail = (verb, ep, err) => {
|
|
81
|
+
const status = err && (err.status ?? err.statusCode);
|
|
82
|
+
const msg = (err && err.message) || String(err);
|
|
83
|
+
const head = `quox kuma ${verb}: ${ep.method} ${ep.path} failed${status ? ` with HTTP ${status}` : ''}`;
|
|
84
|
+
if (status === 422) return new KumaFail(`${head}: Uptime Kuma is not configured for this org (add the uptime_kuma credential in the vault).`);
|
|
85
|
+
if (status === 404) return new KumaFail(`${head}: not found in this org.`);
|
|
86
|
+
return new KumaFail(`${head}: ${msg}`);
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const sla = kuma
|
|
90
|
+
.command('sla')
|
|
91
|
+
.description('Signed, verifiable uptime (SLA) report from Kuma heartbeats; `sla verify <id|file>` checks one')
|
|
92
|
+
.option('--month <YYYY-MM>', 'calendar month (UTC), e.g. 2026-09')
|
|
93
|
+
.option('--from <iso>', 'range start (with --to)')
|
|
94
|
+
.option('--to <iso>', 'range end, exclusive')
|
|
95
|
+
.option('--out <file>', 'also write the signed bundle here for offline verification')
|
|
96
|
+
.addHelpText('after', '\nExit codes: 0 ok, 1 error, 2 `sla verify` found the report does NOT verify.')
|
|
97
|
+
.action(guard('sla', async (options) => {
|
|
98
|
+
auth.requireAuth();
|
|
99
|
+
if (!options.month && !(options.from && options.to)) throw new KumaFail('quox kuma sla: give --month YYYY-MM, or both --from and --to.');
|
|
100
|
+
const ep = resolveEndpoint('mon.kuma.sla.create');
|
|
101
|
+
const body = options.month ? { month: options.month } : { from: options.from, to: options.to };
|
|
102
|
+
let data;
|
|
103
|
+
try { data = await client.post(ep.path, { service: ep.service, body }); } catch (err) { throw slaFail('sla', ep, err); }
|
|
104
|
+
if (!data || data.success === false || data.error || !data.report || !data.signature) {
|
|
105
|
+
throw new KumaFail(`quox kuma sla: ${ep.method} ${ep.path} returned an error body: ${(data && data.error) || 'response had no report'}`);
|
|
106
|
+
}
|
|
107
|
+
if (options.out) {
|
|
108
|
+
writeFileSync(options.out, `${JSON.stringify({ report_id: data.report_id, report: data.report, report_sha256: data.report_sha256, signature: data.signature }, null, 2)}\n`);
|
|
109
|
+
}
|
|
110
|
+
if (jsonOut()) { console.log(JSON.stringify(data, null, 2)); return; }
|
|
111
|
+
const r = data.report; const ro = r.rollup;
|
|
112
|
+
console.log(glyphLine('kuma', 'sla', { result: `${r.period.label} · ${ro.uptime_pct === null ? 'no data' : `${ro.uptime_pct}%`}`, status: ro.uptime_pct === null ? 'warn' : 'ok' }));
|
|
113
|
+
console.log(`Report ${data.report_id} sha256 ${data.report_sha256}`);
|
|
114
|
+
console.log(`Period ${r.period.from} to ${r.period.effective_to}${r.period.partial ? ' (partial: the period has not ended)' : ''}`);
|
|
115
|
+
const rows = [['MONITOR', 'UPTIME', 'OUTAGES', 'DOWNTIME', 'LONGEST', 'MTTR', 'MAINT', 'GAPS']];
|
|
116
|
+
for (const m of r.monitors) {
|
|
117
|
+
rows.push([m.name || m.monitor_id, m.status === 'no_data' ? 'no data' : `${m.uptime_pct}%`, m.outages.count, dur(m.outages.total_downtime_seconds), dur(m.outages.longest_seconds), dur(m.outages.mttr_seconds), m.beats.maintenance, m.coverage.gaps.length]);
|
|
118
|
+
}
|
|
119
|
+
console.log(`\n${pad(rows).join('\n')}`);
|
|
120
|
+
console.log(`\nOrg: ${ro.uptime_pct === null ? 'no data' : `${ro.uptime_pct}%`} over ${ro.monitors_with_data}/${ro.monitors_total} monitors with data; ${ro.outages.count} ${ro.outages.count === 1 ? 'outage' : 'outages'}, ${dur(ro.outages.total_downtime_seconds)} down, MTTR ${dur(ro.outages.mttr_seconds)}.`);
|
|
121
|
+
if (ro.monitors_no_data) console.log(`${ro.monitors_no_data} ${ro.monitors_no_data === 1 ? 'monitor has' : 'monitors have'} no heartbeats in the period: reported as no data, not as 100%.`);
|
|
122
|
+
console.log(data.trust_anchored ? 'Signed with the WARD key and witnessed.' : 'WARNING: signed with an ephemeral key (no persistent WARD key on this collector): integrity only, not trust anchored.');
|
|
123
|
+
console.log(`Verify: quox kuma sla verify ${options.out || data.report_id}`);
|
|
124
|
+
}));
|
|
125
|
+
|
|
126
|
+
sla
|
|
127
|
+
.command('list')
|
|
128
|
+
.description('This org\'s stored SLA reports')
|
|
129
|
+
.action(guard('sla list', async () => {
|
|
130
|
+
auth.requireAuth();
|
|
131
|
+
const ep = resolveEndpoint('mon.kuma.sla.list');
|
|
132
|
+
let data;
|
|
133
|
+
try { data = await client.get(ep.path, { service: ep.service }); } catch (err) { throw slaFail('sla list', ep, err); }
|
|
134
|
+
if (!data || data.success === false || data.error || !Array.isArray(data.reports)) {
|
|
135
|
+
throw new KumaFail(`quox kuma sla list: ${ep.method} ${ep.path} returned an error body: ${(data && data.error) || 'response had no reports array'}`);
|
|
136
|
+
}
|
|
137
|
+
if (jsonOut()) { console.log(JSON.stringify(data, null, 2)); return; }
|
|
138
|
+
console.log(glyphLine('kuma', 'sla list', { result: `${data.reports.length} ${data.reports.length === 1 ? 'report' : 'reports'}`, status: 'ok' }));
|
|
139
|
+
if (!data.reports.length) { console.log('No reports yet. Generate one: quox kuma sla --month YYYY-MM'); return; }
|
|
140
|
+
const rows = [['REPORT', 'PERIOD', 'CREATED', 'SHA256']];
|
|
141
|
+
for (const r of data.reports) rows.push([r.report_id, r.period, r.created_at, String(r.report_sha256).slice(0, 16)]);
|
|
142
|
+
console.log(pad(rows).join('\n'));
|
|
143
|
+
}));
|
|
144
|
+
|
|
145
|
+
sla
|
|
146
|
+
.command('verify')
|
|
147
|
+
.description('Recompute the report hash and check the signature (id: via the collector, pinned WARD key; file: fully offline)')
|
|
148
|
+
.argument('<id|file>', 'a report id from `quox kuma sla list`, or a bundle file written by `sla --out`')
|
|
149
|
+
.option('--public-key <base64>', 'offline: pin this raw 32 byte Ed25519 WARD public key instead of trusting the key in the file')
|
|
150
|
+
.action(guard('sla verify', async (target, options) => {
|
|
151
|
+
let result;
|
|
152
|
+
if (existsSync(target)) {
|
|
153
|
+
let bundle;
|
|
154
|
+
try { bundle = JSON.parse(readFileSync(target, 'utf-8')); } catch (err) { throw new KumaFail(`quox kuma sla verify: ${target} is not valid JSON: ${err.message}`); }
|
|
155
|
+
result = { report_id: bundle && bundle.report_id, mode: 'offline', ...verifyBundleOffline(bundle, { publicKey: options.publicKey }) };
|
|
156
|
+
} else {
|
|
157
|
+
auth.requireAuth();
|
|
158
|
+
const ep = resolveEndpoint('mon.kuma.sla.verify', { id: target });
|
|
159
|
+
let data;
|
|
160
|
+
try { data = await client.get(ep.path, { service: ep.service }); } catch (err) { throw slaFail('sla verify', ep, err); }
|
|
161
|
+
if (!data || data.success === false || data.error || typeof data.valid !== 'boolean') {
|
|
162
|
+
throw new KumaFail(`quox kuma sla verify: ${ep.method} ${ep.path} returned an error body: ${(data && data.error) || 'response had no verdict'}`);
|
|
163
|
+
}
|
|
164
|
+
result = { mode: 'collector', ...data };
|
|
165
|
+
}
|
|
166
|
+
if (jsonOut()) { console.log(JSON.stringify(result, null, 2)); } else {
|
|
167
|
+
console.log(glyphLine('kuma', 'sla verify', { result: result.valid ? `verified (${result.mode})` : `NOT verified: ${result.reason}`, status: result.valid ? 'ok' : 'error' }));
|
|
168
|
+
console.log(`hash ${result.hash_ok ? 'matches' : 'DOES NOT match'} binding ${result.binding_ok ? 'ok' : 'FAILED'} signature ${result.signature_ok ? 'valid' : 'INVALID'} key ${result.key_source}${result.trust_anchored ? ' (trust anchored)' : ' (not trust anchored)'}`);
|
|
169
|
+
if (result.valid && !result.trust_anchored) console.log('Integrity holds, but the signer was not checked against the WARD key. Pin it with --public-key, or verify by id through the collector.');
|
|
170
|
+
if (result.warning) console.log(`Warning: ${result.warning}`);
|
|
171
|
+
}
|
|
172
|
+
if (!result.valid) process.exitCode = 2;
|
|
173
|
+
}));
|
|
174
|
+
}
|
package/lib/commands/licence.js
CHANGED
|
@@ -5,53 +5,26 @@
|
|
|
5
5
|
* Phase 1 (instance-local): status reads the lease (never the key), refresh
|
|
6
6
|
* runs the renewal, seats lists/cleans band holders (dry run default).
|
|
7
7
|
*
|
|
8
|
-
* Phase 2 (owner-only
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* never sees, prints or sends key material or licence keys.
|
|
8
|
+
* Phase 2 (owner-only vendor tooling for the quox.ai website) lives in
|
|
9
|
+
* lib/vendor/licenceOwner.js and is registered only when that file is present.
|
|
10
|
+
* lib/vendor/ is excluded from the npm package (quox#1134): the public build
|
|
11
|
+
* never carries it.
|
|
13
12
|
*/
|
|
14
13
|
|
|
15
|
-
import { writeFileSync } from 'node:fs';
|
|
16
14
|
import { formatOutput, formatDetail, printSuccess, printInfo, printWarning } from '../core/output.js';
|
|
17
15
|
import { resolveEndpoint } from '../api/endpoints.js';
|
|
18
16
|
import { QuoxValidationError } from '../core/errors.js';
|
|
19
|
-
import {
|
|
20
|
-
DEFAULT_WEBSITE_URL, DEFAULT_WEBSITE_PROFILE, normaliseWebsiteUrl, websiteLogin, websitePost,
|
|
21
|
-
saveWebsiteSession, clearWebsiteSession, getWebsiteSession,
|
|
22
|
-
} from '../core/websiteSession.js';
|
|
23
17
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const ID_LIST_RE = /^[a-z0-9][a-z0-9._-]{0,63}$/;
|
|
33
|
-
|
|
34
|
-
export function parseIdList(value, flag) {
|
|
35
|
-
if (value === undefined) return undefined;
|
|
36
|
-
const ids = String(value).split(',').map((s) => s.trim()).filter(Boolean);
|
|
37
|
-
for (const id of ids) {
|
|
38
|
-
if (!ID_LIST_RE.test(id)) throw new QuoxValidationError(`${flag}: invalid plugin id "${id}"`);
|
|
39
|
-
}
|
|
40
|
-
return ids;
|
|
18
|
+
// quox#1134: the owner-only website verbs are vendor tooling, kept out of the published package (package.json
|
|
19
|
+
// "files" excludes lib/vendor/). Present in the dev tree, absent for every npm user.
|
|
20
|
+
let VENDOR_OWNER = null;
|
|
21
|
+
try {
|
|
22
|
+
VENDOR_OWNER = await import('../vendor/licenceOwner.js');
|
|
23
|
+
} catch (e) {
|
|
24
|
+
if (!(e?.code === 'ERR_MODULE_NOT_FOUND' && String(e?.message || '').includes('licenceOwner'))) throw e;
|
|
41
25
|
}
|
|
42
26
|
|
|
43
|
-
export function
|
|
44
|
-
if (value === undefined) return undefined;
|
|
45
|
-
const n = Number(value);
|
|
46
|
-
if (!Number.isInteger(n) || n < 1 || n > 1000) throw new QuoxValidationError('--orgs must be an integer between 1 and 1000');
|
|
47
|
-
return n;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function list(arr) {
|
|
51
|
-
return arr && arr.length ? arr.join(', ') : '(none)';
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export function registerLicenceCommands(program, ctx) {
|
|
27
|
+
export function registerLicenceCommands(program, ctx, { owner = VENDOR_OWNER } = {}) {
|
|
55
28
|
const { client, auth } = ctx;
|
|
56
29
|
const fetchImpl = ctx.websiteFetch || globalThis.fetch;
|
|
57
30
|
const json = () => program.opts().output === 'json';
|
|
@@ -60,8 +33,8 @@ export function registerLicenceCommands(program, ctx) {
|
|
|
60
33
|
.command('licence')
|
|
61
34
|
.alias('license')
|
|
62
35
|
.description('Instance licence status, lease refresh and seat bands')
|
|
63
|
-
.addHelpText('after', OWNER_HELP)
|
|
64
36
|
.action(() => { licence.help(); });
|
|
37
|
+
if (owner) licence.addHelpText('after', owner.OWNER_HELP);
|
|
65
38
|
|
|
66
39
|
licence
|
|
67
40
|
.command('status')
|
|
@@ -136,9 +109,21 @@ export function registerLicenceCommands(program, ctx) {
|
|
|
136
109
|
.description('List a plugin band holders; --release-dead frees non-live holders (dry run unless --yes)')
|
|
137
110
|
.argument('<pluginId>', 'plugin id, e.g. notesq')
|
|
138
111
|
.option('--release-dead', 'release holders that are not live orgs (deleted orgs, user: ids)')
|
|
112
|
+
.option('--release <orgId>', 'release a LIVE org seat (its install of the plugin is disabled); dry run unless --yes')
|
|
113
|
+
.option('--to <orgId>', 'with --release: hand the freed seat to this org in the same call')
|
|
139
114
|
.option('--yes', 'actually release (otherwise dry run)')
|
|
140
115
|
.action(async (pluginId, opts) => {
|
|
141
116
|
auth.requireAuth();
|
|
117
|
+
if (opts.to && !opts.release) {
|
|
118
|
+
const e = new Error('--to only works together with --release <orgId>');
|
|
119
|
+
e.exitCode = 2;
|
|
120
|
+
throw e;
|
|
121
|
+
}
|
|
122
|
+
if (opts.release && opts.releaseDead) {
|
|
123
|
+
const e = new Error('use either --release or --release-dead, not both');
|
|
124
|
+
e.exitCode = 2;
|
|
125
|
+
throw e;
|
|
126
|
+
}
|
|
142
127
|
const lep = resolveEndpoint('setup.licensing.seats.list', { pluginId });
|
|
143
128
|
const list = await client.get(lep.path, { service: lep.service });
|
|
144
129
|
let rel = null;
|
|
@@ -146,7 +131,15 @@ export function registerLicenceCommands(program, ctx) {
|
|
|
146
131
|
const rep = resolveEndpoint('setup.licensing.seats.releaseDead', { pluginId });
|
|
147
132
|
rel = await client.post(rep.path, { service: rep.service, body: { dry_run: !opts.yes } });
|
|
148
133
|
}
|
|
149
|
-
|
|
134
|
+
let live = null;
|
|
135
|
+
if (opts.release) {
|
|
136
|
+
const rp = resolveEndpoint('setup.licensing.seats.release', { pluginId });
|
|
137
|
+
live = await client.post(rp.path, {
|
|
138
|
+
service: rp.service,
|
|
139
|
+
body: { org_id: opts.release, ...(opts.to ? { to_org_id: opts.to } : {}), dry_run: !opts.yes },
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
if (json()) { console.log(JSON.stringify({ ...list, release: rel, live_release: live }, null, 2)); return; }
|
|
150
143
|
printInfo(`${pluginId}: ${list.holders.length} holder(s), cap ${list.allowance ?? '(no band)'}`);
|
|
151
144
|
formatOutput({
|
|
152
145
|
columns: ['id', 'name', 'status'],
|
|
@@ -160,121 +153,25 @@ export function registerLicenceCommands(program, ctx) {
|
|
|
160
153
|
printSuccess(`Released ${rel.released.length} non-live holder(s): ${rel.released.join(', ') || '(none)'}`);
|
|
161
154
|
}
|
|
162
155
|
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
for await (const c of process.stdin) chunks.push(Buffer.from(c));
|
|
181
|
-
password = Buffer.concat(chunks).toString('utf8').split('\n')[0].trim();
|
|
182
|
-
}
|
|
183
|
-
if (!username || !password) {
|
|
184
|
-
const { default: inquirer } = await import('inquirer');
|
|
185
|
-
if (!username) username = (await inquirer.prompt([{ type: 'input', name: 'u', message: 'Website admin username:' }])).u;
|
|
186
|
-
if (!password) password = (await inquirer.prompt([{ type: 'password', name: 'p', message: 'Password:', mask: '*' }])).p;
|
|
187
|
-
}
|
|
188
|
-
if (!username || !password) throw new QuoxValidationError('username and password are required');
|
|
189
|
-
const session = await websiteLogin({ url: opts.url, username, password, fetchImpl });
|
|
190
|
-
password = null;
|
|
191
|
-
saveWebsiteSession(opts.websiteProfile, session);
|
|
192
|
-
if (json()) { console.log(JSON.stringify({ profile: opts.websiteProfile, url: session.url, username: session.username, must_change_password: session.must_change_password }, null, 2)); return; }
|
|
193
|
-
printSuccess(`Website session saved for ${session.username} at ${session.url} (profile ${opts.websiteProfile}, valid ~24h). Password not stored.`);
|
|
194
|
-
if (session.must_change_password) printWarning('This admin must change their password on the website before licence operations will be allowed (HTTP 403 until then).');
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
licence
|
|
198
|
-
.command('website-logout')
|
|
199
|
-
.description('End the website admin session and delete the stored cookie')
|
|
200
|
-
.option('--website-profile <name>', 'website session profile', DEFAULT_WEBSITE_PROFILE)
|
|
201
|
-
.action(async (opts) => {
|
|
202
|
-
if (getWebsiteSession(opts.websiteProfile)) {
|
|
203
|
-
try { await websitePost(opts.websiteProfile, '/api/admin/logout', {}, { fetchImpl }); } catch { /* session may already be dead */ }
|
|
156
|
+
if (live) {
|
|
157
|
+
const who = `${live.org_name || live.org_id} (${live.org_id})`;
|
|
158
|
+
const loses = live.will_disable_install
|
|
159
|
+
? `its install of ${pluginId} goes disabled and the plugin stops working there`
|
|
160
|
+
: `it loses access to ${pluginId} (no enabled install to disable)`;
|
|
161
|
+
if (live.dry_run) {
|
|
162
|
+
printInfo(`DRY RUN: would release the ${pluginId} seat held by ${who}: ${loses}.`);
|
|
163
|
+
if (live.would_assign_to) printInfo(`DRY RUN: the freed seat would go to ${live.would_assign_to}.`);
|
|
164
|
+
else printInfo(`Seats are first-come: add --to <orgId> to hand the freed seat to a specific org, otherwise the first org to use ${pluginId} takes it.`);
|
|
165
|
+
printInfo(`Re-run with --yes to release.`);
|
|
166
|
+
} else if (live.released) {
|
|
167
|
+
printSuccess(`Released the ${pluginId} seat held by ${who}: ${loses}.`);
|
|
168
|
+
if (live.assigned_to) printSuccess(`Seat now held by ${live.assigned_to}.`);
|
|
169
|
+
else if (opts.to) printWarning(`The seat was freed but ${opts.to} could not take it (see the band with: quox licence seats ${pluginId}).`);
|
|
170
|
+
} else {
|
|
171
|
+
printWarning(`Nothing released: ${who} no longer holds a ${pluginId} seat.`);
|
|
172
|
+
}
|
|
204
173
|
}
|
|
205
|
-
const had = clearWebsiteSession(opts.websiteProfile);
|
|
206
|
-
printSuccess(had ? `Website session ${opts.websiteProfile} removed.` : `No website session ${opts.websiteProfile} was stored.`);
|
|
207
174
|
});
|
|
208
175
|
|
|
209
|
-
licence
|
|
210
|
-
.command('reissue')
|
|
211
|
-
.description('Owner-only: band bare paid ids and add ids on a licence record, re-signed on the website (dry run unless --apply)')
|
|
212
|
-
.argument('<licenseId>', 'licence id, e.g. lic_abc123')
|
|
213
|
-
.option('--orgs <n>', 'org band for every banded/added id (default 10)')
|
|
214
|
-
.option('--add <ids>', 'comma-separated ids to ADD as <id>@orgs=N')
|
|
215
|
-
.option('--band <ids>', 'comma-separated ids already present to FORCE-band')
|
|
216
|
-
.option('--apply', 'actually reissue (otherwise dry run, nothing changes)')
|
|
217
|
-
.option('--website-profile <name>', 'website session profile', DEFAULT_WEBSITE_PROFILE)
|
|
218
|
-
.action(async (licenseId, opts) => {
|
|
219
|
-
const body = { dryRun: !opts.apply };
|
|
220
|
-
const orgs = parseOrgs(opts.orgs);
|
|
221
|
-
if (orgs !== undefined) body.orgs = orgs;
|
|
222
|
-
const add = parseIdList(opts.add, '--add');
|
|
223
|
-
if (add) body.add = add;
|
|
224
|
-
const band = parseIdList(opts.band, '--band');
|
|
225
|
-
if (band) body.band = band;
|
|
226
|
-
|
|
227
|
-
const d = await websitePost(opts.websiteProfile, `/api/admin/licenses/${encodeURIComponent(licenseId)}/reissue`, body, { fetchImpl });
|
|
228
|
-
if (json()) { console.log(JSON.stringify(d, null, 2)); return; }
|
|
229
|
-
|
|
230
|
-
printInfo(`Licence ${d.license_id}${d.customer_email ? ` (${d.customer_email})` : ''}: max_servers ${d.max_servers ?? '?'}, expires ${d.expires_at || 'never'}`);
|
|
231
|
-
const removed = new Set(d.diff?.removed || []);
|
|
232
|
-
const added = new Set(d.diff?.added || []);
|
|
233
|
-
for (const e of d.current || []) console.log(` ${removed.has(e) ? '-' : ' '} ${e}`);
|
|
234
|
-
for (const e of d.next || []) if (added.has(e)) console.log(` + ${e}`);
|
|
235
|
-
if (d.unknown_ids?.length) printWarning(`Not issuable (unknown to the signed catalogue), apply will be refused: ${d.unknown_ids.join(', ')}`);
|
|
236
|
-
|
|
237
|
-
if (!d.changed) { printSuccess('No change needed: the licence already has these entitlements.'); return; }
|
|
238
|
-
if (d.dry_run) { printInfo('DRY RUN: nothing changed. Re-run with --apply to reissue.'); return; }
|
|
239
|
-
printSuccess(`Reissued ${d.license_id}. Audit/backup id: ${d.backup_id}`);
|
|
240
|
-
printInfo('Instances pick this up on their next lease renewal (~6h). To pull it now, on the instance: quox licence refresh');
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
licence
|
|
244
|
-
.command('catalogue-sign')
|
|
245
|
-
.alias('catalog-sign')
|
|
246
|
-
.description('Owner-only: re-sign the product catalogue with the live key on the website (dry run unless --apply)')
|
|
247
|
-
.option('--apply', 'actually sign and publish to the website API (otherwise dry run)')
|
|
248
|
-
.option('--out <path>', 'after --apply, save the signed catalogue (public artifact) here for committing')
|
|
249
|
-
.option('--website-profile <name>', 'website session profile', DEFAULT_WEBSITE_PROFILE)
|
|
250
|
-
.action(async (opts) => {
|
|
251
|
-
if (opts.out && !opts.apply) throw new QuoxValidationError('--out is only meaningful with --apply');
|
|
252
|
-
const d = await websitePost(opts.websiteProfile, '/api/admin/catalog/sign', { dryRun: !opts.apply }, { fetchImpl });
|
|
253
|
-
if (opts.out && d.artifact) writeFileSync(opts.out, JSON.stringify(d.artifact, null, 2) + '\n', 'utf8');
|
|
254
|
-
if (json()) {
|
|
255
|
-
const { artifact, ...rest } = d;
|
|
256
|
-
console.log(JSON.stringify({ ...rest, artifact_saved_to: opts.out && artifact ? opts.out : null }, null, 2));
|
|
257
|
-
return;
|
|
258
|
-
}
|
|
259
|
-
const p = d.published || {};
|
|
260
|
-
formatDetail({
|
|
261
|
-
published: p.exists ? `${p.verified ? 'verified' : 'NOT VERIFIED'}, sha256 ${p.sha256 ? p.sha256.slice(0, 16) : '?'}..., ${p.generated_at || '?'}` : '(none)',
|
|
262
|
-
products: `${p.product_count ?? 0} -> ${d.candidate?.product_count ?? '?'}`,
|
|
263
|
-
governed: `${p.governed_count ?? 0} -> ${d.candidate?.governed_count ?? '?'}`,
|
|
264
|
-
products_added: list(d.diff?.products_added),
|
|
265
|
-
products_removed: list(d.diff?.products_removed),
|
|
266
|
-
products_changed: list(d.diff?.products_changed),
|
|
267
|
-
governed_added: list(d.diff?.governed_added),
|
|
268
|
-
governed_removed: list(d.diff?.governed_removed),
|
|
269
|
-
signing_key: d.signing ? (d.signing.trusted ? `trusted (${d.signing.trusted_key_prefix}...)` : (d.signing.configured ? 'NOT the trusted key: apply will be refused' : 'not configured: apply will be refused')) : '?',
|
|
270
|
-
}, { title: d.dry_run ? 'Catalogue sign (DRY RUN)' : 'Catalogue sign' });
|
|
271
|
-
if (d.diff?.governed_added?.length) {
|
|
272
|
-
printWarning(`Newly governed: ${d.diff.governed_added.join(', ')}. Reissue the owner licence with these ids (quox licence reissue <id> --add ...) and refresh its lease BEFORE instances install this catalogue, or they lock out.`);
|
|
273
|
-
}
|
|
274
|
-
if (!d.changed) { printSuccess('No change: the published catalogue already matches.'); return; }
|
|
275
|
-
if (d.dry_run) { printInfo('DRY RUN: nothing signed. Re-run with --apply to sign.'); return; }
|
|
276
|
-
printSuccess(`Catalogue signed. sha256 ${d.sha256}, governed ${d.governed_count}, audit id ${d.audit_id}`);
|
|
277
|
-
if (opts.out) printInfo(`Signed catalogue saved to ${opts.out}.`);
|
|
278
|
-
printInfo('The website container copy does not survive a rebuild: commit the artifact to quox-website api/data/signed-catalog.json and quox-dashboard services/auth/data/signed-catalog.json (use --out to save it).');
|
|
279
|
-
});
|
|
176
|
+
if (owner) owner.registerLicenceOwnerCommands(licence, { json, fetchImpl });
|
|
280
177
|
}
|