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
package/lib/core/errors.js
CHANGED
|
@@ -25,6 +25,15 @@ export class QuoxApiError extends QuoxError {
|
|
|
25
25
|
* handlers read this to render something actionable; --json emits it raw.
|
|
26
26
|
*/
|
|
27
27
|
constructor(status, code, message, detail, body) {
|
|
28
|
+
// #1005: some services (quoxflow) answer { error: { code, message, details } }.
|
|
29
|
+
// Passed through as `code`, it printed as "Status: 400 [object Object]" and the
|
|
30
|
+
// real message and field errors were lost. Lift them out.
|
|
31
|
+
const structured = code && typeof code === 'object' ? code : null;
|
|
32
|
+
if (structured) {
|
|
33
|
+
if (structured.message && (!message || /^(Stream request|Request) failed with status/.test(message))) message = structured.message;
|
|
34
|
+
if (detail == null && structured.details != null) detail = structured.details;
|
|
35
|
+
code = typeof structured.code === 'string' ? structured.code : 'api_error';
|
|
36
|
+
}
|
|
28
37
|
super(message, statusToExitCode(status));
|
|
29
38
|
this.name = 'QuoxApiError';
|
|
30
39
|
this.status = status;
|
|
@@ -73,6 +82,25 @@ export class QuoxValidationError extends QuoxError {
|
|
|
73
82
|
}
|
|
74
83
|
}
|
|
75
84
|
|
|
85
|
+
/**
|
|
86
|
+
* CLI-ERRORS (owner 2026-10-17): the shared error contract. A command that knows what went wrong throws
|
|
87
|
+
* this: `headline` says what happened and why in plain words, `next` is the ONE best next step. The
|
|
88
|
+
* renderer prints exactly those two lines; technical detail (status, code, raw server text) only with
|
|
89
|
+
* --verbose. Exit code is the caller's choice (6 for "already decided differently", 5 for not found...).
|
|
90
|
+
*/
|
|
91
|
+
export class QuoxUserError extends QuoxError {
|
|
92
|
+
constructor(headline, { next = null, exitCode = 1, code = 'user_error', status, detail, body } = {}) {
|
|
93
|
+
super(headline, exitCode);
|
|
94
|
+
this.name = 'QuoxUserError';
|
|
95
|
+
this.headline = headline;
|
|
96
|
+
this.next = next;
|
|
97
|
+
this.code = code;
|
|
98
|
+
if (status) this.status = status;
|
|
99
|
+
if (detail != null) this.detail = detail;
|
|
100
|
+
if (body && typeof body === 'object') this.body = body;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
76
104
|
function statusToExitCode(status) {
|
|
77
105
|
if (status === 401) return 3;
|
|
78
106
|
if (status === 403) return 4;
|
|
@@ -85,7 +113,8 @@ function statusToExitCode(status) {
|
|
|
85
113
|
const STATUS_HINTS = {
|
|
86
114
|
401: 'Run `quox login` to authenticate.',
|
|
87
115
|
403: 'You may not have permission. Try `quox org switch` to change context.',
|
|
88
|
-
|
|
116
|
+
// #995: never name a verb that may not exist; handleError names the real one when it knows the group
|
|
117
|
+
404: 'Resource not found. Check the id; `quox <command> --help` shows how to list what exists.',
|
|
89
118
|
409: 'Resource conflict. The item may already exist.',
|
|
90
119
|
429: 'Rate limited. Wait a moment and try again.',
|
|
91
120
|
500: 'Server error. Use --debug for details.',
|
|
@@ -93,40 +122,320 @@ const STATUS_HINTS = {
|
|
|
93
122
|
503: 'Service temporarily unavailable. Check `quox service health`.',
|
|
94
123
|
};
|
|
95
124
|
|
|
96
|
-
|
|
125
|
+
// quox#803: a 403 signature_invalid is not a permissions problem; the org
|
|
126
|
+
// enforces trusted plugin keys. Only real verbs are named: `quox plugin
|
|
127
|
+
// trusted-keys add` (CLI) and `quox-plugin sign` (@quoxai/quox-plugin-vite).
|
|
128
|
+
function signatureInvalidHint(reason) {
|
|
129
|
+
const why = reason ? ` (reason: ${reason})` : '';
|
|
130
|
+
return `This org enforces trusted plugin signing keys${why}. Sign the bundle with \`npx quox-plugin sign\` (from @quoxai/quox-plugin-vite) and pass \`--signature <file>\`, `
|
|
131
|
+
+ 'or have an org admin register your public key with `quox plugin trusted-keys add`.';
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Z2H-3c: a 402 LICENSE_REQUIRED printed only "Request failed with status 402" (seen on `quox fleet hosts`
|
|
135
|
+
// on a fresh install). Name what is locked and the real verbs that unlock or inspect it.
|
|
136
|
+
function licenseRequiredHint(body) {
|
|
137
|
+
const plugin = body?.plugin || body?.requiredPlugin;
|
|
138
|
+
return `${plugin ? `This needs a licence that includes \`${plugin}\`` : 'This needs a licence entitlement'}, and this install does not have it. `
|
|
139
|
+
+ 'Activate one with `quox setup license activate --key-file <path>`; see what you have with `quox setup license status`.';
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// An object `detail` (e.g. {holder_session}) printed as "[object Object]". Show message/code when present,
|
|
143
|
+
// else compact JSON.
|
|
144
|
+
export function renderDetail(detail) {
|
|
145
|
+
if (detail == null || detail === '') return '';
|
|
146
|
+
if (typeof detail === 'string') return detail;
|
|
147
|
+
// #1005: validation details as [{ path, message }] read as "field: problem".
|
|
148
|
+
if (Array.isArray(detail) && detail.length && detail.every((d) => d && typeof d === 'object' && typeof d.message === 'string')) {
|
|
149
|
+
return detail.map((d) => {
|
|
150
|
+
const path = Array.isArray(d.path) ? d.path.join('.') : d.path;
|
|
151
|
+
return path ? `${path}: ${d.message}` : d.message;
|
|
152
|
+
}).join('; ');
|
|
153
|
+
}
|
|
154
|
+
if (typeof detail === 'object') {
|
|
155
|
+
const parts = [detail.message, detail.code].filter((x) => typeof x === 'string' && x);
|
|
156
|
+
if (parts.length) return parts.join(' ');
|
|
157
|
+
try { return JSON.stringify(detail); } catch { return String(detail); }
|
|
158
|
+
}
|
|
159
|
+
return String(detail);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// A human-only route refused this login (an agent or resident identity). Switching org cannot help; a
|
|
163
|
+
// human profile can.
|
|
164
|
+
function humanOnlyHint() {
|
|
165
|
+
return 'This needs a human login, and the active profile is not one (it is an agent or service login). '
|
|
166
|
+
+ 'Run it from a human profile: `quox -p <profile> ...`; `quox config profile list` shows them.';
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function notSlotHolderHint() {
|
|
170
|
+
return 'You don\'t hold this slot. To post as a member: `quox slot join <slot> --session <S>`, then `quox slot say <slot> --room --session <S> <text>`.';
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// #995: the 404 hint names `quox <group> list` only when that group really has a list verb, else its --help.
|
|
174
|
+
function notFoundHint(ctx) {
|
|
175
|
+
if (!ctx || !ctx.group) return null;
|
|
176
|
+
return (ctx.subcommands || []).includes('list')
|
|
177
|
+
? `Resource not found. \`quox ${ctx.group} list\` shows what exists.`
|
|
178
|
+
: `Resource not found. \`quox ${ctx.group} --help\` shows how to list what exists.`;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const RAW_HEADLINE = /^(Stream request|Request) failed with status \d+$/;
|
|
182
|
+
|
|
183
|
+
function sentence(text) {
|
|
184
|
+
const t = String(text || '').trim();
|
|
185
|
+
if (!t) return t;
|
|
186
|
+
return /[.!?]$/.test(t) ? t : `${t}.`;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function lowerFirst(text) {
|
|
190
|
+
return text ? text[0].toLowerCase() + text.slice(1) : text;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/** The server's own words for this error: its message, else its prose `error`, else null. */
|
|
194
|
+
function serverReason(err) {
|
|
195
|
+
const m = String(err.message || '').split('\n')[0];
|
|
196
|
+
if (m && !RAW_HEADLINE.test(m)) return m;
|
|
197
|
+
const code = typeof err.code === 'string' ? err.code : (typeof err.body?.error === 'string' ? err.body.error : '');
|
|
198
|
+
return /\s/.test(code) ? code : null;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* CLI-ERRORS: map an error to the contract { headline, next }, or null to keep the legacy rendering
|
|
203
|
+
* (an error whose message already says it well; existing tests pin that output, see the audit).
|
|
204
|
+
*/
|
|
205
|
+
export function explainError(err, ctx = null) {
|
|
206
|
+
if (!err) return null;
|
|
207
|
+
if (err instanceof QuoxUserError) return { headline: err.headline, next: err.next };
|
|
208
|
+
if (!(err instanceof QuoxApiError)) return null;
|
|
209
|
+
const status = err.status;
|
|
210
|
+
const body = err.body && typeof err.body === 'object' ? err.body : {};
|
|
211
|
+
const code = typeof err.code === 'string' ? err.code : '';
|
|
212
|
+
const all = `${code} ${typeof body.error === 'string' ? body.error : ''} ${err.message || ''}`;
|
|
213
|
+
// reasonFromError: the client lifted a prose `error` into the message because the body had no `message`
|
|
214
|
+
const raw = RAW_HEADLINE.test(String(err.message || '').split('\n')[0]) || err.reasonFromError === true;
|
|
215
|
+
const reason = serverReason(err);
|
|
216
|
+
|
|
217
|
+
// #8 service-key fallback: the real problem is "no stored session", say it once.
|
|
218
|
+
if (err.serviceKeyFallback && (status === 401 || status === 403)) {
|
|
219
|
+
return {
|
|
220
|
+
headline: 'This needs a signed-in user, and this profile has no stored session, so the request went out with the service key, which carries no user.',
|
|
221
|
+
next: 'run `quox login` (or `quox -p <profile> login`), then retry.',
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
// #3 non-JSON error page from a proxy in front of a service
|
|
225
|
+
if ((code === 'non_json_response' || code === 'unknown') && status >= 500) {
|
|
226
|
+
return {
|
|
227
|
+
headline: 'The server answered with an error page instead of Quox data, so the service behind it is down or restarting.',
|
|
228
|
+
next: 'check `quox service health`, then retry.',
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
// #9 / #10 sign-in
|
|
232
|
+
if (status === 401 && code === 'invalid_credentials') {
|
|
233
|
+
return {
|
|
234
|
+
headline: 'Sign-in failed: the email or password is wrong for this server.',
|
|
235
|
+
next: 'check the profile and server with `quox config profile list`, or reset the password from the dashboard sign-in page.',
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
if (status === 401 && code === 'account_locked') {
|
|
239
|
+
return { headline: sentence(reason || 'This account is locked after too many failed sign-ins'), next: 'wait for the lock to expire, then run `quox login` again; nothing else is needed.' };
|
|
240
|
+
}
|
|
241
|
+
// #6 an agent login tried to decide a HITL item
|
|
242
|
+
if (status === 403 && /agents cannot resolve hitl/i.test(all)) {
|
|
243
|
+
return {
|
|
244
|
+
headline: 'Only a person can approve or reject inbox items, and this profile is an agent login.',
|
|
245
|
+
next: 'run it from a human profile: `quox -p <profile> ...`; `quox config profile list` shows them.',
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
// #13 / LICENSE_REQUIRED
|
|
249
|
+
if ((status === 402 || status === 403) && (code === 'LICENSE_REQUIRED' || body.error === 'LICENSE_REQUIRED')) {
|
|
250
|
+
if (body.reason === 'no_org_context') {
|
|
251
|
+
return { headline: 'This command needs an org, and none is selected for this profile.', next: 'pick one with `quox org switch <org>`; `quox org list` shows them.' };
|
|
252
|
+
}
|
|
253
|
+
const plugin = body.plugin || body.requiredPlugin;
|
|
254
|
+
return {
|
|
255
|
+
headline: `${plugin ? `This needs a licence that includes \`${plugin}\`` : 'This needs a licence entitlement'}, and this install does not have it.`,
|
|
256
|
+
next: 'activate one with `quox setup license activate --key-file <path>`; see what you have with `quox setup license status`.',
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
// #11 / #12 prose "not licensed" (only when the headline would otherwise be raw)
|
|
260
|
+
if ((status === 402 || status === 403) && /not licen[cs]ed/i.test(all) && (raw || status === 402)) {
|
|
261
|
+
const text = (reason || 'This feature is not licensed for this org').replace(/^(\S+) not licen/i, '$1 is not licen');
|
|
262
|
+
return { headline: sentence(text), next: '`quox setup license status` shows what this install has.' };
|
|
263
|
+
}
|
|
264
|
+
// #14 / #15 approvals
|
|
265
|
+
if (/^approval (request )?has expired$/i.test(reason || '')) {
|
|
266
|
+
return { headline: 'That approval expired before anyone decided it, so nothing ran.', next: 're-run the original command to ask for a fresh approval.' };
|
|
267
|
+
}
|
|
268
|
+
if (status === 404 && /^approval not found$/i.test(reason || '')) {
|
|
269
|
+
return { headline: 'No approval with that id in this org; it may belong to another org or be past retention.', next: notFoundNext(ctx) || '`quox approvals --help` shows how to list what exists.' };
|
|
270
|
+
}
|
|
271
|
+
// #5 a role refusal is not an org-context problem
|
|
272
|
+
if (status === 403 && /insufficient permissions?|lacks?[ _]capability|requires? (an? |the )?(admin|owner) role|not an? (org )?(admin|owner)/i.test(all)) {
|
|
273
|
+
return { headline: sentence(reason || 'Your role does not allow this'), next: 'your role in this org does not allow this; ask an org owner or admin to do it or to raise your role.' };
|
|
274
|
+
}
|
|
275
|
+
// #18 a 409 that already says "already exists" needs no echo
|
|
276
|
+
if (status === 409 && /already exists/i.test(reason || '')) {
|
|
277
|
+
return { headline: sentence(reason), next: 'pick a different id, or update the existing one.' };
|
|
278
|
+
}
|
|
279
|
+
// #1 / #4 the headline would be "Request failed with status N": lead with the server's reason
|
|
280
|
+
if (raw && reason) {
|
|
281
|
+
const next = (typeof body.hint === 'string' && body.hint)
|
|
282
|
+
|| (status === 404 && notFoundNext(ctx))
|
|
283
|
+
|| CONTRACT_NEXT[status]
|
|
284
|
+
|| (STATUS_HINTS[status] ? lowerFirst(STATUS_HINTS[status]) : null);
|
|
285
|
+
return { headline: sentence(reason), next };
|
|
286
|
+
}
|
|
287
|
+
return null;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// The contract's own next steps: an action, never a restatement of the headline.
|
|
291
|
+
const CONTRACT_NEXT = {
|
|
292
|
+
401: 'run `quox login`, then retry.',
|
|
293
|
+
403: 'if this belongs to another org, switch with `quox org switch <org>`; otherwise ask an org owner or admin.',
|
|
294
|
+
404: 'check the id; `quox <command> --help` shows how to list what exists.',
|
|
295
|
+
409: 'it may already exist or be in use; check its current state before retrying.',
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
function notFoundNext(ctx) {
|
|
299
|
+
const h = notFoundHint(ctx);
|
|
300
|
+
return h ? h.replace(/^Resource not found\. /, '') : null;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function isVerbose(ctx) {
|
|
304
|
+
if (ctx && ctx.verbose) return true;
|
|
305
|
+
const a = process.argv || [];
|
|
306
|
+
return a.includes('--verbose') || a.includes('-v') || a.includes('--debug') || Boolean(process.env.DEBUG);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* CLI-ERRORS: the contract rendering for an API error with no specific mapping. Headline = the server's
|
|
311
|
+
* own first line; next = the most specific hint we have (the cases the legacy renderer knew), once.
|
|
312
|
+
*/
|
|
313
|
+
function fallbackExplain(err, ctx) {
|
|
314
|
+
const body = err.body && typeof err.body === 'object' ? err.body : {};
|
|
315
|
+
const tags = `${typeof err.code === 'string' ? err.code : ''} ${typeof body.error === 'string' ? body.error : ''}`;
|
|
316
|
+
const status = err.status;
|
|
317
|
+
let next;
|
|
318
|
+
if (status === 403 && /not (the current )?slot holder/i.test(tags)) next = notSlotHolderHint();
|
|
319
|
+
else if (status === 403 && /\b(only a human|requires a human)\b/i.test(`${tags} ${err.message || ''}`)) next = humanOnlyHint();
|
|
320
|
+
else if (status === 403 && (err.code === 'signature_invalid' || body.error === 'signature_invalid')) next = signatureInvalidHint(body.reason);
|
|
321
|
+
else if (status === 403 && /not licen[cs]ed/i.test(tags)) next = '`quox setup license status` shows what this install has.';
|
|
322
|
+
else if (err.code === 'PLUGIN_SERVICE_UNAVAILABLE') next = null;
|
|
323
|
+
else {
|
|
324
|
+
next = (typeof body.hint === 'string' && body.hint) || (status === 404 && notFoundNext(ctx)) || CONTRACT_NEXT[status]
|
|
325
|
+
|| (STATUS_HINTS[status] ? lowerFirst(STATUS_HINTS[status]) : null);
|
|
326
|
+
}
|
|
327
|
+
const plain = { 400: 'The server could not accept that request', 401: 'You are not signed in, or the session has expired', 402: 'This needs a licence this install does not have',
|
|
328
|
+
403: 'This profile is not allowed to do that', 404: 'That was not found', 409: 'That conflicts with its current state', 429: 'Too many requests right now' };
|
|
329
|
+
const msg = String(err.message || '').split('\n')[0];
|
|
330
|
+
const first = serverReason(err) || (RAW_HEADLINE.test(msg) || !msg ? (plain[status] || (status >= 500 ? 'The server hit an error' : 'The request was refused')) : msg);
|
|
331
|
+
return { headline: sentence(first), next: next || null };
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// CLI-ERRORS: an instruction sentence (the step the user takes) vs the rest (what happened).
|
|
335
|
+
const INSTRUCTION = /^(run|check|use|retry|try|pass|fix|set|pick|switch|ask|log ?in|re-?run|please)\b/i;
|
|
336
|
+
|
|
337
|
+
function splitInstruction(text) {
|
|
338
|
+
const first = String(text || '').split('\n')[0].trim();
|
|
339
|
+
const rest = String(text || '').split('\n').slice(1).map((l) => l.trim()).filter(Boolean);
|
|
340
|
+
const parts = first.split(/(?<=[.!?;])\s+/);
|
|
341
|
+
let idx = parts.findIndex((p, i) => i > 0 && INSTRUCTION.test(p));
|
|
342
|
+
let head = idx === -1 ? parts.join(' ') : parts.slice(0, idx).join(' ');
|
|
343
|
+
let next = idx === -1 ? null : parts[idx];
|
|
344
|
+
const more = [...(idx === -1 ? [] : parts.slice(idx + 1)), ...rest];
|
|
345
|
+
head = head.replace(/;$/, '');
|
|
346
|
+
if (next && /^please log ?in again\.?$/i.test(next)) next = 'run `quox login`, then retry.';
|
|
347
|
+
return { headline: sentence(head), next: next ? lowerFirst(next) : null, more };
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* CLI-ERRORS: errors the CLI raises itself (auth, network, validation, anything else) in the same
|
|
352
|
+
* contract as API errors: one headline, at most one Next, the rest (`more`) only with --verbose.
|
|
353
|
+
*/
|
|
354
|
+
export function explainLocal(err) {
|
|
355
|
+
if (!err || err instanceof QuoxApiError || err instanceof QuoxUserError) return null;
|
|
356
|
+
const message = String(err.message || '');
|
|
357
|
+
if (err instanceof QuoxNetworkError) {
|
|
358
|
+
const code = err.cause?.cause?.code || err.cause?.code || null;
|
|
359
|
+
const m = /^Could not connect to (\S+) service at (\S+?): /.exec(message);
|
|
360
|
+
const svc = m ? m[1] : null;
|
|
361
|
+
const url = m ? m[2] : null;
|
|
362
|
+
const more = message.split('\n').slice(1).map((l) => l.trim()).filter(Boolean);
|
|
363
|
+
if (m && code && /CERT|SELF_SIGNED|UNABLE_TO_VERIFY/.test(code)) {
|
|
364
|
+
return { headline: `Could not reach the ${svc} service at ${url}: its certificate is not trusted (${code}).`,
|
|
365
|
+
next: 'trust it with export NODE_EXTRA_CA_CERTS=<path to quox.crt> (on the server: ~/quox-install/quox/ssl/quox.crt); never disable TLS verification.', more };
|
|
366
|
+
}
|
|
367
|
+
if (m && (code === 'ERR_SSL_WRONG_VERSION_NUMBER' || code === 'EPROTO')) {
|
|
368
|
+
return { headline: `The ${svc} service at ${url} speaks plain http, but this profile's URL says https://.`,
|
|
369
|
+
next: `quox config set url ${url.replace(/^https:/, 'http:')}`, more };
|
|
370
|
+
}
|
|
371
|
+
if (m && code === 'ECONNREFUSED') {
|
|
372
|
+
return { headline: `Nothing is listening at ${url} for the ${svc} service: it is down, or this profile's URL is stale.`,
|
|
373
|
+
next: `run \`quox doctor\`; to point this shell elsewhere: export QUOX_${svc.toUpperCase()}_URL=<url>`, more };
|
|
374
|
+
}
|
|
375
|
+
const t = /^Request timed out after (\d+)ms$/.exec(message);
|
|
376
|
+
if (t) {
|
|
377
|
+
const secs = Math.round(Number(t[1]) / 1000);
|
|
378
|
+
return { headline: `The server did not answer within ${secs} second${secs === 1 ? '' : 's'}.`, next: 'check `quox service health`, then retry.', more };
|
|
379
|
+
}
|
|
380
|
+
return { headline: sentence(message.split('\n')[0]), next: 'check `quox service health`, then retry.', more };
|
|
381
|
+
}
|
|
382
|
+
if (err instanceof QuoxAuthError) {
|
|
383
|
+
const sp = splitInstruction(message);
|
|
384
|
+
if (!sp.next) {
|
|
385
|
+
sp.next = /QUOX_TOKEN_FILE/.test(message)
|
|
386
|
+
? 'this principal runs only on its issued token; ask the owner to re-issue the token file.'
|
|
387
|
+
: err.transient ? 'wait a moment and retry; your stored session is kept.' : 'run `quox login`, then retry.';
|
|
388
|
+
}
|
|
389
|
+
return sp;
|
|
390
|
+
}
|
|
391
|
+
if (err instanceof QuoxValidationError) return splitInstruction(message);
|
|
392
|
+
const first = message.split('\n')[0];
|
|
393
|
+
return { headline: sentence(first || 'Something went wrong'), next: null, more: message.split('\n').slice(1), stack: true };
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
function renderContract(err, ex, ctx) {
|
|
397
|
+
console.error(chalk.red(`✖ ${ex.headline}`));
|
|
398
|
+
if (ex.next) console.error(chalk.yellow(` Next: ${ex.next}`));
|
|
399
|
+
// #1000: a server that names the allowed values is telling the user the fix; keep it
|
|
400
|
+
if (err.body && typeof err.body === 'object') {
|
|
401
|
+
for (const [k, v] of Object.entries(err.body)) {
|
|
402
|
+
if (k.startsWith('valid_') && Array.isArray(v) && v.length) console.error(chalk.dim(` Valid ${k.slice(6).replace(/_/g, ' ')}: ${v.join(', ')}`));
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
if (err.detail) console.error(chalk.dim(` Detail: ${renderDetail(err.detail)}`));
|
|
406
|
+
if (isVerbose(ctx)) {
|
|
407
|
+
if (err.status) console.error(chalk.dim(` Status: ${err.status} ${typeof err.code === 'object' && err.code ? renderDetail(err.code) : (err.code || '')}`));
|
|
408
|
+
if (err instanceof QuoxApiError && err.message && err.message !== ex.headline) console.error(chalk.dim(` Server said: ${err.message}`));
|
|
409
|
+
for (const line of ex.more || []) if (line) console.error(chalk.dim(` More: ${line}`));
|
|
410
|
+
if (ex.stack && err.stack) console.error(chalk.dim((err.stack || '').replace(/eyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/g, '[JWT_REDACTED]')));
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
export function handleError(err, outputFormat = 'table', ctx = null) {
|
|
97
415
|
if (outputFormat === 'json') {
|
|
98
416
|
const obj = {
|
|
99
417
|
error: true,
|
|
100
418
|
code: err.code || err.name,
|
|
101
|
-
message: err.message,
|
|
419
|
+
message: err.headline || err.message,
|
|
102
420
|
};
|
|
103
421
|
if (err.status) obj.status = err.status;
|
|
104
422
|
if (err.detail) obj.detail = err.detail;
|
|
105
423
|
// LL-3d: scripts parsing --json get the server's own body verbatim, not
|
|
106
424
|
// our prose rendering of it.
|
|
107
425
|
if (err.body && typeof err.body === 'object') obj.body = err.body;
|
|
426
|
+
const ex = explainError(err, ctx) || (err instanceof QuoxApiError ? fallbackExplain(err, ctx) : explainLocal(err));
|
|
427
|
+
if (ex) { obj.headline = ex.headline; if (ex.next) obj.next = ex.next; }
|
|
108
428
|
process.stderr.write(JSON.stringify(obj) + '\n');
|
|
109
429
|
return;
|
|
110
430
|
}
|
|
111
431
|
|
|
432
|
+
const ex = explainError(err, ctx);
|
|
433
|
+
if (ex) { renderContract(err, ex, ctx); return; }
|
|
434
|
+
|
|
112
435
|
if (err instanceof QuoxApiError) {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
console.error(chalk.dim(` Status: ${err.status} ${err.code || ''}`));
|
|
116
|
-
const hint = STATUS_HINTS[err.status];
|
|
117
|
-
if (hint) console.error(chalk.yellow(` Hint: ${hint}`));
|
|
118
|
-
} else if (err instanceof QuoxAuthError) {
|
|
119
|
-
console.error(chalk.red(`Auth: ${err.message}`));
|
|
120
|
-
} else if (err instanceof QuoxNetworkError) {
|
|
121
|
-
console.error(chalk.red(`Network: ${err.message}`));
|
|
122
|
-
console.error(chalk.yellow(' Hint: Check that services are running with `quox service health`.'));
|
|
123
|
-
} else if (err instanceof QuoxValidationError) {
|
|
124
|
-
console.error(chalk.red(`Validation: ${err.message}`));
|
|
436
|
+
// CLI-ERRORS (PIN 01K55JRA11YHH73TN67X9ZDEGM): every API error renders in the contract now.
|
|
437
|
+
renderContract(err, fallbackExplain(err, ctx), ctx);
|
|
125
438
|
} else {
|
|
126
|
-
|
|
127
|
-
if (process.env.DEBUG || process.argv.includes('--debug')) {
|
|
128
|
-
const sanitized = (err.stack || '').replace(/eyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/g, '[JWT_REDACTED]');
|
|
129
|
-
console.error(chalk.dim(sanitized));
|
|
130
|
-
}
|
|
439
|
+
renderContract(err, explainLocal(err), ctx);
|
|
131
440
|
}
|
|
132
441
|
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SITREP-FINDINGS: gather the evidence that a security finding was fixed, on the host where
|
|
3
|
+
* git and gh live, for `quox engagement reconcile`. The collector makes the decision
|
|
4
|
+
* (securityFindings.reconcileFinding); this module only finds facts and never guesses:
|
|
5
|
+
* - issue: the stored issue_ref, the gripe_url Quox filed, a --link, or quox#N / #N in the
|
|
6
|
+
* title (quox#N in the description). Its state comes from `gh issue view`; unreadable = left out.
|
|
7
|
+
* - fix: commits on origin/main whose message cites the finding id or the linked issue
|
|
8
|
+
* number, or a --link commit. landed = an ancestor of origin/main; deployed = an ancestor of
|
|
9
|
+
* the build tree's HEAD (the tree quox-build last built from). A commit not on main is no fix.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const GH_ISSUE_URL = /github\.com\/([\w.-]+)\/([\w.-]+)\/issues\/(\d+)/;
|
|
13
|
+
|
|
14
|
+
export function issueRefsFor(finding, link = {}, issueRepo = 'quoxai/quox') {
|
|
15
|
+
const out = [];
|
|
16
|
+
const add = (r) => { if (r && !out.includes(r)) out.push(r); };
|
|
17
|
+
if (finding.issue_ref) add(finding.issue_ref);
|
|
18
|
+
const m = GH_ISSUE_URL.exec(finding.gripe_url || '');
|
|
19
|
+
if (m) add(`${m[1]}/${m[2]}#${m[3]}`);
|
|
20
|
+
if (link.issue) add(normIssue(link.issue, issueRepo));
|
|
21
|
+
const t = /(?:quox)?#(\d{1,6})\b/.exec(String(finding.title || ''));
|
|
22
|
+
if (t) add(`${issueRepo}#${t[1]}`);
|
|
23
|
+
const d = /\bquox#(\d{1,6})\b/.exec(String(finding.description || ''));
|
|
24
|
+
if (d) add(`${issueRepo}#${d[1]}`);
|
|
25
|
+
return out;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function normIssue(ref, issueRepo = 'quoxai/quox') {
|
|
29
|
+
const s = String(ref).trim();
|
|
30
|
+
const m = GH_ISSUE_URL.exec(s);
|
|
31
|
+
if (m) return `${m[1]}/${m[2]}#${m[3]}`;
|
|
32
|
+
if (/^[\w.-]+\/[\w.-]+#\d+$/.test(s)) return s;
|
|
33
|
+
const n = /^(?:quox)?#?(\d+)$/.exec(s);
|
|
34
|
+
return n ? `${issueRepo}#${n[1]}` : null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function issueState(ref, run) {
|
|
38
|
+
const [repo, n] = ref.split('#');
|
|
39
|
+
const r = run('gh', ['issue', 'view', n, '-R', repo, '--json', 'state']);
|
|
40
|
+
if (!r.ok) return null;
|
|
41
|
+
try {
|
|
42
|
+
const st = String(JSON.parse(r.out).state || '').toLowerCase();
|
|
43
|
+
return st === 'open' || st === 'closed' ? st : null;
|
|
44
|
+
} catch { return null; }
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function commitsCiting(term, regex, { repoDir, run }) {
|
|
48
|
+
const r = run('git', ['-C', repoDir, 'log', 'origin/main', '--format=%H', ...(regex ? ['-E'] : ['--fixed-strings']), `--grep=${term}`]);
|
|
49
|
+
if (!r.ok) return [];
|
|
50
|
+
return r.out.split('\n').map((x) => x.trim()).filter((x) => /^[0-9a-f]{40}$/i.test(x));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function gatherEvidence(finding, link = {}, { repoDir, buildDir, issueRepo = 'quoxai/quox', run }) {
|
|
54
|
+
const ev = {};
|
|
55
|
+
const refs = issueRefsFor(finding, link, issueRepo);
|
|
56
|
+
if (refs.length) {
|
|
57
|
+
ev.issue_ref = refs[0];
|
|
58
|
+
const st = issueState(refs[0], run);
|
|
59
|
+
if (st) ev.issue_state = st;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const shas = [];
|
|
63
|
+
const add = (s) => { if (s && !shas.includes(s)) shas.push(s); };
|
|
64
|
+
if (link.commit) {
|
|
65
|
+
const full = run('git', ['-C', repoDir, 'rev-parse', '--verify', `${link.commit}^{commit}`]);
|
|
66
|
+
add(full.ok && /^[0-9a-f]{40}$/i.test(full.out.trim()) ? full.out.trim() : link.commit);
|
|
67
|
+
}
|
|
68
|
+
if (finding.fix_commit) add(finding.fix_commit);
|
|
69
|
+
for (const s of commitsCiting(finding.id, false, { repoDir, run })) add(s);
|
|
70
|
+
if (ev.issue_ref && ev.issue_ref.startsWith(`${issueRepo}#`)) {
|
|
71
|
+
const n = ev.issue_ref.split('#')[1];
|
|
72
|
+
for (const s of commitsCiting(`#${n}([^0-9]|$)`, true, { repoDir, run })) add(s);
|
|
73
|
+
}
|
|
74
|
+
if (!shas.length) return ev;
|
|
75
|
+
|
|
76
|
+
const head = run('git', ['-C', buildDir, 'rev-parse', 'HEAD']);
|
|
77
|
+
const buildHead = head.ok ? head.out.trim() : null;
|
|
78
|
+
const isAnc = (dir, sha, tip) => run('git', ['-C', dir, 'merge-base', '--is-ancestor', sha, tip]).ok;
|
|
79
|
+
const landed = shas.filter((s) => isAnc(repoDir, s, 'origin/main'));
|
|
80
|
+
const deployed = buildHead ? landed.filter((s) => isAnc(buildDir, s, buildHead)) : [];
|
|
81
|
+
if (deployed.length) { ev.fix_commit = deployed[0]; ev.fix_state = 'deployed'; } else if (landed.length) { ev.fix_commit = landed[0]; ev.fix_state = 'landed'; }
|
|
82
|
+
return ev;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function evidenceChanged(finding, ev) {
|
|
86
|
+
for (const k of ['issue_ref', 'issue_state', 'fix_commit', 'fix_state']) {
|
|
87
|
+
if (ev[k] != null && ev[k] !== (finding[k] ?? null)) return true;
|
|
88
|
+
}
|
|
89
|
+
return false;
|
|
90
|
+
}
|
package/lib/core/global-flags.js
CHANGED
|
@@ -39,15 +39,41 @@
|
|
|
39
39
|
export function mergeShadowedGlobals(rootCommand, actionCommand) {
|
|
40
40
|
if (!rootCommand || !actionCommand || actionCommand === rootCommand) return [];
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
// Nearest ancestor first, root last. quox#1077 follow-up: a PARENT group
|
|
43
|
+
// that declares the same option shadows its leaf exactly like the root does
|
|
44
|
+
// (`quox lucid since-last --no-advance` set the flag on `lucid`, and the
|
|
45
|
+
// leaf kept its default and advanced the cursor). Only the root used to be
|
|
46
|
+
// consulted.
|
|
47
|
+
const ancestors = [];
|
|
48
|
+
for (let c = actionCommand.parent; c; c = c.parent) {
|
|
49
|
+
ancestors.push(c);
|
|
50
|
+
if (c === rootCommand) break;
|
|
51
|
+
}
|
|
52
|
+
const typed = (src) => src === 'cli' || src === 'env';
|
|
43
53
|
const merged = [];
|
|
44
54
|
|
|
45
55
|
for (const option of actionCommand.options) {
|
|
46
56
|
const name = option.attributeName();
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
57
|
+
const localValue = actionCommand.getOptionValue(name);
|
|
58
|
+
const localSource = actionCommand.getOptionValueSource(name);
|
|
59
|
+
if (typed(localSource)) continue; // the user typed it on the leaf itself
|
|
60
|
+
|
|
61
|
+
// 1. A value the user TYPED on an ancestor wins over the leaf's default.
|
|
62
|
+
// A shadowed option can never carry a user value (commander gave it to
|
|
63
|
+
// the ancestor), so the leaf's value here is at most a default. This is
|
|
64
|
+
// what makes --no-* options work: their default is true, never undefined.
|
|
65
|
+
const typedOn = ancestors.find((a) => a.options.some((o) => o.attributeName() === name)
|
|
66
|
+
&& typed(a.getOptionValueSource(name)));
|
|
67
|
+
if (typedOn) {
|
|
68
|
+
actionCommand.setOptionValue(name, typedOn.getOptionValue(name));
|
|
69
|
+
merged.push(name);
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// 2. Otherwise fill an empty leaf from the root (its default included),
|
|
74
|
+
// as before. A subcommand default is a real value and is left alone.
|
|
75
|
+
const rootOpts = rootCommand.opts();
|
|
76
|
+
if (localValue === undefined && rootOpts[name] !== undefined) {
|
|
51
77
|
actionCommand.setOptionValue(name, rootOpts[name]);
|
|
52
78
|
merged.push(name);
|
|
53
79
|
}
|
|
@@ -56,6 +82,80 @@ export function mergeShadowedGlobals(rootCommand, actionCommand) {
|
|
|
56
82
|
return merged;
|
|
57
83
|
}
|
|
58
84
|
|
|
85
|
+
const HONOURS_GLOBAL_DRY_RUN = Symbol.for('quox.forwardsRawArgs');
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Mark a leaf that honours a --dry-run WITHOUT declaring its own option,
|
|
89
|
+
* because it forwards every raw argv token after its verb word to a wrapped
|
|
90
|
+
* tool (claude, codex, wrap). Such a leaf honours the flag only when
|
|
91
|
+
* it is typed AFTER the verb (it then reaches the wrapped tool); typed before
|
|
92
|
+
* the verb it reaches nothing, so it is refused like any other.
|
|
93
|
+
*/
|
|
94
|
+
export function markForwardsRawArgs(command, reason) {
|
|
95
|
+
command[HONOURS_GLOBAL_DRY_RUN] = reason || true;
|
|
96
|
+
return command;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** 'declared' | 'forwards' | false */
|
|
100
|
+
export function dryRunSupport(command) {
|
|
101
|
+
if (!command) return false;
|
|
102
|
+
if (command.options.some((o) => o.attributeName() === 'dryRun')) return 'declared';
|
|
103
|
+
if (command[HONOURS_GLOBAL_DRY_RUN]) return 'forwards';
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** The raw argv tokens before the leaf's own verb word (commander's root rawArgs). */
|
|
108
|
+
function tokensBeforeVerb(rootCommand, actionCommand) {
|
|
109
|
+
const raw = Array.isArray(rootCommand.rawArgs) ? rootCommand.rawArgs : [];
|
|
110
|
+
const words = [];
|
|
111
|
+
for (let c = actionCommand; c && c !== rootCommand; c = c.parent) words.unshift(c.name());
|
|
112
|
+
let at = 0;
|
|
113
|
+
for (const w of words) {
|
|
114
|
+
const i = raw.indexOf(w, at);
|
|
115
|
+
if (i < 0) return raw; // cannot locate: treat everything as "before" (refuse)
|
|
116
|
+
at = i + 1;
|
|
117
|
+
}
|
|
118
|
+
return raw.slice(0, at - 1);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function honoursGlobalDryRun(command, rootCommand) {
|
|
122
|
+
const support = dryRunSupport(command);
|
|
123
|
+
if (support === 'declared') return true;
|
|
124
|
+
if (support === 'forwards') return !rootCommand || !tokensBeforeVerb(rootCommand, command).includes('--dry-run');
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Refuse a --dry-run the running leaf cannot honour (quox#1077 follow-up).
|
|
130
|
+
*
|
|
131
|
+
* `--dry-run` is a ROOT option, so commander accepts it after ANY verb. A leaf
|
|
132
|
+
* that never declared it then ignores it and runs for real: `quox offload npm
|
|
133
|
+
* publish --dry-run` published (the flag never reached npm), `quox discord bind
|
|
134
|
+
* ... --dry-run` bound (qdp bind has no dry run). A requested dry run must
|
|
135
|
+
* either be honoured or refused; it is never silently dropped.
|
|
136
|
+
*
|
|
137
|
+
* @throws {Error} exitCode 2, before the action runs
|
|
138
|
+
*/
|
|
139
|
+
export function assertDryRunHonoured(rootCommand, actionCommand) {
|
|
140
|
+
if (!rootCommand || !actionCommand || actionCommand === rootCommand) return;
|
|
141
|
+
const typed = (c) => ['cli', 'env'].includes(c.getOptionValueSource('dryRun')) && c.getOptionValue('dryRun') === true;
|
|
142
|
+
let requested = false;
|
|
143
|
+
for (let c = actionCommand; c; c = c.parent) {
|
|
144
|
+
if (typed(c)) { requested = true; break; }
|
|
145
|
+
if (c === rootCommand) break;
|
|
146
|
+
}
|
|
147
|
+
if (!requested || honoursGlobalDryRun(actionCommand, rootCommand)) return;
|
|
148
|
+
const path = [];
|
|
149
|
+
for (let c = actionCommand; c && c !== rootCommand; c = c.parent) path.unshift(c.name());
|
|
150
|
+
const err = new Error(
|
|
151
|
+
`\`quox ${path.join(' ')}\` has no dry run, so --dry-run cannot be honoured. Nothing was run. `
|
|
152
|
+
+ 'If --dry-run belongs to a command you are passing through, put that command after -- .',
|
|
153
|
+
);
|
|
154
|
+
err.code = 'quox.dryRunNotHonoured';
|
|
155
|
+
err.exitCode = 2;
|
|
156
|
+
throw err;
|
|
157
|
+
}
|
|
158
|
+
|
|
59
159
|
/**
|
|
60
160
|
* Walk a command tree and report every subcommand option that collides with a
|
|
61
161
|
* root option. Used by the regression test to keep the blast radius visible:
|
|
@@ -37,8 +37,6 @@ export function autoName(now = new Date()) {
|
|
|
37
37
|
export const PROGRAMS = {
|
|
38
38
|
claude: { tag: '', bin: 'claude', resumeHint: (n) => `quox claude --resume ${n}` },
|
|
39
39
|
codex: { tag: 'codex-', bin: 'codex', resumeHint: (n) => `quox codex resume ${n}` },
|
|
40
|
-
// gemini resumes by index or "latest" (--resume latest | --resume N), not by name.
|
|
41
|
-
gemini: { tag: 'gemini-', bin: 'gemini', resumeHint: () => 'quox gemini --resume latest' },
|
|
42
40
|
tui: { tag: 'tui-', bin: 'quox', resumeHint: (n) => `quox tui --governed --name ${n}` },
|
|
43
41
|
};
|
|
44
42
|
|
|
@@ -335,7 +333,7 @@ export function runLs(over) {
|
|
|
335
333
|
return 1;
|
|
336
334
|
}
|
|
337
335
|
if (!s.length) {
|
|
338
|
-
d.write('No Quox sessions running. Start one with: quox claude (or quox codex, quox
|
|
336
|
+
d.write('No Quox sessions running. Start one with: quox claude (or quox codex, quox wrap <program>)\n');
|
|
339
337
|
return 0;
|
|
340
338
|
}
|
|
341
339
|
const pad = (v, n) => String(v).padEnd(n);
|