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,282 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dragon scenes: pure timelines t (ms) -> { pose, eyes, caption }. No timers, no I/O (portable to the web).
|
|
3
|
+
* ONE dragon everywhere (v8): googly dot eyes (qeyes.js), a smile that opens to talk, pink cheeks, dim bat wings,
|
|
4
|
+
* a spade tail with spines, fire from the mouth. /dragon (the pick), /dragon classic, hero and fire, the cinematic
|
|
5
|
+
* moods, the greeting and /dragon-fly are all this dragon; moods are shown through the eyes and the smile only.
|
|
6
|
+
*/
|
|
7
|
+
import { restPose, kf, hash, clamp, smooth, dragonLayout, renderDragon } from './qdragon.js';
|
|
8
|
+
import { eyesForDragon, eyeState, blinkOpen, overlay, GOOGLY } from './qeyes.js';
|
|
9
|
+
|
|
10
|
+
const TAU = Math.PI * 2;
|
|
11
|
+
|
|
12
|
+
// ---- the pick: googly dot eyes, sway sway sway whip, then fire from the mouth (ported from cli.quox.ai) -------
|
|
13
|
+
// The same pure functions the website lab plays (src/components/lab/dragon/dragonlab.js pickCells), so the CLI
|
|
14
|
+
// and the web draw the same frames. Eyes are qeyes.js Braille dots (Dots v3), laid over the dragon by composeCells:
|
|
15
|
+
// they are the dragon's only eyes.
|
|
16
|
+
export const SWAY_MS = TAU / 0.0068; // one sway cycle (about 924 ms)
|
|
17
|
+
export const PICK = { sway: 3 * SWAY_MS, whip: 950, fire: 2500, rest: 700 };
|
|
18
|
+
export const PICK_EYES = 'dots';
|
|
19
|
+
export const PICK_MS = PICK.sway + PICK.whip + PICK.fire + PICK.rest;
|
|
20
|
+
/** Which phase of the pick t (ms) falls in: 'sway' | 'whip' | 'fire' | 'rest'. */
|
|
21
|
+
export function pickPhase(t) {
|
|
22
|
+
const c = ((t % PICK_MS) + PICK_MS) % PICK_MS, w0 = PICK.sway, f0 = w0 + PICK.whip, r0 = f0 + PICK.fire;
|
|
23
|
+
return c < w0 ? 'sway' : c < f0 ? 'whip' : c < r0 ? 'fire' : 'rest';
|
|
24
|
+
}
|
|
25
|
+
/** The eyes at t: shut (open 0) while it breathes, otherwise the idle blinks and glances. */
|
|
26
|
+
export function pickEyeState(t) {
|
|
27
|
+
const c = ((t % PICK_MS) + PICK_MS) % PICK_MS, ft = c - PICK.sway - PICK.whip;
|
|
28
|
+
return pickPhase(t) === 'fire' && ft > 120 && ft < 1500 ? { open: 0 } : eyeState('idle', t);
|
|
29
|
+
}
|
|
30
|
+
/** The dragon's pose (no quadrant eyes) at absolute time t. */
|
|
31
|
+
export function pickPose(t) {
|
|
32
|
+
const p = restPose(), c = ((t % PICK_MS) + PICK_MS) % PICK_MS;
|
|
33
|
+
p.tail.len = 1.55; p.tail.k = 3.2; p.tail.phase = t * 0.0068;
|
|
34
|
+
const w0 = PICK.sway, f0 = w0 + PICK.whip, r0 = f0 + PICK.fire;
|
|
35
|
+
if (c < w0) p.tail.amp = 0.6 + 1.6 * smooth(c / 350);
|
|
36
|
+
else if (c < f0) {
|
|
37
|
+
const u = (c - w0) / PICK.whip, crack = smooth(u / 0.2) * (1 - smooth((u - 0.35) / 0.55));
|
|
38
|
+
p.tail.amp = 2.2 + 2.8 * crack; p.tail.phase += crack * 4; p.tail.k = 3.2 + 2.8 * crack; p.tail.curl = 0.8 * crack;
|
|
39
|
+
} else if (c < r0) {
|
|
40
|
+
const ft = c - f0;
|
|
41
|
+
p.tail.amp = 2.2 - 1.6 * smooth(ft / 600);
|
|
42
|
+
p.mouth = smooth(ft / 160) * (1 - smooth((ft - 1500) / 500));
|
|
43
|
+
p.fire = { t: ft, dur: 1300, seed: 3 };
|
|
44
|
+
} else p.tail.amp = 0.6;
|
|
45
|
+
return p;
|
|
46
|
+
}
|
|
47
|
+
const EYES2 = GOOGLY; // the owner's eyes, unchanged: every scene, mood, greeting and the flight use them
|
|
48
|
+
const INK = [30, 41, 59]; // eye ink on the light theme (white would vanish on a light page)
|
|
49
|
+
/**
|
|
50
|
+
* The pick at t ms. opts: style ('dots'), theme, mono (NO_COLOR: no colour), still (reduced motion: the settled
|
|
51
|
+
* dragon, eyes open, no tail motion, no fire). Cells are { ch, rgb, bg } like every other clip.
|
|
52
|
+
*/
|
|
53
|
+
export function pickCells(t, cols = 64, style = PICK_EYES, { theme = 'dark', mono = false, still = false, size, lm, mouthSide = 'right', talk } = {}) {
|
|
54
|
+
const L = dragonLayout(cols, { size, lm });
|
|
55
|
+
let pose, eyes;
|
|
56
|
+
if (still) { pose = restPose(); eyes = eyeState('open', 0); }
|
|
57
|
+
else if (talk) { pose = talkPose(t, talk); eyes = eyeState('idle', t); } // talking: the pick's calm sway, the mouth working, no fire
|
|
58
|
+
else { pose = pickPose(t); eyes = pickEyeState(t); }
|
|
59
|
+
pose.mouthSide = mouthSide;
|
|
60
|
+
return composeCells(pose, eyes, L, style, { theme, mono, t });
|
|
61
|
+
}
|
|
62
|
+
/** Body (renderDragon) with the Braille googly eyes laid over it: the one way a dragon frame is made. */
|
|
63
|
+
export function composeCells(pose, eyes, L, style, { theme, mono, t }) {
|
|
64
|
+
const body = renderDragon(pose, L, { mono, t, theme });
|
|
65
|
+
const layer = eyesForDragon(L, eyes, EYES2, style).map((row) => row.map((c) => c && {
|
|
66
|
+
ch: c.ch, bg: null, rgb: !c.rgb || mono ? null : theme === 'light' ? INK : c.rgb,
|
|
67
|
+
}));
|
|
68
|
+
return overlay(body, layer);
|
|
69
|
+
}
|
|
70
|
+
/** How open the mouth is at t while it talks: 0..1, a deterministic syllable cadence (about 6 a second, with the odd pause). */
|
|
71
|
+
export const TALK_SYLLABLE_MS = 160;
|
|
72
|
+
export function talkOpen(t) {
|
|
73
|
+
const k = Math.floor(t / TALK_SYLLABLE_MS), u = (t - k * TALK_SYLLABLE_MS) / TALK_SYLLABLE_MS;
|
|
74
|
+
if (hash(k * 5 + 3) < 0.14) return 0; // a pause between words
|
|
75
|
+
return (0.45 + 0.55 * hash(k * 7 + 11)) * Math.sin(Math.PI * u) ** 0.8;
|
|
76
|
+
}
|
|
77
|
+
/** The pose while it talks: calm tail sway, the mouth open by `talk` (a 0..1 number, or true for the cadence), no fire, no glow. */
|
|
78
|
+
export function talkPose(t, talk = true) {
|
|
79
|
+
const p = restPose();
|
|
80
|
+
p.tail.len = 1.55; p.tail.k = 3.2; p.tail.phase = t * 0.0068; p.tail.amp = 0.6;
|
|
81
|
+
p.mouth = 0; p.smile = typeof talk === 'number' ? clamp(talk) : talkOpen(t);
|
|
82
|
+
return p;
|
|
83
|
+
}
|
|
84
|
+
/** pickCells with the mouth talking and no fire: talkCells(t, cols, style, { mouthSide }) . */
|
|
85
|
+
export const talkCells = (t, cols = 64, style = PICK_EYES, opts = {}) => pickCells(t, cols, style, { ...opts, talk: opts.talk || true });
|
|
86
|
+
|
|
87
|
+
// ---- sleep: the screensaver dragon (eyes closed, a slow minimal sway, a breath of z; no whip, no fire) -------
|
|
88
|
+
export const SLEEP_Z = [' ', 'z', 'z z', 'z z Z'];
|
|
89
|
+
/** The z that drifts up with each slow breath at t ms (4.8 s cycle). */
|
|
90
|
+
export const sleepZ = (t) => SLEEP_Z[Math.floor((((t % 4800) + 4800) % 4800) / 1200)];
|
|
91
|
+
/** The sleeping pose at t ms: tail barely swaying (amp 0.1 vs the pick's 0.6), eyes shut, mouth closed, no fire. */
|
|
92
|
+
export function sleepPose(t, { still = false } = {}) {
|
|
93
|
+
const p = restPose();
|
|
94
|
+
p.fire = null; p.mouth = 0;
|
|
95
|
+
if (!still) { p.tail.len = 1.4; p.tail.k = 2.6; p.tail.phase = t * 0.0011; p.tail.amp = 0.1; }
|
|
96
|
+
const z = still ? 'z' : sleepZ(t);
|
|
97
|
+
if (z.trim()) p.label = { text: z, rgb: [186, 230, 253] };
|
|
98
|
+
return p;
|
|
99
|
+
}
|
|
100
|
+
/** Sleeping dragon cells: sleepCells(t, cols, style, { theme, mono, still, size, lm }). Eyes are always shut. */
|
|
101
|
+
export function sleepCells(t, cols = 64, style = PICK_EYES, { theme = 'dark', mono = false, still = false, size, lm } = {}) {
|
|
102
|
+
const L = dragonLayout(cols, { size, lm });
|
|
103
|
+
return composeCells(sleepPose(t, { still }), eyeState('closed', 0), L, style, { theme, mono, t });
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// ---- the hero: talk on the right, sway sway sway whip, the mouth moves left, fire leftwards (cli.quox.ai hero) ---
|
|
107
|
+
export const HERO_TALK_MS = 1400, HERO_MOVE_MS = 450;
|
|
108
|
+
export const heroMs = (talkMs = HERO_TALK_MS) => talkMs + PICK.sway + PICK.whip + HERO_MOVE_MS + PICK.fire + PICK.rest;
|
|
109
|
+
export const HERO_MS = heroMs();
|
|
110
|
+
/** Hero timeline: 'talk' | 'sway' | 'whip' | 'move' | 'fire' | 'rest'. Loops on heroMs(talkMs). */
|
|
111
|
+
export function heroPhase(t, { talkMs = HERO_TALK_MS } = {}) {
|
|
112
|
+
const T = heroMs(talkMs), c = ((t % T) + T) % T;
|
|
113
|
+
const a = talkMs, b = a + PICK.sway, d = b + PICK.whip, e = d + HERO_MOVE_MS, f = e + PICK.fire;
|
|
114
|
+
return c < a ? 'talk' : c < b ? 'sway' : c < d ? 'whip' : c < e ? 'move' : c < f ? 'fire' : 'rest';
|
|
115
|
+
}
|
|
116
|
+
/** The hero's pose and eye state at t. The tail keeps swaying through the move; fire comes from the left mouth. */
|
|
117
|
+
function heroFrame(t, talkMs) {
|
|
118
|
+
const fr = heroFrame0(t, talkMs);
|
|
119
|
+
fr.pose.flap = 0.12 * Math.sin(t * 0.0068 * 0.5); // the wings keep a gentle beat
|
|
120
|
+
return fr;
|
|
121
|
+
}
|
|
122
|
+
function heroFrame0(t, talkMs) {
|
|
123
|
+
const T = heroMs(talkMs), c = ((t % T) + T) % T, ph = heroPhase(t, { talkMs });
|
|
124
|
+
const w0 = PICK.sway, f0 = w0 + PICK.whip;
|
|
125
|
+
if (ph === 'talk') return { pose: talkPose(t, true), eyes: eyeState('idle', t) };
|
|
126
|
+
// pick time pt: 0 at the start of the sway; frozen at the end of the whip during the move
|
|
127
|
+
const base = c - talkMs, pt = ph === 'move' ? f0 - 1 : ph === 'fire' || ph === 'rest' ? base - HERO_MOVE_MS : base;
|
|
128
|
+
const pose = pickPose(pt);
|
|
129
|
+
pose.tail.phase += (t - pt) * 0.0068; // the sway never stutters at the seams
|
|
130
|
+
pose.fire = ph === 'fire' && pt > f0 ? pose.fire : null;
|
|
131
|
+
pose.mouthSide = ph === 'move' ? (c - talkMs - w0 - PICK.whip < HERO_MOVE_MS / 2 ? 'right' : 'left') : ph === 'fire' || ph === 'rest' ? 'left' : 'right';
|
|
132
|
+
if (ph === 'move') pose.mouth = 0;
|
|
133
|
+
// v2: the face turns left (mouth and cheeks slide, eyes look left), then the mouth opens wide to breathe
|
|
134
|
+
const u = ph === 'move' ? smooth((c - talkMs - w0 - PICK.whip) / HERO_MOVE_MS) : ph === 'fire' ? 1 : ph === 'rest' ? 1 - smooth((c - talkMs - w0 - PICK.whip - HERO_MOVE_MS - PICK.fire) / PICK.rest) : 0;
|
|
135
|
+
pose.faceX = -0.06 * u;
|
|
136
|
+
if (ph === 'fire') pose.smile = pose.mouth; // the mouth opens wide to breathe
|
|
137
|
+
pose.mouth = 0;
|
|
138
|
+
const ft = pt - f0;
|
|
139
|
+
if (ph === 'move') return { pose, eyes: { ...eyeState('idle', t), gx: -1 * u, gy: 0.4 } };
|
|
140
|
+
return { pose, eyes: ph === 'fire' && ft > 120 && ft < 1500 ? { open: 0 } : eyeState('idle', t) };
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* The hero at t ms (cli.quox.ai): the mouth on the right talks (no fire) for talkMs, then the tail sways three times
|
|
144
|
+
* and whips, the mouth moves to the left and the dragon breathes fire leftwards, eyes shut. The Q needs room on its
|
|
145
|
+
* left for the fire: lm defaults to as much margin as cols allow. still: eyes open, mouth closed, no fire.
|
|
146
|
+
*/
|
|
147
|
+
export function heroCells(t, cols = 64, style = PICK_EYES, { talkMs = HERO_TALK_MS, theme = 'dark', mono = false, still = false, size, lm } = {}) {
|
|
148
|
+
if (lm === undefined) { const L0 = dragonLayout(cols, { size }); lm = Math.max(L0.qx0 / 2, Math.min(Math.ceil(L0.H * 1.25 / 2) + 4, Math.floor((cols * 2 - L0.qw) / 2) - 2)); }
|
|
149
|
+
const L = dragonLayout(cols, { size, lm });
|
|
150
|
+
if (still) return composeCells(restPose(), eyeState('open', 0), L, style, { theme, mono, t: 0 });
|
|
151
|
+
const { pose, eyes } = heroFrame(t, talkMs);
|
|
152
|
+
return composeCells(pose, eyes, L, style, { theme, mono, t });
|
|
153
|
+
}
|
|
154
|
+
const HERO_CAP = { talk: 'talking', sway: 'tail sway', whip: 'tail whip', move: 'the mouth moves to the left', fire: 'fire breath to the left, eyes shut', rest: 'settle' };
|
|
155
|
+
|
|
156
|
+
// ---- moods: the same dragon, expressed through the eyes and the smile only ----------------------------------------
|
|
157
|
+
export const MOODS = ['waiting', 'success', 'failure', 'greeting'];
|
|
158
|
+
const GREETING = "Hi, I'm Quox!";
|
|
159
|
+
const MOOD_NOTE = {
|
|
160
|
+
waiting: 'waiting: eyes wide on you, a slow look about, a patient blink',
|
|
161
|
+
success: 'success: a blink, then happy closed eyes and a big smile',
|
|
162
|
+
failure: 'failure: the eyes sink and look away, the smile turns down',
|
|
163
|
+
greeting: 'greeting: eyes open, look at you, a blink, the smile says hi',
|
|
164
|
+
};
|
|
165
|
+
const greetCells = (text, t) => {
|
|
166
|
+
const n = Math.floor(clamp(t / 45, 0, text.length));
|
|
167
|
+
return [...text].map((ch, i) => ({ ch: i < n ? ch : ' ', rgb: [56 + (168 - 56) * (i / (text.length - 1)), 189 + (85 - 189) * (i / (text.length - 1)), 248 + (247 - 248) * (i / (text.length - 1))].map(Math.round) }));
|
|
168
|
+
};
|
|
169
|
+
/** The calm base every mood sits on: the pick's easy tail sway, wings gently flapping, smile closed. */
|
|
170
|
+
function calmPose(t) {
|
|
171
|
+
const p = talkPose(t, 0);
|
|
172
|
+
p.flap = 0.12 * Math.sin(t * 0.0068 * 0.5);
|
|
173
|
+
return p;
|
|
174
|
+
}
|
|
175
|
+
/** A mood at t ms since it began: { pose, eyes, caption }. still: the static representative form. */
|
|
176
|
+
export function moodFrame(mood, t, { still = false } = {}) {
|
|
177
|
+
const p = calmPose(still ? 0 : t);
|
|
178
|
+
let eyes = eyeState('open', 0);
|
|
179
|
+
switch (mood) {
|
|
180
|
+
case 'waiting': {
|
|
181
|
+
const u = still ? 0 : t;
|
|
182
|
+
eyes = { open: still ? 1 : blinkOpen((u % 2600) - 2200), gx: still ? 0 : 0.55 * Math.sin(u / 1500), gy: 0.55 };
|
|
183
|
+
break;
|
|
184
|
+
}
|
|
185
|
+
case 'success': {
|
|
186
|
+
const shut = still || (t >= 1100 && t < 3300);
|
|
187
|
+
eyes = { open: shut ? 0 : blinkOpen(t - 450), gx: 0, gy: 1 };
|
|
188
|
+
p.smile = still ? 0.9 : kf(t, [[1000, 0], [1400, 0.9], [3000, 0.9], [3400, 0]]);
|
|
189
|
+
break;
|
|
190
|
+
}
|
|
191
|
+
case 'failure': {
|
|
192
|
+
const k = still ? 1 : kf(t, [[0, 0], [350, 1]]);
|
|
193
|
+
eyes = { open: 1 - 0.45 * k, gx: -0.7 * k, gy: 1 };
|
|
194
|
+
p.frown = k > 0.5 ? 1 : 0;
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
197
|
+
case 'greeting': {
|
|
198
|
+
eyes = { open: still ? 1 : kf(t, [[0, 0], [450, 1]]) * Math.min(blinkOpen(t - 1700), blinkOpen(t - 1960)), gx: 0, gy: 1 };
|
|
199
|
+
p.smile = still ? 0 : t > 700 && t < 1700 ? talkOpen(t) : 0;
|
|
200
|
+
p.label = { cells: greetCells(GREETING, still ? 1e9 : t - 400) };
|
|
201
|
+
break;
|
|
202
|
+
}
|
|
203
|
+
default: throw new Error(`unknown dragon mood: ${mood}`);
|
|
204
|
+
}
|
|
205
|
+
return { pose: p, eyes, caption: MOOD_NOTE[mood] };
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// ---- client clips (CLI-QUOX F3) ---------------------------------------------------------------------------
|
|
209
|
+
|
|
210
|
+
/** Where the fire clip starts inside the hero: just before the face turns left. */
|
|
211
|
+
const FIRE_OFFSET = HERO_TALK_MS + PICK.sway + PICK.whip;
|
|
212
|
+
const FIRE_MS = HERO_MOVE_MS + PICK.fire + PICK.rest;
|
|
213
|
+
const heroClipFrame = (t, offset = 0) => { const tt = t + offset; return { ...heroFrame(tt, HERO_TALK_MS), caption: HERO_CAP[heroPhase(tt)] }; };
|
|
214
|
+
/** The hero: talk, sway sway sway whip, the face turns left, fire from the mouth, settle. /dragon, classic and the pick all play it. */
|
|
215
|
+
const heroClip = {
|
|
216
|
+
dur: HERO_MS,
|
|
217
|
+
at: (t) => heroClipFrame(t),
|
|
218
|
+
still: () => ({ pose: restPose(), eyes: eyeState('open', 0), caption: 'the dragon (static form)' }),
|
|
219
|
+
cells: (t, cols, o) => heroCells(t, cols, PICK_EYES, o),
|
|
220
|
+
width: 100, title: 'The dragon',
|
|
221
|
+
caption: 'googly dot eyes, a smile that talks; the tail sways and whips, then the face turns left and the dragon breathes fire from its mouth, eyes squeezed shut (/dragon)',
|
|
222
|
+
};
|
|
223
|
+
/** /dragon fire: the hero's last act on its own: the face turns left, fire from the mouth, settle. */
|
|
224
|
+
const fireClip = {
|
|
225
|
+
dur: FIRE_MS,
|
|
226
|
+
at: (t) => heroClipFrame(t, FIRE_OFFSET),
|
|
227
|
+
still: heroClip.still,
|
|
228
|
+
cells: (t, cols, o) => heroCells(t + FIRE_OFFSET, cols, PICK_EYES, o),
|
|
229
|
+
width: 100, title: 'Fire',
|
|
230
|
+
caption: 'the face turns left and the dragon breathes fire from its mouth, eyes squeezed shut (/dragon fire)',
|
|
231
|
+
};
|
|
232
|
+
/** A mood as a short vignette (cinematic level only). No label unless asked: the status line already says what happened. */
|
|
233
|
+
const moodClip = (mood, dur, width, title, caption, label = false) => ({
|
|
234
|
+
dur, width, title, caption,
|
|
235
|
+
at(t) { const fr = moodFrame(mood, t); if (!label) fr.pose.label = null; return fr; },
|
|
236
|
+
still() { const fr = moodFrame(mood, 0, { still: true }); if (!label) fr.pose.label = null; return fr; },
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Everything the client plays, by name. width = the frame the clip wants (the hero and fire need room to the left
|
|
241
|
+
* for the fire; moods are compact). pick and idle (/dragon, /dragon classic) are the hero itself: no separate designs.
|
|
242
|
+
*/
|
|
243
|
+
export const CLIPS = {
|
|
244
|
+
hero: heroClip,
|
|
245
|
+
pick: heroClip,
|
|
246
|
+
idle: heroClip,
|
|
247
|
+
fire: fireClip,
|
|
248
|
+
waiting: moodClip('waiting', 2500, 40, 'Mood: waiting', 'waiting for your approval: eyes wide on you (cinematic motion)'),
|
|
249
|
+
success: moodClip('success', 2300, 40, 'Mood: success', 'done: a blink, then happy eyes and a big smile (cinematic motion)'),
|
|
250
|
+
failure: moodClip('failure', 2200, 40, 'Mood: failure', 'failed: the eyes sink and look away, the smile turns down (cinematic motion)'),
|
|
251
|
+
greeting: moodClip('greeting', 2600, 64, 'Mood: greeting', 'hello: the eyes open, look at you, blink, the smile says hi (the cinematic intro)', true),
|
|
252
|
+
};
|
|
253
|
+
export const CLIP_NAMES = Object.keys(CLIPS);
|
|
254
|
+
/** The moods the app plays at the cinematic level, keyed by what happened. */
|
|
255
|
+
export const MOOD_CLIPS = ['waiting', 'success', 'failure', 'greeting'];
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Cells of a clip at t ms: rows of { ch, rgb, bg }. opts: cols (the room there is), theme 'dark' | 'light',
|
|
259
|
+
* mono (NO_COLOR: shapes only), still (reduced motion: the static form), size / lm (force a layout), label (false:
|
|
260
|
+
* no words in the frame).
|
|
261
|
+
*/
|
|
262
|
+
export function clipCells(name, t, { cols = 100, theme = 'dark', mono = false, still = false, size, lm, label = true } = {}) {
|
|
263
|
+
const c = CLIPS[name];
|
|
264
|
+
if (!c) throw new Error(`unknown dragon clip: ${name}`);
|
|
265
|
+
const L = dragonLayout(Math.max(size ? 8 : 40, Math.min(cols, c.width)), { size, lm }); // a forced size (the intro) may be narrower than 40
|
|
266
|
+
const tt = Math.max(0, Math.min(t, c.dur));
|
|
267
|
+
if (c.cells) return c.cells(tt, L.cols, { theme, mono, still, size, lm });
|
|
268
|
+
const fr = still ? c.still() : c.at(tt);
|
|
269
|
+
if (!label) fr.pose.label = null; // the intro has its own words beside the art
|
|
270
|
+
return composeCells(fr.pose, fr.eyes, L, PICK_EYES, { theme, mono, t: tt });
|
|
271
|
+
}
|
|
272
|
+
/** The caption of a clip at t (what the dragon is doing now). */
|
|
273
|
+
export const clipCaption = (name, t) => CLIPS[name].at(Math.max(0, Math.min(t, CLIPS[name].dur))).caption;
|
|
274
|
+
|
|
275
|
+
// ---- the launch egg: the v8 dragon's googly-eye greeting beside the settled Q ------------------------------
|
|
276
|
+
import { qDims } from './qart.js';
|
|
277
|
+
export const EGG_MS = CLIPS.greeting.dur;
|
|
278
|
+
/** The egg at t ms (after the Q has settled): the v8 dragon opens its googly eyes, looks, blinks and smiles, at the Q's size. */
|
|
279
|
+
export function eggCells(size, t, { color = true, theme = 'dark' } = {}) {
|
|
280
|
+
const L = dragonLayout(qDims(size).cols + 6, { size, lm: 0 });
|
|
281
|
+
return clipCells('greeting', t, { cols: L.cols, size, lm: 0, theme, mono: !color, label: false });
|
|
282
|
+
}
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
// Quox eyes (canonical source: quox cli/lib/quoxon/qeyes.js; the cli.quox.ai site vendors this file as is).
|
|
2
|
+
// Two friendly cartoon eyes in the Q's own terminal cell pipeline, so the web (cellsToHTML) and the
|
|
3
|
+
// terminal (cellsToAnsi) draw identical eyes. Two techniques, both terminal-safe:
|
|
4
|
+
// dots Braille dots (the Q's own particles): a closed disc of white dots, a pupil of UNLIT dots (the
|
|
5
|
+
// background shows through, so it reads black), a 2x2 dot glint, and a 2-dot arc when closed.
|
|
6
|
+
// blocks Unicode quadrant blocks with a foreground AND a background colour (as qdragon's eye layer).
|
|
7
|
+
// Plus motion as pure functions of time: blink, look around, idle (eyeState).
|
|
8
|
+
//
|
|
9
|
+
// PURE AND PORTABLE: no DOM, no timers, no Node APIs. A function of (geometry, state) or (mode, t).
|
|
10
|
+
// The left eye is computed once and the right eye is the same eye TRANSLATED (both centres snapped to even
|
|
11
|
+
// dots, so both sit on the same Braille cell phase): the two always match, and both pupils look the same way.
|
|
12
|
+
// Compositing is per cell: a cell inside an eye's footprint is drawn from the eye layer only, so a Q dot never
|
|
13
|
+
// shares a cell (and its one colour) with an eye. The Q itself is never redrawn.
|
|
14
|
+
//
|
|
15
|
+
// Geometry is the Q's own unit space (qart.js): y 0..1 down the Q, x 0..0.82, the bowl's axis at x 0.41.
|
|
16
|
+
|
|
17
|
+
const AXIS = 0.41;
|
|
18
|
+
const BR = [[0x1, 0x8], [0x2, 0x10], [0x4, 0x20], [0x40, 0x80]];
|
|
19
|
+
const QUAD = [' ', '▘', '▝', '▀', '▖', '▌', '▞', '▛', '▗', '▚', '▐', '▜', '▄', '▙', '▟', '█'];
|
|
20
|
+
export const EYE_WHITE = [255, 255, 255]; // pure white (owner: whiter whites)
|
|
21
|
+
export const EYE_PUPIL = [10, 12, 18];
|
|
22
|
+
const clamp = (x, a = 0, b = 1) => Math.max(a, Math.min(b, x));
|
|
23
|
+
const smooth = (e) => { e = clamp(e); return e * e * (3 - 2 * e); };
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The owner's pick ("big and googly"). r, sep, y in Q units. pupil: pupil radius as a share of r. rest: where the
|
|
27
|
+
* pupils rest (gaze -1..1; a little down reads as "looking at you"). rim: the least white (dots) kept round the
|
|
28
|
+
* pupil at every gaze. glint: the 2x2 glint's offset from the pupil centre, as a share of the pupil radius (out
|
|
29
|
+
* towards the upper left, near the pupil's edge, so it reads as a highlight and not a bullseye). sag: closed lid.
|
|
30
|
+
*/
|
|
31
|
+
export const GOOGLY = { r: 0.104, sep: 0.124, y: 0.53, ox: 0.95, oy: 1.02, pupil: 0.46, rest: [0, 1], rim: 2, glint: 0.62, sag: 0.34 };
|
|
32
|
+
/** Variant A of the bullseye fix (rejected by screenshot: the thinner rim still read as a donut). */
|
|
33
|
+
export const GOOGLY_A = { ...GOOGLY, pupil: 0.52 };
|
|
34
|
+
/** The v2 dots (before the bullseye fix): smaller pupil, glint near its centre. Kept for comparison. */
|
|
35
|
+
export const GOOGLY_V2 = { ...GOOGLY, pupil: 0.43, glint: 0.45, rest: [0, 0.6] };
|
|
36
|
+
|
|
37
|
+
function centres(geo, v) {
|
|
38
|
+
const R = v.r * geo.Hq, axis = 2 * Math.round((geo.ox + AXIS * geo.Hq) / 2), cy = (geo.oy ?? 0) + v.y * geo.Hq;
|
|
39
|
+
const cxL = axis - 2 * Math.ceil((v.sep * geo.Hq) / 2);
|
|
40
|
+
return { R, cy, cxL, shift: 2 * (axis - cxL) }; // right eye = left eye + shift (an even number of dots)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** The left eye in dot units: f(x, y) -> 0 none | 1 white | 2 pupil | 3 glint, for an open eye. */
|
|
44
|
+
function eyeShape(cx, cy, R, v, open, gx, gy) {
|
|
45
|
+
const rx = R * v.ox, ry = R * v.oy, pr = R * v.pupil;
|
|
46
|
+
const lidY = cy - ry + (1 - open) * 2 * ry * 0.92; // the upper lid: everything above it is closed
|
|
47
|
+
// the pupil travels inside the white, shrunk by its radius plus the rim, and snaps to whole dots
|
|
48
|
+
const ax = Math.max(0, rx - pr - v.rim), ay = Math.max(0, ry - pr - v.rim), n = Math.hypot(gx, gy), s = n > 1 ? 1 / n : 1;
|
|
49
|
+
const px = cx + Math.trunc(gx * s * ax), py = cy + Math.trunc(gy * s * ay); // truncate: never into the rim
|
|
50
|
+
const glint = pr >= 2.6, g = pr * v.glint * 0.75, gx0 = Math.round(px - g), gy0 = Math.round(py - g); // up-left, a dark dot clear of the rim
|
|
51
|
+
return (x, y) => {
|
|
52
|
+
if (((x - cx) / rx) ** 2 + ((y - cy) / ry) ** 2 > 1 || y < lidY) return 0;
|
|
53
|
+
if (y < lidY + 2) return 1; // the lid's edge: a 2-dot white line
|
|
54
|
+
if (Math.hypot(x - px, y - py) > pr) return 1;
|
|
55
|
+
if (glint && Math.floor(x) >= gx0 && Math.floor(x) <= gx0 + 1 && Math.floor(y) >= gy0 && Math.floor(y) <= gy0 + 1) return 3;
|
|
56
|
+
return 2;
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/** The closed lid's centre line y(x) (an arc bowed upwards, like a shut lid; owner flipped it from a smile) and its half-width, in dot units. */
|
|
60
|
+
function lid(cx, cy, R, v) {
|
|
61
|
+
const w = R * v.ox * 0.88, d = R >= 6 ? R * v.sag : 0.6, y0 = cy - d / 2;
|
|
62
|
+
return { n: Math.round(w), y: (x) => { const u = (x - cx) / w; return y0 + d * u * u; } };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Footprint: the cells each eye owns (its white ellipse plus a little margin), as a Uint8Array cols*rows. */
|
|
66
|
+
function footprint(geo, v) {
|
|
67
|
+
const { R, cy, cxL, shift } = centres(geo, v), foot = new Uint8Array(geo.cols * geo.rows);
|
|
68
|
+
for (const cx of [cxL, cxL + shift]) for (let r = 0; r < geo.rows; r++) for (let c = 0; c < geo.cols; c++) {
|
|
69
|
+
const x = c * 2 + 1, y = r * 4 + 2;
|
|
70
|
+
if (((x - cx) / (R * v.ox + 0.4)) ** 2 + ((y - cy) / (R * v.oy + 0.4)) ** 2 <= 1) foot[r * geo.cols + c] = 1;
|
|
71
|
+
}
|
|
72
|
+
return foot;
|
|
73
|
+
}
|
|
74
|
+
const gaze = (state, v) => [state.gx ?? v.rest[0], state.gy ?? v.rest[1]];
|
|
75
|
+
|
|
76
|
+
// ---- dots ---------------------------------------------------------------------------------------------------
|
|
77
|
+
/** Lit dots of both eyes on the W x H dot grid (Uint8Array, 1 = lit). state: { open 0..1, gx, gy -1..1 }. */
|
|
78
|
+
export function eyeDots(geo, state = {}, v = GOOGLY) {
|
|
79
|
+
const W = geo.cols * 2, H = geo.rows * 4, lit = new Uint8Array(W * H);
|
|
80
|
+
const { R, cy, cxL, shift } = centres(geo, v), open = clamp(state.open ?? 1);
|
|
81
|
+
const set = (x, y) => { for (const xx of [x, x + shift]) if (xx >= 0 && y >= 0 && xx < W && y < H) lit[y * W + xx] = 1; };
|
|
82
|
+
if (open < 0.15) {
|
|
83
|
+
const L = lid(cxL, cy, R, v); // one clean arc, 2 dots thick, symmetric about the eye centre
|
|
84
|
+
for (let x = cxL - L.n; x < cxL + L.n; x++) { const y = Math.round(L.y(x + 0.5) - 0.5); set(x, y); set(x, y + 1); }
|
|
85
|
+
return lit;
|
|
86
|
+
}
|
|
87
|
+
const [gx, gy] = gaze(state, v), f = eyeShape(cxL, cy, R, v, open, gx, gy);
|
|
88
|
+
for (let y = Math.floor(cy - R * 2); y <= Math.ceil(cy + R * 2); y++) for (let x = Math.floor(cxL - R * 2); x <= Math.ceil(cxL + R * 2); x++) {
|
|
89
|
+
const k = f(x + 0.5, y + 0.5);
|
|
90
|
+
if (k === 1 || k === 3) set(x, y);
|
|
91
|
+
}
|
|
92
|
+
return lit;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** The dot eye layer: rows of cells (null = no eye here). */
|
|
96
|
+
export function eyeCells(geo, state, v = GOOGLY) {
|
|
97
|
+
const W = geo.cols * 2, lit = eyeDots(geo, state, v), foot = footprint(geo, v), rows = [];
|
|
98
|
+
for (let r = 0; r < geo.rows; r++) {
|
|
99
|
+
const row = [];
|
|
100
|
+
for (let c = 0; c < geo.cols; c++) {
|
|
101
|
+
let bits = 0;
|
|
102
|
+
for (let dy = 0; dy < 4; dy++) for (let dx = 0; dx < 2; dx++) if (lit[(r * 4 + dy) * W + c * 2 + dx]) bits |= BR[dy][dx];
|
|
103
|
+
row.push(bits ? { ch: String.fromCharCode(0x2800 + bits), rgb: EYE_WHITE } : foot[r * geo.cols + c] ? { ch: ' ', rgb: null } : null);
|
|
104
|
+
}
|
|
105
|
+
rows.push(row);
|
|
106
|
+
}
|
|
107
|
+
return rows;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// ---- blocks (reference) -------------------------------------------------------------------------------------
|
|
111
|
+
/** The block eye layer: 2x2 quadrant pixels per cell (a pixel is 1 dot wide, 2 tall), fg + bg colours. */
|
|
112
|
+
export function blockEyeCells(geo, state = {}, v = GOOGLY) {
|
|
113
|
+
const PW = geo.cols * 2, PH = geo.rows * 2, px = new Uint8Array(PW * PH); // 0 none, 1 white, 2 pupil
|
|
114
|
+
const { R, cy, cxL, shift } = centres(geo, v), open = clamp(state.open ?? 1);
|
|
115
|
+
const put = (x, y, k) => { for (const xx of [x, x + shift]) if (xx >= 0 && y >= 0 && xx < PW && y < PH) px[y * PW + xx] = k; };
|
|
116
|
+
if (open < 0.15) {
|
|
117
|
+
const L = lid(cxL, cy, R, v);
|
|
118
|
+
for (let x = cxL - L.n; x < cxL + L.n; x++) put(x, Math.floor(L.y(x + 0.5) / 2), 1);
|
|
119
|
+
} else {
|
|
120
|
+
const [gx, gy] = gaze(state, v), f = eyeShape(cxL, cy, R, { ...v, pupil: 0.43 }, open, gx, gy), small = R < 6;
|
|
121
|
+
for (let y = Math.floor((cy - R * 2) / 2); y <= Math.ceil((cy + R * 2) / 2); y++) for (let x = Math.floor(cxL - R * 2); x <= Math.ceil(cxL + R * 2); x++) {
|
|
122
|
+
const k = f(x + 0.5, y * 2 + 1);
|
|
123
|
+
if (k) put(x, y, k === 3 ? 1 : k === 2 && small ? 1 : k);
|
|
124
|
+
}
|
|
125
|
+
if (small) { const pr0 = Math.round(cy / 2); for (const x of [cxL - 1, cxL]) for (const y of [pr0 - 1, pr0]) put(x, y, 2); }
|
|
126
|
+
}
|
|
127
|
+
const foot = footprint(geo, v), rows = [];
|
|
128
|
+
for (let r = 0; r < geo.rows; r++) {
|
|
129
|
+
const row = [];
|
|
130
|
+
for (let c = 0; c < geo.cols; c++) {
|
|
131
|
+
const q = [px[(r * 2) * PW + c * 2], px[(r * 2) * PW + c * 2 + 1], px[(r * 2 + 1) * PW + c * 2], px[(r * 2 + 1) * PW + c * 2 + 1]];
|
|
132
|
+
if (!q.some((k) => k)) { row.push(foot[r * geo.cols + c] ? { ch: ' ', rgb: null } : null); continue; }
|
|
133
|
+
let m = 0; q.forEach((k, i) => { if (k === 1) m |= 1 << i; });
|
|
134
|
+
if (q.includes(1)) row.push({ ch: QUAD[m], rgb: EYE_WHITE, bg: q.includes(2) ? EYE_PUPIL : null });
|
|
135
|
+
else row.push({ ch: '█', rgb: EYE_PUPIL });
|
|
136
|
+
}
|
|
137
|
+
rows.push(row);
|
|
138
|
+
}
|
|
139
|
+
return rows;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// ---- motion: pure functions of time ---------------------------------------------------------------------------
|
|
143
|
+
export const BLINK_MS = 200;
|
|
144
|
+
/** Lid openness u ms into a blink: shut in 70 ms, held 40, open again by 200. */
|
|
145
|
+
export function blinkOpen(u) {
|
|
146
|
+
if (u < 0 || u >= BLINK_MS) return 1;
|
|
147
|
+
if (u < 70) return 1 - smooth(u / 70);
|
|
148
|
+
if (u < 110) return 0;
|
|
149
|
+
return smooth((u - 110) / 90);
|
|
150
|
+
}
|
|
151
|
+
const hash = (n) => { let x = Math.imul((n | 0) ^ 0x9e3779b9, 0x85ebca6b); x ^= x >>> 13; x = Math.imul(x, 0xc2b2ae35); x ^= x >>> 16; return (x >>> 0) / 4294967296; };
|
|
152
|
+
// keyframes [[ms, [gx, gy]], ...], eased between neighbours, held at the ends
|
|
153
|
+
function gazeKf(t, keys) {
|
|
154
|
+
if (t <= keys[0][0]) return keys[0][1];
|
|
155
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
156
|
+
const [t0, a] = keys[i], [t1, b] = keys[i + 1];
|
|
157
|
+
if (t < t1) { const e = smooth((t - t0) / (t1 - t0)); return [a[0] + (b[0] - a[0]) * e, a[1] + (b[1] - a[1]) * e]; }
|
|
158
|
+
}
|
|
159
|
+
return keys[keys.length - 1][1];
|
|
160
|
+
}
|
|
161
|
+
const REST = GOOGLY.rest;
|
|
162
|
+
/** Look around: centre, left, right, up, down, centre; 260 ms moves, 600 ms holds. */
|
|
163
|
+
export const LOOK_KEYS = (() => {
|
|
164
|
+
const pts = [REST, [-1, 0.2], [1, 0.2], [0, -1], [0, 1], REST], keys = [];
|
|
165
|
+
let t = 0;
|
|
166
|
+
for (const p of pts) { keys.push([t, p]); t += 600; keys.push([t, p]); t += 260; }
|
|
167
|
+
return keys;
|
|
168
|
+
})();
|
|
169
|
+
export const LOOK_MS = LOOK_KEYS[LOOK_KEYS.length - 1][0] + 400;
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* The eyes' state at t ms for a mode: 'open' | 'closed' | 'blink' | 'look' | 'idle'. Returns { open, gx, gy }.
|
|
173
|
+
* blink a blink every 1.6 s; every third one is a double blink.
|
|
174
|
+
* look the look-around loop (LOOK_KEYS).
|
|
175
|
+
* idle blinks every 2.6 to 5 s (one in five a double), and now and then a glance that holds and returns.
|
|
176
|
+
*/
|
|
177
|
+
export function eyeState(mode, t) {
|
|
178
|
+
if (mode === 'closed') return { open: 0, gx: REST[0], gy: REST[1] };
|
|
179
|
+
if (mode === 'open' || t < 0) return { open: 1, gx: REST[0], gy: REST[1] };
|
|
180
|
+
if (mode === 'blink') {
|
|
181
|
+
const k = Math.floor(t / 1600), u = t - k * 1600;
|
|
182
|
+
return { open: Math.min(blinkOpen(u - 300), k % 3 === 2 ? blinkOpen(u - 560) : 1), gx: REST[0], gy: REST[1] };
|
|
183
|
+
}
|
|
184
|
+
if (mode === 'look') { const [gx, gy] = gazeKf(t % LOOK_MS, LOOK_KEYS); return { open: 1, gx, gy }; }
|
|
185
|
+
// idle: a seeded schedule of events, each 2.6 to 5 s long, so any t maps to the same frame
|
|
186
|
+
let start = 0, i = 0;
|
|
187
|
+
for (;;) { const len = 2600 + hash(i * 7 + 1) * 2400; if (t < start + len) break; start += len; i++; if (i > 100000) break; }
|
|
188
|
+
const u = t - start, len = 2600 + hash(i * 7 + 1) * 2400;
|
|
189
|
+
let open = 1, g = REST;
|
|
190
|
+
if (hash(i * 7 + 2) < 0.4) {
|
|
191
|
+
// a glance: move in 240 ms, hold, come back before the event ends
|
|
192
|
+
const dir = [[-1, 0.2], [1, 0.2], [-0.8, -0.7], [0.8, -0.7], [0, 1]][Math.floor(hash(i * 7 + 3) * 5)];
|
|
193
|
+
g = gazeKf(u, [[300, REST], [540, dir], [540 + 900, dir], [540 + 900 + 260, REST]]);
|
|
194
|
+
}
|
|
195
|
+
const b = len - 700; // the blink near the end of each event
|
|
196
|
+
open = Math.min(blinkOpen(u - b), hash(i * 7 + 4) < 0.2 ? blinkOpen(u - b - 260) : 1);
|
|
197
|
+
return { open, gx: g[0], gy: g[1] };
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// ---- compose ------------------------------------------------------------------------------------------------
|
|
201
|
+
/** Composite: eye cells over base cells (same grid). Base cells are untouched wherever the eye layer is null. */
|
|
202
|
+
export function overlay(base, eyes) {
|
|
203
|
+
return base.map((row, r) => row.map((cell, c) => (eyes[r] && eyes[r][c]) || cell));
|
|
204
|
+
}
|
|
205
|
+
const layer = (style) => (style === 'blocks' ? blockEyeCells : eyeCells);
|
|
206
|
+
/** Eyes for the settled qart Q, given its mask ({ W, H, ox } from qMask). style: 'dots' | 'blocks'. */
|
|
207
|
+
export function eyesForQ(mask, state, v = GOOGLY, style = 'dots') {
|
|
208
|
+
return layer(style)({ cols: mask.W / 2, rows: mask.H / 4, ox: mask.ox, Hq: mask.H }, state, v);
|
|
209
|
+
}
|
|
210
|
+
/** Eyes for a qdragon layout ({ cols, rows, qx0, qy0, H }). */
|
|
211
|
+
export function eyesForDragon(L, state, v = GOOGLY, style = 'dots') {
|
|
212
|
+
return layer(style)({ cols: L.cols, rows: L.rows, ox: L.qx0, oy: L.qy0, Hq: L.H }, state, v);
|
|
213
|
+
}
|