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,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Perpetual status indicator model (CLI-QUOX): a one-cell Q whose COLOUR carries state.
|
|
3
|
+
* resting dim sky, slow breathing working sky <-> violet shimmer (katakana rain beside it)
|
|
4
|
+
* waiting amber pulse (needs approval) failing red, static
|
|
5
|
+
* The glyph is Ⓠ (U+24C0, the documented Quox stand-in): compared in xterm.js against `◖Q`, `Q` and `◖Q◗`
|
|
6
|
+
* it was the only one that reads as a Q at a glance in every colour; the bare Q is lost beside text and
|
|
7
|
+
* the half-circle forms look like a bullet. Pure functions, tick t advances every 120 ms.
|
|
8
|
+
*/
|
|
9
|
+
export const Q_GLYPH = 'Ⓠ';
|
|
10
|
+
export const STATES = ['resting', 'working', 'waiting', 'failing'];
|
|
11
|
+
const SKY = [56, 189, 248], VIOLET = [168, 85, 247], AMBER = [245, 158, 11], RED = [239, 68, 68];
|
|
12
|
+
const mix = (a, b, t) => a.map((v, i) => Math.round(v + (b[i] - v) * t));
|
|
13
|
+
const tri = (t, period) => { const k = (t % period) / period; return k < 0.5 ? k * 2 : 2 - k * 2; };
|
|
14
|
+
|
|
15
|
+
/** working / waiting / failing from what the app knows; Working is only mounted while a turn runs. */
|
|
16
|
+
export function indicatorState(activity, failing = false) {
|
|
17
|
+
if (failing) return 'failing';
|
|
18
|
+
if (/^waiting for approval/i.test(String(activity || ''))) return 'waiting';
|
|
19
|
+
return 'working';
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** rgb for the Q at tick t. quiet (reduced motion) freezes the animation at a representative colour. */
|
|
23
|
+
export function qColor(state, t = 0, quiet = false) {
|
|
24
|
+
switch (state) {
|
|
25
|
+
case 'resting': return mix([40, 70, 90], SKY, quiet ? 0.5 : 0.25 + 0.5 * tri(t, 20));
|
|
26
|
+
case 'waiting': return mix([120, 76, 6], AMBER, quiet ? 1 : 0.3 + 0.7 * tri(t, 10));
|
|
27
|
+
case 'failing': return RED;
|
|
28
|
+
default: return mix(SKY, VIOLET, quiet ? 0.5 : tri(t, 16));
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** The word shown for a state when the activity text is not the whole story. */
|
|
33
|
+
export function stateWord(state, activity) {
|
|
34
|
+
if (state === 'failing') return 'Failed';
|
|
35
|
+
if (state === 'resting') return 'Ready';
|
|
36
|
+
const a = String(activity || '').replace(/[.…\s]+$/u, '');
|
|
37
|
+
return !a || a === 'Working' ? 'Thinking' : a;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** 842 -> "842", 1234 -> "1.2k", 12400 -> "12k". */
|
|
41
|
+
export function fmtTokens(n) {
|
|
42
|
+
n = Math.max(0, Math.round(Number(n) || 0));
|
|
43
|
+
if (n < 1000) return String(n);
|
|
44
|
+
const k = n / 1000;
|
|
45
|
+
return k < 10 ? `${k.toFixed(1).replace(/\.0$/, '')}k` : `${Math.round(k)}k`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** USD for the closing line: undefined (omit) when absent, zero or not a number; never invented. */
|
|
49
|
+
export function fmtCost(usd) {
|
|
50
|
+
const v = Number(usd);
|
|
51
|
+
if (!Number.isFinite(v) || v <= 0) return null;
|
|
52
|
+
return `$${v < 0.01 ? v.toFixed(4) : v.toFixed(2)}`;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Normalise a server usage object. Real numbers only: { tokens, cost } with null for anything missing.
|
|
57
|
+
* tokens = output tokens (what streamed back); falls back to input+output only when no split is given.
|
|
58
|
+
*/
|
|
59
|
+
export function normUsage(u, doneCost) {
|
|
60
|
+
const o = (u && typeof u === 'object') ? u : {};
|
|
61
|
+
const out = o.output_tokens ?? o.completion_tokens;
|
|
62
|
+
const inp = o.input_tokens ?? o.prompt_tokens;
|
|
63
|
+
const total = o.total_tokens;
|
|
64
|
+
const tokens = Number.isFinite(Number(out)) && out != null ? Number(out)
|
|
65
|
+
: Number.isFinite(Number(total)) && total != null ? Number(total)
|
|
66
|
+
: Number.isFinite(Number(inp)) && inp != null ? Number(inp) : null;
|
|
67
|
+
// cost_source 'claude-cli': the claude CLI's own API-equivalent estimate on the subscription path, not a price.
|
|
68
|
+
const costSource = typeof o.cost_source === 'string' ? o.cost_source : null;
|
|
69
|
+
const cost = Number(o.cost_usd ?? doneCost);
|
|
70
|
+
return { tokens, cost: !costSource && Number.isFinite(cost) && cost > 0 ? cost : null, real: tokens != null, costSource };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Live token figure: the real number once the server reported it, else an estimate from received text (~chars/4). */
|
|
74
|
+
export function turnTokens(turn) {
|
|
75
|
+
if (!turn) return 0;
|
|
76
|
+
if (turn.usage?.real) return turn.usage.tokens;
|
|
77
|
+
return Math.ceil((turn.chars || 0) / 4);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Glyphs that stand in for letters while the word "decrypts" (owner 2026-10-12: far more variety). Half-width
|
|
82
|
+
* katakana only: one cell each, the width of a Latin letter, so the word never jitters. 42 of them: the whole
|
|
83
|
+
* U+FF66..U+FF9D block minus forms that read as a Latin letter, digit, bracket or bare box/stroke in DejaVu
|
|
84
|
+
* Sans Mono fallback (ェエ like I/E, ュョユヨ boxes, ニ "=", ノ "/", フ "7", ヘ "^", リ "U", レ "L", ロ a box, ー a dash)
|
|
85
|
+
* and the voiced marks ゙ ゚.
|
|
86
|
+
*/
|
|
87
|
+
/** Decode glyphs: every half-width katakana (U+FF66-FF9D, one cell each, minus the long-vowel bar ー) plus the
|
|
88
|
+
* code-rain digits. Owner 2026-10-15: the reveal felt like "just a few characters" with 44 look-alike kana. */
|
|
89
|
+
export const SCRAMBLE_GLYPHS = 'ヲァィゥェォャュョッアイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワン0123456789';
|
|
90
|
+
|
|
91
|
+
/** Deterministic hash in [0,1). */
|
|
92
|
+
const hsh = (a, b, c = 0) => { let x = (a * 374761393 + b * 668265263 + c * 2147483647) | 0; x = Math.imul(x ^ (x >>> 13), 1274126177); return ((x ^ (x >>> 16)) >>> 0) / 4294967296; };
|
|
93
|
+
|
|
94
|
+
/** Per-cell flicker period (ms), 60..180, so cells never pulse in lockstep. */
|
|
95
|
+
export const cellPeriod = (i, seed = 0) => 60 + Math.floor(hsh(i, 911, seed) * 121);
|
|
96
|
+
export const SCRAMBLE_CHANCE = 0.5; // x mean cascade 3/5 of an episode -> ~30% of letters scrambled at any time
|
|
97
|
+
const EPISODE = 5; // an episode is 5 flicker periods: a cascade of 2..4 glyphs, then the letter
|
|
98
|
+
const FLASH_MS = 90; // one frame of bright flash when a cell resolves
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Glyph for cell i in flicker bucket k. Even and odd buckets draw from disjoint halves of the set, so a cell
|
|
102
|
+
* never shows the same glyph twice in a row.
|
|
103
|
+
*/
|
|
104
|
+
export function cellGlyph(i, k, seed = 0) {
|
|
105
|
+
const half = Math.floor(SCRAMBLE_GLYPHS.length / 2);
|
|
106
|
+
const odd = ((k % 2) + 2) % 2;
|
|
107
|
+
return SCRAMBLE_GLYPHS[odd * half + Math.floor(hsh(i, k, seed) * half)];
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* The activity word at time `now` (ms): "Matrix rain through the word". Pure. Returns [{ ch, glyph, flash }].
|
|
112
|
+
* decode-in: a word that just changed (at `since`) resolves left to right over DECODE_MS (unresolved = glyph)
|
|
113
|
+
* steady: each letter runs its own clock (cellPeriod); per episode of 4 periods it is either plain or
|
|
114
|
+
* cascades through 2..4 different glyphs and resolves with one bright flash frame
|
|
115
|
+
* Spaces are never scrambled; every cell is one column, so the width never changes.
|
|
116
|
+
*/
|
|
117
|
+
export function wordFrame(word, now, { since = -Infinity, seed = 0 } = {}) {
|
|
118
|
+
const chars = [...String(word)];
|
|
119
|
+
const letters = chars.filter(ch => ch.trim()).length;
|
|
120
|
+
let li = -1;
|
|
121
|
+
return chars.map((ch, i) => {
|
|
122
|
+
if (!ch.trim()) return { ch, glyph: false, flash: false };
|
|
123
|
+
li++;
|
|
124
|
+
const P = cellPeriod(i, seed), phase = Math.floor(hsh(i, 313, seed) * P * EPISODE);
|
|
125
|
+
const tt = now + phase, k = Math.floor(tt / P);
|
|
126
|
+
const decodeAt = since + ((li + 1) / letters) * DECODE_MS;
|
|
127
|
+
if (now < decodeAt) return { ch: cellGlyph(i, k, seed), glyph: true, flash: false };
|
|
128
|
+
if (now - decodeAt < FLASH_MS) return { ch, glyph: false, flash: true }; // resolve flash on decode-in too
|
|
129
|
+
if (now < since + DECODE_MS + 200) return { ch, glyph: false, flash: false }; // a fresh word reads cleanly first
|
|
130
|
+
const E = Math.floor(k / EPISODE), pos = k - E * EPISODE;
|
|
131
|
+
const cascade = (e) => (hsh(i, e, seed + 17) < SCRAMBLE_CHANCE ? 2 + Math.floor(hsh(i, e, seed + 29) * 3) : 0);
|
|
132
|
+
const m = cascade(E);
|
|
133
|
+
if (pos < m) return { ch: cellGlyph(i, k, seed), glyph: true, flash: false };
|
|
134
|
+
const resolvedAt = (E * EPISODE + m) * P;
|
|
135
|
+
if (m > 0 && pos === m && tt - resolvedAt < Math.min(P, FLASH_MS)) return { ch, glyph: false, flash: true };
|
|
136
|
+
return { ch, glyph: false, flash: false };
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** Gradient stops per state for the coloured word (same families as the composer ring). */
|
|
141
|
+
export const WORD_PALETTES = {
|
|
142
|
+
working: [SKY, VIOLET, [6, 182, 212]],
|
|
143
|
+
writing: [[34, 197, 94], [74, 222, 128], [20, 184, 166]],
|
|
144
|
+
waiting: [AMBER, [234, 88, 12], [251, 191, 36]],
|
|
145
|
+
failing: [RED, RED, RED],
|
|
146
|
+
};
|
|
147
|
+
/**
|
|
148
|
+
* Colour of a word cell: one gradient sweeping across the word over time (~1.6 s per pass). Resolved letters
|
|
149
|
+
* are slightly brighter than glyphs; a flash is near white. quiet (reduced motion) freezes the sweep.
|
|
150
|
+
*/
|
|
151
|
+
export function wordColor(state, i, n, now, kind = 'letter', quiet = false) {
|
|
152
|
+
const pal = WORD_PALETTES[state] || WORD_PALETTES.working;
|
|
153
|
+
const u = ((((n > 1 ? i / (n - 1) : 0) * 0.65 - (quiet ? 0 : now / 1600)) % 1) + 1) % 1;
|
|
154
|
+
const x = u * pal.length, a = Math.floor(x) % pal.length, b = (a + 1) % pal.length;
|
|
155
|
+
const base = mix(pal[a], pal[b], x - Math.floor(x));
|
|
156
|
+
if (kind === 'flash') return mix(base, [255, 255, 255], 0.7);
|
|
157
|
+
if (kind === 'glyph') return mix(base, [30, 36, 48], 0.28);
|
|
158
|
+
return mix(base, [255, 255, 255], 0.12);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Previous model (kept for the drop-in reveal and existing callers): one frame with a random 30-45% of the
|
|
163
|
+
* letter positions scrambled. Returns [{ ch, glyph }]. rand is injectable for tests.
|
|
164
|
+
*/
|
|
165
|
+
export function scrambleFrame(word, rand = Math.random) {
|
|
166
|
+
const chars = [...String(word)];
|
|
167
|
+
const idx = chars.map((c, i) => (c.trim() ? i : -1)).filter((i) => i >= 0);
|
|
168
|
+
const chosen = new Set();
|
|
169
|
+
if (idx.length > 1) {
|
|
170
|
+
const k = Math.min(idx.length - 1, Math.max(1, Math.round(idx.length * (0.3 + 0.15 * rand()))));
|
|
171
|
+
const pool = [...idx];
|
|
172
|
+
while (chosen.size < k) chosen.add(pool.splice(Math.floor(rand() * pool.length), 1)[0]);
|
|
173
|
+
}
|
|
174
|
+
return chars.map((ch, i) => (chosen.has(i) ? { ch: SCRAMBLE_GLYPHS[Math.floor(rand() * SCRAMBLE_GLYPHS.length)], glyph: true } : { ch, glyph: false }));
|
|
175
|
+
}
|
|
176
|
+
/** Shimmer colour for the glyph at position i, tick t (sky to violet). */
|
|
177
|
+
export const glyphColor = (i, t) => mix(SKY, VIOLET, tri(t + i * 2, 16));
|
|
178
|
+
|
|
179
|
+
export const DECODE_MS = 300;
|
|
180
|
+
/**
|
|
181
|
+
* A word that just changed decodes in: letters resolve left to right over DECODE_MS. Unresolved positions
|
|
182
|
+
* are always glyphs; resolved ones follow the normal 30-45% scramble. progress 0..1 (>=1 = steady state).
|
|
183
|
+
*/
|
|
184
|
+
export function decodeFrame(word, progress, rand = Math.random) {
|
|
185
|
+
const base = scrambleFrame(word, rand);
|
|
186
|
+
if (progress >= 1) return base;
|
|
187
|
+
const n = base.filter((g) => g.ch.trim()).length;
|
|
188
|
+
let seen = 0;
|
|
189
|
+
const revealed = Math.floor(clamp01(progress) * n);
|
|
190
|
+
return base.map((g) => {
|
|
191
|
+
if (!g.ch.trim()) return g;
|
|
192
|
+
const i = seen++;
|
|
193
|
+
if (i < revealed) return g;
|
|
194
|
+
return g.glyph ? g : { ch: SCRAMBLE_GLYPHS[Math.floor(rand() * SCRAMBLE_GLYPHS.length)], glyph: true };
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
const clamp01 = (x) => Math.max(0, Math.min(1, x));
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local intent hints. When a message is plainly a client command in words, the turn is NOT intercepted
|
|
3
|
+
* (the model still answers); the client only prints one dim `tip: /command` line above the answer.
|
|
4
|
+
* Exact, anchored patterns on the whole message: anything with extra words is ordinary chat.
|
|
5
|
+
*/
|
|
6
|
+
const WORD = '([A-Za-z0-9][\\w.-]{0,39})';
|
|
7
|
+
// Questions answered by a local panel (/context, /cost, /status): the panel prints above the answer.
|
|
8
|
+
const PANELS = [
|
|
9
|
+
[/^(?:how much (?:of (?:the|my|our) )?context (?:have (?:we|i) used|has been used|is (?:used|left)|are we using|am i using|do (?:we|i) have left)|what(?:'s| is) (?:the|my|our) context(?: usage)?|context usage)$/i, '/context'],
|
|
10
|
+
[/^(?:what(?:'s| is) (?:this|it) costing(?: (?:me|us))?|how much (?:has|did) (?:this|it) cost(?: (?:me|us))?(?: so far)?|how much (?:have (?:i|we)|am i|are we) spen(?:t|ding)(?: so far)?|what(?:'s| is| has been) (?:the|my|our) (?:cost|spend|usage)(?: so far)?)$/i, '/cost'],
|
|
11
|
+
[/^(?:what(?:'s| is) my status|what(?:'s| is) the status|show (?:me )?my status|status)$/i, '/status']
|
|
12
|
+
];
|
|
13
|
+
function PANEL_RULES() { return PANELS.map(([re, cmd]) => [re, () => cmd]); }
|
|
14
|
+
|
|
15
|
+
const RULES = [
|
|
16
|
+
// owner 2026-10-15: "attach to TRIAGE-Q agent" / "reattach to ..." took four turns; the tip must catch them
|
|
17
|
+
[new RegExp(`^(?:please )?(?:re)?attach(?: me)?(?: to)?(?: the)? ${WORD}(?: agent)?(?: please)?$`, 'i'), (m) => `/attach ${m[1].toLowerCase()}`],
|
|
18
|
+
[/^rename (?:this|the|my) (?:session|conversation|chat) (?:to|as) ([\w.-]+(?: [\w.-]+){0,2})$/i, (m) => m[1].split(' ').some(w => /^(and|then|but|so|please)$/i.test(w)) ? null : `/rename ${m[1]}`],
|
|
19
|
+
[/^(?:what(?:'s| is) running|what is running now)$/i, () => '/roster'],
|
|
20
|
+
[/^(?:show|list|open) (?:my |the )?(?:pending )?approvals$/i, () => '/approvals'],
|
|
21
|
+
[/^(?:what(?:'s| is)|show me what(?:'s| is)) (?:waiting|pending) (?:for )?(?:my )?approval$/i, () => '/approvals'],
|
|
22
|
+
[/^(?:so )?(?:what (?:did i miss|have i missed)(?: (?:while|when) i was (?:away|out|gone|offline))?|what (?:happened|changed) (?:while|when) i was (?:away|out|gone|offline)|(?:did )?anything happen(?:ed)? while i was (?:away|out|gone|offline)|catch me up)$/i, () => '/away'],
|
|
23
|
+
[/^(?:(?:give me |show me |can i get )?(?:a |the )?sit ?rep(?: please)?|what(?:'s| is) the situation(?: now)?|situation report(?: please)?)$/i, () => '/sitrep'],
|
|
24
|
+
[/^(?:how(?:'s| is) (?:the |my |our )?fleet(?: doing)?|(?:show|list) (?:me )?(?:the |my |our )?fleet|fleet (?:status|health)|what(?:'s| is) (?:the )?(?:state|health) of (?:the |my |our )?(?:fleet|computers|servers))$/i, () => '/fleet'],
|
|
25
|
+
[/^(?:open|show|take me to|go to) (?:me )?(?:to )?(?:the |my )?(?:quox )?(?:web )?dashboard$/i, () => '/dashboard'],
|
|
26
|
+
[/^detach(?: from (?:the )?agent)?$/i, () => '/detach'],
|
|
27
|
+
[/^(?:clear|cls|clear (?:the )?screen)$/i, () => '/clear'], // owner typed bare "clear" (2026-10-15)
|
|
28
|
+
[/^(?:start )?(?:a )?new conversation$/i, () => '/new'],
|
|
29
|
+
[/^(?:please )?(?:compact|summari[sz]e and compact|compress) (?:this|the|our|my) (?:conversation|chat|session|context)(?: please)?$/i, () => '/compact'],
|
|
30
|
+
[/^(?:please )?(?:update|upgrade) (?:the |my |this )?(?:quox )?(?:server|install|instance|quox)(?: (?:to (?:the )?latest|please))?$/i, () => 'quox server update --dry-run (in a terminal; shows the plan first)'],
|
|
31
|
+
[/^(?:please )?roll ?back (?:the |my |this )?(?:quox )?(?:server|install|instance|update)$/i, () => 'quox server rollback --dry-run (in a terminal)'],
|
|
32
|
+
[/^(?:please )?uninstall (?:the |my |this )?(?:quox )?(?:server|install|instance|quox)$/i, () => 'quox server uninstall --dry-run (in a terminal; lists what goes first)'],
|
|
33
|
+
[/^(?:what version is (?:the |my )?(?:quox )?server(?: on| running)?|is (?:the |my )?(?:quox )?server (?:up to date|healthy)|server status)$/i, () => '/server'],
|
|
34
|
+
[/^(?:make|create|write) (?:a )?(?:support|diag(?:nostic)?s?) bundle$/i, () => 'quox server diag (in a terminal; written locally, never uploaded)'],
|
|
35
|
+
...PANEL_RULES()
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
/** -> `tip: /attach seer` or null. */
|
|
39
|
+
export function intentHint(text) {
|
|
40
|
+
const t = String(text || '').trim().replace(/[.?!]+$/, '').trim();
|
|
41
|
+
if (!t || t.includes('\n') || t.startsWith('/')) return null;
|
|
42
|
+
for (const [re, fmt] of RULES) { const m = re.exec(t); const r = m && fmt(m); if (r) return `tip: ${r}`; }
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** -> '/context' | '/cost' | '/status' when the message asks a question a local panel answers, else null. */
|
|
47
|
+
export function intentPanel(text) {
|
|
48
|
+
const t = String(text || '').trim().replace(/[.?!]+$/, '').trim();
|
|
49
|
+
if (!t || t.includes('\n') || t.startsWith('/')) return null;
|
|
50
|
+
for (const [re, cmd] of PANELS) if (re.test(t)) return cmd;
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Raw stdin tokeniser for the QUOXON composer. We own input parsing (instead of
|
|
3
|
+
* Ink's useInput) because we need bracketed paste, Shift+Enter, Ctrl+_ and a
|
|
4
|
+
* paste-burst heuristic, none of which Ink 6 exposes.
|
|
5
|
+
*
|
|
6
|
+
* parseChunk(chunk, state) -> { events, state }
|
|
7
|
+
* Events: { type: 'text', text } | { type: 'paste', text } | { type: 'key', name, ctrl?, alt? } | { type: 'focus', focused }
|
|
8
|
+
* | { type: 'mouse', action: 'press'|'release'|'drag'|'move'|'wheel', button, x, y, shift, alt, ctrl } (SGR 1006, QUOXON-MOUSE)
|
|
9
|
+
* | { type: 'cursor', row, col } (a cursor position report, the answer to DSR ESC[6n)
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const BUTTONS = ['left', 'middle', 'right', 'none'];
|
|
13
|
+
const WHEEL = ['up', 'down', 'left', 'right'];
|
|
14
|
+
/** One SGR mouse report (ESC [ < b ; x ; y M|m) as an event. */
|
|
15
|
+
function mouseEvent(b, x, y, final) {
|
|
16
|
+
const low = b & 3;
|
|
17
|
+
const mods = { shift: Boolean(b & 4), alt: Boolean(b & 8), ctrl: Boolean(b & 16) };
|
|
18
|
+
if (b & 64) return { type: 'mouse', action: 'wheel', button: WHEEL[low], x, y, ...mods };
|
|
19
|
+
if (b & 32) return { type: 'mouse', action: low === 3 ? 'move' : 'drag', button: BUTTONS[low], x, y, ...mods };
|
|
20
|
+
return { type: 'mouse', action: final === 'm' ? 'release' : 'press', button: BUTTONS[low], x, y, ...mods };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const PASTE_ON = '\x1b[?2004h';
|
|
24
|
+
export const PASTE_OFF = '\x1b[?2004l';
|
|
25
|
+
const PASTE_START = '\x1b[200~';
|
|
26
|
+
const PASTE_END = '\x1b[201~';
|
|
27
|
+
|
|
28
|
+
export const initialKeyState = () => ({ inPaste: false, pasteBuf: '', partial: '' });
|
|
29
|
+
/** Time after which a dangling paste start / partial marker is flushed (app owns the timer). */
|
|
30
|
+
export const PASTE_DANGLING_MS = 1500;
|
|
31
|
+
/** Non-bracketed input arriving within this many ms of the previous chunk is a paste burst. */
|
|
32
|
+
export const BURST_MS = 20;
|
|
33
|
+
|
|
34
|
+
/** True when the key state holds something a timer must eventually flush. */
|
|
35
|
+
export const hasDangling = (st) => Boolean(st?.inPaste || st?.partial);
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Flush a dangling paste start or half marker after a timeout. Buffered paste text becomes a PASTE
|
|
39
|
+
* event (inserted, never submitted); a half marker is dropped. Never produces Enter.
|
|
40
|
+
*/
|
|
41
|
+
export function flushDangling(state) {
|
|
42
|
+
const events = [];
|
|
43
|
+
if (state?.inPaste && state.pasteBuf) {
|
|
44
|
+
let t = state.pasteBuf;
|
|
45
|
+
for (let n = Math.min(PASTE_END.length - 1, t.length); n > 0; n--) { if (t.endsWith(PASTE_END.slice(0, n))) { t = t.slice(0, -n); break; } }
|
|
46
|
+
if (t) events.push({ type: 'paste', text: t });
|
|
47
|
+
}
|
|
48
|
+
return { events, state: initialKeyState() };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const CSI_KEYS = {
|
|
52
|
+
'\x1b[A': 'up', '\x1b[B': 'down', '\x1b[C': 'right', '\x1b[D': 'left',
|
|
53
|
+
'\x1bOA': 'up', '\x1bOB': 'down', '\x1bOC': 'right', '\x1bOD': 'left',
|
|
54
|
+
'\x1b[H': 'home', '\x1b[F': 'end', '\x1bOH': 'home', '\x1bOF': 'end',
|
|
55
|
+
'\x1b[1~': 'home', '\x1b[4~': 'end', '\x1b[7~': 'home', '\x1b[8~': 'end',
|
|
56
|
+
'\x1b[3~': 'delete', '\x1b[Z': 'shift-tab',
|
|
57
|
+
'\x1b[13;2u': 'shift-enter', '\x1b[27;2;13~': 'shift-enter', '\x1b\r': 'shift-enter', '\x1b\n': 'shift-enter',
|
|
58
|
+
'\x1b[1;5D': 'word-left', '\x1b[1;5C': 'word-right', '\x1b[1;3D': 'word-left', '\x1b[1;3C': 'word-right',
|
|
59
|
+
// COMPOSER-EDIT (owner 2026-10-17: edit like Claude Code's prompt box). Alt+B/F (macOS Option+Left/Right), iTerm2 Alt+arrows,
|
|
60
|
+
// Alt+Backspace, Alt+D, Ctrl/Alt+Delete, and Shift (+Ctrl/Alt) arrows, Home and End for selection.
|
|
61
|
+
'\x1bb': 'word-left', '\x1bf': 'word-right', '\x1b\x1b[D': 'word-left', '\x1b\x1b[C': 'word-right',
|
|
62
|
+
'\x1b\x7f': 'delete-word', '\x1b\x08': 'delete-word', '\x1bd': 'delete-word-forward',
|
|
63
|
+
'\x1b[3;5~': 'delete-word-forward', '\x1b[3;3~': 'delete-word-forward',
|
|
64
|
+
'\x1b[1;2D': 'select-left', '\x1b[1;2C': 'select-right', '\x1b[1;2A': 'select-up', '\x1b[1;2B': 'select-down',
|
|
65
|
+
'\x1b[1;2H': 'select-home', '\x1b[1;2F': 'select-end',
|
|
66
|
+
'\x1b[1;6D': 'select-word-left', '\x1b[1;6C': 'select-word-right', '\x1b[1;4D': 'select-word-left', '\x1b[1;4C': 'select-word-right'
|
|
67
|
+
};
|
|
68
|
+
const CSI_LIST = Object.keys(CSI_KEYS).sort((a, b) => b.length - a.length);
|
|
69
|
+
|
|
70
|
+
const CTRL = {
|
|
71
|
+
'\x01': 'home', '\x05': 'end', '\x03': 'ctrl-c', '\x04': 'ctrl-d', '\x07': 'ctrl-g',
|
|
72
|
+
'\x0b': 'kill-line-forward', '\x0f': 'ctrl-o', '\x13': 'ctrl-s', '\x15': 'kill-line', '\x17': 'delete-word',
|
|
73
|
+
'\x1f': 'undo', '\x19': 'redo', '\x0a': 'newline', '\x7f': 'backspace', '\x08': 'backspace',
|
|
74
|
+
'\r': 'enter', '\t': 'tab', '\x02': 'left', '\x06': 'right'
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
/** `draft` = the composer text before this chunk (a burst split mid-line continues the line already typed). */
|
|
78
|
+
export function parseChunk(chunk, state = initialKeyState(), draft = '') {
|
|
79
|
+
const events = [];
|
|
80
|
+
let s = String(chunk);
|
|
81
|
+
let st = { partial: '', ...state };
|
|
82
|
+
if (st.partial) { s = st.partial + s; st.partial = ''; }
|
|
83
|
+
|
|
84
|
+
while (s.length) {
|
|
85
|
+
if (st.inPaste) {
|
|
86
|
+
// search the joined buffer so an end marker split across chunks is still found
|
|
87
|
+
const buf = st.pasteBuf + s;
|
|
88
|
+
const end = buf.indexOf(PASTE_END);
|
|
89
|
+
if (end === -1) { st.pasteBuf = buf; s = ''; break; }
|
|
90
|
+
events.push({ type: 'paste', text: buf.slice(0, end) });
|
|
91
|
+
st = { inPaste: false, pasteBuf: '', partial: '' };
|
|
92
|
+
s = buf.slice(end + PASTE_END.length);
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
if (s.startsWith(PASTE_START)) { st = { inPaste: true, pasteBuf: '', partial: '' }; s = s.slice(PASTE_START.length); continue; }
|
|
96
|
+
// a half-received start marker (ESC [ 2 0 ...): hold it for the next chunk, never read it as Esc + text
|
|
97
|
+
if (s.length >= 2 && s.length < PASTE_START.length && PASTE_START.startsWith(s)) { st.partial = s; s = ''; break; }
|
|
98
|
+
if (s === '\x1b') { events.push({ type: 'key', name: 'escape' }); s = ''; continue; }
|
|
99
|
+
if (s[0] === '\x1b') {
|
|
100
|
+
// Focus reporting (CSI ?1004h, enabled by sound/index.js): CSI I = focus in, CSI O = focus out.
|
|
101
|
+
if (s.startsWith('\x1b[I') || s.startsWith('\x1b[O')) { events.push({ type: 'focus', focused: s[2] === 'I' }); s = s.slice(3); continue; }
|
|
102
|
+
// QUOXON-MOUSE: SGR mouse reports; a report split across chunks is held, never typed into the composer.
|
|
103
|
+
const mm = /^\x1b\[<(\d+);(\d+);(\d+)([Mm])/.exec(s);
|
|
104
|
+
if (mm) { events.push(mouseEvent(Number(mm[1]), Number(mm[2]), Number(mm[3]), mm[4])); s = s.slice(mm[0].length); continue; }
|
|
105
|
+
if (/^\x1b\[<[\d;]*$/.test(s)) { st.partial = s; s = ''; break; }
|
|
106
|
+
const cr = /^\x1b\[(\d+);(\d+)R/.exec(s);
|
|
107
|
+
if (cr) { events.push({ type: 'cursor', row: Number(cr[1]), col: Number(cr[2]) }); s = s.slice(cr[0].length); continue; }
|
|
108
|
+
const hit = CSI_LIST.find(k => s.startsWith(k));
|
|
109
|
+
if (hit) { events.push({ type: 'key', name: CSI_KEYS[hit] }); s = s.slice(hit.length); continue; }
|
|
110
|
+
// Unknown escape sequence: swallow ESC [ ... final byte, or a bare ESC+char (alt-x).
|
|
111
|
+
const m = /^\x1b\[[0-9;?]*[ -/]*[@-~]/.exec(s) || /^\x1bO./.exec(s);
|
|
112
|
+
if (m) { s = s.slice(m[0].length); continue; }
|
|
113
|
+
if (s.length >= 2) { events.push({ type: 'key', name: 'escape' }); s = s.slice(1); continue; }
|
|
114
|
+
}
|
|
115
|
+
// Plain run: printable text up to the next control char.
|
|
116
|
+
// Burst heuristic: text plus newlines delivered in ONE chunk, with content after
|
|
117
|
+
// the first newline, is a paste (terminal without bracketed paste), not typing.
|
|
118
|
+
const run = /^[^\x00-\x1f\x7f]+/.exec(s);
|
|
119
|
+
if (run) { events.push({ type: 'text', text: run[0] }); s = s.slice(run[0].length); continue; }
|
|
120
|
+
const c = s[0];
|
|
121
|
+
if (CTRL[c]) { events.push({ type: 'key', name: CTRL[c] }); }
|
|
122
|
+
s = s.slice(1);
|
|
123
|
+
}
|
|
124
|
+
return { events: coalesceBurst(events, draft), state: st };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* If a chunk produced text and then an Enter/newline FOLLOWED by more text, it is
|
|
129
|
+
* a non-bracketed paste: fold it into one paste event. A trailing single Enter
|
|
130
|
+
* (text then Enter at the end) stays as text + enter so fast typing still submits.
|
|
131
|
+
*
|
|
132
|
+
* Except a burst whose every line is a slash command (R3 D3): that is typed input batched by tmux,
|
|
133
|
+
* SSH/mosh or a script ("/help" Enter "/model x" Enter), never a paste, so each Enter runs its line.
|
|
134
|
+
* Folding it made a multi-line "/" draft that trapped every later Enter.
|
|
135
|
+
*/
|
|
136
|
+
const isEnter = (e) => e.type === 'key' && (e.name === 'enter' || e.name === 'newline');
|
|
137
|
+
function commandLines(events, draft = '') {
|
|
138
|
+
let line = draft.includes('\n') ? '\n' : draft; // a multi-line draft is never a command line
|
|
139
|
+
for (const e of events) {
|
|
140
|
+
if (e.type === 'text') line += e.text;
|
|
141
|
+
else if (e.name === 'enter') { if (!line.startsWith('/')) return false; line = ''; }
|
|
142
|
+
else return false; // newline (Ctrl+J), tab or any other key: not plain typed commands
|
|
143
|
+
}
|
|
144
|
+
return line === '' || line.startsWith('/');
|
|
145
|
+
}
|
|
146
|
+
function coalesceBurst(events, draft = '') {
|
|
147
|
+
const hasInner = events.some((e, i) =>
|
|
148
|
+
isEnter(e) && i > 0 && i < events.length - 1 &&
|
|
149
|
+
events.slice(i + 1).some(x => x.type === 'text'));
|
|
150
|
+
if (!hasInner) return events;
|
|
151
|
+
if (commandLines(events, draft)) return events;
|
|
152
|
+
let text = '';
|
|
153
|
+
const rest = [];
|
|
154
|
+
for (let i = 0; i < events.length; i++) {
|
|
155
|
+
const e = events[i];
|
|
156
|
+
if (e.type === 'text') text += e.text;
|
|
157
|
+
else if (e.type === 'key' && (e.name === 'enter' || e.name === 'newline')) {
|
|
158
|
+
// a trailing Enter after the burst is a submit, not part of the paste
|
|
159
|
+
if (i === events.length - 1) rest.push(e); else text += '\n';
|
|
160
|
+
} else if (e.type === 'key' && e.name === 'tab') text += '\t';
|
|
161
|
+
else rest.push(e);
|
|
162
|
+
}
|
|
163
|
+
return [{ type: 'paste', text }, ...rest];
|
|
164
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QUOXON-RESTART-LEAK: the conversation client's top-level launcher.
|
|
3
|
+
*
|
|
4
|
+
* Before this, /restart (and /update, /restart auto, a profile switch that restarts) ran the successor with
|
|
5
|
+
* spawnSync from INSIDE the old client and waited for it. The old client froze but stayed resident with its
|
|
6
|
+
* whole heap, so every restart nested one more TUI under the last: the owner's terminal reached 98 nested
|
|
7
|
+
* clients and 15 GB RSS (on the dev box, 2026-10-17).
|
|
8
|
+
*
|
|
9
|
+
* Now the first interactive `quox` on a terminal becomes this launcher: no React, no Ink, no api, no timers,
|
|
10
|
+
* never touches stdin. It runs the client as its child and waits. A client that wants a restart writes a
|
|
11
|
+
* handoff file (what to run, the one-shot env it carries) and exits with RESTART_EXIT_CODE; the launcher
|
|
12
|
+
* reads it, deletes it, and runs the successor. Any other exit is passed straight through. So after any
|
|
13
|
+
* number of restarts there is exactly one client process per terminal (plus this launcher).
|
|
14
|
+
*
|
|
15
|
+
* Tiny on purpose: it must import nothing heavy (bin/quox.js loads it before React, Ink or any command).
|
|
16
|
+
*/
|
|
17
|
+
import { spawn } from 'node:child_process';
|
|
18
|
+
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
19
|
+
import { tmpdir, constants as osConstants } from 'node:os';
|
|
20
|
+
import { join } from 'node:path';
|
|
21
|
+
|
|
22
|
+
/** The client asks its launcher for a restart with this exit code AND a handoff file (both, never one alone). */
|
|
23
|
+
export const RESTART_EXIT_CODE = 75;
|
|
24
|
+
/** Env var naming the handoff file. Read and DELETED by the client at startup, so nothing it spawns inherits it. */
|
|
25
|
+
export const LAUNCHER_HANDOFF_ENV = 'QUOX_LAUNCHER_HANDOFF';
|
|
26
|
+
/** QUOX_NO_LAUNCHER=1 turns the launcher off (the client then restarts the old, nested way). */
|
|
27
|
+
export const NO_LAUNCHER_ENV = 'QUOX_NO_LAUNCHER';
|
|
28
|
+
|
|
29
|
+
const ONE_SHOT = /^QUOX_RESTART_/; // carried into ONE successor only, never re-applied by the launcher
|
|
30
|
+
|
|
31
|
+
let handoff; // undefined = not read yet; null = not launched; { path, base } = launched
|
|
32
|
+
/**
|
|
33
|
+
* Client side, once at startup: am I running under a launcher? Reads and deletes the env var (a shell or
|
|
34
|
+
* `quox` the client spawns must never think it is the launched client). -> { path, base } | null.
|
|
35
|
+
*/
|
|
36
|
+
export function takeLauncherHandoff(env = process.env) {
|
|
37
|
+
if (env === process.env && handoff !== undefined) return handoff;
|
|
38
|
+
const path = env[LAUNCHER_HANDOFF_ENV] || null;
|
|
39
|
+
delete env[LAUNCHER_HANDOFF_ENV];
|
|
40
|
+
const r = path ? { path, base: { ...env } } : null; // base: the env as launched, to send only what a restart changes
|
|
41
|
+
if (env === process.env) handoff = r;
|
|
42
|
+
return r;
|
|
43
|
+
}
|
|
44
|
+
/** Test hook: forget the cached handoff. */
|
|
45
|
+
export function resetLauncherHandoff() { handoff = undefined; }
|
|
46
|
+
|
|
47
|
+
/** Should this invocation become the launcher? Only an interactive client on a real terminal, never a launched child. */
|
|
48
|
+
export function shouldLaunch({ argv = [], env = process.env, isTTY = Boolean(process.stdin.isTTY && process.stdout.isTTY) } = {}) {
|
|
49
|
+
if (!isTTY || env[LAUNCHER_HANDOFF_ENV] || env[NO_LAUNCHER_ENV] === '1') return false;
|
|
50
|
+
const head = argv.indexOf('--') === -1 ? argv : argv.slice(0, argv.indexOf('--'));
|
|
51
|
+
return !head.some(a => a === '--print' || a.startsWith('--print=') || a === '-h' || a === '--help');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Client side: hand a restart to the launcher. `plan` is { cmd, args, env } (restartPlan in slash/restart.js);
|
|
56
|
+
* only the env entries that differ from the launched env are written (the launcher adds them to its own).
|
|
57
|
+
* -> true when the handoff file was written (the caller then exits with RESTART_EXIT_CODE).
|
|
58
|
+
*/
|
|
59
|
+
export function writeRestartHandoff(h, plan, { write = writeFileSync } = {}) {
|
|
60
|
+
if (!h?.path || !plan?.cmd) return false;
|
|
61
|
+
const env = {};
|
|
62
|
+
for (const [k, v] of Object.entries(plan.env || {})) if (v !== undefined && (ONE_SHOT.test(k) || h.base[k] !== v)) env[k] = v; // a one-shot always rides: the launcher strips them between runs
|
|
63
|
+
try { write(h.path, JSON.stringify({ cmd: plan.cmd, args: plan.args || [], env }), { mode: 0o600 }); return true; }
|
|
64
|
+
catch { return false; }
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Launcher side: run the client, and again each time it asks for a restart. Resolves with the exit code to
|
|
69
|
+
* leave with. `deps` lets tests stand in for spawn and the handoff directory.
|
|
70
|
+
*/
|
|
71
|
+
export async function runLauncher({ argv = [], execPath = process.execPath, script = process.argv[1], env = process.env, deps = {}, onSpawn = null } = {}) {
|
|
72
|
+
const d = { spawn, mkdtemp: () => mkdtempSync(join(tmpdir(), 'quox-launch-')), read: (p) => readFileSync(p, 'utf8'), rm: (p) => rmSync(p, { force: true, recursive: true }), signals: process, ...deps };
|
|
73
|
+
let dir;
|
|
74
|
+
try { dir = d.mkdtemp(); } catch { return null; } // no handoff dir: the caller runs the client in-process (old behaviour)
|
|
75
|
+
const file = join(dir, 'restart.json');
|
|
76
|
+
const carry = (e) => Object.fromEntries(Object.entries(e).filter(([k]) => !ONE_SHOT.test(k) && k !== LAUNCHER_HANDOFF_ENV)); // later runs never re-apply a one-shot
|
|
77
|
+
let child = null;
|
|
78
|
+
const forward = (sig) => () => { try { child?.kill(sig); } catch { /* gone */ } };
|
|
79
|
+
const handlers = { SIGINT: () => {}, SIGQUIT: () => {}, SIGTERM: forward('SIGTERM'), SIGHUP: forward('SIGHUP') }; // ^C belongs to the client (same process group)
|
|
80
|
+
for (const [s, f] of Object.entries(handlers)) d.signals.on(s, f);
|
|
81
|
+
let next = { cmd: execPath, args: [script, ...argv], env: { ...env } }; // the first run keeps any one-shot it was started with
|
|
82
|
+
try {
|
|
83
|
+
for (;;) {
|
|
84
|
+
d.rm(file);
|
|
85
|
+
const r = await new Promise((done) => {
|
|
86
|
+
try { child = d.spawn(next.cmd, next.args, { stdio: 'inherit', env: { ...next.env, [LAUNCHER_HANDOFF_ENV]: file } }); }
|
|
87
|
+
catch (e) { done({ error: e }); return; }
|
|
88
|
+
onSpawn?.(child);
|
|
89
|
+
child.once('error', (e) => done({ error: e }));
|
|
90
|
+
child.once('exit', (code, signal) => done({ code, signal }));
|
|
91
|
+
});
|
|
92
|
+
child = null;
|
|
93
|
+
if (r.error) { process.stderr.write(`quox: could not start ${next.cmd} (${r.error.code || r.error.message})\n`); return 1; }
|
|
94
|
+
let ask = null;
|
|
95
|
+
if (r.code === RESTART_EXIT_CODE) { try { ask = JSON.parse(d.read(file)); } catch { ask = null; } }
|
|
96
|
+
if (!ask?.cmd) return r.signal ? 128 + (osConstants.signals[r.signal] || 0) : (r.code ?? 1);
|
|
97
|
+
next = { cmd: ask.cmd, args: Array.isArray(ask.args) ? ask.args : [], env: { ...carry(next.env), ...(ask.env || {}) } }; // the env the client was launched with, plus what its restart changed
|
|
98
|
+
}
|
|
99
|
+
} finally {
|
|
100
|
+
for (const [s, f] of Object.entries(handlers)) d.signals.off(s, f);
|
|
101
|
+
d.rm(dir);
|
|
102
|
+
}
|
|
103
|
+
}
|