quoxcli 0.0.0-stage → 0.3.1
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 +35 -0
- package/README.md +144 -2
- package/bin/quox.js +265 -0
- package/bin/quoxon.js +8 -0
- package/lib/api/endpoints/admin.js +151 -0
- package/lib/api/endpoints/aee.js +38 -0
- package/lib/api/endpoints/agent-attach.js +126 -0
- package/lib/api/endpoints/agent-schedules.js +26 -0
- package/lib/api/endpoints/agents.js +63 -0
- package/lib/api/endpoints/assistants.js +62 -0
- package/lib/api/endpoints/audit.js +14 -0
- package/lib/api/endpoints/auth.js +50 -0
- package/lib/api/endpoints/awareness.js +34 -0
- package/lib/api/endpoints/away.js +15 -0
- package/lib/api/endpoints/backup-schedules.js +20 -0
- package/lib/api/endpoints/backups.js +86 -0
- package/lib/api/endpoints/brain.js +140 -0
- package/lib/api/endpoints/budgets.js +62 -0
- package/lib/api/endpoints/calendarq.js +71 -0
- package/lib/api/endpoints/catalog.js +14 -0
- package/lib/api/endpoints/chat.js +61 -0
- package/lib/api/endpoints/consent.js +26 -0
- package/lib/api/endpoints/contactsq.js +68 -0
- package/lib/api/endpoints/conversations.js +46 -0
- package/lib/api/endpoints/custom-roles.js +92 -0
- package/lib/api/endpoints/divisions.js +56 -0
- package/lib/api/endpoints/domains.js +38 -0
- package/lib/api/endpoints/engagement.js +231 -0
- package/lib/api/endpoints/engine.js +11 -0
- package/lib/api/endpoints/entity.js +50 -0
- package/lib/api/endpoints/executions.js +45 -0
- package/lib/api/endpoints/fetchAllowlist.js +27 -0
- package/lib/api/endpoints/file.js +20 -0
- package/lib/api/endpoints/files.js +40 -0
- package/lib/api/endpoints/github.js +21 -0
- package/lib/api/endpoints/governance.js +43 -0
- package/lib/api/endpoints/hat.js +66 -0
- package/lib/api/endpoints/inbox.js +52 -0
- package/lib/api/endpoints/inboxq.js +120 -0
- package/lib/api/endpoints/index.js +242 -0
- package/lib/api/endpoints/invites.js +19 -0
- package/lib/api/endpoints/llm-policy.js +43 -0
- package/lib/api/endpoints/lucid.js +89 -0
- package/lib/api/endpoints/members.js +32 -0
- package/lib/api/endpoints/memory.js +94 -0
- package/lib/api/endpoints/mfa.js +32 -0
- package/lib/api/endpoints/mind.js +13 -0
- package/lib/api/endpoints/mon.js +184 -0
- package/lib/api/endpoints/notesq.js +43 -0
- package/lib/api/endpoints/objectives.js +68 -0
- package/lib/api/endpoints/orgs.js +38 -0
- package/lib/api/endpoints/plugins.js +152 -0
- package/lib/api/endpoints/powers.js +75 -0
- package/lib/api/endpoints/presence.js +33 -0
- package/lib/api/endpoints/prompts.js +69 -0
- package/lib/api/endpoints/qlarity.js +122 -0
- package/lib/api/endpoints/quoxbox.js +55 -0
- package/lib/api/endpoints/quoxchat.js +54 -0
- package/lib/api/endpoints/quoxflow.js +32 -0
- package/lib/api/endpoints/quoxplan.js +121 -0
- package/lib/api/endpoints/quoxseo.js +114 -0
- package/lib/api/endpoints/reach.js +47 -0
- package/lib/api/endpoints/router.js +17 -0
- package/lib/api/endpoints/runs.js +38 -0
- package/lib/api/endpoints/safety.js +53 -0
- package/lib/api/endpoints/scim.js +46 -0
- package/lib/api/endpoints/scopes.js +14 -0
- package/lib/api/endpoints/security.js +69 -0
- package/lib/api/endpoints/setup.js +90 -0
- package/lib/api/endpoints/skills.js +200 -0
- package/lib/api/endpoints/sso.js +67 -0
- package/lib/api/endpoints/stepup.js +20 -0
- package/lib/api/endpoints/tags.js +32 -0
- package/lib/api/endpoints/teams.js +56 -0
- package/lib/api/endpoints/telephony.js +46 -0
- package/lib/api/endpoints/tg.js +56 -0
- package/lib/api/endpoints/tools.js +50 -0
- package/lib/api/endpoints/traces.js +157 -0
- package/lib/api/endpoints/triggers.js +96 -0
- package/lib/api/endpoints/user-library.js +41 -0
- package/lib/api/endpoints/users.js +20 -0
- package/lib/api/endpoints/v1.js +585 -0
- package/lib/api/endpoints/vault.js +192 -0
- package/lib/api/endpoints/voice.js +22 -0
- package/lib/api/endpoints/volt.js +84 -0
- package/lib/api/endpoints/ward.js +47 -0
- package/lib/api/endpoints/webhooks.js +46 -0
- package/lib/api/endpoints/workflows.js +169 -0
- package/lib/api/endpoints/x.js +32 -0
- package/lib/api/endpoints.js +15 -0
- package/lib/commands/actions.js +61 -0
- package/lib/commands/admin.js +375 -0
- package/lib/commands/aee.js +99 -0
- package/lib/commands/agent.js +337 -0
- package/lib/commands/agentAttach.js +1528 -0
- package/lib/commands/approvals.js +119 -0
- package/lib/commands/aside.js +24 -0
- package/lib/commands/ask.js +144 -0
- package/lib/commands/assistant.js +169 -0
- package/lib/commands/auth.js +464 -0
- package/lib/commands/awareness.js +88 -0
- package/lib/commands/backup.js +377 -0
- package/lib/commands/brain.js +1085 -0
- package/lib/commands/budget.js +241 -0
- package/lib/commands/build.js +170 -0
- package/lib/commands/calendar.js +216 -0
- package/lib/commands/capabilities.js +34 -0
- package/lib/commands/chat.js +295 -0
- package/lib/commands/claude.js +380 -0
- package/lib/commands/config-cmd.js +106 -0
- package/lib/commands/consent.js +84 -0
- package/lib/commands/contacts.js +1016 -0
- package/lib/commands/contactsSelftest.js +132 -0
- package/lib/commands/conversation.js +105 -0
- package/lib/commands/dashboard.js +43 -0
- package/lib/commands/department.js +96 -0
- package/lib/commands/dev.js +620 -0
- package/lib/commands/devResident.js +326 -0
- package/lib/commands/devSupervisor.js +1062 -0
- package/lib/commands/discord.js +132 -0
- package/lib/commands/doctor.js +181 -0
- package/lib/commands/domain.js +114 -0
- package/lib/commands/done.js +347 -0
- package/lib/commands/engagement.js +597 -0
- package/lib/commands/entity.js +119 -0
- package/lib/commands/facebook.js +212 -0
- package/lib/commands/fetchAllowlist.js +71 -0
- package/lib/commands/file.js +165 -0
- package/lib/commands/fleet.js +1189 -0
- package/lib/commands/governance.js +647 -0
- package/lib/commands/gripe.js +355 -0
- package/lib/commands/hat.js +374 -0
- package/lib/commands/helpers.js +96 -0
- package/lib/commands/hyperion.js +58 -0
- package/lib/commands/inbox.js +173 -0
- package/lib/commands/instagram.js +264 -0
- package/lib/commands/integration.js +100 -0
- package/lib/commands/inventory.js +115 -0
- package/lib/commands/invites.js +45 -0
- package/lib/commands/licence.js +139 -0
- package/lib/commands/linkedin.js +178 -0
- package/lib/commands/llm-policy.js +117 -0
- package/lib/commands/llm.js +45 -0
- package/lib/commands/lucid.js +597 -0
- package/lib/commands/mail.js +531 -0
- package/lib/commands/memory.js +361 -0
- package/lib/commands/mind.js +71 -0
- package/lib/commands/mon.js +581 -0
- package/lib/commands/monitoring.js +86 -0
- package/lib/commands/motion.js +22 -0
- package/lib/commands/notes.js +1246 -0
- package/lib/commands/notesBatch.js +160 -0
- package/lib/commands/notesBridges.js +56 -0
- package/lib/commands/notesLinks.js +60 -0
- package/lib/commands/notification.js +57 -0
- package/lib/commands/nucleus.js +123 -0
- package/lib/commands/objective.js +240 -0
- package/lib/commands/offload.js +89 -0
- package/lib/commands/onboard.js +329 -0
- package/lib/commands/open.js +56 -0
- package/lib/commands/org.js +276 -0
- package/lib/commands/peek.js +28 -0
- package/lib/commands/plan.js +944 -0
- package/lib/commands/plugin-doctor.js +1091 -0
- package/lib/commands/plugin.js +649 -0
- package/lib/commands/policy.js +78 -0
- package/lib/commands/powers.js +296 -0
- package/lib/commands/presence.js +89 -0
- package/lib/commands/prompt.js +350 -0
- package/lib/commands/provider.js +159 -0
- package/lib/commands/qlarity.js +706 -0
- package/lib/commands/quoxbox.js +270 -0
- package/lib/commands/quoxchat.js +275 -0
- package/lib/commands/quoxseo.js +378 -0
- package/lib/commands/reach.js +159 -0
- package/lib/commands/role.js +286 -0
- package/lib/commands/router.js +77 -0
- package/lib/commands/rows.js +358 -0
- package/lib/commands/run.js +187 -0
- package/lib/commands/safety.js +107 -0
- package/lib/commands/scim.js +88 -0
- package/lib/commands/scope.js +58 -0
- package/lib/commands/security.js +157 -0
- package/lib/commands/server.js +292 -0
- package/lib/commands/service.js +89 -0
- package/lib/commands/settings.js +99 -0
- package/lib/commands/setup.js +569 -0
- package/lib/commands/skillManifest.js +113 -0
- package/lib/commands/skillRightsFormat.js +38 -0
- package/lib/commands/skills.js +687 -0
- package/lib/commands/slot.js +1451 -0
- package/lib/commands/sso.js +229 -0
- package/lib/commands/status.js +137 -0
- package/lib/commands/tag.js +91 -0
- package/lib/commands/team.js +142 -0
- package/lib/commands/tg.js +240 -0
- package/lib/commands/tiktok.js +218 -0
- package/lib/commands/tips.js +23 -0
- package/lib/commands/tool.js +304 -0
- package/lib/commands/trace.js +530 -0
- package/lib/commands/trigger.js +223 -0
- package/lib/commands/update.js +46 -0
- package/lib/commands/vault.js +1447 -0
- package/lib/commands/vm.js +237 -0
- package/lib/commands/voice.js +213 -0
- package/lib/commands/volt.js +570 -0
- package/lib/commands/walkthrough.js +32 -0
- package/lib/commands/watch.js +167 -0
- package/lib/commands/webhook.js +137 -0
- package/lib/commands/welcome.js +14 -0
- package/lib/commands/workflow.js +660 -0
- package/lib/commands/x.js +448 -0
- package/lib/core/actingAs.js +136 -0
- package/lib/core/activeSummary.js +186 -0
- package/lib/core/aee.js +53 -0
- package/lib/core/agentAlias.js +163 -0
- package/lib/core/approvalContext.js +61 -0
- package/lib/core/aside.js +54 -0
- package/lib/core/askDecision.js +60 -0
- package/lib/core/auth.js +501 -0
- package/lib/core/banner.js +143 -0
- package/lib/core/brainPrime.js +90 -0
- package/lib/core/callerSource.js +129 -0
- package/lib/core/claudeLs.js +94 -0
- package/lib/core/cliBuild.js +62 -0
- package/lib/core/client.js +693 -0
- package/lib/core/commandRegistrars.js +121 -0
- package/lib/core/completions.js +163 -0
- package/lib/core/config.js +669 -0
- package/lib/core/confirm.js +37 -0
- package/lib/core/customHats.js +168 -0
- package/lib/core/deeplinks.js +76 -0
- package/lib/core/doneEngineFetch.js +227 -0
- package/lib/core/errors.js +441 -0
- package/lib/core/global-flags.js +190 -0
- package/lib/core/governedSession.js +402 -0
- package/lib/core/hyperion.js +102 -0
- package/lib/core/inboxResolution.js +65 -0
- package/lib/core/lazyCommands.js +122 -0
- package/lib/core/liveSessionsReport.js +112 -0
- package/lib/core/offload.js +504 -0
- package/lib/core/org-override.js +52 -0
- package/lib/core/orgResolve.js +70 -0
- package/lib/core/output.js +239 -0
- package/lib/core/peek.js +126 -0
- package/lib/core/prodEnv.js +7 -0
- package/lib/core/profileUsers.js +51 -0
- package/lib/core/promptCancel.js +18 -0
- package/lib/core/quoxglyph.js +158 -0
- package/lib/core/repoPath.js +11 -0
- package/lib/core/residentAlarm.js +282 -0
- package/lib/core/residentAuth.js +243 -0
- package/lib/core/residentPump.js +229 -0
- package/lib/core/residentSeed.js +106 -0
- 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 +192 -0
- package/lib/core/sessionsOverview.js +224 -0
- package/lib/core/slotKeeper.js +527 -0
- package/lib/core/slotWake.js +120 -0
- package/lib/core/supervisorEngine.js +933 -0
- package/lib/core/systemdInstaller.js +256 -0
- package/lib/core/version.js +87 -0
- package/lib/core/vm.js +361 -0
- package/lib/core/vmTui.js +86 -0
- package/lib/core/walkthrough.js +35 -0
- package/lib/hyperion/deck.js +29 -0
- package/lib/lucid/LucidCard.jsx +68 -0
- package/lib/lucid/LucidIntro.jsx +92 -0
- package/lib/lucid/blind.js +71 -0
- package/lib/lucid/cards.js +86 -0
- package/lib/lucid/deck.js +32 -0
- package/lib/lucid/fmt.js +139 -0
- package/lib/lucid/markdown.js +81 -0
- package/lib/lucid/motion.js +56 -0
- package/lib/lucid/sitrep.js +173 -0
- package/lib/plugins/loader.js +93 -0
- package/lib/quoxon/activity.js +116 -0
- package/lib/quoxon/aocl.js +117 -0
- package/lib/quoxon/api.js +952 -0
- package/lib/quoxon/app.js +4032 -0
- package/lib/quoxon/approvalFacts.js +94 -0
- package/lib/quoxon/approvalsView.js +325 -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 +82 -0
- package/lib/quoxon/cells.js +53 -0
- package/lib/quoxon/commandShort.js +112 -0
- package/lib/quoxon/commands.js +479 -0
- package/lib/quoxon/connect.js +654 -0
- package/lib/quoxon/connectCard.js +45 -0
- package/lib/quoxon/consent.js +86 -0
- package/lib/quoxon/context.js +68 -0
- package/lib/quoxon/copy.js +34 -0
- package/lib/quoxon/countdownLine.js +11 -0
- package/lib/quoxon/demo.autoplay.js +29 -0
- package/lib/quoxon/demo.fixtures.js +24 -0
- package/lib/quoxon/demo.js +76 -0
- package/lib/quoxon/devObserver.js +20 -0
- package/lib/quoxon/dividerAocl.js +191 -0
- package/lib/quoxon/dividerRule.js +57 -0
- package/lib/quoxon/dragonModes.js +150 -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 +36 -0
- package/lib/quoxon/events.js +677 -0
- package/lib/quoxon/feedback.js +50 -0
- package/lib/quoxon/gallery.js +237 -0
- 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/indicator.js +197 -0
- package/lib/quoxon/intent.js +52 -0
- package/lib/quoxon/keys.js +164 -0
- package/lib/quoxon/launcher.js +103 -0
- package/lib/quoxon/main.js +176 -0
- package/lib/quoxon/markdown.js +341 -0
- 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 +311 -0
- package/lib/quoxon/modelPrefs.js +345 -0
- package/lib/quoxon/motion/clock.js +70 -0
- package/lib/quoxon/motion/color.js +35 -0
- package/lib/quoxon/motion/frame.js +14 -0
- package/lib/quoxon/motion/level.js +91 -0
- package/lib/quoxon/motion/reveal.js +54 -0
- package/lib/quoxon/motion/ring.js +79 -0
- package/lib/quoxon/mouse.js +211 -0
- package/lib/quoxon/noai.js +57 -0
- package/lib/quoxon/objects/agent.js +61 -0
- package/lib/quoxon/objects/away.js +72 -0
- package/lib/quoxon/objects/crystal.js +100 -0
- package/lib/quoxon/objects/fetch.js +182 -0
- package/lib/quoxon/objects/fleet.js +149 -0
- package/lib/quoxon/objects/health.js +186 -0
- package/lib/quoxon/objects/index.js +106 -0
- package/lib/quoxon/objects/ladder.js +71 -0
- package/lib/quoxon/objects/memory.js +44 -0
- package/lib/quoxon/objects/mirror.js +137 -0
- package/lib/quoxon/objects/model.js +118 -0
- package/lib/quoxon/objects/peek.js +17 -0
- package/lib/quoxon/objects/proof.js +110 -0
- 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/skill.js +105 -0
- package/lib/quoxon/objects/view.js +87 -0
- package/lib/quoxon/objects/workflow.js +119 -0
- package/lib/quoxon/observers.js +42 -0
- package/lib/quoxon/onboarding/copy.js +263 -0
- package/lib/quoxon/onboarding/credential.js +108 -0
- package/lib/quoxon/onboarding/detect.js +22 -0
- package/lib/quoxon/onboarding/dockerOffer.js +45 -0
- package/lib/quoxon/onboarding/failureCause.js +33 -0
- package/lib/quoxon/onboarding/finish.js +134 -0
- package/lib/quoxon/onboarding/index.js +94 -0
- package/lib/quoxon/onboarding/installRunner.js +284 -0
- package/lib/quoxon/onboarding/intent.js +53 -0
- package/lib/quoxon/onboarding/journey.js +73 -0
- package/lib/quoxon/onboarding/preflight.js +156 -0
- package/lib/quoxon/onboarding/probe.js +65 -0
- package/lib/quoxon/onboarding/runner.js +58 -0
- package/lib/quoxon/onboarding/view.js +682 -0
- package/lib/quoxon/orgLabel.js +10 -0
- package/lib/quoxon/orgSwitch.js +33 -0
- package/lib/quoxon/ownBroadcast.js +22 -0
- package/lib/quoxon/palette.js +43 -0
- package/lib/quoxon/print.js +98 -0
- 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 +348 -0
- package/lib/quoxon/qdragon.js +410 -0
- package/lib/quoxon/qdragonFly.js +61 -0
- package/lib/quoxon/qdragonScenes.js +282 -0
- package/lib/quoxon/qeyes.js +213 -0
- package/lib/quoxon/qglyph.js +143 -0
- package/lib/quoxon/qhello.js +58 -0
- package/lib/quoxon/qmotion.js +132 -0
- package/lib/quoxon/render.js +1167 -0
- package/lib/quoxon/resend.js +72 -0
- package/lib/quoxon/roster.js +307 -0
- package/lib/quoxon/route.js +39 -0
- package/lib/quoxon/rows.js +711 -0
- package/lib/quoxon/rowsStore.js +80 -0
- package/lib/quoxon/rules.js +130 -0
- package/lib/quoxon/screenContext.js +150 -0
- package/lib/quoxon/screenFrames.js +254 -0
- package/lib/quoxon/session.js +238 -0
- package/lib/quoxon/sessionDead.js +17 -0
- package/lib/quoxon/signin.js +125 -0
- package/lib/quoxon/skills.js +30 -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 +53 -0
- package/lib/quoxon/slash/cliLeaves.json +948 -0
- package/lib/quoxon/slash/compact.js +90 -0
- 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/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 +88 -0
- package/lib/quoxon/slash/listView.js +30 -0
- package/lib/quoxon/slash/modelCmd.js +261 -0
- package/lib/quoxon/slash/palette.js +118 -0
- package/lib/quoxon/slash/paletteController.js +169 -0
- package/lib/quoxon/slash/panels.js +116 -0
- package/lib/quoxon/slash/picker.js +56 -0
- package/lib/quoxon/slash/restart.js +202 -0
- package/lib/quoxon/slash/resume.js +59 -0
- package/lib/quoxon/slash/rowCmd.js +157 -0
- package/lib/quoxon/slash/rowWalk.js +175 -0
- package/lib/quoxon/slash/settings.js +60 -0
- 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/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/index.js +186 -0
- package/lib/quoxon/sound/mapping.js +120 -0
- package/lib/quoxon/sound/output.js +52 -0
- package/lib/quoxon/sound/synth.js +122 -0
- package/lib/quoxon/stray.js +22 -0
- package/lib/quoxon/suggestCommand.js +59 -0
- package/lib/quoxon/term.js +141 -0
- package/lib/quoxon/textBuffer.js +416 -0
- package/lib/quoxon/theme.js +286 -0
- package/lib/quoxon/tips.js +433 -0
- package/lib/quoxon/toolError.js +41 -0
- package/lib/quoxon/transcript.js +188 -0
- package/lib/quoxon/web/qface.js +89 -0
- package/lib/quoxon/welcome.js +52 -0
- package/lib/quoxon/whatsnew.js +136 -0
- package/lib/quoxon/whatsnew.jsonl +89 -0
- package/lib/quoxon/windowOrg.js +113 -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/app.jsx +289 -0
- package/lib/tui/components/Breadcrumb.jsx +42 -0
- package/lib/tui/components/ChatInput.jsx +68 -0
- package/lib/tui/components/Confirm.jsx +32 -0
- package/lib/tui/components/DataTable.jsx +270 -0
- package/lib/tui/components/DetailPanel.jsx +241 -0
- package/lib/tui/components/HelpOverlay.jsx +67 -0
- package/lib/tui/components/Layout.jsx +129 -0
- package/lib/tui/components/Palette.jsx +141 -0
- package/lib/tui/components/SearchInput.jsx +125 -0
- package/lib/tui/components/Sparkline.jsx +55 -0
- package/lib/tui/components/StatusBar.jsx +133 -0
- package/lib/tui/components/Toast.jsx +52 -0
- package/lib/tui/hooks/useApi.js +44 -0
- package/lib/tui/hooks/useHealth.js +90 -0
- package/lib/tui/hooks/usePolledApi.js +86 -0
- package/lib/tui/hooks/useTerminalSize.js +46 -0
- package/lib/tui/index.js +56 -0
- package/lib/tui/intro/Intro.jsx +332 -0
- package/lib/tui/jsx-loader.js +32 -0
- package/lib/tui/keys.js +87 -0
- package/lib/tui/registry.js +49 -0
- package/lib/tui/theme.js +103 -0
- package/lib/tui/views/AdminView.jsx +398 -0
- package/lib/tui/views/AgentView.jsx +204 -0
- package/lib/tui/views/BackupView.jsx +225 -0
- package/lib/tui/views/BrainView.jsx +223 -0
- package/lib/tui/views/ChatView.jsx +468 -0
- package/lib/tui/views/FilesView.jsx +218 -0
- package/lib/tui/views/FleetView.jsx +234 -0
- package/lib/tui/views/GovernanceView.jsx +453 -0
- package/lib/tui/views/HealthView.jsx +216 -0
- package/lib/tui/views/HyperionDeck.jsx +79 -0
- package/lib/tui/views/InboxView.jsx +228 -0
- package/lib/tui/views/LucidDeck.jsx +719 -0
- package/lib/tui/views/MainMenu.jsx +123 -0
- package/lib/tui/views/MemoryView.jsx +377 -0
- package/lib/tui/views/ObjectivesView.jsx +230 -0
- package/lib/tui/views/OrgView.jsx +229 -0
- package/lib/tui/views/ToolView.jsx +221 -0
- package/lib/tui/views/VaultView.jsx +422 -0
- package/lib/tui/views/WorkflowView.jsx +329 -0
- package/package.json +75 -4
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QUOXON-FW6: model favourites and the default model. Pure helpers plus one quiet live-list reader.
|
|
3
|
+
*
|
|
4
|
+
* The preference is { favourites: [{ provider, model }], default: { provider, model } | null }.
|
|
5
|
+
* `provider` is a /model provider id (openai, anthropic, claude-cli, ollama, a compat preset).
|
|
6
|
+
* Persistence is the caller's (api.modelPrefs / api.setModelPrefs: the user's server-side
|
|
7
|
+
* preferences, with the profile as a cache). There is no model list in this file: ids come from
|
|
8
|
+
* the provider's own live list (liveModels) or from what the user typed. A typed id that is not on
|
|
9
|
+
* the live list is kept with a warning, never refused.
|
|
10
|
+
*/
|
|
11
|
+
import { CHOICES, CLAUDE_FAMILIES, providerId } from './model.js';
|
|
12
|
+
|
|
13
|
+
export const MAX_FAVS = 20;
|
|
14
|
+
const nameOf = (id) => CHOICES.find((c) => c.id === id)?.name || id;
|
|
15
|
+
|
|
16
|
+
const cleanOne = (v) => {
|
|
17
|
+
const provider = providerId(v?.provider);
|
|
18
|
+
const model = typeof v?.model === 'string' ? v.model.trim().slice(0, 200) : '';
|
|
19
|
+
return provider && model && !/\s/.test(model) ? { provider, model } : null;
|
|
20
|
+
};
|
|
21
|
+
export const sameModel = (a, b) =>
|
|
22
|
+
Boolean(a && b) && a.provider === b.provider && a.model === b.model;
|
|
23
|
+
|
|
24
|
+
/** Anything stored or received -> a clean prefs object (bad rows dropped, duplicates removed). */
|
|
25
|
+
export function normPrefs(v) {
|
|
26
|
+
const favs = [];
|
|
27
|
+
for (const f of Array.isArray(v?.favourites) ? v.favourites : []) {
|
|
28
|
+
const c = cleanOne(f);
|
|
29
|
+
if (c && !favs.some((x) => sameModel(x, c)) && favs.length < MAX_FAVS) favs.push(c);
|
|
30
|
+
}
|
|
31
|
+
return { favourites: favs, default: cleanOne(v?.default) };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const label = (m) => `${m.provider} ${m.model}`;
|
|
35
|
+
|
|
36
|
+
/** -> { prefs, added, text, tone }. */
|
|
37
|
+
export function addFav(prefs, provider, model) {
|
|
38
|
+
const c = cleanOne({ provider, model });
|
|
39
|
+
if (!c)
|
|
40
|
+
return { prefs, added: false, text: 'Usage: /model fav add <provider> <model>', tone: 'warn' };
|
|
41
|
+
const at = prefs.favourites.findIndex((f) => sameModel(f, c));
|
|
42
|
+
if (at >= 0)
|
|
43
|
+
return {
|
|
44
|
+
prefs,
|
|
45
|
+
added: false,
|
|
46
|
+
text: `${label(c)} is already favourite ${at + 1}.`,
|
|
47
|
+
tone: 'plain',
|
|
48
|
+
};
|
|
49
|
+
if (prefs.favourites.length >= MAX_FAVS)
|
|
50
|
+
return {
|
|
51
|
+
prefs,
|
|
52
|
+
added: false,
|
|
53
|
+
text: `You have ${MAX_FAVS} favourites already. /model fav rm <n> makes room.`,
|
|
54
|
+
tone: 'warn',
|
|
55
|
+
};
|
|
56
|
+
const next = { ...prefs, favourites: [...prefs.favourites, c] };
|
|
57
|
+
const n = next.favourites.length;
|
|
58
|
+
return {
|
|
59
|
+
prefs: next,
|
|
60
|
+
added: true,
|
|
61
|
+
text: `Added ${label(c)} as favourite ${n}. /model ${n} switches to it.`,
|
|
62
|
+
tone: 'ok',
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* ref = a 1-based number, or a model id (exact, "provider model", "provider/model", or a
|
|
68
|
+
* unique substring).
|
|
69
|
+
*/
|
|
70
|
+
export function rmFav(prefs, ref) {
|
|
71
|
+
const q = String(ref ?? '')
|
|
72
|
+
.trim()
|
|
73
|
+
.toLowerCase();
|
|
74
|
+
const favs = prefs.favourites;
|
|
75
|
+
if (!q) return { prefs, removed: null, text: 'Usage: /model fav rm <n|model>', tone: 'warn' };
|
|
76
|
+
let idx = -1;
|
|
77
|
+
if (/^\d+$/.test(q)) idx = Number(q) - 1;
|
|
78
|
+
else {
|
|
79
|
+
const exact = favs
|
|
80
|
+
.map((f, i) => i)
|
|
81
|
+
.filter(
|
|
82
|
+
(i) =>
|
|
83
|
+
favs[i].model.toLowerCase() === q ||
|
|
84
|
+
label(favs[i]).toLowerCase() === q ||
|
|
85
|
+
`${favs[i].provider}/${favs[i].model}`.toLowerCase() === q,
|
|
86
|
+
);
|
|
87
|
+
const part = favs.map((f, i) => i).filter((i) => favs[i].model.toLowerCase().includes(q));
|
|
88
|
+
const hits = exact.length ? exact : part;
|
|
89
|
+
if (hits.length > 1)
|
|
90
|
+
return {
|
|
91
|
+
prefs,
|
|
92
|
+
removed: null,
|
|
93
|
+
text:
|
|
94
|
+
`"${ref}" matches ${hits.map((i) => `${i + 1} ${label(favs[i])}`).join(', ')}. ` +
|
|
95
|
+
'Use the number.',
|
|
96
|
+
tone: 'warn',
|
|
97
|
+
};
|
|
98
|
+
idx = hits.length ? hits[0] : -1;
|
|
99
|
+
}
|
|
100
|
+
if (idx < 0 || idx >= favs.length)
|
|
101
|
+
return {
|
|
102
|
+
prefs,
|
|
103
|
+
removed: null,
|
|
104
|
+
text: favs.length
|
|
105
|
+
? `No favourite "${ref}". /model fav lists them.`
|
|
106
|
+
: 'You have no favourites yet. /model fav add <provider> <model>',
|
|
107
|
+
tone: 'warn',
|
|
108
|
+
};
|
|
109
|
+
const removed = favs[idx];
|
|
110
|
+
return {
|
|
111
|
+
prefs: { ...prefs, favourites: favs.filter((_, i) => i !== idx) },
|
|
112
|
+
removed,
|
|
113
|
+
text: `Removed ${label(removed)}. The numbers after it moved up by one.`,
|
|
114
|
+
tone: 'ok',
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function setDefault(prefs, provider, model) {
|
|
119
|
+
const c = cleanOne({ provider, model });
|
|
120
|
+
if (!c)
|
|
121
|
+
return { prefs, ok: false, text: 'Usage: /model default <provider> <model>', tone: 'warn' };
|
|
122
|
+
return {
|
|
123
|
+
prefs: { ...prefs, default: c },
|
|
124
|
+
ok: true,
|
|
125
|
+
text:
|
|
126
|
+
`Default model: ${label(c)}. New sessions start on it when ${nameOf(c.provider)} is ` +
|
|
127
|
+
`connected. /model ${label(c)} uses it now.`,
|
|
128
|
+
tone: 'ok',
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
export const clearDefault = (prefs) => ({
|
|
132
|
+
prefs: { ...prefs, default: null },
|
|
133
|
+
text: 'Default model cleared. New sessions use the last choice, else the server policy.',
|
|
134
|
+
tone: 'ok',
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
/** Toggle one model in the favourites -> { prefs, added }. */
|
|
138
|
+
export function toggleFav(prefs, m) {
|
|
139
|
+
const i = prefs.favourites.findIndex((f) => sameModel(f, m));
|
|
140
|
+
if (i >= 0)
|
|
141
|
+
return {
|
|
142
|
+
prefs: { ...prefs, favourites: prefs.favourites.filter((_, k) => k !== i) },
|
|
143
|
+
added: false,
|
|
144
|
+
};
|
|
145
|
+
return {
|
|
146
|
+
prefs: {
|
|
147
|
+
...prefs,
|
|
148
|
+
favourites: [...prefs.favourites, { provider: m.provider, model: m.model }].slice(
|
|
149
|
+
0,
|
|
150
|
+
MAX_FAVS,
|
|
151
|
+
),
|
|
152
|
+
},
|
|
153
|
+
added: true,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** /model fav lines. `current` = the stored choice ({ provider, model?, via? }) or null. */
|
|
158
|
+
export function favLines(prefs, currentKey = null) {
|
|
159
|
+
if (!prefs.favourites.length)
|
|
160
|
+
return [
|
|
161
|
+
[
|
|
162
|
+
'No favourites yet. Add one: /model fav add <provider> <model> ' +
|
|
163
|
+
'(or open /model and press f)',
|
|
164
|
+
],
|
|
165
|
+
];
|
|
166
|
+
const out = [['Favourite models (/model <n> switches):']];
|
|
167
|
+
prefs.favourites.forEach((f, i) => {
|
|
168
|
+
const marks = [
|
|
169
|
+
currentKey && sameModel(f, currentKey) ? 'current' : null,
|
|
170
|
+
prefs.default && sameModel(f, prefs.default) ? 'default' : null,
|
|
171
|
+
].filter(Boolean);
|
|
172
|
+
out.push([` ${i + 1} ${label(f)}${marks.length ? ` (${marks.join(', ')})` : ''}`, 'plain']);
|
|
173
|
+
});
|
|
174
|
+
out.push([
|
|
175
|
+
prefs.default
|
|
176
|
+
? `Default: ${label(prefs.default)}`
|
|
177
|
+
: 'No default model set. /model default <provider> <model>',
|
|
178
|
+
]);
|
|
179
|
+
return out;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const FAV_WORDS = new Set(['fav', 'favs', 'favourite', 'favourites', 'favorite', 'favorites']);
|
|
183
|
+
const SUBS_ONE = { list: 'list', status: 'list' };
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* The words after /model -> what to do. kinds: chooser list use fav-list fav-add fav-rm
|
|
187
|
+
* default-show default-set default-clear error pass.
|
|
188
|
+
*/
|
|
189
|
+
export function parseModelSub(arg) {
|
|
190
|
+
const w = String(arg || '')
|
|
191
|
+
.trim()
|
|
192
|
+
.split(/\s+/)
|
|
193
|
+
.filter(Boolean);
|
|
194
|
+
if (!w.length) return { kind: 'chooser' };
|
|
195
|
+
const first = w[0].toLowerCase();
|
|
196
|
+
if (SUBS_ONE[first] && w.length === 1) return { kind: SUBS_ONE[first] };
|
|
197
|
+
if (/^\d+$/.test(first) && w.length === 1) return { kind: 'use', n: Number(first) };
|
|
198
|
+
if (FAV_WORDS.has(first)) {
|
|
199
|
+
const sub = (w[1] || '').toLowerCase();
|
|
200
|
+
if (!sub) return { kind: 'fav-list' };
|
|
201
|
+
if (/^\d+$/.test(sub) && w.length === 2) return { kind: 'use', n: Number(sub) };
|
|
202
|
+
if (sub === 'add') {
|
|
203
|
+
if (w.length !== 4)
|
|
204
|
+
return {
|
|
205
|
+
kind: 'error',
|
|
206
|
+
text:
|
|
207
|
+
'Usage: /model fav add <provider> <model> ' +
|
|
208
|
+
'e.g. /model fav add openai gpt-5.6-terra',
|
|
209
|
+
};
|
|
210
|
+
return { kind: 'fav-add', provider: w[2], model: w[3] };
|
|
211
|
+
}
|
|
212
|
+
if (['rm', 'remove', 'del', 'delete'].includes(sub))
|
|
213
|
+
return w.length < 3
|
|
214
|
+
? { kind: 'error', text: 'Usage: /model fav rm <n|model>' }
|
|
215
|
+
: { kind: 'fav-rm', ref: w.slice(2).join(' ') };
|
|
216
|
+
return {
|
|
217
|
+
kind: 'error',
|
|
218
|
+
text: 'Usage: /model fav [add <provider> <model> | rm <n|model> | <n>]',
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
if (first === 'default') {
|
|
222
|
+
if (w.length === 1) return { kind: 'default-show' };
|
|
223
|
+
if (w.length === 2 && ['clear', 'none', 'off', 'unset'].includes(w[1].toLowerCase()))
|
|
224
|
+
return { kind: 'default-clear' };
|
|
225
|
+
if (w.length === 3) return { kind: 'default-set', provider: w[1], model: w[2] };
|
|
226
|
+
return { kind: 'error', text: 'Usage: /model default [<provider> <model> | clear]' };
|
|
227
|
+
}
|
|
228
|
+
return { kind: 'pass' };
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* A provider's list response (the shape differs per provider) -> ids, newest first as the
|
|
233
|
+
* server gave them.
|
|
234
|
+
*/
|
|
235
|
+
export function idsOf(choiceId, res) {
|
|
236
|
+
if (choiceId === 'claude-cli') return [...CLAUDE_FAMILIES];
|
|
237
|
+
if (!res?.ok || !Array.isArray(res.models)) return [];
|
|
238
|
+
const raw =
|
|
239
|
+
choiceId === 'ollama'
|
|
240
|
+
? res.models.map((m) => (typeof m === 'string' ? m : m?.name))
|
|
241
|
+
: res.models.map((m) => (typeof m === 'string' ? m : m?.id));
|
|
242
|
+
return raw.filter((x) => typeof x === 'string' && x);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* The live model ids for one provider, never throws. -> { ok, ids, def }. ok=false means
|
|
247
|
+
* "could not list" (the caller decides how quiet to be); ids is [] then.
|
|
248
|
+
*/
|
|
249
|
+
export async function liveModels(api, id, { refresh = false } = {}) {
|
|
250
|
+
try {
|
|
251
|
+
if (id === 'claude-cli') return { ok: true, ids: [...CLAUDE_FAMILIES], def: null };
|
|
252
|
+
const row = CHOICES.find((c) => c.id === id);
|
|
253
|
+
if (!row) return { ok: false, ids: [], def: null };
|
|
254
|
+
let res = null;
|
|
255
|
+
if (id === 'ollama') res = await api.ollamaModels?.();
|
|
256
|
+
else if (row.compat) res = await api.compatModels?.(id);
|
|
257
|
+
else if (row.discover)
|
|
258
|
+
res = await api.providerModels?.(row.provider, refresh ? { refresh: true } : undefined);
|
|
259
|
+
if (!res?.ok) return { ok: false, ids: [], def: null };
|
|
260
|
+
return { ok: true, ids: idsOf(id, res), def: res.default_model || null };
|
|
261
|
+
} catch {
|
|
262
|
+
return { ok: false, ids: [], def: null };
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Typed id vs the live list -> { model, known: true|false|null, note? }. null = no list to
|
|
268
|
+
* check against.
|
|
269
|
+
*/
|
|
270
|
+
export function checkModelId(ids, typed, providerName = '') {
|
|
271
|
+
if (!ids?.length) return { model: typed, known: null };
|
|
272
|
+
if (ids.includes(typed)) return { model: typed, known: true };
|
|
273
|
+
const ci =
|
|
274
|
+
ids.find((i) => i.toLowerCase() === typed.toLowerCase()) ||
|
|
275
|
+
ids.find((i) => i === `${typed}:latest`);
|
|
276
|
+
if (ci) return { model: ci, known: true };
|
|
277
|
+
return {
|
|
278
|
+
model: typed,
|
|
279
|
+
known: false,
|
|
280
|
+
note:
|
|
281
|
+
`${typed} is not on ${providerName || 'the provider'}'s live list right now; ` +
|
|
282
|
+
'kept as typed in case it is new.',
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/* ---- the /model chooser ---------------------------------------------------------- */
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Selectable items, in display order: favourites first (numbered by their place in the full
|
|
290
|
+
* list; ones on a provider that is not connected are left out), then each connected provider's
|
|
291
|
+
* models.
|
|
292
|
+
* groups: [{ id, name, ids, note? }] for CONNECTED providers only.
|
|
293
|
+
*/
|
|
294
|
+
export function chooserItems(prefs, groups) {
|
|
295
|
+
const connected = new Set(groups.map((g) => g.id));
|
|
296
|
+
const items = [];
|
|
297
|
+
prefs.favourites.forEach((f, i) => {
|
|
298
|
+
if (connected.has(f.provider))
|
|
299
|
+
items.push({ kind: 'fav', n: i + 1, provider: f.provider, model: f.model });
|
|
300
|
+
});
|
|
301
|
+
for (const g of groups) {
|
|
302
|
+
if (!g.ids.length)
|
|
303
|
+
items.push({
|
|
304
|
+
kind: 'provider',
|
|
305
|
+
provider: g.id,
|
|
306
|
+
model: null,
|
|
307
|
+
name: g.name,
|
|
308
|
+
note: g.note || 'no model list; the server picks',
|
|
309
|
+
});
|
|
310
|
+
else
|
|
311
|
+
for (const id of g.ids)
|
|
312
|
+
items.push({ kind: 'model', provider: g.id, model: id, name: g.name });
|
|
313
|
+
}
|
|
314
|
+
return items;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* One picker row ({ key, text, note? }) for an item. `current` = { provider, model } of the
|
|
319
|
+
* stored choice, or null.
|
|
320
|
+
*/
|
|
321
|
+
export function chooserRow(item, prefs, current) {
|
|
322
|
+
if (item.kind === 'provider')
|
|
323
|
+
return { key: item.provider, text: item.name, note: item.note, dim: false };
|
|
324
|
+
const here = current && current.model ? sameModel(current, item) : false;
|
|
325
|
+
const marks = [
|
|
326
|
+
here ? 'current' : null,
|
|
327
|
+
prefs.default && sameModel(prefs.default, item) ? 'default' : null,
|
|
328
|
+
item.kind === 'model' && prefs.favourites.some((f) => sameModel(f, item)) ? '★' : null,
|
|
329
|
+
].filter(Boolean);
|
|
330
|
+
const body = item.kind === 'fav' ? label(item) : item.model;
|
|
331
|
+
return {
|
|
332
|
+
key: item.kind === 'fav' ? `★ ${item.n}` : item.provider,
|
|
333
|
+
text: body + (marks.length ? ` ${marks.join(' · ')}` : ''),
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* The stored choice as { provider: <CHOICES id>, model } for marking; null when it carries no
|
|
339
|
+
* model.
|
|
340
|
+
*/
|
|
341
|
+
export function currentKey(choice, idOf) {
|
|
342
|
+
if (!choice?.model) return null;
|
|
343
|
+
const provider = idOf(choice);
|
|
344
|
+
return provider ? { provider, model: choice.model } : null;
|
|
345
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The motion engine's one clock (CLI-QUOX motion). Every animation and every visible tick in the client
|
|
3
|
+
* (activity word decode, Q state colour, elapsed timer, roster clock, composer ring, drop-in reveals, intro,
|
|
4
|
+
* approval arming) declares a DEMAND each render; the clock keeps exactly one timer armed for the soonest
|
|
5
|
+
* demand and none at all when nothing asks. Idle is still: no timers, no renders.
|
|
6
|
+
*
|
|
7
|
+
* Demand shapes: { every: ms } (repeat while declared) or { at: epochMs } (one wake at that time).
|
|
8
|
+
*/
|
|
9
|
+
import { normaliseLevel } from './level.js';
|
|
10
|
+
export const FRAME_MS = 83; // ~12 fps for anything that moves
|
|
11
|
+
export const SECOND_MS = 1000;
|
|
12
|
+
|
|
13
|
+
/** Delay until the next wake for these demands, or null when nothing is waiting. Pure. */
|
|
14
|
+
export function nextDelay(demands, now) {
|
|
15
|
+
let best = null;
|
|
16
|
+
for (const d of demands || []) {
|
|
17
|
+
if (!d) continue;
|
|
18
|
+
let ms = null;
|
|
19
|
+
if (Number.isFinite(d.every) && d.every > 0) {
|
|
20
|
+
// second ticks align to the wall-clock second so "3s" changes when a second actually passes
|
|
21
|
+
ms = d.every >= SECOND_MS && Number.isFinite(d.since) ? d.every - ((now - d.since) % d.every) : d.every;
|
|
22
|
+
} else if (Number.isFinite(d.at)) ms = Math.max(0, d.at - now);
|
|
23
|
+
if (ms != null && (best == null || ms < best)) best = ms;
|
|
24
|
+
}
|
|
25
|
+
return best == null ? null : Math.max(1, Math.round(best));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export class Clock {
|
|
29
|
+
constructor(onTick, timers = { setTimeout, clearTimeout }) {
|
|
30
|
+
this.onTick = onTick; this.timers = timers; this.id = null; this.dueAt = null; this.stopped = false;
|
|
31
|
+
this.ticks = 0; this.lastTickAt = null;
|
|
32
|
+
}
|
|
33
|
+
/** Re-arm for the given demands (called after every render). Keeps the timer when it is already due sooner or equal. */
|
|
34
|
+
schedule(demands, now = Date.now()) {
|
|
35
|
+
if (this.stopped) return null;
|
|
36
|
+
const d = nextDelay(demands, now);
|
|
37
|
+
if (d == null) { this.cancel(); return null; }
|
|
38
|
+
const due = now + d;
|
|
39
|
+
if (this.id != null && this.dueAt != null && this.dueAt <= due + 2) return this.dueAt - now;
|
|
40
|
+
this.cancel();
|
|
41
|
+
this.dueAt = due;
|
|
42
|
+
this.id = this.timers.setTimeout(() => {
|
|
43
|
+
this.id = null; this.dueAt = null; this.ticks++; this.lastTickAt = Date.now();
|
|
44
|
+
if (!this.stopped) this.onTick();
|
|
45
|
+
}, d);
|
|
46
|
+
return d;
|
|
47
|
+
}
|
|
48
|
+
cancel() { if (this.id != null) this.timers.clearTimeout(this.id); this.id = null; this.dueAt = null; }
|
|
49
|
+
stop() { this.stopped = true; this.cancel(); }
|
|
50
|
+
get armed() { return this.id != null; }
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Motion is decoration: off at the quiet level (QUOX_MOTION=quiet, QUOX_REDUCED_MOTION=1, QUOX_TUI_LITE), or when not on a real terminal. Colour is separate (NO_COLOR). */
|
|
54
|
+
export const reducedMotion = (env = process.env) => env.QUOX_REDUCED_MOTION === '1' || Boolean(env.QUOX_TUI_LITE) || normaliseLevel(env.QUOX_MOTION) === 'quiet';
|
|
55
|
+
export const motionAllowed = (isTTY, env = process.env) => Boolean(isTTY) && !reducedMotion(env);
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Frame source: the clock's ticks fan out to the animated leaves only (Working, the composer ring, items that
|
|
59
|
+
* are revealing), so a frame re-renders those components instead of the whole app. get() is the frame time.
|
|
60
|
+
*/
|
|
61
|
+
export function createFrames(now = Date.now()) {
|
|
62
|
+
let t = now;
|
|
63
|
+
const subs = new Set();
|
|
64
|
+
return {
|
|
65
|
+
get: () => t,
|
|
66
|
+
subscribe: (fn) => { subs.add(fn); return () => subs.delete(fn); },
|
|
67
|
+
emit(at = Date.now()) { t = at; for (const fn of [...subs]) fn(); },
|
|
68
|
+
get size() { return subs.size; }
|
|
69
|
+
};
|
|
70
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/** Colour helpers for the motion engine: rgb triples, mixing, hex parsing. Pure. */
|
|
2
|
+
export const hexRgb = (hex) => {
|
|
3
|
+
const m = String(hex || '').replace('#', '');
|
|
4
|
+
const v = m.length === 3 ? m.split('').map((x) => x + x).join('') : m;
|
|
5
|
+
return [0, 2, 4].map((i) => parseInt(v.slice(i, i + 2), 16) || 0);
|
|
6
|
+
};
|
|
7
|
+
export const mix = (a, b, t) => a.map((v, i) => v + (b[i] - v) * t);
|
|
8
|
+
export const clamp01 = (x) => Math.max(0, Math.min(1, x));
|
|
9
|
+
export const rgbStr = (rgb) => `rgb(${rgb.map((v) => Math.max(0, Math.min(255, Math.round(v)))).join(',')})`;
|
|
10
|
+
export const smooth = (x) => { const t = clamp01(x); return t * t * (3 - 2 * t); };
|
|
11
|
+
/** Deterministic hash in [0,1) so a frame is reproducible from (cell, time bucket, salt). */
|
|
12
|
+
export function hash3(a, b, c) {
|
|
13
|
+
let h = (a * 374761393 + b * 668265263 + c * 2147483647) | 0;
|
|
14
|
+
h = Math.imul(h ^ (h >>> 13), 1274126177);
|
|
15
|
+
return ((h ^ (h >>> 16)) >>> 0) / 4294967296;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// ---- perceptual interpolation (OKLab) --------------------------------------------------------------
|
|
19
|
+
const toLin = (v) => { const c = v / 255; return c <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4; };
|
|
20
|
+
const toSrgb = (c) => { const v = c <= 0.0031308 ? 12.92 * c : 1.055 * c ** (1 / 2.4) - 0.055; return Math.max(0, Math.min(255, v * 255)); };
|
|
21
|
+
export function rgbToOklab([r, g, b]) {
|
|
22
|
+
const R = toLin(r), G = toLin(g), B = toLin(b);
|
|
23
|
+
const l = Math.cbrt(0.4122214708 * R + 0.5363325363 * G + 0.0514459929 * B);
|
|
24
|
+
const m = Math.cbrt(0.2119034982 * R + 0.6806995451 * G + 0.1073969566 * B);
|
|
25
|
+
const s = Math.cbrt(0.0883024619 * R + 0.2817188376 * G + 0.6299787005 * B);
|
|
26
|
+
return [0.2104542553 * l + 0.793617785 * m - 0.0040720468 * s, 1.9779984951 * l - 2.428592205 * m + 0.4505937099 * s, 0.0259040371 * l + 0.7827717662 * m - 0.808675766 * s];
|
|
27
|
+
}
|
|
28
|
+
export function oklabToRgb([L, a, b]) {
|
|
29
|
+
const l = (L + 0.3963377774 * a + 0.2158037573 * b) ** 3, m = (L - 0.1055613458 * a - 0.0638541728 * b) ** 3, s = (L - 0.0894841775 * a - 1.291485548 * b) ** 3;
|
|
30
|
+
return [toSrgb(4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s), toSrgb(-1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s), toSrgb(-0.0041960863 * l - 0.7034186147 * m + 1.707614701 * s)];
|
|
31
|
+
}
|
|
32
|
+
/** Mix two rgb colours in OKLab (perceptually even: no muddy midpoint). */
|
|
33
|
+
export const mixOk = (a, b, t) => { const A = rgbToOklab(a), B = rgbToOklab(b); return oklabToRgb(A.map((v, i) => v + (B[i] - v) * t)); };
|
|
34
|
+
/** A smooth 0..1 pulse with period `ms` (cosine ease, no hard edges). */
|
|
35
|
+
export const pulse01 = (now, ms = 1200) => 0.5 - 0.5 * Math.cos((2 * Math.PI * (now % ms)) / ms);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** React side of the frame source: a context plus a hook that re-renders the caller on every clock frame. */
|
|
2
|
+
import React from 'react';
|
|
3
|
+
|
|
4
|
+
export const FrameContext = React.createContext(null);
|
|
5
|
+
const noop = () => () => {};
|
|
6
|
+
/**
|
|
7
|
+
* Re-render the caller on every clock frame and return the current time (ms). The wall clock, not the frame
|
|
8
|
+
* stamp, so a render caused by anything else is never stale. Outside a provider it returns `fallback`.
|
|
9
|
+
*/
|
|
10
|
+
export function useFrame(fallback) {
|
|
11
|
+
const frames = React.useContext(FrameContext);
|
|
12
|
+
React.useSyncExternalStore(frames ? frames.subscribe : noop, frames ? frames.get : () => 0);
|
|
13
|
+
return frames ? Date.now() : (fallback ?? Date.now());
|
|
14
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Motion levels (CLI-QUOX F3). One setting, four steps, chosen in /config or with QUOX_MOTION:
|
|
3
|
+
*
|
|
4
|
+
* quiet static except the elapsed timer (the old "reduced motion"; QUOX_REDUCED_MOTION=1 still means it)
|
|
5
|
+
* calm the default: exactly what shipped before levels (Orbit & Lock, the status Q, reveals, the ring)
|
|
6
|
+
* expressive calm, plus a short ignition glow when a tool row starts and a handoff dot while delegating
|
|
7
|
+
* cinematic expressive, plus the dragon: it greets you after Orbit & Lock on every launch, and plays a mood
|
|
8
|
+
* for approvals (wide eyes, amber glint), success (a wink) and failure (a puff of smoke)
|
|
9
|
+
*
|
|
10
|
+
* Pure: no process access except through the env argument, no timers. The frame helpers return cells
|
|
11
|
+
* { ch, rgb } like every other motion module, so the terminal and the web draw them the same way.
|
|
12
|
+
*/
|
|
13
|
+
export const LEVELS = ['quiet', 'calm', 'expressive', 'cinematic'];
|
|
14
|
+
export const DEFAULT_LEVEL = 'calm';
|
|
15
|
+
|
|
16
|
+
/** A saved or typed value -> a level, or null. The pre-levels values map across: full = calm, reduced = quiet. */
|
|
17
|
+
export function normaliseLevel(v) {
|
|
18
|
+
const s = String(v ?? '').trim().toLowerCase();
|
|
19
|
+
if (s === 'full') return 'calm';
|
|
20
|
+
if (s === 'reduced') return 'quiet';
|
|
21
|
+
return LEVELS.includes(s) ? s : null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** The level in force. QUOX_REDUCED_MOTION=1 and QUOX_TUI_LITE always mean quiet (accessibility wins). */
|
|
25
|
+
export function motionLevel(env = process.env) {
|
|
26
|
+
if (env.QUOX_REDUCED_MOTION === '1' || env.QUOX_TUI_LITE) return 'quiet';
|
|
27
|
+
return normaliseLevel(env.QUOX_MOTION) || DEFAULT_LEVEL;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const atLeast = (level, min) => LEVELS.indexOf(normaliseLevel(level) || DEFAULT_LEVEL) >= LEVELS.indexOf(min);
|
|
31
|
+
|
|
32
|
+
/** What each level turns on. launchEgg: the rare dragon greeting on launch (cinematic greets every time instead). */
|
|
33
|
+
export function levelFeatures(level) {
|
|
34
|
+
const l = normaliseLevel(level) || DEFAULT_LEVEL;
|
|
35
|
+
return {
|
|
36
|
+
animate: l !== 'quiet',
|
|
37
|
+
ignition: atLeast(l, 'expressive'),
|
|
38
|
+
handoff: atLeast(l, 'expressive'),
|
|
39
|
+
introDragon: l === 'cinematic',
|
|
40
|
+
moods: l === 'cinematic',
|
|
41
|
+
launchEgg: l === 'calm' || l === 'expressive',
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const clamp = (x, a = 0, b = 1) => Math.max(a, Math.min(b, x));
|
|
46
|
+
|
|
47
|
+
// ---- tool-row ignition glow --------------------------------------------------------------------------------
|
|
48
|
+
export const IGNITION_MS = 700;
|
|
49
|
+
/** Glow 0..1 at ms since a tool row started running: up in 80 ms, then an eased fall to 0 at IGNITION_MS. */
|
|
50
|
+
export function ignitionGlow(age) {
|
|
51
|
+
if (!(age > 0) || age >= IGNITION_MS) return 0;
|
|
52
|
+
if (age <= 80) return age / 80;
|
|
53
|
+
const u = (age - 80) / (IGNITION_MS - 80);
|
|
54
|
+
return (1 - u) ** 2;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// ---- handoff dot -------------------------------------------------------------------------------------------
|
|
58
|
+
export const HANDOFF_MS = 900;
|
|
59
|
+
const SKY = [56, 189, 248], VIOLET = [168, 85, 247], DIM = [58, 66, 82];
|
|
60
|
+
const mix = (a, b, t) => a.map((v, i) => Math.round(v + (b[i] - v) * t));
|
|
61
|
+
/**
|
|
62
|
+
* The handoff track at t ms: n cells, a bright dot travelling from Quox (left) to the agent it is handing to
|
|
63
|
+
* (right) once every HANDOFF_MS, with one fading cell behind it. The dot shifts sky to violet as it goes.
|
|
64
|
+
* still: the dot rests at the target (reduced motion never calls this, but a frozen frame must read right).
|
|
65
|
+
*/
|
|
66
|
+
export function handoffFrame(t, n = 6, { mono = false, still = false } = {}) {
|
|
67
|
+
const u = still ? 0.999 : (((t % HANDOFF_MS) + HANDOFF_MS) % HANDOFF_MS) / HANDOFF_MS;
|
|
68
|
+
const at = Math.min(n - 1, Math.floor(u * n));
|
|
69
|
+
return Array.from({ length: n }, (_, i) => {
|
|
70
|
+
const pos = n > 1 ? i / (n - 1) : 1;
|
|
71
|
+
if (i === at) return { ch: '●', rgb: mono ? null : mix(SKY, VIOLET, pos) };
|
|
72
|
+
if (i === at - 1 && !still) return { ch: '•', rgb: mono ? null : mix(DIM, mix(SKY, VIOLET, pos), 0.55) };
|
|
73
|
+
return { ch: '·', rgb: mono ? null : mix(DIM, SKY, 0.25 * clamp(pos)) };
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// ---- dragon moods (cinematic) ------------------------------------------------------------------------------
|
|
78
|
+
const FAILED_TURN = new Set(['failed', 'unconfirmed']); // the same reading as the sound earcons (sound/mapping.js)
|
|
79
|
+
/**
|
|
80
|
+
* prevState + action + nextState -> the mood the dragon should play, or null. Only accepted state CHANGES
|
|
81
|
+
* count: an approval card appearing (waiting), a running turn finishing (success / failure). An interrupted
|
|
82
|
+
* turn is the human pressing Esc: no reaction.
|
|
83
|
+
*/
|
|
84
|
+
export function moodForEvent(prev, action, next) {
|
|
85
|
+
if (!prev || !action || !next) return null;
|
|
86
|
+
if ((next.approvals?.length || 0) > (prev.approvals?.length || 0)) return 'waiting';
|
|
87
|
+
if (action.type !== 'event' || action.ev?.type !== 'turn.finished' || !prev.turn) return null;
|
|
88
|
+
const status = action.ev.status || 'completed';
|
|
89
|
+
if (FAILED_TURN.has(status)) return 'failure';
|
|
90
|
+
return status === 'completed' ? 'success' : null;
|
|
91
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Drop-in reveals (lab prototype `quox-intro-lab fx`): when an item lands in the live region its text decodes
|
|
3
|
+
* in left to right. Just ahead of the resolving edge (LEAD) cells flicker as curated half-width katakana;
|
|
4
|
+
* further ahead they are blank; just behind it a phosphor afterglow tints the real character and fades.
|
|
5
|
+
* Decorative only: every cell is the real character once the reveal ends, widths are identical every frame
|
|
6
|
+
* (a katakana or blank stands only in a one-column cell; wide graphemes are never replaced by a glyph).
|
|
7
|
+
* Pure: (text, elapsed seconds, timing) -> cells.
|
|
8
|
+
*/
|
|
9
|
+
import stringWidth from 'string-width';
|
|
10
|
+
import { SCRAMBLE_GLYPHS } from '../indicator.js';
|
|
11
|
+
import { hash3 } from './color.js';
|
|
12
|
+
import { graphemes } from '../textBuffer.js';
|
|
13
|
+
|
|
14
|
+
export const LEAD = 0.14;
|
|
15
|
+
/** Timings (seconds). row: tool rows, strongest. dim: grey lines (closing line, notes); short glow, never bold. */
|
|
16
|
+
export const TIMING = {
|
|
17
|
+
row: { dur: 0.28, glow: 0.7, bold: true },
|
|
18
|
+
dim: { dur: 0.4, glow: 0.15, bold: false },
|
|
19
|
+
title: { dur: 0.3, glow: 0.35, bold: true },
|
|
20
|
+
roster: { dur: 0.3, glow: 0.3, bold: false },
|
|
21
|
+
settle: { dur: 0.2, glow: 0.5, bold: true },
|
|
22
|
+
};
|
|
23
|
+
export const revealSpan = (t) => t.dur + t.glow; // seconds from start until every cell is plain
|
|
24
|
+
const KEEP = new Set([' ', '│', '─']); // structure only: a '·' separator arrives with its text, never floats ahead of it
|
|
25
|
+
const TEXTISH = /^[\p{L}\p{N}\p{P}]+$/u;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Cells for `text` at `elapsed` seconds since the reveal started.
|
|
29
|
+
* Each cell: { ch, kind: 'plain'|'kana'|'blank'|'glow', g (glow 0..1), bold }.
|
|
30
|
+
* offset/total let several segments of one row share one sweeping edge.
|
|
31
|
+
*/
|
|
32
|
+
export function revealCells(text, elapsed, timing = TIMING.row, { offset = 0, total = null, salt = 1 } = {}) {
|
|
33
|
+
const gs = graphemes(String(text ?? '')).map((g) => g.text);
|
|
34
|
+
const n = total ?? gs.length;
|
|
35
|
+
const bucket = Math.floor(elapsed * 30);
|
|
36
|
+
return gs.map((ch, j) => {
|
|
37
|
+
const i = offset + j;
|
|
38
|
+
// Only text decodes: letters, digits, punctuation. Icons and symbols (◐ ✔ ⚠ ⛓ ◉ …) never swap glyphs (owner 2026-10-12).
|
|
39
|
+
if (KEEP.has(ch) || !TEXTISH.test(ch)) return { ch, kind: 'plain', g: 0, bold: false };
|
|
40
|
+
const r = n > 1 ? (i / (n - 1)) * timing.dur : 0;
|
|
41
|
+
const dt = elapsed - r;
|
|
42
|
+
const w = stringWidth(ch);
|
|
43
|
+
if (dt < 0) {
|
|
44
|
+
if (w !== 1) return { ch: ' '.repeat(Math.max(1, w)), kind: 'blank', g: 0, bold: false };
|
|
45
|
+
if (dt < -LEAD) return { ch: ' ', kind: 'blank', g: 0, bold: false };
|
|
46
|
+
return { ch: SCRAMBLE_GLYPHS[Math.floor(hash3(i, bucket, salt) * SCRAMBLE_GLYPHS.length)], kind: 'kana', g: hash3(i, bucket + 7, salt), bold: false };
|
|
47
|
+
}
|
|
48
|
+
const g = timing.glow > 0 && dt < timing.glow ? (1 - dt / timing.glow) ** 1.6 : 0;
|
|
49
|
+
return g > 0 ? { ch, kind: 'glow', g, bold: Boolean(timing.bold) && (dt < 0.05 || g > 0.55) } : { ch, kind: 'plain', g: 0, bold: false };
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** True when the reveal has fully ended (all cells plain). */
|
|
54
|
+
export const revealDone = (elapsed, timing) => elapsed >= revealSpan(timing);
|