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,276 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extension host (QUOXON, 2026-10-26; contract: EXTENSIONS.md). Optional npm packages that add surfaces to quoxon
|
|
3
|
+
* (a MIDI pad deck, a status bar, ...). Nothing loads unless the package is installed AND listed in
|
|
4
|
+
* ~/.quox/extensions.json (`quox ext add <pkg>`, `/ext list|on|off <name>`). Core knows nothing about any one extension:
|
|
5
|
+
* it pushes one generic snapshot (buildSnapshot) and accepts three emit actions (open_approval, pick, alert_ack).
|
|
6
|
+
* An extension can OPEN an approval or PRESELECT an answer; it can never resolve one.
|
|
7
|
+
*/
|
|
8
|
+
import fs from 'node:fs';
|
|
9
|
+
import os from 'node:os';
|
|
10
|
+
import path from 'node:path';
|
|
11
|
+
import { execFileSync } from 'node:child_process';
|
|
12
|
+
import { pathToFileURL } from 'node:url';
|
|
13
|
+
|
|
14
|
+
export const THROTTLE_MS = 1000;
|
|
15
|
+
export const notInstalled = (name) => `not installed: npm i -g ${name}`;
|
|
16
|
+
const HEALTH = new Set(['ok', 'warn', 'down']);
|
|
17
|
+
const CHAIN = new Set(['ok', 'broken', 'unknown']);
|
|
18
|
+
const FLEET_MAX = 16;
|
|
19
|
+
|
|
20
|
+
// ---- the listing: ~/.quox/extensions.json ---------------------------------------------------------------------------
|
|
21
|
+
export const extListPath = (home = os.homedir()) => path.join(home, '.quox', 'extensions.json');
|
|
22
|
+
const validName = (n) => typeof n === 'string' && /^(@[a-z0-9][\w.-]*\/)?[a-z0-9][\w.-]*$/i.test(n);
|
|
23
|
+
|
|
24
|
+
export function readExtList(file = extListPath()) {
|
|
25
|
+
try {
|
|
26
|
+
const j = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
27
|
+
return (Array.isArray(j?.extensions) ? j.extensions : []).filter((x) => validName(x?.name)).map((x) => ({ name: x.name, enabled: x.enabled !== false }));
|
|
28
|
+
} catch { return []; } // no file or unreadable: nothing is listed, so nothing loads
|
|
29
|
+
}
|
|
30
|
+
export function writeExtList(list, file = extListPath()) {
|
|
31
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
32
|
+
fs.writeFileSync(file, JSON.stringify({ extensions: list }, null, 2) + '\n');
|
|
33
|
+
}
|
|
34
|
+
export function extAdd(name, file = extListPath()) {
|
|
35
|
+
if (!validName(name)) return { ok: false, text: `"${name}" is not a package name.` };
|
|
36
|
+
const list = readExtList(file);
|
|
37
|
+
const had = list.find((x) => x.name === name);
|
|
38
|
+
if (had) had.enabled = true; else list.push({ name, enabled: true });
|
|
39
|
+
writeExtList(list, file);
|
|
40
|
+
return { ok: true, text: `${name} is listed. It loads in quoxon once installed (npm i -g ${name}).` };
|
|
41
|
+
}
|
|
42
|
+
export function extRemove(name, file = extListPath()) {
|
|
43
|
+
const list = readExtList(file);
|
|
44
|
+
if (!list.some((x) => x.name === name)) return { ok: false, text: `${name} is not listed.` };
|
|
45
|
+
writeExtList(list.filter((x) => x.name !== name), file);
|
|
46
|
+
return { ok: true, text: `${name} is no longer listed.` };
|
|
47
|
+
}
|
|
48
|
+
export function extSetEnabled(name, enabled, file = extListPath()) {
|
|
49
|
+
const list = readExtList(file);
|
|
50
|
+
const it = list.find((x) => x.name === name);
|
|
51
|
+
if (!it) return { ok: false, text: `${name} is not listed. quox ext add ${name}` };
|
|
52
|
+
it.enabled = enabled; writeExtList(list, file);
|
|
53
|
+
return { ok: true, text: `${name} ${enabled ? 'enabled' : 'disabled'}.` };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ---- resolution: env override, local, global ------------------------------------------------------------------------
|
|
57
|
+
export const envKey = (name) => `QUOX_EXT_${String(name).replace(/^@/, '').replace(/[^A-Za-z0-9]+/g, '_').toUpperCase()}_MODULE`;
|
|
58
|
+
const defaultImporter = (spec) => import(spec);
|
|
59
|
+
const NOT_FOUND = (e) => e?.code === 'ERR_MODULE_NOT_FOUND' || e?.code === 'MODULE_NOT_FOUND';
|
|
60
|
+
|
|
61
|
+
let npmRootCache; // `npm root -g` runs at most once per process
|
|
62
|
+
export function globalNpmRoot(run = execFileSync) {
|
|
63
|
+
if (npmRootCache !== undefined) return npmRootCache;
|
|
64
|
+
try { npmRootCache = String(run('npm', ['root', '-g'], { encoding: 'utf8', timeout: 8000, stdio: ['ignore', 'pipe', 'ignore'] })).trim() || null; } catch { npmRootCache = null; }
|
|
65
|
+
return npmRootCache;
|
|
66
|
+
}
|
|
67
|
+
export const _resetNpmRootCache = () => { npmRootCache = undefined; };
|
|
68
|
+
|
|
69
|
+
/** The file a package directory imports: exports["."] (import/default), module, main, index.js. */
|
|
70
|
+
export function packageEntry(dir) {
|
|
71
|
+
try {
|
|
72
|
+
const pj = JSON.parse(fs.readFileSync(path.join(dir, 'package.json'), 'utf8'));
|
|
73
|
+
const ex = pj.exports;
|
|
74
|
+
const root = typeof ex === 'string' ? ex : (ex?.['.'] ?? ex);
|
|
75
|
+
const pick = typeof root === 'string' ? root : (root?.import || root?.default || (typeof root?.import === 'object' ? root.import.default : null));
|
|
76
|
+
return path.join(dir, pick || pj.module || pj.main || 'index.js');
|
|
77
|
+
} catch { return path.join(dir, 'index.js'); }
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Find and import one extension. Order: QUOX_EXT_<NAME>_MODULE, local import(name), then the global npm root
|
|
82
|
+
* (dirname(execPath)/../lib/node_modules first, which costs no spawn; `npm root -g` once, cached, only if that misses).
|
|
83
|
+
* -> { ok:true, mod, where } | { ok:false, notFound:true, tried:[...] } | { ok:false, error }
|
|
84
|
+
*/
|
|
85
|
+
export async function resolveExtension(name, { env = process.env, importer = defaultImporter, execPath = process.execPath, npmRoot = globalNpmRoot, exists = fs.existsSync } = {}) {
|
|
86
|
+
const tried = [];
|
|
87
|
+
const ov = env?.[envKey(name)];
|
|
88
|
+
if (ov) {
|
|
89
|
+
tried.push(`${envKey(name)}=${ov}`);
|
|
90
|
+
try { return { ok: true, mod: await importer(path.isAbsolute(ov) ? pathToFileURL(ov).href : ov), where: `env ${envKey(name)} (${ov})` }; }
|
|
91
|
+
catch (e) { return { ok: false, error: e?.message || String(e), tried }; } // an override that fails is an error, never a silent fall-through
|
|
92
|
+
}
|
|
93
|
+
tried.push(`local import('${name}')`);
|
|
94
|
+
try { return { ok: true, mod: await importer(name), where: `local (${name})` }; }
|
|
95
|
+
catch (e) { if (!NOT_FOUND(e)) return { ok: false, error: e?.message || String(e), tried }; }
|
|
96
|
+
const roots = [];
|
|
97
|
+
const guess = path.join(path.dirname(execPath || ''), '..', 'lib', 'node_modules');
|
|
98
|
+
roots.push(guess);
|
|
99
|
+
let spawned = false;
|
|
100
|
+
for (let i = 0; i < 2; i++) {
|
|
101
|
+
for (const root of roots.splice(0)) {
|
|
102
|
+
const dir = path.join(root, name);
|
|
103
|
+
tried.push(`global ${dir}`);
|
|
104
|
+
if (!exists(dir)) continue;
|
|
105
|
+
try { return { ok: true, mod: await importer(pathToFileURL(packageEntry(dir)).href), where: `global (${dir})` }; }
|
|
106
|
+
catch (e) { return { ok: false, error: e?.message || String(e), tried }; }
|
|
107
|
+
}
|
|
108
|
+
if (spawned) break;
|
|
109
|
+
spawned = true;
|
|
110
|
+
const g = npmRoot();
|
|
111
|
+
if (g && path.resolve(g) !== path.resolve(guess)) roots.push(g);
|
|
112
|
+
}
|
|
113
|
+
return { ok: false, notFound: true, tried };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** An extension module -> { name, version, commands, create } or null. Accepts default export or the module itself. */
|
|
117
|
+
export function normalizeExtension(mod, fallbackName) {
|
|
118
|
+
const ext = mod?.default && typeof mod.default === 'object' ? mod.default : mod;
|
|
119
|
+
const create = typeof ext?.create === 'function' ? ext.create : (typeof mod?.createDeck === 'function' ? mod.createDeck : null); // createDeck: quoxdeck v1
|
|
120
|
+
if (!create) return null;
|
|
121
|
+
const commands = (Array.isArray(ext.commands) ? ext.commands : []).filter((c) => /^\/[a-z][\w-]*$/i.test(c?.name || '') && typeof c.run === 'function');
|
|
122
|
+
return { name: ext.name || fallbackName, version: ext.version || null, commands, create };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// ---- the snapshot every extension gets ------------------------------------------------------------------------------
|
|
126
|
+
function itemRisk(it) {
|
|
127
|
+
return it?.privileged === true || it?.payload?.privileged === true || it?.risk === 'high' || it?.payload?.risk === 'high' ? 'high' : 'low';
|
|
128
|
+
}
|
|
129
|
+
const hostState = (s) => (s === 'ok' || s === 'warn' || s === 'down' ? s : (/^(down|offline|failed|unreachable)$/i.test(s || '') ? 'down' : (/^(warn|warning|degraded)$/i.test(s || '') ? 'warn' : 'ok')));
|
|
130
|
+
|
|
131
|
+
/** Pure. fleet input: [{id|name, health|state}] or null. */
|
|
132
|
+
export function buildFleet(hosts) {
|
|
133
|
+
if (!Array.isArray(hosts)) return { total: 0, online: 0, offline: 0, warn: 0, hosts: [] };
|
|
134
|
+
const list = hosts.map((h) => ({ name: String(h.name ?? h.id ?? '').slice(0, 60), state: hostState(h.state ?? h.health) }));
|
|
135
|
+
return {
|
|
136
|
+
total: list.length, online: list.filter((h) => h.state !== 'down').length,
|
|
137
|
+
offline: list.filter((h) => h.state === 'down').length, warn: list.filter((h) => h.state === 'warn').length,
|
|
138
|
+
hosts: list.slice(0, FLEET_MAX)
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function buildSnapshot({ org = null, items = [], health = {}, loops = [], event = null, fleet = null, evidence = null, sessions = null } = {}) {
|
|
143
|
+
const list = (items || []).map((it, i) => {
|
|
144
|
+
const row = {
|
|
145
|
+
n: i + 1, id: it.id ?? null, risk: itemRisk(it),
|
|
146
|
+
title: String(it.title || it.summary || it.payload?.title || it.payload?.question || it.payload?.summary || '').slice(0, 120),
|
|
147
|
+
agent: it.agent || it.requester || it.payload?.agent || null
|
|
148
|
+
};
|
|
149
|
+
const opts = Array.isArray(it.payload?.options) ? it.payload.options.filter((o) => o?.key != null).map((o) => ({ key: String(o.key), label: String(o.label ?? o.key) })) : [];
|
|
150
|
+
if (opts.length) row.options = opts;
|
|
151
|
+
return row;
|
|
152
|
+
});
|
|
153
|
+
const h = {};
|
|
154
|
+
for (const [k, v] of Object.entries(health || {})) if (HEALTH.has(v)) h[k] = v;
|
|
155
|
+
const state = {
|
|
156
|
+
org: { id: org?.id ?? null, name: org?.name ?? null },
|
|
157
|
+
approvals: { count: list.length, items: list },
|
|
158
|
+
health: h,
|
|
159
|
+
loops: (loops || []).map((l) => ({ id: l.id, title: String(l.title || ''), turn: Number(l.turn) || 0, max: Number(l.max) || 0, status: l.status || 'running' })),
|
|
160
|
+
fleet: Array.isArray(fleet) ? buildFleet(fleet) : (fleet && typeof fleet === 'object' ? { total: fleet.total | 0, online: fleet.online | 0, offline: fleet.offline | 0, warn: fleet.warn | 0, hosts: (fleet.hosts || []).slice(0, FLEET_MAX) } : buildFleet(null)),
|
|
161
|
+
evidence: { chain: CHAIN.has(evidence?.chain) ? evidence.chain : 'unknown', checked_at: evidence?.checked_at ?? null },
|
|
162
|
+
sessions: { active: Math.max(0, Number(sessions?.active) || 0) }
|
|
163
|
+
};
|
|
164
|
+
if (event) state.event = { kind: event.kind, text: event.text, level: event.level || 'info' };
|
|
165
|
+
return state;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** What the composer is filled with when an extension picks an option; the user still presses Enter. null = nothing to preselect. */
|
|
169
|
+
export function pickDraft(item, n, option) {
|
|
170
|
+
if (!item) return null;
|
|
171
|
+
const opt = String(option ?? '').trim();
|
|
172
|
+
if (!opt) return null;
|
|
173
|
+
const keys = (item.payload?.options || []).map((o) => String(o.key));
|
|
174
|
+
if (keys.length) return keys.includes(opt) ? `/answer ${n} ${opt}` : null;
|
|
175
|
+
if (/^(y|yes|approve)$/i.test(opt)) return `/approve ${n}`;
|
|
176
|
+
if (/^(n|no|deny)$/i.test(opt)) return `/deny ${n} `;
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// ---- the host -------------------------------------------------------------------------------------------------------
|
|
181
|
+
/**
|
|
182
|
+
* handlers: { openApproval(n), preselect(n, option), changed() } are the window's own actions. getSettings() feeds create().
|
|
183
|
+
* Loaded extensions all receive the same deduped, 1/s-throttled snapshot.
|
|
184
|
+
*/
|
|
185
|
+
export function createExtensionHost({ importer = defaultImporter, env = process.env, execPath, npmRoot, exists, now = Date.now, setTimer = setTimeout, clearTimer = clearTimeout,
|
|
186
|
+
handlers = {}, log = () => {}, getSettings = () => ({}) } = {}) {
|
|
187
|
+
const loaded = new Map(); // name -> { ext, inst, where }
|
|
188
|
+
const starting = new Map();
|
|
189
|
+
let lastSent = 0; let lastKey = null; let pending = null; let timer = null;
|
|
190
|
+
let event = null; const prevLoops = new Map(); let prevCount = null;
|
|
191
|
+
|
|
192
|
+
const sendAll = (state) => {
|
|
193
|
+
const key = JSON.stringify(state);
|
|
194
|
+
if (key === lastKey) return;
|
|
195
|
+
lastKey = key; lastSent = now();
|
|
196
|
+
for (const [name, l] of loaded) { try { l.inst?.update?.(state); } catch (e) { log(`extension ${name} update failed: ${e?.message || e}`); } }
|
|
197
|
+
};
|
|
198
|
+
const flush = () => { timer = null; if (pending && loaded.size) { const s = pending; pending = null; sendAll(s); } };
|
|
199
|
+
|
|
200
|
+
const emitFor = (name) => (msg) => {
|
|
201
|
+
if (!loaded.has(name) || !msg || typeof msg !== 'object') return false; // ignored unless this extension is loaded
|
|
202
|
+
try {
|
|
203
|
+
if (msg.type === 'open_approval' && Number.isInteger(msg.n) && msg.n >= 1) { handlers.openApproval?.(msg.n); return true; }
|
|
204
|
+
if (msg.type === 'pick' && Number.isInteger(msg.n) && msg.n >= 1) { handlers.preselect?.(msg.n, msg.option); return true; }
|
|
205
|
+
if (msg.type === 'alert_ack') { event = null; handlers.changed?.(); return true; }
|
|
206
|
+
} catch (e) { log(`extension ${name} emit failed: ${e?.message || e}`); }
|
|
207
|
+
return false;
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
const host = {
|
|
211
|
+
armed: () => loaded.size > 0,
|
|
212
|
+
isLoaded: (name) => loaded.has(name),
|
|
213
|
+
loadedNames: () => [...loaded.keys()],
|
|
214
|
+
where: (name) => loaded.get(name)?.where || null,
|
|
215
|
+
/** emit as one extension (tests and the shim); extensions get their own bound emit in create(). */
|
|
216
|
+
emit: (msg, name) => (name ? emitFor(name)(msg) : [...loaded.keys()].some((n) => emitFor(n)(msg))),
|
|
217
|
+
async load(name) {
|
|
218
|
+
if (loaded.has(name)) return { ok: true, text: `${name} is already loaded.` };
|
|
219
|
+
if (starting.has(name)) return starting.get(name);
|
|
220
|
+
const p = (async () => {
|
|
221
|
+
try {
|
|
222
|
+
const r = await resolveExtension(name, { env, importer, execPath, npmRoot, exists });
|
|
223
|
+
if (!r.ok && r.notFound) return { ok: false, notFound: true, text: `${name} ${notInstalled(name)} (looked in: ${r.tried.join('; ')})`, tried: r.tried };
|
|
224
|
+
if (!r.ok) return { ok: false, text: `${name} could not load: ${r.error}` };
|
|
225
|
+
const ext = normalizeExtension(r.mod, name);
|
|
226
|
+
if (!ext) return { ok: false, text: `${name} could not load: the package has no create().` };
|
|
227
|
+
let inst;
|
|
228
|
+
try { inst = ext.create({ emit: emitFor(name), log, settings: getSettings() }); } catch (e) { return { ok: false, text: `${name} could not start: ${e?.message || e}` }; }
|
|
229
|
+
loaded.set(name, { ext, inst, where: r.where });
|
|
230
|
+
lastKey = null; lastSent = 0; pending = null; event = null; prevLoops.clear(); prevCount = null;
|
|
231
|
+
handlers.changed?.();
|
|
232
|
+
return { ok: true, text: `${ext.name} is on (found: ${r.where}).`, ext };
|
|
233
|
+
} finally { starting.delete(name); }
|
|
234
|
+
})();
|
|
235
|
+
starting.set(name, p);
|
|
236
|
+
return p;
|
|
237
|
+
},
|
|
238
|
+
unload(name) {
|
|
239
|
+
const l = loaded.get(name);
|
|
240
|
+
if (!l) return { ok: true, text: `${name} was not loaded.` };
|
|
241
|
+
loaded.delete(name);
|
|
242
|
+
if (!loaded.size) { pending = null; lastKey = null; if (timer) { clearTimer(timer); timer = null; } }
|
|
243
|
+
try { l.inst?.close?.(); } catch (e) { log(`extension ${name} close failed: ${e?.message || e}`); }
|
|
244
|
+
handlers.changed?.();
|
|
245
|
+
return { ok: true, text: `${name} is off.` };
|
|
246
|
+
},
|
|
247
|
+
unloadAll() { for (const n of [...loaded.keys()]) host.unload(n); },
|
|
248
|
+
/** Slash commands of loaded extensions only. -> [{ name, help, ext }] */
|
|
249
|
+
commands: () => [...loaded.values()].flatMap((l) => l.ext.commands.map((c) => ({ name: c.name, help: c.help || '', ext: l.ext.name }))),
|
|
250
|
+
async runCommand(cmd, args, ctx = {}) {
|
|
251
|
+
for (const l of loaded.values()) {
|
|
252
|
+
const c = l.ext.commands.find((x) => x.name === cmd);
|
|
253
|
+
if (c) { try { return { handled: true, result: await c.run(args, { ...ctx, extension: l.ext.name, loaded: host.loadedNames() }) }; } catch (e) { return { handled: true, error: e?.message || String(e) }; } }
|
|
254
|
+
}
|
|
255
|
+
return { handled: false };
|
|
256
|
+
},
|
|
257
|
+
/** Push the current snapshot inputs: derives an event from changes, throttles to one update per second. */
|
|
258
|
+
push(input) {
|
|
259
|
+
if (!loaded.size) return;
|
|
260
|
+
const items = input?.items || [];
|
|
261
|
+
for (const l of input?.loops || []) {
|
|
262
|
+
const was = prevLoops.get(l.id);
|
|
263
|
+
if (was && was !== l.status && (l.status === 'done' || l.status === 'failed')) event = { kind: 'loop', text: `${l.title || l.id} ${l.status}`, level: l.status === 'failed' ? 'error' : 'ok' };
|
|
264
|
+
prevLoops.set(l.id, l.status);
|
|
265
|
+
}
|
|
266
|
+
if (prevCount != null && items.length > prevCount) event = { kind: 'approval', text: `${items.length} waiting`, level: 'warn' };
|
|
267
|
+
prevCount = items.length;
|
|
268
|
+
const state = buildSnapshot({ ...input, event });
|
|
269
|
+
const wait = THROTTLE_MS - (now() - lastSent);
|
|
270
|
+
if (wait <= 0 && !timer) { pending = null; sendAll(state); return; }
|
|
271
|
+
pending = state;
|
|
272
|
+
if (!timer) timer = setTimer(flush, Math.max(wait, 0));
|
|
273
|
+
}
|
|
274
|
+
};
|
|
275
|
+
return host;
|
|
276
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* /feedback: tell the Quox team something, through the public quox.ai feedback API
|
|
3
|
+
* (POST https://api.quox.ai/api/feedback, anonymous, 5 per hour per IP; lands in the quox.ai admin feedback view).
|
|
4
|
+
* /gripe stays the GitHub path. What is sent is type + message (the CLI version is appended to the message)
|
|
5
|
+
* and nothing else: no name, email, org, profile or token. The user sees the exact payload and says y first.
|
|
6
|
+
*/
|
|
7
|
+
export const FEEDBACK_URL = 'https://api.quox.ai/api/feedback';
|
|
8
|
+
const MIN = 5, MAX = 5000; // the endpoint refuses outside these (api/routes/feedback.js in quox-website)
|
|
9
|
+
const TIMEOUT_MS = 15000;
|
|
10
|
+
|
|
11
|
+
/** -> { type:'feedback', message } | null when the endpoint would refuse it. */
|
|
12
|
+
export function feedbackPayload(text, version) {
|
|
13
|
+
const body = String(text || '').trim();
|
|
14
|
+
if (body.length < MIN) return null;
|
|
15
|
+
const message = `${body}\n\n(sent with /feedback from quox CLI ${version || 'unknown'})`;
|
|
16
|
+
if (message.length > MAX) return null;
|
|
17
|
+
return { type: 'feedback', message };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** The lines shown before asking: where it goes and exactly what is in the body. */
|
|
21
|
+
export function feedbackPreview(payload, url = FEEDBACK_URL) {
|
|
22
|
+
return [
|
|
23
|
+
`POST ${url} (the Quox team at quox.ai, not your Quox server)`,
|
|
24
|
+
'type: feedback',
|
|
25
|
+
'message:',
|
|
26
|
+
...String(payload.message).split('\n').map(l => ` ${l}`),
|
|
27
|
+
'Nothing else: no name, email, org or token.',
|
|
28
|
+
];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** -> { ok:true, id, url } | { ok:false, message }. Status is checked before the body is trusted. */
|
|
32
|
+
export async function sendFeedback(payload, { fetchImpl = globalThis.fetch, url = FEEDBACK_URL, version = null, timeoutMs = TIMEOUT_MS } = {}) {
|
|
33
|
+
const ctl = new AbortController();
|
|
34
|
+
const t = setTimeout(() => ctl.abort(), timeoutMs);
|
|
35
|
+
try {
|
|
36
|
+
const res = await fetchImpl(url, {
|
|
37
|
+
method: 'POST',
|
|
38
|
+
headers: { 'Content-Type': 'application/json', Accept: 'application/json', 'User-Agent': `quox-cli/${version || 'unknown'}` },
|
|
39
|
+
body: JSON.stringify({ type: payload.type, message: payload.message }),
|
|
40
|
+
signal: ctl.signal,
|
|
41
|
+
});
|
|
42
|
+
let data = null;
|
|
43
|
+
try { data = await res.json(); } catch { data = null; }
|
|
44
|
+
if (!res.ok) return { ok: false, message: `HTTP ${res.status}: ${(data && data.error) || 'the feedback API refused it'}` };
|
|
45
|
+
if (!data || data.success !== true) return { ok: false, message: `HTTP ${res.status} but the reply did not confirm it was stored` };
|
|
46
|
+
return { ok: true, id: data.id ?? null, url };
|
|
47
|
+
} catch (e) {
|
|
48
|
+
return { ok: false, message: e?.name === 'AbortError' ? `no answer from ${url} within ${Math.round(timeoutMs / 1000)} s` : (e?.message || 'failed') };
|
|
49
|
+
} finally { clearTimeout(t); }
|
|
50
|
+
}
|
package/lib/quoxon/gallery.js
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `quox motion`: the product version of the animation lab (~/quoxon-work/intro/lab). Plays every Q motion
|
|
3
|
-
* with a caption: Orbit & Lock, Portal, Particle collapse, the
|
|
3
|
+
* with a caption: Orbit & Lock, Portal, Particle collapse, the perpetual status Q in its
|
|
4
4
|
* four states, the hello, the composer ring and the reveal effects. No server, no account, nothing sent.
|
|
5
5
|
*
|
|
6
6
|
* Scenes are pure: galleryScenes() returns { id, title, caption, ms, frame(t) -> ANSI lines }. runGallery()
|
|
7
7
|
* is the only part that touches a terminal (cursor, timers, signals).
|
|
8
8
|
*/
|
|
9
9
|
import {
|
|
10
|
-
labSize, orbitCellsAt, portalCellsAt, particleCellsAt,
|
|
11
|
-
INTRO_MS, PORTAL_MS,
|
|
10
|
+
labSize, orbitCellsAt, portalCellsAt, particleCellsAt, cellsToAnsi, qCells,
|
|
11
|
+
INTRO_MS, PORTAL_MS, SKY, VIOLET, AMBER,
|
|
12
12
|
} from './qart.js';
|
|
13
13
|
import { qGlyph, glyphAnsi, GLYPH_STATES } from './qglyph.js';
|
|
14
14
|
import { helloCells, helloAnsi, helloGradient, helloMs } from './qhello.js';
|
|
15
|
+
import { CLIPS, MOOD_CLIPS, clipCells, clipCaption } from './qdragonScenes.js';
|
|
16
|
+
import { isLight } from './theme.js';
|
|
15
17
|
|
|
16
18
|
const RESET = '\x1b[0m';
|
|
17
19
|
const fg = (rgb) => `\x1b[38;2;${rgb.map((v) => Math.max(0, Math.min(255, Math.round(v)))).join(';')}m`;
|
|
@@ -125,8 +127,12 @@ export const PERP_MS = 2600;
|
|
|
125
127
|
* The gallery's scenes for a terminal. narrow forces the 40-column layout (the compact particle Q).
|
|
126
128
|
* Each scene: { id, title, caption, ms, frame(t, { mono }) -> string[] }.
|
|
127
129
|
*/
|
|
128
|
-
export function galleryScenes({ cols = 100, rows = 30, narrow = false } = {}) {
|
|
130
|
+
export function galleryScenes({ cols = 100, rows = 30, narrow = false, theme = isLight() ? 'light' : 'dark' } = {}) {
|
|
129
131
|
const W = narrow ? 40 : cols;
|
|
132
|
+
// the dragon (qdragonScenes.js): eyes as quadrant blocks with a background colour, the lab's light palette on light terminals
|
|
133
|
+
const fit = (str) => (str.length > W ? str.slice(0, W - 1) + '…' : str);
|
|
134
|
+
const dragon = (name, t, o) => [...art(clipCells(name, t, { cols: W, theme, mono: o.mono })), '', paint(fit(clipCaption(name, t)), MUTED, o.mono)];
|
|
135
|
+
const MOOD_SPAN = MOOD_CLIPS.map((m) => CLIPS[m].dur + 400);
|
|
130
136
|
const size = narrow ? 'small' : labSize(W, rows);
|
|
131
137
|
const big = size === 'small' ? 'small' : size;
|
|
132
138
|
const art = (cells) => cellsToAnsi(cells);
|
|
@@ -138,8 +144,6 @@ export function galleryScenes({ cols = 100, rows = 30, narrow = false } = {}) {
|
|
|
138
144
|
frame: (t, o) => art(portalCellsAt(big, t, { color: !o.mono })) },
|
|
139
145
|
{ id: 'particle', title: 'Particle collapse', caption: 'density rises on a circle, then collapses into the compact Q', ms: 1100 + 500,
|
|
140
146
|
frame: (t, o) => art(particleCellsAt('small', t, { color: !o.mono })) },
|
|
141
|
-
{ id: 'eye', title: "The Dragon's Eye", caption: 'a slit-pupil eye opens in the bowl, blinks once, and dissolves back into the Q (/eye; rarely on launch)', ms: EYE_MS + 500,
|
|
142
|
-
frame: (t, o) => art(eyeCellsAt(big, t, { color: !o.mono })) },
|
|
143
147
|
{ id: 'hello', title: 'Hello', caption: 'the Q locks, then says hello: the line decodes in from katakana with a short afterglow', ms: INTRO_MS + helloMs(hello) + 600,
|
|
144
148
|
frame: (t, o) => {
|
|
145
149
|
const q = art(t < INTRO_MS ? orbitCellsAt(big, t, { color: !o.mono }) : qCells(big, { color: !o.mono }));
|
|
@@ -162,9 +166,19 @@ export function galleryScenes({ cols = 100, rows = 30, narrow = false } = {}) {
|
|
|
162
166
|
frame: (t, o) => ringLines(Math.min(W - 2, 72), ringState(t), o.mono) },
|
|
163
167
|
{ id: 'fx', title: 'Reveal effects', caption: 'tool row, answer drop-in, approval arming, receipt seal, ProofFlow, closing line', ms: FX_MS,
|
|
164
168
|
frame: (t, o) => fxLines(t, Math.min(W - 2, 78), o.mono) },
|
|
169
|
+
{ id: 'dragon', title: CLIPS.pick.title, caption: CLIPS.pick.caption, ms: CLIPS.pick.dur, frame: (t, o) => dragon('pick', t, o) },
|
|
170
|
+
{ id: 'dragon-fire', title: CLIPS.fire.title, caption: CLIPS.fire.caption, ms: CLIPS.fire.dur, frame: (t, o) => dragon('fire', t, o) },
|
|
171
|
+
{ id: 'dragon-moods', title: 'Dragon moods', caption: 'at the cinematic motion level the dragon reacts: wide eyes and an amber glint while it waits for you, a wink when done, a puff of smoke on failure, and a greeting on launch',
|
|
172
|
+
ms: MOOD_SPAN.reduce((a, b) => a + b, 0),
|
|
173
|
+
frame: (t, o) => {
|
|
174
|
+
let i = 0, s0 = 0;
|
|
175
|
+
while (i < MOOD_CLIPS.length - 1 && t >= s0 + MOOD_SPAN[i]) s0 += MOOD_SPAN[i++];
|
|
176
|
+
const m = MOOD_CLIPS[i];
|
|
177
|
+
return [...art(clipCells(m, t - s0, { cols: Math.min(W, 64), theme, mono: o.mono })), '', paint(fit(`${m}: ${CLIPS[m].caption.split(':')[1].replace(/\s*\(.*\)$/, '').trim()}`), MUTED, o.mono)];
|
|
178
|
+
} },
|
|
165
179
|
];
|
|
166
180
|
}
|
|
167
|
-
export const SCENE_IDS = ['orbit', 'portal', 'particle', '
|
|
181
|
+
export const SCENE_IDS = ['orbit', 'portal', 'particle', 'hello', 'perpetual', 'ring', 'fx', 'dragon', 'dragon-fire', 'dragon-moods'];
|
|
168
182
|
|
|
169
183
|
// ---- runner (the only impure part) ---------------------------------------------------------------------------
|
|
170
184
|
const visible = (s) => s.replace(/\x1b\[[0-9;?]*[A-Za-z]/g, ''); // eslint-disable-line no-control-regex
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QUOXON handoff offer (owner, live 2026-10-17 19:51). Attached to TRIAGE-Q, the owner asked which VMs use the most CPU
|
|
3
|
+
* and memory; TRIAGE-Q had no fleet tools and told him to detach and ask CommanderQ. Offer, don't instruct: the server
|
|
4
|
+
* sends turn.handoff_suggested (from the turn's tool selection, never the answer text) and the TUI asks once:
|
|
5
|
+
*
|
|
6
|
+
* ⚠ TRIAGE-Q asks: Hand this question to CommanderQ?
|
|
7
|
+
* do ask CommanderQ "which VMs use the most CPU and memory" once; you stay attached to TRIAGE-Q
|
|
8
|
+
* why TRIAGE-Q can't read VM stats.
|
|
9
|
+
* ▶ Hand this question to CommanderQ? y yes · n no
|
|
10
|
+
*
|
|
11
|
+
* y sends the SAME question to CommanderQ for that one turn; the window stays attached. n does nothing.
|
|
12
|
+
*/
|
|
13
|
+
import { askText } from './ask.js';
|
|
14
|
+
|
|
15
|
+
const clipQ = (s, n) => { const t = String(s ?? '').replace(/\s+/g, ' ').trim(); return t.length > n ? `${t.slice(0, n - 1)}…` : t; };
|
|
16
|
+
|
|
17
|
+
/** The suggestion from a turn event, when it applies to THIS window (attached to the agent it names). Else null. */
|
|
18
|
+
export function handoffFromEvent(ev, attachedAgent) {
|
|
19
|
+
if (!ev || ev.type !== 'turn.handoff_suggested') return null;
|
|
20
|
+
const h = ev.handoff_suggested;
|
|
21
|
+
const at = String(attachedAgent || '').toLowerCase();
|
|
22
|
+
if (!h || typeof h !== 'object' || !h.agent || !at) return null;
|
|
23
|
+
if (String(h.agent).toLowerCase() === at) return null; // already talking to the target
|
|
24
|
+
if (h.from && String(h.from).toLowerCase() !== at) return null; // a suggestion about some other agent
|
|
25
|
+
return { agent: String(h.agent), name: String(h.name || h.agent), from: at, fromName: String(h.from_name || at.toUpperCase()), reason: String(h.reason || '').trim() };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** The ask line (drawn as the approval-style card). question: the user's own words being handed over. */
|
|
29
|
+
export function handoffAskText(h, question, cols = 100) {
|
|
30
|
+
const action = `ask ${h.name} "${clipQ(question, Math.max(20, cols - 60))}" once; you stay attached to ${h.fromName}`;
|
|
31
|
+
return askText(`Hand this question to ${h.name}?`, { keys: 'y/N', who: h.fromName, reason: h.reason, action });
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** The one line after a yes-path answer: who answered, and that nothing about the attach changed. */
|
|
35
|
+
export const handoffAnsweredNote = (h) => `↪ ${h.name} answered that one. Still attached to ${h.fromName}.`;
|
|
36
|
+
|
|
37
|
+
/** The agent's own suggested command, dropped because the handoff answers the question (never silently). */
|
|
38
|
+
export const handoffSupersededNote = (h, line) => `↷ ${h.fromName} also suggested ${line || 'a command'}; not offered, because ${h.name} can answer the question itself.`;
|
|
39
|
+
|
|
40
|
+
/** A handoff that could not be offered because a plan card is open (never silently). */
|
|
41
|
+
export const handoffSkippedNote = (h) => `↷ ${h.name} could answer this, but the plan card is open, so the handoff is not offered.`;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* QUOXON handoff streak (owner, live 05:10-05:17): attached to TRIAGE-Q he asked three infra questions and every one ended
|
|
45
|
+
* in "Hand this question to CommanderQ?". When the SAME attached agent has suggested a handoff on 2 turns in a row, the
|
|
46
|
+
* card offers the bigger move (back to CommanderQ) instead of the same small one. c ("keep asking") silences the bigger
|
|
47
|
+
* offer until a new streak starts; a turn with no suggestion, or a different agent, breaks the streak.
|
|
48
|
+
*/
|
|
49
|
+
export function createHandoffStreak() {
|
|
50
|
+
let agent = null, count = 0, muted = false;
|
|
51
|
+
return {
|
|
52
|
+
/** A turn ended with a handoff suggestion from `from`. -> { count, escalate } */
|
|
53
|
+
offered(from) {
|
|
54
|
+
const f = String(from || '').toLowerCase();
|
|
55
|
+
if (f === agent) count += 1; else { agent = f; count = 1; muted = false; }
|
|
56
|
+
return { count, escalate: count >= 2 && !muted };
|
|
57
|
+
},
|
|
58
|
+
/** A turn ended without a suggestion (or the window moved agents): the streak is over. */
|
|
59
|
+
clear() { agent = null; count = 0; muted = false; },
|
|
60
|
+
/** n (just this one): the bigger offer needs a fresh run of two before it returns. */
|
|
61
|
+
restart() { count = 0; },
|
|
62
|
+
/** c (keep asking): no bigger offer until a new streak starts. */
|
|
63
|
+
mute() { muted = true; },
|
|
64
|
+
peek: () => ({ agent, count, muted }),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export const HANDOFF_BIG_KEYS = (h) => `y switch · n just this one · c keep asking ${h.fromName}`;
|
|
69
|
+
|
|
70
|
+
/** The bigger card: offer to move the whole window back instead of handing over one more question. */
|
|
71
|
+
export function handoffBiggerAskText(h, count) {
|
|
72
|
+
return askText(`Switch this window back to ${h.name}?`, {
|
|
73
|
+
keys: HANDOFF_BIG_KEYS(h),
|
|
74
|
+
who: h.fromName,
|
|
75
|
+
reason: `${h.fromName} has handed off your last ${count} questions`,
|
|
76
|
+
action: `/detach: this window stops being attached to ${h.fromName} and talks to ${h.name}`,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* /hat helpers (pure), quox#1183. Match a hat the way a person names it (key, display name, "the DevOps hat", a unique
|
|
3
|
+
* prefix) and the lines /hat prints. Never guesses between ties; an unknown name is { none: true }.
|
|
4
|
+
*/
|
|
5
|
+
const norm = (s) => String(s ?? '').toLowerCase().replace(/[^a-z0-9]+/g, ' ').trim().replace(/^the /, '').replace(/ hat$/, '').trim();
|
|
6
|
+
|
|
7
|
+
export const isPrivileged = (hat) => hat?.definition?.transitions?.privileged === true || hat?.privileged === true;
|
|
8
|
+
|
|
9
|
+
/** -> { hat } | { ambiguous: [hats] } | { none: true } */
|
|
10
|
+
export function matchHat(query, hats) {
|
|
11
|
+
const rows = Array.isArray(hats) ? hats.filter(Boolean) : [];
|
|
12
|
+
const raw = String(query ?? '').trim();
|
|
13
|
+
const q = norm(raw);
|
|
14
|
+
if (!q) return { none: true };
|
|
15
|
+
const byId = rows.filter((h) => h.id && h.id === raw);
|
|
16
|
+
if (byId.length === 1) return { hat: byId[0] };
|
|
17
|
+
const exact = rows.filter((h) => norm(h.key) === q || norm(h.name) === q);
|
|
18
|
+
if (exact.length === 1) return { hat: exact[0] };
|
|
19
|
+
if (exact.length > 1) return { ambiguous: exact };
|
|
20
|
+
const pre = rows.filter((h) => norm(h.key).startsWith(q) || norm(h.name).startsWith(q));
|
|
21
|
+
if (pre.length === 1) return { hat: pre[0] };
|
|
22
|
+
if (pre.length > 1) return { ambiguous: pre };
|
|
23
|
+
return { none: true };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** "key (Name)" for each hat, for an error that tells you what to type. */
|
|
27
|
+
export const hatChoices = (hats) => (Array.isArray(hats) ? hats : []).map((h) => (h.name && h.name !== h.key ? `${h.key} (${h.name})` : h.key)).join(', ');
|
|
28
|
+
|
|
29
|
+
/** One line per hat; the active one is marked with *. */
|
|
30
|
+
export function hatLines(hats, activeKey) {
|
|
31
|
+
const rows = Array.isArray(hats) ? hats : [];
|
|
32
|
+
const w = Math.min(28, Math.max(4, ...rows.map((h) => [...String(h.name || h.key)].length)));
|
|
33
|
+
return rows.map((h) => `${h.key === activeKey ? '* ' : ' '}${String(h.name || h.key).padEnd(w)} ${h.key}${isPrivileged(h) ? ' (privileged: asks you first)' : ''}`);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** A conversation id in the collector's own shape (turnStream ensureConversationId), for a window with none yet. */
|
|
37
|
+
export const mintConversationId = (uuid) => `QXC_${uuid}`;
|