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,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where a terminal earcon goes: a local OS player, the terminal bell, or
|
|
3
|
+
* nowhere. Pure decisions here; the controller (index.js) does the I/O.
|
|
4
|
+
*/
|
|
5
|
+
import { spawnSync } from 'node:child_process';
|
|
6
|
+
|
|
7
|
+
const OFF_VALUES = new Set(['off', '0', 'false', 'no', 'silent']);
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* 'off' : --print, non-TTY, CI, or QUOX_SOUND=off. Never a sound, never an escape write.
|
|
11
|
+
* 'bel' : over SSH the speaker is on the other end, so only the terminal bell can reach it.
|
|
12
|
+
* 'audio' : a local desktop; play the WAV (falls back to the bell when no player exists).
|
|
13
|
+
*/
|
|
14
|
+
export function soundMode({ env = {}, stdout, print = false } = {}) {
|
|
15
|
+
if (print || !stdout?.isTTY) return 'off';
|
|
16
|
+
if (env.CI && !OFF_VALUES.has(String(env.CI).toLowerCase())) return 'off';
|
|
17
|
+
if (OFF_VALUES.has(String(env.QUOX_SOUND ?? '').toLowerCase())) return 'off';
|
|
18
|
+
if (env.SSH_CONNECTION || env.SSH_TTY) return 'bel';
|
|
19
|
+
return 'audio';
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const psQuote = (s) => `'${String(s).replace(/'/g, "''")}'`;
|
|
23
|
+
|
|
24
|
+
const PLAYERS = {
|
|
25
|
+
linux: [
|
|
26
|
+
{ cmd: 'pw-play', args: (f) => [f] },
|
|
27
|
+
{ cmd: 'paplay', args: (f) => [f] },
|
|
28
|
+
{ cmd: 'aplay', args: (f) => ['-q', f] }
|
|
29
|
+
],
|
|
30
|
+
darwin: [{ cmd: 'afplay', args: (f) => [f] }],
|
|
31
|
+
win32: [{ cmd: 'powershell', args: (f) => ['-NoProfile', '-NonInteractive', '-Command', `(New-Object Media.SoundPlayer(${psQuote(f)})).PlaySync()`] }]
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/** Is `cmd` on PATH? (one lookup per player, cached by the controller) */
|
|
35
|
+
export function onPath(cmd, platform = process.platform) {
|
|
36
|
+
try {
|
|
37
|
+
const r = platform === 'win32'
|
|
38
|
+
? spawnSync('where', [cmd], { stdio: 'ignore', timeout: 2000 })
|
|
39
|
+
: spawnSync('sh', ['-c', `command -v ${cmd}`], { stdio: 'ignore', timeout: 2000 });
|
|
40
|
+
return r.status === 0;
|
|
41
|
+
} catch { return false; }
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** First available player for this platform, or null. */
|
|
45
|
+
export function findPlayer({ platform = process.platform, has = (c) => onPath(c, platform) } = {}) {
|
|
46
|
+
const list = PLAYERS[platform] || PLAYERS.linux;
|
|
47
|
+
return list.find(p => has(p.cmd)) || null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Only these may ring the bell: the bell is for a human, not for feedback. */
|
|
51
|
+
export const BEL_IDS = new Set(['hitl', 'critical']);
|
|
52
|
+
export const BEL = '\x07';
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Offline renderer for the QuoxSound earcons: the dashboard's Web Audio
|
|
3
|
+
* recipes (src/services/quoxSound.js RECIPES) rendered sample by sample into
|
|
4
|
+
* 16-bit mono PCM WAV. No assets, no dependencies.
|
|
5
|
+
*
|
|
6
|
+
* Timbre: fundamental + octave (0.18) + twelfth (0.07), linear attack then an
|
|
7
|
+
* exponential decay to near-silence, same as the phone earcons. `success`
|
|
8
|
+
* reuses the granted interval (587 -> 880 Hz), `denied` the denied floor
|
|
9
|
+
* (233 Hz gliding down through a 600 Hz lowpass).
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export const SAMPLE_RATE = 44100;
|
|
13
|
+
const HARMONICS = [[1, 1], [2, 0.18], [3, 0.07]];
|
|
14
|
+
const FLOOR = 0.0001;
|
|
15
|
+
|
|
16
|
+
/** The recipes as data: the same notes, times and envelopes as the dashboard. */
|
|
17
|
+
export const RECIPES = {
|
|
18
|
+
hitl: { notes: [
|
|
19
|
+
{ freq: 523.25, at: 0, decay: 0.30, peak: 0.20 },
|
|
20
|
+
{ freq: 659.25, at: 0.15, decay: 0.30, peak: 0.19 },
|
|
21
|
+
{ freq: 880.0, at: 0.30, decay: 0.32, peak: 0.18 }
|
|
22
|
+
] },
|
|
23
|
+
notify: { notes: [
|
|
24
|
+
{ freq: 698.46, at: 0, attack: 0.01, decay: 0.10, peak: 0.11 },
|
|
25
|
+
{ freq: 932.33, at: 0.10, attack: 0.01, decay: 0.14, peak: 0.10 }
|
|
26
|
+
] },
|
|
27
|
+
success: { notes: [
|
|
28
|
+
{ freq: 587.33, at: 0, attack: 0.003, decay: 0.04, peak: 0.09 },
|
|
29
|
+
{ freq: 880.0, at: 0.045, attack: 0.003, decay: 0.07, peak: 0.09 }
|
|
30
|
+
] },
|
|
31
|
+
denied: { lowpass: 600, notes: [
|
|
32
|
+
{ freq: 233.08, glideTo: 150, at: 0, attack: 0.004, decay: 0.16, peak: 0.28, harmonics: [[1, 1]] }
|
|
33
|
+
] },
|
|
34
|
+
critical: { lowpass: 2400, notes: [0, 1, 2].map(i => (
|
|
35
|
+
{ freq: 622.25, at: i * 0.12, type: 'square', attack: 0.003, decay: 0.07, peak: 0.12, harmonics: [[1, 1]] }
|
|
36
|
+
)) }
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
function envelope(t, attack, decay, peak) {
|
|
40
|
+
if (t < 0) return 0;
|
|
41
|
+
if (t < attack) return FLOOR + (peak - FLOOR) * (t / attack);
|
|
42
|
+
const d = t - attack;
|
|
43
|
+
if (d < decay) return peak * Math.pow(FLOOR / peak, d / decay);
|
|
44
|
+
return 0;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function renderNote(out, sr, n) {
|
|
48
|
+
const attack = n.attack ?? 0.008;
|
|
49
|
+
const decay = n.decay ?? 0.2;
|
|
50
|
+
const peak = n.peak ?? 0.2;
|
|
51
|
+
const harmonics = n.harmonics || HARMONICS;
|
|
52
|
+
const len = attack + decay + 0.02;
|
|
53
|
+
const start = Math.round(n.at * sr);
|
|
54
|
+
const count = Math.min(out.length - start, Math.round(len * sr));
|
|
55
|
+
for (const [mult, level] of harmonics) {
|
|
56
|
+
let phase = 0;
|
|
57
|
+
for (let i = 0; i < count; i++) {
|
|
58
|
+
const t = i / sr;
|
|
59
|
+
// exponential glide over attack + decay, then held (as exponentialRampToValueAtTime)
|
|
60
|
+
const f = n.glideTo ? n.freq * Math.pow(n.glideTo / n.freq, Math.min(1, t / (attack + decay))) : n.freq;
|
|
61
|
+
phase += (2 * Math.PI * f * mult) / sr;
|
|
62
|
+
const osc = n.type === 'square' ? (Math.sin(phase) >= 0 ? 1 : -1) : Math.sin(phase);
|
|
63
|
+
out[start + i] += osc * level * envelope(t, attack, decay, peak);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** RBJ biquad lowpass, in place. */
|
|
69
|
+
function lowpass(buf, sr, cutoff, q = Math.SQRT1_2) {
|
|
70
|
+
const w0 = (2 * Math.PI * cutoff) / sr;
|
|
71
|
+
const alpha = Math.sin(w0) / (2 * q);
|
|
72
|
+
const cos = Math.cos(w0);
|
|
73
|
+
const a0 = 1 + alpha;
|
|
74
|
+
const b0 = (1 - cos) / 2 / a0, b1 = (1 - cos) / a0, b2 = (1 - cos) / 2 / a0;
|
|
75
|
+
const a1 = (-2 * cos) / a0, a2 = (1 - alpha) / a0;
|
|
76
|
+
let x1 = 0, x2 = 0, y1 = 0, y2 = 0;
|
|
77
|
+
for (let i = 0; i < buf.length; i++) {
|
|
78
|
+
const x = buf[i];
|
|
79
|
+
const y = b0 * x + b1 * x1 + b2 * x2 - a1 * y1 - a2 * y2;
|
|
80
|
+
x2 = x1; x1 = x; y2 = y1; y1 = y;
|
|
81
|
+
buf[i] = y;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Render one earcon to float samples in [-1, 1]. */
|
|
86
|
+
export function renderEarcon(id, { volume = 0.6, sampleRate = SAMPLE_RATE } = {}) {
|
|
87
|
+
const r = RECIPES[id];
|
|
88
|
+
if (!r) throw new Error(`unknown earcon ${id}`);
|
|
89
|
+
const secs = Math.max(...r.notes.map(n => n.at + (n.attack ?? 0.008) + (n.decay ?? 0.2) + 0.02));
|
|
90
|
+
const out = new Float32Array(Math.ceil(secs * sampleRate));
|
|
91
|
+
for (const n of r.notes) renderNote(out, sampleRate, n);
|
|
92
|
+
if (r.lowpass) lowpass(out, sampleRate, r.lowpass);
|
|
93
|
+
const g = Math.min(1, Math.max(0, volume));
|
|
94
|
+
for (let i = 0; i < out.length; i++) out[i] = Math.max(-1, Math.min(1, out[i] * g));
|
|
95
|
+
return out;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Float samples -> 16-bit mono PCM WAV (44-byte RIFF header). */
|
|
99
|
+
export function toWav(samples, sampleRate = SAMPLE_RATE) {
|
|
100
|
+
const data = samples.length * 2;
|
|
101
|
+
const buf = Buffer.alloc(44 + data);
|
|
102
|
+
buf.write('RIFF', 0, 'ascii');
|
|
103
|
+
buf.writeUInt32LE(36 + data, 4);
|
|
104
|
+
buf.write('WAVE', 8, 'ascii');
|
|
105
|
+
buf.write('fmt ', 12, 'ascii');
|
|
106
|
+
buf.writeUInt32LE(16, 16); // fmt chunk size
|
|
107
|
+
buf.writeUInt16LE(1, 20); // PCM
|
|
108
|
+
buf.writeUInt16LE(1, 22); // mono
|
|
109
|
+
buf.writeUInt32LE(sampleRate, 24);
|
|
110
|
+
buf.writeUInt32LE(sampleRate * 2, 28); // byte rate
|
|
111
|
+
buf.writeUInt16LE(2, 32); // block align
|
|
112
|
+
buf.writeUInt16LE(16, 34); // bits per sample
|
|
113
|
+
buf.write('data', 36, 'ascii');
|
|
114
|
+
buf.writeUInt32LE(data, 40);
|
|
115
|
+
for (let i = 0; i < samples.length; i++) {
|
|
116
|
+
const v = Math.max(-1, Math.min(1, samples[i]));
|
|
117
|
+
buf.writeInt16LE(Math.round(v < 0 ? v * 0x8000 : v * 0x7fff), 44 + i * 2);
|
|
118
|
+
}
|
|
119
|
+
return buf;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export const earconWav = (id, opts = {}) => toWav(renderEarcon(id, opts), opts.sampleRate ?? SAMPLE_RATE);
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TUI-INPUT: a letter then "/" ("n/help") is a card answer that leaked into the composer ahead of a command.
|
|
3
|
+
* Pure: -> { letter, command } when the text looks like that and the rest names a known slash command, else null.
|
|
4
|
+
* (Ordinary "w/o", "b/c" or "and/or" never match: the word after the slash must be a command.)
|
|
5
|
+
*/
|
|
6
|
+
export function strayCardAnswer(text, names = []) {
|
|
7
|
+
const t = String(text ?? '').trim();
|
|
8
|
+
const m = /^([a-z])\/([a-z][\w-]*)(?=\s|$)/i.exec(t);
|
|
9
|
+
if (!m) return null;
|
|
10
|
+
if (!names.includes(`/${m[2].toLowerCase()}`)) return null;
|
|
11
|
+
return { letter: m[1].toLowerCase(), command: t.slice(1) };
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** The one-line question (askText adds the y/N/c keys). */
|
|
15
|
+
export const strayQuestion = (s) => `Did you mean ${s.command.split(/\s+/)[0]}? (the '${s.letter}' looks like a stray card answer)`;
|
|
16
|
+
|
|
17
|
+
/** True when the same message is already queued, steered or waiting for the server. */
|
|
18
|
+
export function alreadyQueued(text, { pending = [], steering = [], waiting = null } = {}) {
|
|
19
|
+
const t = String(text ?? '').trim();
|
|
20
|
+
if (!t) return false;
|
|
21
|
+
return [...pending, ...steering].some(p => String(p.text ?? '').trim() === t) || String(waiting?.text ?? '').trim() === t;
|
|
22
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The assistant can hand the TUI a slash command for things only the terminal can do (attach a slot, join a room,
|
|
3
|
+
* open a view). The TUI is the authority: only an active registry command, never a `!` shell, never the commands the
|
|
4
|
+
* human must type (approvals, grants, session end, sign-in). The offer is "Run /slot hitl? y/N"; y runs it through the
|
|
5
|
+
* normal command path so every existing guard applies. A read-only view may run without asking when the user's
|
|
6
|
+
* ask was explicit.
|
|
7
|
+
*/
|
|
8
|
+
import { findCommandEntry } from './commands.js';
|
|
9
|
+
|
|
10
|
+
// Commands that need the human's own typing: never offered, never run from a model suggestion.
|
|
11
|
+
export const HUMAN_TYPED = new Set(['approve', 'yestoall', 'deny', 'authorise', 'authorize', 'authenticate', 'override', 'restart', 'quit', 'exit', 'login']);
|
|
12
|
+
// Pure views, no effect: may run without asking when explicit, and only with no arguments.
|
|
13
|
+
export const READ_ONLY_VIEWS = new Set(['approvals', 'mind', 'which', 'context', 'rooms', 'active', 'slots']);
|
|
14
|
+
|
|
15
|
+
/** -> { ok: true, line, id, readOnly } | { ok: false, reason } */
|
|
16
|
+
export function vetSuggestion(raw) {
|
|
17
|
+
const line = typeof raw === 'string' ? raw.trim() : '';
|
|
18
|
+
if (!line) return { ok: false, reason: 'empty' };
|
|
19
|
+
if (/[\r\n]/.test(line) || line.length > 120) return { ok: false, reason: 'not a single short line' };
|
|
20
|
+
if (!line.startsWith('/') || line.startsWith('//')) return { ok: false, reason: 'only slash commands can be suggested' };
|
|
21
|
+
const word = line.slice(1).split(/\s+/)[0];
|
|
22
|
+
const entry = findCommandEntry(word);
|
|
23
|
+
if (!entry || entry.kind !== 'command' || entry.status !== 'active') return { ok: false, reason: `/${word} is not a command here` };
|
|
24
|
+
if (HUMAN_TYPED.has(entry.id) || HUMAN_TYPED.has(word.toLowerCase())) return { ok: false, reason: `/${word} must be typed by you` };
|
|
25
|
+
const bare = line.slice(1 + word.length).trim() === '';
|
|
26
|
+
return { ok: true, line, id: entry.id, readOnly: READ_ONLY_VIEWS.has(entry.id) && bare };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Safety net (owner 2026-10-15 "STOP TELLING ME TO RUN COMMANDS"): the answer text named a command instead of the
|
|
31
|
+
* model calling terminal_suggest_command. Exactly ONE distinct slash command in backticks that passes vetSuggestion
|
|
32
|
+
* -> that vetted suggestion; none, two or more distinct, or a human-typed one (/approve, /restart...) -> null.
|
|
33
|
+
*/
|
|
34
|
+
export function namedCommand(text) {
|
|
35
|
+
const src = String(text || '');
|
|
36
|
+
const seen = new Set();
|
|
37
|
+
for (const m of src.matchAll(/`(\/[a-z][^`\n]{0,118})`/gi)) {
|
|
38
|
+
if (mentionsAPath(src, m.index, m[0].length, m[1])) continue; // `/health` the endpoint is not /health the command
|
|
39
|
+
seen.add(m[1].trim().replace(/\s+/g, ' '));
|
|
40
|
+
}
|
|
41
|
+
if (seen.size !== 1) return null;
|
|
42
|
+
const v = vetSuggestion([...seen][0]);
|
|
43
|
+
return v.ok ? v : null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// A URL path, not a command (owner live 05:26: "the `/health` endpoint is erroring" offered "Run /health?").
|
|
47
|
+
const PATHY = /\b(endpoints?|paths?|routes?|urls?|uris?|ports?|curl|wget|https?|localhost)\b|:\d{2,5}\b/i;
|
|
48
|
+
const VERBS = /\b(GET|POST|PUT|PATCH|DELETE|HEAD)\b/; // upper-case only: "to get a view" is not an HTTP verb
|
|
49
|
+
function mentionsAPath(src, at, len, inner) {
|
|
50
|
+
const first = String(inner).trim().split(/\s+/)[0];
|
|
51
|
+
if (/^\/[^/]*\//.test(first)) return true; // /api/x or /health/ready: a longer path
|
|
52
|
+
const sentence = (from, dir) => { // the same sentence, up to 40 characters away
|
|
53
|
+
const t = dir < 0 ? src.slice(Math.max(0, from - 40), from) : src.slice(from, from + 40);
|
|
54
|
+
const parts = t.split(/[.!?\n]/);
|
|
55
|
+
return dir < 0 ? parts[parts.length - 1] : parts[0];
|
|
56
|
+
};
|
|
57
|
+
const near = sentence(at, -1) + ' ' + sentence(at + len, 1);
|
|
58
|
+
return PATHY.test(near) || VERBS.test(near);
|
|
59
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { graphemes } from './textBuffer.js';
|
|
2
|
+
import { createScreenMirror, MOUSE_ON, MOUSE_OFF, DSR } from './mouse.js';
|
|
3
|
+
/**
|
|
4
|
+
* The terminal as Ink sees it.
|
|
5
|
+
*
|
|
6
|
+
* Ink 6.8 has a bookkeeping defect on its "frame was as tall as the screen" path (ink.js onRender): it
|
|
7
|
+
* repaints with clearTerminal + all static output + the frame WITHOUT a trailing newline, then records the
|
|
8
|
+
* frame WITH one (log.sync(outputToRender)). log-update then believes the cursor is one row lower than it
|
|
9
|
+
* is, so its next erase removes one extra row: the last line of whatever <Static> printed last. On
|
|
10
|
+
* 2026-10-12 that silently deleted "status writeup that's supposed to accompany completion." from a LUCID
|
|
11
|
+
* answer at 64x34 (test/quoxon/dropped.test.jsx replays it).
|
|
12
|
+
*
|
|
13
|
+
* Fix: Ink is told the screen has unlimited rows, so it never takes that path (only the fullscreen and
|
|
14
|
+
* repaint checks read stdout.rows). The app reads the real height from termRows() and keeps its live
|
|
15
|
+
* region shorter than the screen itself (app.js), so log-update can always erase it.
|
|
16
|
+
*/
|
|
17
|
+
export function inkStdout(out, { tap = null } = {}) {
|
|
18
|
+
if (!out || out.__quoxInk) return out;
|
|
19
|
+
// QUOXON-MOUSE: what Ink has drawn, as plain lines, for click hit-testing (mouse.js). Fed the same writes.
|
|
20
|
+
const mirror = out.isTTY ? createScreenMirror({ columns: () => out.columns }) : null;
|
|
21
|
+
// SCREEN-FRAMES: the same feed goes to the screen recorder's tap (screenFrames.js), which only queues it.
|
|
22
|
+
const feed = mirror && tap ? {
|
|
23
|
+
write: (c) => { mirror.write(c); try { tap.write(c); } catch { /* never break a frame */ } },
|
|
24
|
+
reset: () => { mirror.reset(); try { tap.reset(); } catch { /* ignore */ } }
|
|
25
|
+
} : mirror;
|
|
26
|
+
const diff = out.isTTY ? createLineDiff({ columns: () => out.columns, mirror: feed }) : null;
|
|
27
|
+
// One frame = one write: Ink brackets each frame in separate synchronized-update writes (?2026h ... ?2026l);
|
|
28
|
+
// they are held and sent together with the (diffed) frame, and a frame that changed nothing sends nothing.
|
|
29
|
+
// A write that sends nothing (or is held for the frame) must still call its callback: Ink's unmount waits
|
|
30
|
+
// on write('', cb) before waitUntilExit() resolves, and a dropped callback let the event loop drain, so
|
|
31
|
+
// the process exited without running anything after the app (/restart never started the new client).
|
|
32
|
+
let held = null;
|
|
33
|
+
let waiting = [];
|
|
34
|
+
const settle = () => { const cbs = waiting; waiting = []; for (const cb of cbs) cb(); };
|
|
35
|
+
const flush = () => {
|
|
36
|
+
if (held == null) return;
|
|
37
|
+
const body = held; held = null;
|
|
38
|
+
if (body) out.write(SYNC_ON + body + SYNC_OFF, settle); else if (waiting.length) out.write('', settle);
|
|
39
|
+
};
|
|
40
|
+
const write = diff ? (chunk, ...rest) => {
|
|
41
|
+
if (typeof chunk !== 'string') { flush(); diff.reset(); return out.write(chunk, ...rest); }
|
|
42
|
+
const cb = rest.find(a => typeof a === 'function');
|
|
43
|
+
if (chunk === SYNC_ON) { flush(); held = ''; setImmediate(flush); if (cb) waiting.push(cb); return true; }
|
|
44
|
+
if (chunk === SYNC_OFF && held != null) { if (cb) waiting.push(cb); flush(); return true; }
|
|
45
|
+
const d = diff(chunk);
|
|
46
|
+
if (held != null) { held += d; if (cb) waiting.push(cb); return true; }
|
|
47
|
+
if (d) return out.write(d, ...rest);
|
|
48
|
+
if (cb) out.write('', cb); // nothing to send: still a barrier behind the earlier writes
|
|
49
|
+
return true;
|
|
50
|
+
} : null;
|
|
51
|
+
return new Proxy(out, {
|
|
52
|
+
get(target, prop) {
|
|
53
|
+
if (prop === 'write' && write) return write;
|
|
54
|
+
if (prop === 'rows') return Infinity;
|
|
55
|
+
if (prop === 'termRows') return target.rows;
|
|
56
|
+
if (prop === 'frameRows') { const n = diff?.rows(); return n == null ? null : Math.min(n, Math.max(0, (target.rows || n + 1) - 1)); }
|
|
57
|
+
if (prop === '__quoxInk') return true;
|
|
58
|
+
if (prop === 'screen') return mirror;
|
|
59
|
+
const v = Reflect.get(target, prop, target);
|
|
60
|
+
return typeof v === 'function' ? v.bind(target) : v;
|
|
61
|
+
},
|
|
62
|
+
set(target, prop, value) { return Reflect.set(target, prop, value, target); }
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Real terminal height, or null when unknown (not a TTY, tests). */
|
|
67
|
+
export function termRows(stdout) {
|
|
68
|
+
const r = stdout?.__quoxInk ? stdout.termRows : stdout?.rows;
|
|
69
|
+
return Number.isFinite(r) && r > 0 ? r : null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Line-diffed live frames (so native text selection survives animation).
|
|
74
|
+
*
|
|
75
|
+
* Ink repaints its live region as ONE write: eraseLines(previous line count) followed by the whole frame. A 12 fps
|
|
76
|
+
* ring or activity word would then rewrite the input line, and the user's selection on it, every frame. This
|
|
77
|
+
* transform recognises exactly that shape; when the new frame has the same height as the last one it moves up
|
|
78
|
+
* and rewrites only the lines that changed, leaving the cursor where Ink expects it (start of the line after the
|
|
79
|
+
* frame). Anything else (static history, clears, a height change, a resize, a write it did not see) passes
|
|
80
|
+
* through untouched and resets the diff, so the next frame is drawn in full. Cursor moves never scroll.
|
|
81
|
+
* (Ink's own incrementalRendering was tried first and duplicates lines on the dropped-text replay.)
|
|
82
|
+
*/
|
|
83
|
+
const ANSI_RE = /\x1b\[[0-9;?]*[ -\/]*[@-~]|\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)/g;
|
|
84
|
+
const visibleWidth = (line) => graphemes(String(line).replace(ANSI_RE, '')).length;
|
|
85
|
+
const FRAME_RE = /^((?:\x1b\[2K\x1b\[1A)*\x1b\[2K\x1b\[G)([\s\S]*)$/;
|
|
86
|
+
const SYNC_ON = '\x1b[?2026h', SYNC_OFF = '\x1b[?2026l';
|
|
87
|
+
const TRANSPARENT = new Set(['\x1b[?2026h', '\x1b[?2026l', '\x1b[?25l', '\x1b[?25h', '', MOUSE_ON, MOUSE_OFF, DSR]); // + QUOXON-MOUSE mode switches and the click's cursor query
|
|
88
|
+
export function createLineDiff({ columns = () => null, mirror = null } = {}) {
|
|
89
|
+
let prev = null; // lines of the last frame, trailing '' included
|
|
90
|
+
const bodyLines = (body) => (body && body.endsWith('\n') && !body.includes('\x1b[2J') ? body.split('\n') : null);
|
|
91
|
+
const diff = (chunk) => {
|
|
92
|
+
if (TRANSPARENT.has(chunk)) return chunk;
|
|
93
|
+
mirror?.write(chunk); // synchronized-update brackets and cursor hide/show around a frame
|
|
94
|
+
const m = FRAME_RE.exec(chunk);
|
|
95
|
+
// Not an erase+frame: a frame Ink drew after clearing (no erase prefix), static history, or something else.
|
|
96
|
+
// The last such write before the next erase is the frame on screen (Ink writes static output, then the frame).
|
|
97
|
+
if (!m) { prev = bodyLines(chunk); return chunk; }
|
|
98
|
+
const erased = (m[1].match(/\x1b\[2K/g) || []).length;
|
|
99
|
+
const cols = columns();
|
|
100
|
+
if (prev && cols > 0) {
|
|
101
|
+
// R3 D2: after a resize the terminal has reflowed the old frame (a narrower screen wraps each line onto more
|
|
102
|
+
// rows), and Ink's erase counts lines, not rows: ghost composers, or history reprinted to cover them. Count
|
|
103
|
+
// the rows the frame really occupies at this width; when that differs, go to its top and clear down.
|
|
104
|
+
const rows = prev.slice(0, -1).reduce((n, l) => n + Math.max(1, Math.ceil(visibleWidth(l) / cols)), 0);
|
|
105
|
+
if (rows !== erased - 1) {
|
|
106
|
+
prev = bodyLines(m[2]);
|
|
107
|
+
return `\r${rows ? `\x1b[${rows}A` : ''}\x1b[J${m[2]}`;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (!m[2].endsWith('\n') || m[2].includes('\x1b[2J')) { prev = null; return chunk; }
|
|
111
|
+
const next = m[2].split('\n');
|
|
112
|
+
if (!prev || erased !== prev.length || next.length !== prev.length || next.length < 2) { prev = next; return chunk; }
|
|
113
|
+
const body = next.length - 1;
|
|
114
|
+
let out = body > 1 ? `\x1b[${body}A` : (body === 1 ? '\x1b[1A' : '');
|
|
115
|
+
let changed = 0;
|
|
116
|
+
for (let i = 0; i < body; i++) {
|
|
117
|
+
if (next[i] !== prev[i]) { out += `\r${next[i]}\x1b[K`; changed++; }
|
|
118
|
+
out += '\x1b[1B';
|
|
119
|
+
}
|
|
120
|
+
out += '\r';
|
|
121
|
+
prev = next;
|
|
122
|
+
return changed ? out : '';
|
|
123
|
+
};
|
|
124
|
+
diff.reset = () => { prev = null; mirror?.reset(); };
|
|
125
|
+
/** Rows the live frame occupies on screen (wrapped at the current width), or null when unknown. SLOT-RUN hand-over. */
|
|
126
|
+
diff.rows = () => {
|
|
127
|
+
if (!prev) return null;
|
|
128
|
+
const cols = columns();
|
|
129
|
+
return prev.slice(0, -1).reduce((n, l) => n + (cols > 0 ? Math.max(1, Math.ceil(visibleWidth(l) / cols)) : 1), 0);
|
|
130
|
+
};
|
|
131
|
+
return diff;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Ink render options for the interactive client. Standard rendering (Ink's incrementalRendering duplicated lines on
|
|
136
|
+
* the dropped-text replay); the stdout proxy line-diffs same-height frames instead. Never the alternate screen, and
|
|
137
|
+
* no mouse tracking unless the user turns it on (/mouse on, QUOXON-MOUSE): native selection keeps working by default.
|
|
138
|
+
*/
|
|
139
|
+
export function inkRenderOptions(stdout, { tap = null } = {}) {
|
|
140
|
+
return { exitOnCtrlC: false, stdout: inkStdout(stdout, { tap }) };
|
|
141
|
+
}
|