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,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The assistant can hand the TUI a slash command for things only the terminal can do (attach a slot, join a room,
|
|
3
|
+
* open a view). The TUI is the authority: only an active registry command, never a `!` shell, never the commands the
|
|
4
|
+
* human must type (approvals, grants, session end, sign-in). The offer is "Run /slot hitl? y/N"; y runs it through the
|
|
5
|
+
* normal command path so every existing guard applies. A read-only view may run without asking when the user's
|
|
6
|
+
* ask was explicit.
|
|
7
|
+
*/
|
|
8
|
+
import { findCommandEntry } from './commands.js';
|
|
9
|
+
|
|
10
|
+
// Commands that need the human's own typing: never offered, never run from a model suggestion.
|
|
11
|
+
export const HUMAN_TYPED = new Set(['approve', 'yestoall', 'deny', 'authorise', 'authorize', 'authenticate', 'override', 'restart', 'quit', 'exit', 'login']);
|
|
12
|
+
// Pure views, no effect: may run without asking when explicit, and only with no arguments.
|
|
13
|
+
export const READ_ONLY_VIEWS = new Set(['approvals', 'mind', 'which', 'context', 'rooms', 'active', 'slots']);
|
|
14
|
+
|
|
15
|
+
/** -> { ok: true, line, id, readOnly } | { ok: false, reason } */
|
|
16
|
+
export function vetSuggestion(raw) {
|
|
17
|
+
const line = typeof raw === 'string' ? raw.trim() : '';
|
|
18
|
+
if (!line) return { ok: false, reason: 'empty' };
|
|
19
|
+
if (/[\r\n]/.test(line) || line.length > 120) return { ok: false, reason: 'not a single short line' };
|
|
20
|
+
if (!line.startsWith('/') || line.startsWith('//')) return { ok: false, reason: 'only slash commands can be suggested' };
|
|
21
|
+
const word = line.slice(1).split(/\s+/)[0];
|
|
22
|
+
const entry = findCommandEntry(word);
|
|
23
|
+
if (!entry || entry.kind !== 'command' || entry.status !== 'active') return { ok: false, reason: `/${word} is not a command here` };
|
|
24
|
+
if (HUMAN_TYPED.has(entry.id) || HUMAN_TYPED.has(word.toLowerCase())) return { ok: false, reason: `/${word} must be typed by you` };
|
|
25
|
+
const bare = line.slice(1 + word.length).trim() === '';
|
|
26
|
+
return { ok: true, line, id: entry.id, readOnly: READ_ONLY_VIEWS.has(entry.id) && bare };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Safety net (owner 2026-10-15 "STOP TELLING ME TO RUN COMMANDS"): the answer text named a command instead of the
|
|
31
|
+
* model calling terminal_suggest_command. Exactly ONE distinct slash command in backticks that passes vetSuggestion
|
|
32
|
+
* -> that vetted suggestion; none, two or more distinct, or a human-typed one (/approve, /restart...) -> null.
|
|
33
|
+
*/
|
|
34
|
+
export function namedCommand(text) {
|
|
35
|
+
const src = String(text || '');
|
|
36
|
+
const closing = closingProposal(src);
|
|
37
|
+
if (closing !== undefined) return closing; // the answer's own closing question names THE command: that one or nothing
|
|
38
|
+
const seen = new Set();
|
|
39
|
+
for (const m of src.matchAll(/`(\/[a-z][^`\n]{0,118})`/gi)) {
|
|
40
|
+
if (mentionsAPath(src, m.index, m[0].length, m[1])) continue; // `/health` the endpoint is not /health the command
|
|
41
|
+
seen.add(m[1].trim().replace(/\s+/g, ' '));
|
|
42
|
+
}
|
|
43
|
+
if (seen.size !== 1) return null;
|
|
44
|
+
return offerable([...seen][0]);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Commands that SEND or WRITE something: a mention is never an offer (the model must call terminal_suggest_command).
|
|
48
|
+
export const WRITE_COMMANDS = new Set(['broadcast', 'roar', 'say', 'page', 'ack', 'reply', 'r', 'ask', 'aside', 'afk', 'back', 'observe', 'awaken', 'poke', 'wake', 'nudge', 'ping', 'slap', 'feedback', 'gripe', 'gripe!', 'note', 'dragon', 'wish', 'challenge', 'huh']);
|
|
49
|
+
|
|
50
|
+
// vetted AND safe to offer from a mention: not a write/send, and no required <arg> left unfilled.
|
|
51
|
+
function offerable(line) {
|
|
52
|
+
const v = vetSuggestion(line);
|
|
53
|
+
if (!v.ok) return null;
|
|
54
|
+
if (WRITE_COMMANDS.has(v.id) || WRITE_COMMANDS.has(line.slice(1).split(/\s+/)[0].toLowerCase())) return null;
|
|
55
|
+
const entry = findCommandEntry(line.slice(1).split(/\s+/)[0]);
|
|
56
|
+
const bare = line.trim().split(/\s+/).length === 1;
|
|
57
|
+
if (bare && /</.test(String(entry?.usage || ''))) return null; // "/ack <broadcast id>" needs its argument
|
|
58
|
+
return v;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// "Do you want me to run X?" / "Shall I run X?" -> undefined (no such question) | the offer for X | null.
|
|
62
|
+
// X as a slash command is offered when safe; X as a `quox <verb>` CLI line is offered only as the matching read-only slash view.
|
|
63
|
+
function closingProposal(src) {
|
|
64
|
+
const m = /(?:do you want me to|want me to|would you like me to|shall i|should i|can i)\s+(?:run|try|use|show you)\s+`?([^?`\n]{1,120}?)`?\s*(?:and\b[^?\n]*)?\?\s*$/i.exec(src.trim());
|
|
65
|
+
if (!m) return undefined;
|
|
66
|
+
const x = m[1].trim();
|
|
67
|
+
if (x.startsWith('/')) return offerable(x.replace(/\s+/g, ' '));
|
|
68
|
+
const cli = /^!?quox\s+([a-z][\w-]*)\s*$/i.exec(x);
|
|
69
|
+
if (cli) {
|
|
70
|
+
const slash = '/' + cli[1].toLowerCase();
|
|
71
|
+
const v = offerable(slash);
|
|
72
|
+
return v && v.readOnly ? v : null;
|
|
73
|
+
}
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// A URL path, not a command (owner live 05:26: "the `/health` endpoint is erroring" offered "Run /health?").
|
|
78
|
+
const PATHY = /\b(endpoints?|paths?|routes?|urls?|uris?|ports?|curl|wget|https?|localhost)\b|:\d{2,5}\b/i;
|
|
79
|
+
const VERBS = /\b(GET|POST|PUT|PATCH|DELETE|HEAD)\b/; // upper-case only: "to get a view" is not an HTTP verb
|
|
80
|
+
function mentionsAPath(src, at, len, inner) {
|
|
81
|
+
const first = String(inner).trim().split(/\s+/)[0];
|
|
82
|
+
if (/^\/[^/]*\//.test(first)) return true; // /api/x or /health/ready: a longer path
|
|
83
|
+
const sentence = (from, dir) => { // the same sentence, up to 40 characters away
|
|
84
|
+
const t = dir < 0 ? src.slice(Math.max(0, from - 40), from) : src.slice(from, from + 40);
|
|
85
|
+
const parts = t.split(/[.!?\n]/);
|
|
86
|
+
return dir < 0 ? parts[parts.length - 1] : parts[0];
|
|
87
|
+
};
|
|
88
|
+
const near = sentence(at, -1) + ' ' + sentence(at + len, 1);
|
|
89
|
+
return PATHY.test(near) || VERBS.test(near);
|
|
90
|
+
}
|
package/lib/quoxon/term.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { graphemes } from './textBuffer.js';
|
|
2
|
+
import { createScreenMirror, MOUSE_ON, MOUSE_OFF, DSR } from './mouse.js';
|
|
1
3
|
/**
|
|
2
4
|
* The terminal as Ink sees it.
|
|
3
5
|
*
|
|
@@ -12,9 +14,16 @@
|
|
|
12
14
|
* repaint checks read stdout.rows). The app reads the real height from termRows() and keeps its live
|
|
13
15
|
* region shorter than the screen itself (app.js), so log-update can always erase it.
|
|
14
16
|
*/
|
|
15
|
-
export function inkStdout(out) {
|
|
17
|
+
export function inkStdout(out, { tap = null } = {}) {
|
|
16
18
|
if (!out || out.__quoxInk) return out;
|
|
17
|
-
|
|
19
|
+
// QUOXON-MOUSE: what Ink has drawn, as plain lines, for click hit-testing (mouse.js). Fed the same writes.
|
|
20
|
+
const mirror = out.isTTY ? createScreenMirror({ columns: () => out.columns }) : null;
|
|
21
|
+
// SCREEN-FRAMES: the same feed goes to the screen recorder's tap (screenFrames.js), which only queues it.
|
|
22
|
+
const feed = mirror && tap ? {
|
|
23
|
+
write: (c) => { mirror.write(c); try { tap.write(c); } catch { /* never break a frame */ } },
|
|
24
|
+
reset: () => { mirror.reset(); try { tap.reset(); } catch { /* ignore */ } }
|
|
25
|
+
} : mirror;
|
|
26
|
+
const diff = out.isTTY ? createLineDiff({ columns: () => out.columns, mirror: feed }) : null;
|
|
18
27
|
// One frame = one write: Ink brackets each frame in separate synchronized-update writes (?2026h ... ?2026l);
|
|
19
28
|
// they are held and sent together with the (diffed) frame, and a frame that changed nothing sends nothing.
|
|
20
29
|
// A write that sends nothing (or is held for the frame) must still call its callback: Ink's unmount waits
|
|
@@ -44,7 +53,9 @@ export function inkStdout(out) {
|
|
|
44
53
|
if (prop === 'write' && write) return write;
|
|
45
54
|
if (prop === 'rows') return Infinity;
|
|
46
55
|
if (prop === 'termRows') return target.rows;
|
|
56
|
+
if (prop === 'frameRows') { const n = diff?.rows(); return n == null ? null : Math.min(n, Math.max(0, (target.rows || n + 1) - 1)); }
|
|
47
57
|
if (prop === '__quoxInk') return true;
|
|
58
|
+
if (prop === 'screen') return mirror;
|
|
48
59
|
const v = Reflect.get(target, prop, target);
|
|
49
60
|
return typeof v === 'function' ? v.bind(target) : v;
|
|
50
61
|
},
|
|
@@ -69,16 +80,34 @@ export function termRows(stdout) {
|
|
|
69
80
|
* through untouched and resets the diff, so the next frame is drawn in full. Cursor moves never scroll.
|
|
70
81
|
* (Ink's own incrementalRendering was tried first and duplicates lines on the dropped-text replay.)
|
|
71
82
|
*/
|
|
83
|
+
const ANSI_RE = /\x1b\[[0-9;?]*[ -\/]*[@-~]|\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)/g;
|
|
84
|
+
const visibleWidth = (line) => graphemes(String(line).replace(ANSI_RE, '')).length;
|
|
72
85
|
const FRAME_RE = /^((?:\x1b\[2K\x1b\[1A)*\x1b\[2K\x1b\[G)([\s\S]*)$/;
|
|
73
86
|
const SYNC_ON = '\x1b[?2026h', SYNC_OFF = '\x1b[?2026l';
|
|
74
|
-
const TRANSPARENT = new Set(['\x1b[?2026h', '\x1b[?2026l', '\x1b[?25l', '\x1b[?25h', '']);
|
|
75
|
-
export function createLineDiff() {
|
|
87
|
+
const TRANSPARENT = new Set(['\x1b[?2026h', '\x1b[?2026l', '\x1b[?25l', '\x1b[?25h', '', MOUSE_ON, MOUSE_OFF, DSR]); // + QUOXON-MOUSE mode switches and the click's cursor query
|
|
88
|
+
export function createLineDiff({ columns = () => null, mirror = null } = {}) {
|
|
76
89
|
let prev = null; // lines of the last frame, trailing '' included
|
|
90
|
+
const bodyLines = (body) => (body && body.endsWith('\n') && !body.includes('\x1b[2J') ? body.split('\n') : null);
|
|
77
91
|
const diff = (chunk) => {
|
|
78
|
-
if (TRANSPARENT.has(chunk)) return chunk;
|
|
92
|
+
if (TRANSPARENT.has(chunk)) return chunk;
|
|
93
|
+
mirror?.write(chunk); // synchronized-update brackets and cursor hide/show around a frame
|
|
79
94
|
const m = FRAME_RE.exec(chunk);
|
|
80
|
-
|
|
95
|
+
// Not an erase+frame: a frame Ink drew after clearing (no erase prefix), static history, or something else.
|
|
96
|
+
// The last such write before the next erase is the frame on screen (Ink writes static output, then the frame).
|
|
97
|
+
if (!m) { prev = bodyLines(chunk); return chunk; }
|
|
81
98
|
const erased = (m[1].match(/\x1b\[2K/g) || []).length;
|
|
99
|
+
const cols = columns();
|
|
100
|
+
if (prev && cols > 0) {
|
|
101
|
+
// R3 D2: after a resize the terminal has reflowed the old frame (a narrower screen wraps each line onto more
|
|
102
|
+
// rows), and Ink's erase counts lines, not rows: ghost composers, or history reprinted to cover them. Count
|
|
103
|
+
// the rows the frame really occupies at this width; when that differs, go to its top and clear down.
|
|
104
|
+
const rows = prev.slice(0, -1).reduce((n, l) => n + Math.max(1, Math.ceil(visibleWidth(l) / cols)), 0);
|
|
105
|
+
if (rows !== erased - 1) {
|
|
106
|
+
prev = bodyLines(m[2]);
|
|
107
|
+
return `\r${rows ? `\x1b[${rows}A` : ''}\x1b[J${m[2]}`;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (!m[2].endsWith('\n') || m[2].includes('\x1b[2J')) { prev = null; return chunk; }
|
|
82
111
|
const next = m[2].split('\n');
|
|
83
112
|
if (!prev || erased !== prev.length || next.length !== prev.length || next.length < 2) { prev = next; return chunk; }
|
|
84
113
|
const body = next.length - 1;
|
|
@@ -92,15 +121,21 @@ export function createLineDiff() {
|
|
|
92
121
|
prev = next;
|
|
93
122
|
return changed ? out : '';
|
|
94
123
|
};
|
|
95
|
-
diff.reset = () => { prev = null; };
|
|
124
|
+
diff.reset = () => { prev = null; mirror?.reset(); };
|
|
125
|
+
/** Rows the live frame occupies on screen (wrapped at the current width), or null when unknown. SLOT-RUN hand-over. */
|
|
126
|
+
diff.rows = () => {
|
|
127
|
+
if (!prev) return null;
|
|
128
|
+
const cols = columns();
|
|
129
|
+
return prev.slice(0, -1).reduce((n, l) => n + (cols > 0 ? Math.max(1, Math.ceil(visibleWidth(l) / cols)) : 1), 0);
|
|
130
|
+
};
|
|
96
131
|
return diff;
|
|
97
132
|
}
|
|
98
133
|
|
|
99
134
|
/**
|
|
100
135
|
* Ink render options for the interactive client. Standard rendering (Ink's incrementalRendering duplicated lines on
|
|
101
|
-
* the dropped-text replay); the stdout proxy line-diffs same-height frames instead. Never
|
|
102
|
-
*
|
|
136
|
+
* the dropped-text replay); the stdout proxy line-diffs same-height frames instead. Never the alternate screen, and
|
|
137
|
+
* no mouse tracking unless the user turns it on (/mouse on, QUOXON-MOUSE): native selection keeps working by default.
|
|
103
138
|
*/
|
|
104
|
-
export function inkRenderOptions(stdout) {
|
|
105
|
-
return { exitOnCtrlC: false, stdout: inkStdout(stdout) };
|
|
139
|
+
export function inkRenderOptions(stdout, { tap = null } = {}) {
|
|
140
|
+
return { exitOnCtrlC: false, stdout: inkStdout(stdout, { tap }) };
|
|
106
141
|
}
|
package/lib/quoxon/textBuffer.js
CHANGED
|
@@ -18,7 +18,135 @@ const UNDO_LIMIT = 100;
|
|
|
18
18
|
const PASTE_RE = /\[Pasted text #(\d+) \+\d+ lines\]/g;
|
|
19
19
|
|
|
20
20
|
export function initialBuffer(text = '') {
|
|
21
|
-
return { text, cursor: text.length, undo: [], redo: [], pastes: {}, pasteSeq: 0 };
|
|
21
|
+
return { text, cursor: text.length, anchor: null, undo: [], redo: [], pastes: {}, pasteSeq: 0 };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* QUOXON-MOUSE: the composer selection, { from, to } (from < to), or null. A selection is the range between
|
|
26
|
+
* `anchor` (where a drag started) and the cursor; only the mouse sets an anchor.
|
|
27
|
+
*/
|
|
28
|
+
export function selection(s) {
|
|
29
|
+
if (s?.anchor == null || s.anchor === s.cursor) return null;
|
|
30
|
+
return { from: Math.min(s.anchor, s.cursor), to: Math.max(s.anchor, s.cursor) };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** The word under `idx` for a double-click: a run of non-space characters, or the one space character itself. */
|
|
34
|
+
export function wordAt(text, idx) {
|
|
35
|
+
const t = String(text);
|
|
36
|
+
const i = Math.max(0, Math.min(idx, t.length - 1));
|
|
37
|
+
if (!t.length) return { from: 0, to: 0 };
|
|
38
|
+
if (/\s/.test(t[i])) return { from: i, to: nextBoundary(t, i) };
|
|
39
|
+
let a = i; let b = i;
|
|
40
|
+
while (a > 0 && !/\s/.test(t[a - 1])) a--;
|
|
41
|
+
while (b < t.length && !/\s/.test(t[b])) b++;
|
|
42
|
+
return { from: a, to: b };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Snap an index onto a grapheme boundary (at or before it). */
|
|
46
|
+
function snap(text, idx) {
|
|
47
|
+
const i = Math.max(0, Math.min(Number(idx) || 0, text.length));
|
|
48
|
+
return i === text.length ? i : prevBoundary(text, i + 1);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** The selected range widened so it never leaves half a paste placeholder behind. */
|
|
52
|
+
function selRange(s, sel) {
|
|
53
|
+
let { from, to } = sel;
|
|
54
|
+
PASTE_RE.lastIndex = 0;
|
|
55
|
+
let m;
|
|
56
|
+
while ((m = PASTE_RE.exec(s.text))) {
|
|
57
|
+
const a = m.index, b = a + m[0].length;
|
|
58
|
+
if (to > a && from < b) { from = Math.min(from, a); to = Math.max(to, b); }
|
|
59
|
+
}
|
|
60
|
+
return { from, to };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const EDITS = new Set(['insert', 'newline', 'paste', 'backspace', 'delete', 'deleteWordBack', 'killLineBack', 'killLineForward', 'deleteWordForward']);
|
|
64
|
+
|
|
65
|
+
/** COMPOSER-EDIT: the selected text, paste placeholders expanded (what Shift selection copies). '' when none. */
|
|
66
|
+
export function selectedText(s) {
|
|
67
|
+
const sel = selection(s);
|
|
68
|
+
if (!sel) return '';
|
|
69
|
+
const { from, to } = selRange(s, sel);
|
|
70
|
+
return expandPastes({ text: s.text.slice(from, to), pastes: s.pastes || {} });
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// ---- COMPOSER-EDIT: words and soft-wrapped rows ------------------------------------------------
|
|
74
|
+
const WORD = /[\p{L}\p{N}_]/u;
|
|
75
|
+
/** Readline backward-word: skip non-word characters, then the word. */
|
|
76
|
+
function wordLeftIndex(t, i) {
|
|
77
|
+
while (i > 0 && !WORD.test(t[i - 1])) i--;
|
|
78
|
+
while (i > 0 && WORD.test(t[i - 1])) i--;
|
|
79
|
+
return i;
|
|
80
|
+
}
|
|
81
|
+
/** Readline forward-word: skip non-word characters, then the word (lands at its end). */
|
|
82
|
+
function wordRightIndex(t, i) {
|
|
83
|
+
while (i < t.length && !WORD.test(t[i])) i++;
|
|
84
|
+
while (i < t.length && WORD.test(t[i])) i++;
|
|
85
|
+
return i;
|
|
86
|
+
}
|
|
87
|
+
/** Remove [from, to), widened so no paste placeholder is left half cut. */
|
|
88
|
+
function cutRange(s, from, to) {
|
|
89
|
+
if (from >= to) return s;
|
|
90
|
+
const r = selRange(s, { from, to });
|
|
91
|
+
return commit(s, { text: s.text.slice(0, r.from) + s.text.slice(r.to), cursor: r.from });
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const cellW = (g) => (/[ᄀ-ᅟ⺀-〾ぁ-㏿㐀-䶿一-鿿ꀀ-가-힣豈-︰-﹏-⦆¢-₩]/u.test(g) || (/\p{Extended_Pictographic}/u.test(g) && (g.codePointAt(0) >= 0x1f000 || g.includes('\uFE0F'))) ? 2 : 1);
|
|
95
|
+
/**
|
|
96
|
+
* The draft as the rows the terminal shows at `width` cells: [{ start, end }] (UTF-16 indices, end exclusive,
|
|
97
|
+
* newline not included). Word wrap like Ink's: a word that does not fit starts a new row, spaces hang at the row
|
|
98
|
+
* end, and a word longer than a row is split hard.
|
|
99
|
+
*/
|
|
100
|
+
export function visualRows(text, width) {
|
|
101
|
+
const out = [];
|
|
102
|
+
let o = 0;
|
|
103
|
+
for (const line of String(text).split('\n')) {
|
|
104
|
+
if (!(width > 0) || !line.length) { out.push({ start: o, end: o + line.length }); o += line.length + 1; continue; }
|
|
105
|
+
const tokens = line.match(/\s+|\S+/g);
|
|
106
|
+
let rowStart = o; let w = 0; let p = o;
|
|
107
|
+
for (const tok of tokens) {
|
|
108
|
+
const gs = graphemes(tok);
|
|
109
|
+
const tw = gs.reduce((n, g) => n + cellW(g.text), 0);
|
|
110
|
+
if (/^\s/.test(tok) || w + tw <= width) { w += tw; p += tok.length; continue; }
|
|
111
|
+
if (tw <= width) { out.push({ start: rowStart, end: p }); rowStart = p; w = tw; p += tok.length; continue; }
|
|
112
|
+
for (const g of gs) { // longer than a row: split it hard
|
|
113
|
+
const cw = cellW(g.text);
|
|
114
|
+
if (w + cw > width && w > 0) { out.push({ start: rowStart, end: p }); rowStart = p; w = 0; }
|
|
115
|
+
w += cw; p += g.text.length;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
out.push({ start: rowStart, end: o + line.length });
|
|
119
|
+
o += line.length + 1;
|
|
120
|
+
}
|
|
121
|
+
return out;
|
|
122
|
+
}
|
|
123
|
+
/** Index of the visual row holding `cursor`. A cursor at a soft break sits at the start of the next row. */
|
|
124
|
+
function rowOf(rows, cursor) {
|
|
125
|
+
for (let i = rows.length - 1; i >= 0; i--) if (cursor >= rows[i].start) return i;
|
|
126
|
+
return 0;
|
|
127
|
+
}
|
|
128
|
+
/** COMPOSER-EDIT: is the cursor on the first (`up`) or last (`down`) row the terminal shows? */
|
|
129
|
+
export function atVisualEdge(s, dir, width) {
|
|
130
|
+
if (!(width > 0)) {
|
|
131
|
+
return dir === 'up' ? !s.text.slice(0, s.cursor).includes('\n') : !s.text.slice(s.cursor).includes('\n');
|
|
132
|
+
}
|
|
133
|
+
const rows = visualRows(s.text, width);
|
|
134
|
+
const r = rowOf(rows, s.cursor);
|
|
135
|
+
return dir === 'up' ? r === 0 : r === rows.length - 1;
|
|
136
|
+
}
|
|
137
|
+
function moveVisual(s, dir, width) {
|
|
138
|
+
const rows = visualRows(s.text, width);
|
|
139
|
+
const r = rowOf(rows, s.cursor);
|
|
140
|
+
const t = dir === 'up' ? r - 1 : r + 1;
|
|
141
|
+
if (t < 0 || t >= rows.length) return s;
|
|
142
|
+
const col = graphemes(s.text.slice(rows[r].start, s.cursor)).reduce((n, g) => n + cellW(g.text), 0);
|
|
143
|
+
const row = rows[t];
|
|
144
|
+
const soft = t + 1 < rows.length && rows[t + 1].start === row.end; // the row breaks softly: its end is the next row's start
|
|
145
|
+
const gs = graphemes(s.text.slice(row.start, row.end));
|
|
146
|
+
let at = 0; let idx = row.end;
|
|
147
|
+
for (const g of gs) { const w = cellW(g.text); if (col < at + w) { idx = row.start + g.index; break; } at += w; }
|
|
148
|
+
if (soft && idx >= row.end) idx = prevBoundary(s.text, row.end);
|
|
149
|
+
return { ...s, cursor: idx };
|
|
22
150
|
}
|
|
23
151
|
|
|
24
152
|
export function graphemes(text) {
|
|
@@ -86,6 +214,14 @@ function indexAtLineCol(text, line, col) {
|
|
|
86
214
|
return start + gs[col].index;
|
|
87
215
|
}
|
|
88
216
|
|
|
217
|
+
/** First logical line the composer shows (it shows at most `maxLines`, keeping the cursor line in view). */
|
|
218
|
+
export function composerFrom(buf, maxLines = 8) {
|
|
219
|
+
const n = buf.text.split('\n').length;
|
|
220
|
+
if (n <= maxLines) return 0;
|
|
221
|
+
const { line } = lineCol(buf.text, buf.cursor);
|
|
222
|
+
return Math.min(Math.max(0, line - Math.floor(maxLines / 2)), n - maxLines);
|
|
223
|
+
}
|
|
224
|
+
|
|
89
225
|
export function normalizeNewlines(t) { return String(t).replace(/\r\n?/g, '\n'); }
|
|
90
226
|
|
|
91
227
|
/** Build the collapsed form of a paste, or null when it is small enough to inline. */
|
|
@@ -94,7 +230,36 @@ export function shouldCollapse(text) {
|
|
|
94
230
|
return text.length > PASTE_CHAR_LIMIT || lines > PASTE_LINE_LIMIT;
|
|
95
231
|
}
|
|
96
232
|
|
|
97
|
-
export function bufferReducer(
|
|
233
|
+
export function bufferReducer(s0, a) {
|
|
234
|
+
const sel = selection(s0);
|
|
235
|
+
const s = s0.anchor == null ? s0 : { ...s0, anchor: null };
|
|
236
|
+
if (sel && EDITS.has(a.type)) {
|
|
237
|
+
// QUOXON-MOUSE: Backspace/Delete remove the selection; typing or pasting replaces it. One undo step.
|
|
238
|
+
const { from, to } = selRange(s, sel);
|
|
239
|
+
const cut = { ...s, text: s.text.slice(0, from) + s.text.slice(to), cursor: from };
|
|
240
|
+
if (a.type !== 'insert' && a.type !== 'newline' && a.type !== 'paste') return commit(s, { text: cut.text, cursor: from });
|
|
241
|
+
const next = baseReducer({ ...cut, undo: [] }, a);
|
|
242
|
+
return commit(s, { text: next.text, cursor: next.cursor, pastes: next.pastes, pasteSeq: next.pasteSeq });
|
|
243
|
+
}
|
|
244
|
+
if (sel && a.type === 'left') return { ...s, cursor: sel.from };
|
|
245
|
+
if (sel && a.type === 'right') return { ...s, cursor: sel.to };
|
|
246
|
+
switch (a.type) {
|
|
247
|
+
case 'extend': { // COMPOSER-EDIT: Shift+movement grows the selection from where it started
|
|
248
|
+
const anchor = s0.anchor ?? s0.cursor;
|
|
249
|
+
const moved = baseReducer({ ...s, anchor: null }, { type: a.move, width: a.width });
|
|
250
|
+
return { ...s, cursor: moved.cursor, anchor: moved.cursor === anchor ? null : anchor };
|
|
251
|
+
}
|
|
252
|
+
case 'selectAll': return s.text ? { ...s, anchor: 0, cursor: s.text.length } : s;
|
|
253
|
+
case 'place': return { ...s, cursor: snap(s.text, a.index) };
|
|
254
|
+
case 'select': {
|
|
255
|
+
const anchor = snap(s.text, a.anchor); const cursor = snap(s.text, a.cursor);
|
|
256
|
+
return { ...s, anchor: anchor === cursor ? null : anchor, cursor };
|
|
257
|
+
}
|
|
258
|
+
default: return baseReducer(s, a);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function baseReducer(s, a) {
|
|
98
263
|
switch (a.type) {
|
|
99
264
|
case 'insert': {
|
|
100
265
|
const t = normalizeNewlines(a.text);
|
|
@@ -144,6 +309,14 @@ export function bufferReducer(s, a) {
|
|
|
144
309
|
if (from === s.cursor) return s;
|
|
145
310
|
return commit(s, { text: s.text.slice(0, from) + s.text.slice(s.cursor), cursor: from });
|
|
146
311
|
}
|
|
312
|
+
case 'wordLeft': return { ...s, cursor: wordLeftIndex(s.text, s.cursor) };
|
|
313
|
+
case 'wordRight': return { ...s, cursor: wordRightIndex(s.text, s.cursor) };
|
|
314
|
+
case 'killLineForward': { // Ctrl+K: to the end of the line; at the end, join the next line
|
|
315
|
+
const nl = s.text.indexOf('\n', s.cursor);
|
|
316
|
+
const end = nl === -1 ? s.text.length : (nl === s.cursor ? nl + 1 : nl);
|
|
317
|
+
return cutRange(s, s.cursor, end);
|
|
318
|
+
}
|
|
319
|
+
case 'deleteWordForward': return cutRange(s, s.cursor, wordRightIndex(s.text, s.cursor)); // Alt+D
|
|
147
320
|
case 'left': return { ...s, cursor: prevBoundary(s.text, s.cursor) };
|
|
148
321
|
case 'right': return { ...s, cursor: nextBoundary(s.text, s.cursor) };
|
|
149
322
|
case 'home': return { ...s, cursor: lineCol(s.text, s.cursor).start };
|
|
@@ -152,6 +325,7 @@ export function bufferReducer(s, a) {
|
|
|
152
325
|
return { ...s, cursor: nl === -1 ? s.text.length : nl };
|
|
153
326
|
}
|
|
154
327
|
case 'up': case 'down': {
|
|
328
|
+
if (a.width > 0) return moveVisual(s, a.type, a.width); // COMPOSER-EDIT: through soft-wrapped rows
|
|
155
329
|
const { line, col } = lineCol(s.text, s.cursor);
|
|
156
330
|
const target = a.type === 'up' ? line - 1 : line + 1;
|
|
157
331
|
const count = s.text.split('\n').length;
|
package/lib/quoxon/theme.js
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* Purple is for keys and accents only, never whole sentences.
|
|
9
9
|
*/
|
|
10
10
|
import { graphemes } from "./textBuffer.js";
|
|
11
|
+
import { SEMANTIC_DARK, SEMANTIC_LIGHT, stripMarks, MARK } from './palette.js';
|
|
11
12
|
|
|
12
13
|
export const MIN_COLS = 40;
|
|
13
14
|
|
|
@@ -20,14 +21,18 @@ export const DARK = {
|
|
|
20
21
|
value: ['#f1f5f9', 255],
|
|
21
22
|
number: ['#38bdf8', 75],
|
|
22
23
|
link: ['#38bdf8', 75],
|
|
23
|
-
code: ['#
|
|
24
|
-
key: ['#
|
|
25
|
-
muted: ['#
|
|
24
|
+
code: ['#2dd4bf', 80], codeBg: ['#1f2937', 235], codeRule: ['#155e75', 24],
|
|
25
|
+
key: ['#2dd4bf', 80], // a thing you type: the command colour (purple is brand chrome only)
|
|
26
|
+
muted: ['#bcc8e8', 189],
|
|
26
27
|
ok: ['#4ade80', 78], warn: ['#f59e0b', 214], error: ['#f87171', 210],
|
|
28
|
+
ask: ['#f472b6', 211], // QUOXON-ASK: "needs you" (every y/N and approval): glyph and keys, bold line
|
|
29
|
+
gold: ['#d4af37', 178], // the authorisation moment: step-up PIN card frame, filled dots, human-present countdown
|
|
27
30
|
// legacy names, kept so existing call sites read the same
|
|
28
31
|
sky: ['#38bdf8', 75], violet: ['#a855f7', 135], indigo: ['#818cf8', 105], teal: ['#06b6d4', 38],
|
|
29
|
-
fail: ['#f87171', 210], dim: ['#
|
|
30
|
-
text: ['#
|
|
32
|
+
fail: ['#f87171', 210], dim: ['#bcc8e8', 189], faint: ['#7c8fe0', 105],
|
|
33
|
+
text: ['#f1f5f9', 255], input: ['#ffffff', 231], placeholder: ['#bcc8e8', 189],
|
|
34
|
+
// semantic roles (palette.js)
|
|
35
|
+
prose: SEMANTIC_DARK.prose, command: SEMANTIC_DARK.command, id: SEMANTIC_DARK.value, brand: SEMANTIC_DARK.brand, quiet: SEMANTIC_DARK.quiet, rule: SEMANTIC_DARK.rule
|
|
31
36
|
};
|
|
32
37
|
export const LIGHT = {
|
|
33
38
|
heading: ['#0369a1', 25],
|
|
@@ -37,16 +42,19 @@ export const LIGHT = {
|
|
|
37
42
|
value: ['#0f172a', 234],
|
|
38
43
|
number: ['#0369a1', 25],
|
|
39
44
|
link: ['#0369a1', 25],
|
|
40
|
-
code: ['#
|
|
41
|
-
key: ['#
|
|
42
|
-
muted: ['#
|
|
45
|
+
code: ['#0f766e', 30], codeBg: ['#ecfeff', 195], codeRule: ['#67e8f9', 117],
|
|
46
|
+
key: ['#0f766e', 30],
|
|
47
|
+
muted: ['#3b4f87', 60],
|
|
43
48
|
ok: ['#15803d', 28], warn: ['#b45309', 130], error: ['#b91c1c', 124],
|
|
49
|
+
ask: ['#be185d', 161],
|
|
50
|
+
gold: ['#8a6500', 94],
|
|
44
51
|
sky: ['#0369a1', 25], violet: ['#7e22ce', 91], indigo: ['#4338ca', 62], teal: ['#0e7490', 30],
|
|
45
|
-
fail: ['#b91c1c', 124], dim: ['#
|
|
46
|
-
text: ['#
|
|
52
|
+
fail: ['#b91c1c', 124], dim: ['#3b4f87', 60], faint: ['#7280c8', 104],
|
|
53
|
+
text: ['#0f172a', 234], input: ['#0f172a', 234], placeholder: ['#3b4f87', 60],
|
|
54
|
+
prose: SEMANTIC_LIGHT.prose, command: SEMANTIC_LIGHT.command, id: SEMANTIC_LIGHT.value, brand: SEMANTIC_LIGHT.brand, quiet: SEMANTIC_LIGHT.quiet, rule: SEMANTIC_LIGHT.rule
|
|
47
55
|
};
|
|
48
56
|
/** Roles a reader must be able to read: each is held to >= 4.5:1 on its background (tests). */
|
|
49
|
-
export const ROLES = ['heading', 'icon', 'bullet1', 'bullet2', 'bullet3', 'label', 'value', 'number', 'link', 'code', 'key', 'muted', 'ok', 'warn', 'error', 'text', 'placeholder'];
|
|
57
|
+
export const ROLES = ['heading', 'icon', 'bullet1', 'bullet2', 'bullet3', 'label', 'value', 'number', 'link', 'code', 'key', 'muted', 'ok', 'warn', 'error', 'ask', 'gold', 'text', 'placeholder', 'prose', 'command', 'id', 'brand', 'quiet'];
|
|
50
58
|
|
|
51
59
|
/** Heading gradient stops across the word: sky -> indigo -> violet (light: the -700 shades). */
|
|
52
60
|
export const HEADING_STOPS = { dark: ['#38bdf8', '#6366f1', '#a855f7'], light: ['#0369a1', '#4338ca', '#7e22ce'] };
|
|
@@ -59,12 +67,12 @@ export const AGENT_SHADES = {
|
|
|
59
67
|
/** Model providers in their brand colours (dark, light). Unknown = muted. */
|
|
60
68
|
export const PROVIDER_COLORS = {
|
|
61
69
|
openai: ['#f5f5f5', '#111111'], anthropic: ['#D97757', '#b4532e'], google: ['#4796E3', '#1a62c4'],
|
|
62
|
-
ollama: ['#e5e7eb', '#
|
|
70
|
+
ollama: ['#e5e7eb', '#3b4f87'], mistral: ['#FA520F', '#c2410c'], deepseek: ['#4D6BFE', '#2f4fe0'],
|
|
63
71
|
perplexity: ['#20808D', '#1a6b76'], xai: ['#f5f5f5', '#111111'],
|
|
64
72
|
// B5: OpenAI-compatible presets (dark bg shade, light bg shade; all >= 4.5:1 on their background)
|
|
65
73
|
openrouter: ['#6566F1', '#4f46e5'], huggingface: ['#FFD21E', '#8a6500'], groq: ['#F55036', '#c2290f'],
|
|
66
74
|
together: ['#0F6FFF', '#0b57d0'], lmstudio: ['#a78bfa', '#6d28d9'], vllm: ['#30A2FF', '#0b6bb8'],
|
|
67
|
-
openai_compatible: ['#
|
|
75
|
+
openai_compatible: ['#c7d2fe', '#3b4f87']
|
|
68
76
|
};
|
|
69
77
|
/** Protocol trace marks (AEE, AOCL, VOLT, WARD), each its own Q shade (dark, light). */
|
|
70
78
|
export const PROTO_COLORS = {
|
|
@@ -187,21 +195,21 @@ export const bulletRole = (depth) => ['bullet1', 'bullet2', 'bullet3'][((depth %
|
|
|
187
195
|
|
|
188
196
|
// ---- paint: split a plain line into coloured runs; the characters never change ---------------------
|
|
189
197
|
const GLYPH_ROLE = {
|
|
190
|
-
'●': 'ok', '✔': 'ok', '◐': 'warn', '○': '
|
|
191
|
-
'◉': 'icon', 'Ⓠ': 'icon', '▸': '
|
|
198
|
+
'●': 'ok', '✔': 'ok', '◐': 'warn', '○': 'quiet', '?': 'quiet', '✖': 'error', '⚠': 'warn', '⊘': 'warn',
|
|
199
|
+
'◉': 'icon', 'Ⓠ': 'icon', '▸': 'brand', '⛔': 'warn', '↳': 'icon', '»': 'quiet', '·': 'quiet', '⬢': 'icon', '⬡': 'quiet', '▶': 'ask'
|
|
192
200
|
};
|
|
193
201
|
const KEYS = '(?:(?:Ctrl|Shift|Alt|Cmd|Option)\\+\\S+|Enter|Tab|Esc|Up|Down|Left|Right|Space|Backspace|PgUp|PgDn)';
|
|
194
202
|
const TOKEN = new RegExp([
|
|
195
203
|
'(https?:\\/\\/[^\\s)]+)', // 1 link
|
|
196
204
|
'(\\bquox(?: (?![·])[^\\s·,;)]+)+)', // 2 a quox command
|
|
197
|
-
'((?<![\\w/])\\/[a-z][\\w-]*)', // 3 slash command
|
|
205
|
+
'((?<![\\w/])\\/[a-z][\\w-]*(?: (?:--?[a-z][\\w-]*|<[^>\\s]+>|\\[[^\\]\\s]+\\]|\\d+|[\\w-]+(?:\\|[\\w-]+)+))*)', // 3 slash command + its flags/args
|
|
198
206
|
`((?<![\\w+])${KEYS}(?![\\w+]))`, // 4 keyboard key
|
|
199
207
|
'((?<![\\w<])<[^>\\s]+>|\\[[^\\]\\s]+\\])', // 5 placeholder <name> or [args]
|
|
200
208
|
'( · )', // 6 separator
|
|
201
209
|
'((?<![\\w/.-])(?:(?:gpt|claude|gemini|o[134]|mistral|codestral|deepseek|sonar|grok)-[\\w.:-]*\\w|ollama:[\\w.:-]*\\w|OpenAI|openai|Anthropic|anthropic|Claude(?: API)?|Gemini|Ollama|ollama|Mistral|DeepSeek|Perplexity|Grok|xAI)(?![\\w-]))', // 7 provider/model
|
|
202
210
|
'((?<![\\w.#-])\\d+(?:[.,:-]\\d+)*(?:%|ms|s|k|m|h|d|x|KB|MB|GB)?(?![\\w]))' // 8 number
|
|
203
211
|
].join('|'), 'g');
|
|
204
|
-
const ROLE_OF = [null, '
|
|
212
|
+
const ROLE_OF = [null, 'id', 'command', 'command', 'command', 'command', 'quiet', 'provider', 'id'];
|
|
205
213
|
const LETTER_KEY = /^([a-z])(?= (?:approve|deny|open|stop|peek|confirm|edit|retry|cancel|copy|quit)\b)/;
|
|
206
214
|
|
|
207
215
|
/** Runs of `text` with roles; `base` colours the prose between tokens. Pure. */
|
|
@@ -211,6 +219,16 @@ export function paintRuns(text, base) {
|
|
|
211
219
|
const s = String(text ?? '');
|
|
212
220
|
let i = 0;
|
|
213
221
|
const startsSeg = (at) => at === 0 || s.slice(0, at).endsWith(' · ') || /^\s*$/.test(s.slice(0, at));
|
|
222
|
+
if (/[⟦⟪]/.test(s)) { // explicit markup: `command` and «value» are atomic spans, the rest is auto-tokenised
|
|
223
|
+
let j = 0;
|
|
224
|
+
for (const m of s.matchAll(MARK)) {
|
|
225
|
+
for (const r of paintRuns(s.slice(j, m.index), base)) push(r.text, r.role);
|
|
226
|
+
push(m[1] ?? m[2], m[1] !== undefined ? 'command' : 'id');
|
|
227
|
+
j = m.index + m[0].length;
|
|
228
|
+
}
|
|
229
|
+
for (const r of paintRuns(s.slice(j), base)) push(r.text, r.role);
|
|
230
|
+
return out;
|
|
231
|
+
}
|
|
214
232
|
for (const m of s.matchAll(TOKEN)) {
|
|
215
233
|
proseRuns(s.slice(i, m.index), base, push, startsSeg(i));
|
|
216
234
|
const g = m.findIndex((x, k) => k > 0 && x !== undefined);
|
|
@@ -222,32 +240,47 @@ export function paintRuns(text, base) {
|
|
|
222
240
|
}
|
|
223
241
|
function proseRuns(seg, base, push, segStart) {
|
|
224
242
|
const k = segStart && seg.match(LETTER_KEY);
|
|
225
|
-
if (k) { push(k[1], '
|
|
243
|
+
if (k) { push(k[1], 'command'); seg = seg.slice(1); }
|
|
226
244
|
push(seg, base);
|
|
227
245
|
}
|
|
228
246
|
|
|
229
247
|
/**
|
|
230
248
|
* A note/list line as runs: a leading state glyph, then `Label value` (two spaces) or `label: value`,
|
|
231
|
-
* then tokens. tone: undefined/'dim' (neutral), 'ok', 'warn', 'accent', 'error'.
|
|
249
|
+
* then tokens. tone: undefined/'dim' (neutral), 'ok', 'warn', 'accent', 'error', 'ask' (a question for the human).
|
|
232
250
|
*/
|
|
233
251
|
export function paintLine(text, tone) {
|
|
234
252
|
const s = String(text ?? '');
|
|
235
253
|
const out = [];
|
|
236
|
-
|
|
237
|
-
const
|
|
238
|
-
|
|
254
|
+
// The tone colours the GLYPH only. The sentence stays prose, so commands and values inside keep their roles.
|
|
255
|
+
const TONE_GLYPH = { warn: 'warn', error: 'error', fail: 'error', ok: 'ok', accent: 'brand', ask: 'ask' };
|
|
256
|
+
// [\s\S]* not .*: a notice can carry a newline (a server error body), and a failed match crashed the TUI
|
|
257
|
+
// ("m is not iterable", 2026-10-15 03:44 while the collector was down).
|
|
258
|
+
const m = s.match(/^(\s*)(?:([●✔◐○?✖⚠⊘◉Ⓠ▸⛔↳»·⬢⬡▶])( ))?([\s\S]*)$/u) || [s, '', undefined, undefined, s];
|
|
239
259
|
const [, lead, glyph, gsp, rest0] = m;
|
|
240
260
|
if (lead) out.push({ text: lead, role: undefined });
|
|
241
|
-
if (glyph) out.push({ text: glyph, role:
|
|
261
|
+
if (glyph) out.push({ text: glyph, role: ['ok', 'warn', 'error'].includes(GLYPH_ROLE[glyph]) ? GLYPH_ROLE[glyph] : (TONE_GLYPH[tone] || GLYPH_ROLE[glyph]) }, { text: gsp, role: undefined });
|
|
242
262
|
let rest = rest0;
|
|
263
|
+
if (tone === 'ask') { // QUOXON-ASK: "▶ reason. question? keys": the keys after the last "? " are the ask colour
|
|
264
|
+
const q = rest.lastIndexOf('? ');
|
|
265
|
+
if (q >= 0) { out.push(...paintRuns(rest.slice(0, q + 2), 'prose'), ...paintRuns(rest.slice(q + 2), 'ask')); return out; }
|
|
266
|
+
out.push(...paintRuns(rest, 'prose')); return out;
|
|
267
|
+
}
|
|
243
268
|
const lv = rest.match(/^(\S[^·]{0,30}?\S|\S)( {2,})(\S.*)$/) || rest.match(/^([A-Za-z][\w .-]{0,22}:)( )(\S.*)$/);
|
|
244
|
-
|
|
269
|
+
const valueBase = 'prose';
|
|
245
270
|
if (lv && !/^https?:/.test(lv[1])) {
|
|
246
271
|
const pv = providerOf(lv[1].replace(/\s*\(.*\)$/, ''), null);
|
|
247
272
|
out.push({ text: lv[1], role: pv ? `provider:${pv}` : 'label' }, { text: lv[2], role: undefined });
|
|
248
273
|
rest = lv[3];
|
|
249
|
-
if (!prose) valueBase = 'value';
|
|
250
274
|
}
|
|
251
275
|
out.push(...paintRuns(rest, valueBase));
|
|
252
276
|
return out;
|
|
253
277
|
}
|
|
278
|
+
|
|
279
|
+
/** The glyph a bare notice line gets so its tone shows without tinting the sentence. '' for neutral tones. */
|
|
280
|
+
export const TONE_PREFIX = { warn: '⚠ ', error: '✖ ', fail: '✖ ', ok: '✔ ' };
|
|
281
|
+
export const toneText = (text, tone) => {
|
|
282
|
+
const p = TONE_PREFIX[tone];
|
|
283
|
+
const t = String(text ?? '');
|
|
284
|
+
return p && !/^\s*[●✔◐○?✖⚠⊘◉Ⓠ▸⛔↳»·⬢⬡▶] /u.test(t) ? p + t : t;
|
|
285
|
+
};
|
|
286
|
+
export { stripMarks };
|