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,229 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resident pump — QUOXPERTICUS-REVISIT P4 "steer pump".
|
|
3
|
+
*
|
|
4
|
+
* When a resident's slot is held by a FULL INTERACTIVE claude session (the
|
|
5
|
+
* resident itself, deployed via `quox dev resident deploy`), the warden
|
|
6
|
+
* process never answers (see the HOLD-IS-THE-VOICE gate in
|
|
7
|
+
* supervisorEngine.js): the resident already holds the voice. What the
|
|
8
|
+
* warden does instead, while that hold lasts, is PUMP: relay new owner
|
|
9
|
+
* messages from the slot channel into the resident's live tmux pane via the
|
|
10
|
+
* dev-sessions `keys` route (services/collector/lib/devSessionsProxy.js), so
|
|
11
|
+
* the interactive session hears its owner without anyone opening the pane
|
|
12
|
+
* by hand.
|
|
13
|
+
*
|
|
14
|
+
* Live-spike findings this reads (2026-09-04, real injection into a real
|
|
15
|
+
* claude TUI on .131 via tmux send-keys):
|
|
16
|
+
* - Text injected mid-turn queues cleanly in the composer and submits when
|
|
17
|
+
* the turn ends. Injection mid-turn is SAFE.
|
|
18
|
+
* - A permission dialog ("Do you want to proceed? ... Esc to cancel")
|
|
19
|
+
* BLOCKS the pipeline indefinitely. Queued input survives but does not
|
|
20
|
+
* process. This pump NEVER injects bare Enter or any control key to
|
|
21
|
+
* resolve one; a stuck dialog is a residentAlarm.js condition, not
|
|
22
|
+
* something this module resolves. See paneHasDialog below.
|
|
23
|
+
* - `keys` is one line, max 4096 chars (the collector's MAX_KEYS_LENGTH),
|
|
24
|
+
* and the collector appends Enter itself.
|
|
25
|
+
*
|
|
26
|
+
* Every I/O dependency (fetch, pane read, key injection, heartbeat, the
|
|
27
|
+
* clock, sleep) is injected, matching supervisorEngine.js's contract, so
|
|
28
|
+
* this runs under vitest with no real network, no real tmux pane.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
import { pickNewMessages, boundSeenIds, pruneOldTurns, shouldPostErrorNotice, ERROR_NOTICE_WINDOW_MS, DEFAULT_SETTINGS } from './supervisorEngine.js';
|
|
32
|
+
|
|
33
|
+
// The exact literal markers a Claude Code permission dialog shows
|
|
34
|
+
// ("Do you want to proceed? 1. Yes 2. No ... Esc to cancel"). Either marker
|
|
35
|
+
// alone is enough to hold injection: better to over-detect (delay a message
|
|
36
|
+
// one pass) than under-detect and blind-inject into a live approval prompt.
|
|
37
|
+
export const DIALOG_MARKERS = ['Do you want to proceed?', 'Esc to cancel'];
|
|
38
|
+
|
|
39
|
+
export function paneHasDialog(paneText) {
|
|
40
|
+
const text = String(paneText || '');
|
|
41
|
+
return DIALOG_MARKERS.some((marker) => text.includes(marker));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function sanitizeOneLine(text) {
|
|
45
|
+
return String(text || '').replace(/\r?\n+/g, ' ').replace(/[ \t]{2,}/g, ' ').trim();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Build the `keys` line(s) for one owner message: `[slot:<name> owner]
|
|
50
|
+
* <text>`, newlines collapsed to spaces (a literal newline mid-line would
|
|
51
|
+
* submit a half-typed line early — same reasoning as residentSeed.js's
|
|
52
|
+
* oneLine()). A message that overflows maxLen is CHUNKED, never truncated
|
|
53
|
+
* (silent data loss is worse than several turns): each chunk carries its
|
|
54
|
+
* own prefix and a `(i/n)` suffix so the resident can read each line
|
|
55
|
+
* standalone.
|
|
56
|
+
*/
|
|
57
|
+
export function buildPumpLines({ slotName, text, maxLen = 4000 }) {
|
|
58
|
+
const prefix = `[slot:${slotName} owner] `;
|
|
59
|
+
const sanitized = sanitizeOneLine(text);
|
|
60
|
+
if (prefix.length + sanitized.length <= maxLen) {
|
|
61
|
+
return [`${prefix}${sanitized}`];
|
|
62
|
+
}
|
|
63
|
+
// Reserve headroom for the ` (NN/NN)` suffix appended to every chunk.
|
|
64
|
+
const suffixReserve = 10;
|
|
65
|
+
const available = Math.max(1, maxLen - prefix.length - suffixReserve);
|
|
66
|
+
const parts = [];
|
|
67
|
+
for (let i = 0; i < sanitized.length; i += available) {
|
|
68
|
+
parts.push(sanitized.slice(i, i + available));
|
|
69
|
+
}
|
|
70
|
+
return parts.map((part, i) => `${prefix}${part} (${i + 1}/${parts.length})`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function createInitialPumpState() {
|
|
74
|
+
return {
|
|
75
|
+
bootstrapped: false,
|
|
76
|
+
seen_ids: [],
|
|
77
|
+
// Owner messages picked up from the channel but not yet delivered to
|
|
78
|
+
// the pane: held back by a stuck dialog, or by the per-hour cap. A
|
|
79
|
+
// message only ever leaves `pending` by being injected, never dropped.
|
|
80
|
+
pending: [],
|
|
81
|
+
injection_timestamps: [],
|
|
82
|
+
overflow_notices: [],
|
|
83
|
+
// Set the moment a permission dialog is first observed; cleared the
|
|
84
|
+
// moment it is no longer observed. residentAlarm.js reads the age of
|
|
85
|
+
// this field to decide whether the stuck-dialog condition should alarm.
|
|
86
|
+
stuck_dialog_since: null,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Heartbeat the resident's slot hold with retry/backoff. The pump must not
|
|
92
|
+
* rely on the wait long-poll alone to keep the hold warm: a failed poll
|
|
93
|
+
* pass must never stop heartbeats while the collector is genuinely
|
|
94
|
+
* reachable, so this is a small standalone retry loop, called every pump
|
|
95
|
+
* pass regardless of what else happens this pass.
|
|
96
|
+
*/
|
|
97
|
+
export async function heartbeatWithBackoff({
|
|
98
|
+
heartbeatHold,
|
|
99
|
+
log = () => {},
|
|
100
|
+
sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
|
|
101
|
+
maxAttempts = 3,
|
|
102
|
+
baseDelayMs = 2000,
|
|
103
|
+
}) {
|
|
104
|
+
let lastErr = null;
|
|
105
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
106
|
+
try {
|
|
107
|
+
await heartbeatHold();
|
|
108
|
+
return { ok: true, attempts: attempt };
|
|
109
|
+
} catch (err) {
|
|
110
|
+
lastErr = err;
|
|
111
|
+
log('warn', `pump: heartbeat attempt ${attempt}/${maxAttempts} failed: ${err.message}`);
|
|
112
|
+
if (attempt < maxAttempts) {
|
|
113
|
+
await sleep(baseDelayMs * attempt);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
log('warn', `pump: heartbeat failed after ${maxAttempts} attempt(s), giving up this pass (hold may lapse): ${lastErr ? lastErr.message : 'unknown error'}`);
|
|
118
|
+
return { ok: false, attempts: maxAttempts, error: lastErr };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Run one pump pass: heartbeat the resident's hold, read the pane for a
|
|
123
|
+
* stuck permission dialog, pick up new owner messages, and inject whatever
|
|
124
|
+
* is safe to inject this pass (never while a dialog is visible, never past
|
|
125
|
+
* the per-hour cap).
|
|
126
|
+
*
|
|
127
|
+
* Injected dependencies:
|
|
128
|
+
* fetchMessages() -> array of slot messages (same shape the engine
|
|
129
|
+
* uses; a `wait`-mode caller can pass the same
|
|
130
|
+
* closure it already built for the headless path)
|
|
131
|
+
* readPane() -> current pane text (string)
|
|
132
|
+
* injectKeys(line) -> POST one `keys` line to the resident's pane
|
|
133
|
+
* heartbeatHold() -> heartbeat the slot AS the resident's own
|
|
134
|
+
* holder_session (not this warden's identity)
|
|
135
|
+
* postReply(text) -> slot `say`, used only for the once-per-hour
|
|
136
|
+
* overflow notice
|
|
137
|
+
*/
|
|
138
|
+
export async function runPumpOnce({
|
|
139
|
+
hostId,
|
|
140
|
+
slot,
|
|
141
|
+
selfIdentity,
|
|
142
|
+
fromFilters = [],
|
|
143
|
+
fetchMessages,
|
|
144
|
+
readPane,
|
|
145
|
+
injectKeys,
|
|
146
|
+
heartbeatHold,
|
|
147
|
+
postReply,
|
|
148
|
+
state,
|
|
149
|
+
log = () => {},
|
|
150
|
+
now = () => Date.now(),
|
|
151
|
+
maxSeenIds = 500,
|
|
152
|
+
maxTurnsPerHour = DEFAULT_SETTINGS.max_turns_per_hour,
|
|
153
|
+
maxLineLen = 4000,
|
|
154
|
+
heartbeatMaxAttempts = 3,
|
|
155
|
+
heartbeatBaseDelayMs = 2000,
|
|
156
|
+
sleep,
|
|
157
|
+
}) {
|
|
158
|
+
const heartbeat = await heartbeatWithBackoff({
|
|
159
|
+
heartbeatHold, log, sleep, maxAttempts: heartbeatMaxAttempts, baseDelayMs: heartbeatBaseDelayMs,
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
const paneText = await readPane();
|
|
163
|
+
const dialogPresent = paneHasDialog(paneText);
|
|
164
|
+
|
|
165
|
+
let nextState = { ...state, pending: [...(state.pending || [])] };
|
|
166
|
+
|
|
167
|
+
if (dialogPresent) {
|
|
168
|
+
if (nextState.stuck_dialog_since == null) {
|
|
169
|
+
nextState.stuck_dialog_since = now();
|
|
170
|
+
log('warn', `slot ${slot}: permission dialog detected in the resident's pane on ${hostId}; holding owner messages until it clears (never injecting Enter to resolve it).`);
|
|
171
|
+
}
|
|
172
|
+
} else if (nextState.stuck_dialog_since != null) {
|
|
173
|
+
log('info', `slot ${slot}: permission dialog cleared after ${Math.round((now() - nextState.stuck_dialog_since) / 1000)}s; resuming injection.`);
|
|
174
|
+
nextState.stuck_dialog_since = null;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const messages = await fetchMessages();
|
|
178
|
+
const firstRun = nextState.bootstrapped !== true;
|
|
179
|
+
const picked = pickNewMessages({ messages, seenIds: nextState.seen_ids, selfIdentity, fromFilters, firstRun });
|
|
180
|
+
nextState.seen_ids = boundSeenIds(picked.seenIds, maxSeenIds);
|
|
181
|
+
if (firstRun) {
|
|
182
|
+
nextState.bootstrapped = true;
|
|
183
|
+
log('info', `pump first run on ${slot}: seeded seen-set with ${picked.allSeen.length} existing message id(s), queuing none for injection.`);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const newPending = picked.toAnswer.filter((m) => !nextState.pending.some((p) => p.id === m.id));
|
|
187
|
+
nextState.pending = [...nextState.pending, ...newPending];
|
|
188
|
+
if (newPending.length > 0) {
|
|
189
|
+
log('info', `pump: queued ${newPending.length} new owner message(s) for ${slot}'s resident.`);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
let injected = 0;
|
|
193
|
+
let capHit = false;
|
|
194
|
+
|
|
195
|
+
if (!dialogPresent) {
|
|
196
|
+
let timestamps = pruneOldTurns(nextState.injection_timestamps, now(), ERROR_NOTICE_WINDOW_MS);
|
|
197
|
+
const stillPending = [];
|
|
198
|
+
for (const message of nextState.pending) {
|
|
199
|
+
if (timestamps.length >= maxTurnsPerHour) {
|
|
200
|
+
capHit = true;
|
|
201
|
+
stillPending.push(message);
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
const lines = buildPumpLines({ slotName: slot, text: message.text, maxLen: maxLineLen });
|
|
205
|
+
for (const line of lines) {
|
|
206
|
+
await injectKeys(line);
|
|
207
|
+
}
|
|
208
|
+
timestamps = [...timestamps, now()];
|
|
209
|
+
injected += 1;
|
|
210
|
+
log('info', `pump: injected message ${message.id} from ${message.from} into ${slot}'s resident pane (${lines.length} line(s)).`);
|
|
211
|
+
}
|
|
212
|
+
nextState.pending = stillPending;
|
|
213
|
+
nextState.injection_timestamps = timestamps;
|
|
214
|
+
} else if (nextState.pending.length > 0) {
|
|
215
|
+
log('warn', `pump: ${nextState.pending.length} owner message(s) on ${slot} held, waiting for the permission dialog to clear.`);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (capHit && nextState.pending.length > 0) {
|
|
219
|
+
const notice = shouldPostErrorNotice(nextState.overflow_notices, 'pump_rate_cap', now(), ERROR_NOTICE_WINDOW_MS);
|
|
220
|
+
nextState.overflow_notices = notice.notices;
|
|
221
|
+
if (notice.shouldPost) {
|
|
222
|
+
const text = `Pump injection cap reached (${maxTurnsPerHour}/hour) on slot ${slot}: ${nextState.pending.length} owner message(s) still queued, will resume as the hour rolls over.`;
|
|
223
|
+
await postReply(text);
|
|
224
|
+
log('warn', `pump: posted once-per-hour overflow notice on ${slot} (${nextState.pending.length} still queued).`);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return { state: nextState, injected, dialogPresent, pendingCount: nextState.pending.length, heartbeat };
|
|
229
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resident seed prompt — QUOXPERTICUS-REVISIT P3 "Resident lifecycle v1".
|
|
3
|
+
*
|
|
4
|
+
* The seed is the one line of text typed into a freshly-deployed interactive
|
|
5
|
+
* `claude` tmux pane (via the dev-sessions `keys` route) that turns a bare
|
|
6
|
+
* shell into the host's resident: read the revival kit before acting, claim
|
|
7
|
+
* the slot, then say a short hello. This encodes the proven 2026-09-04
|
|
8
|
+
* revival recipe (read MEMORY.md + indexed memories + NOTES_TO_SELF.md
|
|
9
|
+
* before acting, then claim, then greet) as a template, parameterized by
|
|
10
|
+
* host and slot.
|
|
11
|
+
*
|
|
12
|
+
* `keys` is a single line per call (the collector appends Enter — see
|
|
13
|
+
* services/collector/lib/devSessionsProxy.js MAX_KEYS_LENGTH=4096), so the
|
|
14
|
+
* rendered seed collapses to ONE line: no literal newlines, which would
|
|
15
|
+
* otherwise submit a half-typed prompt early.
|
|
16
|
+
*
|
|
17
|
+
* LATER PHASE SEAM: a collector-stored per-org/per-host seed override is a
|
|
18
|
+
* follow-up (dashboard-editable, versioned). Until that lands,
|
|
19
|
+
* `loadSeedOverride()` is the only override point, reading a local file on
|
|
20
|
+
* the CONTROL side (the box running `quox dev resident deploy`) verbatim.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
24
|
+
import { homedir } from 'node:os';
|
|
25
|
+
import { join } from 'node:path';
|
|
26
|
+
|
|
27
|
+
export function defaultSeedOverridePath() {
|
|
28
|
+
return join(homedir(), '.quox', 'resident', 'seed.md');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Read a local seed override file, if present. Returns null when the file
|
|
33
|
+
* is missing, unreadable, or empty — never throws, since a missing override
|
|
34
|
+
* simply means "use the built-in template", not an error.
|
|
35
|
+
*
|
|
36
|
+
* SEAM: a later phase replaces/augments this with a collector-stored
|
|
37
|
+
* config record (org/host-scoped, dashboard-editable). This function is the
|
|
38
|
+
* one place that phase needs to change.
|
|
39
|
+
*/
|
|
40
|
+
export function loadSeedOverride(path = defaultSeedOverridePath()) {
|
|
41
|
+
try {
|
|
42
|
+
if (!existsSync(path)) return null;
|
|
43
|
+
const content = readFileSync(path, 'utf-8');
|
|
44
|
+
return content && content.trim() ? content : null;
|
|
45
|
+
} catch {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Collapse arbitrary text to one line safe for a single `keys` send. */
|
|
51
|
+
function oneLine(text) {
|
|
52
|
+
return String(text || '').replace(/\s*\r?\n+\s*/g, ' ').replace(/[ \t]{2,}/g, ' ').trim();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function builtInTemplate({ hostId, slotName }) {
|
|
56
|
+
// #1021: Claude names a project dir by its path with separators turned into '-'.
|
|
57
|
+
const projectDirName = (process.env.QUOX_HOST_HOME || homedir()).replace(/[\\/]/g, '-');
|
|
58
|
+
const claimCmd = `quox slot checkout ${slotName} --session resident-${slotName} --kind interactive --host ${hostId} --create`;
|
|
59
|
+
return (
|
|
60
|
+
`You are the resident Claude session for host ${hostId} (slot ${slotName}). ` +
|
|
61
|
+
`Before doing anything else: read ~/.claude/projects/${projectDirName}/memory/MEMORY.md and every ` +
|
|
62
|
+
`memory file it indexes, then read ~/NOTES_TO_SELF.md if it exists, then read any identity or ` +
|
|
63
|
+
`onboarding docs in your home directory for this host. Read all of it before you act on anything. ` +
|
|
64
|
+
`Once you have read everything, claim your slot with: ${claimCmd}. ` +
|
|
65
|
+
`Then post one short hello (1-2 lines, no em dashes, lead with outcome) into the slot channel ` +
|
|
66
|
+
`confirming you are up and what you found.`
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Render the seed prompt for a host/slot pair, as one line.
|
|
72
|
+
* @param {object} params
|
|
73
|
+
* @param {string} params.hostId
|
|
74
|
+
* @param {string} params.slotName
|
|
75
|
+
* @param {string} [params.overrideText] - test seam; when omitted, falls
|
|
76
|
+
* back to loadSeedOverride().
|
|
77
|
+
*/
|
|
78
|
+
export function renderSeedPrompt({ hostId, slotName, overrideText } = {}) {
|
|
79
|
+
if (!hostId) throw new Error('renderSeedPrompt: hostId is required');
|
|
80
|
+
if (!slotName) throw new Error('renderSeedPrompt: slotName is required');
|
|
81
|
+
|
|
82
|
+
const override = overrideText !== undefined ? overrideText : loadSeedOverride();
|
|
83
|
+
if (override) {
|
|
84
|
+
return oneLine(
|
|
85
|
+
override.replace(/\{\{?\s*hostId\s*\}?\}/g, hostId).replace(/\{\{?\s*slotName\s*\}?\}/g, slotName)
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
return oneLine(builtInTemplate({ hostId, slotName }));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Split a one-line seed into <= maxLen chunks, each sent as its own `keys`
|
|
93
|
+
* call (each call appends its own Enter). Under maxLen this is a single
|
|
94
|
+
* chunk — the preferred, normal path. Splitting a seed that overflows
|
|
95
|
+
* maxLen means the resident receives it as multiple separate turns rather
|
|
96
|
+
* than one line, which is a documented degrade, not silent truncation.
|
|
97
|
+
*/
|
|
98
|
+
export function chunkSeedText(text, maxLen = 4000) {
|
|
99
|
+
const s = String(text || '');
|
|
100
|
+
if (s.length <= maxLen) return [s];
|
|
101
|
+
const chunks = [];
|
|
102
|
+
for (let i = 0; i < s.length; i += maxLen) {
|
|
103
|
+
chunks.push(s.slice(i, i + maxLen));
|
|
104
|
+
}
|
|
105
|
+
return chunks;
|
|
106
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The root `quox` program's global options, in one place so the binary
|
|
3
|
+
* (bin/quox.js) and the safety-flag test (test/safetyFlagsReachLeaf.test.js)
|
|
4
|
+
* build the SAME root. A test that re-typed this list would pass against a
|
|
5
|
+
* root the binary does not have.
|
|
6
|
+
*/
|
|
7
|
+
import { mergeShadowedGlobals, assertDryRunHonoured } from './global-flags.js';
|
|
8
|
+
|
|
9
|
+
export function applyRootOptions(program) {
|
|
10
|
+
return program
|
|
11
|
+
.option('-o, --output <format>', 'output format: table, json, csv, quiet', 'table')
|
|
12
|
+
.option('--json', 'shorthand for --output json')
|
|
13
|
+
.option('-q, --quiet', 'suppress output, show IDs only')
|
|
14
|
+
.option('--org <slug|id>', 'override active organization (slug, name or ID; must match your session org)')
|
|
15
|
+
.option('-p, --profile <name>', 'use a specific config profile')
|
|
16
|
+
.option('--no-color', 'disable colored output')
|
|
17
|
+
.option('-v, --verbose', 'show detailed output')
|
|
18
|
+
.option('--debug', 'show HTTP request/response details')
|
|
19
|
+
.option('--dry-run', 'show what would happen without executing')
|
|
20
|
+
.option('-y, --yes', 'skip confirmation prompts')
|
|
21
|
+
.option('--insecure', 'allow self-signed SSL certificates');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* The flag-delivery hooks every invocation needs, in order:
|
|
26
|
+
* 1. a value typed on the root or on a parent group reaches the leaf that
|
|
27
|
+
* declared the same option (commander binds it to the first command that
|
|
28
|
+
* knows it, so the leaf would otherwise read undefined or its default);
|
|
29
|
+
* 2. a --dry-run the leaf cannot honour is refused before anything runs.
|
|
30
|
+
*/
|
|
31
|
+
export function installFlagDeliveryHooks(program) {
|
|
32
|
+
program.hook('preAction', (thisCommand, actionCommand) => {
|
|
33
|
+
mergeShadowedGlobals(thisCommand, actionCommand);
|
|
34
|
+
});
|
|
35
|
+
program.hook('preAction', (thisCommand, actionCommand) => {
|
|
36
|
+
assertDryRunHonoured(thisCommand, actionCommand);
|
|
37
|
+
});
|
|
38
|
+
return program;
|
|
39
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ROUTEROS-WIRE: engine for `quox router`, the read-only MikroTik RouterOS views.
|
|
3
|
+
*
|
|
4
|
+
* Every verb is one NETWATCH tool run through the governed tool route
|
|
5
|
+
* (POST /api/v1/tools/execute, agent_id netwatch). The collector's mikrotikExecutor
|
|
6
|
+
* reaches the org's configured router through the allowlisted RouterOS proxy; nothing
|
|
7
|
+
* here talks to a router directly and no verb writes. Formatters are pure so the human
|
|
8
|
+
* output is tested without a router.
|
|
9
|
+
*/
|
|
10
|
+
import { resolveEndpoint } from '../api/endpoints.js';
|
|
11
|
+
|
|
12
|
+
/** verb -> the tool it runs. All read-only (aoclClassification layer 1, autonomy FULL). */
|
|
13
|
+
export const ROUTER_TOOLS = Object.freeze({
|
|
14
|
+
health: 'mikrotik_monitoring_health',
|
|
15
|
+
leases: 'mikrotik_dhcp_leases',
|
|
16
|
+
pools: 'mikrotik_dhcp_pool_status',
|
|
17
|
+
conflicts: 'mikrotik_dhcp_conflict_check',
|
|
18
|
+
'free-ip': 'mikrotik_dhcp_find_free_ip',
|
|
19
|
+
vpn: 'mikrotik_vpn_health',
|
|
20
|
+
wireguard: 'mikrotik_vpn_wireguard_peers',
|
|
21
|
+
inventory: 'mikrotik_discovery_inventory',
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
export function makeRouterExec({ client, orgId }) {
|
|
25
|
+
return async function exec(tool, input = {}) {
|
|
26
|
+
const ep = resolveEndpoint('v1.tools.execute');
|
|
27
|
+
const body = { tool_name: tool, input, agent_id: 'netwatch' };
|
|
28
|
+
if (orgId) body.org_id = orgId;
|
|
29
|
+
const data = await client.post(ep.path, { service: ep.service, body });
|
|
30
|
+
if (data && data.success === false) {
|
|
31
|
+
const detail = data.detail ? ` (${data.detail})` : '';
|
|
32
|
+
const err = new Error(`${tool} via POST ${ep.path} returned success:false: ${data.error || 'unknown error'}${detail}`);
|
|
33
|
+
err.toolError = data.error || 'unknown error';
|
|
34
|
+
err.tool = tool;
|
|
35
|
+
throw err;
|
|
36
|
+
}
|
|
37
|
+
return routerPayload(data);
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Unwrap the tool route envelope to the executor's own result object. */
|
|
42
|
+
export function routerPayload(data) {
|
|
43
|
+
let p = data;
|
|
44
|
+
for (let i = 0; i < 3 && p && typeof p === 'object' && !Array.isArray(p); i++) {
|
|
45
|
+
const inner = ['result', 'output', 'data'].map((k) => p[k]).find((v) => v !== undefined && v !== null);
|
|
46
|
+
if (inner === undefined) break;
|
|
47
|
+
p = inner;
|
|
48
|
+
}
|
|
49
|
+
if (typeof p === 'string') { try { p = JSON.parse(p); } catch { /* keep text */ } }
|
|
50
|
+
if (p && typeof p === 'object' && p.success === false) {
|
|
51
|
+
const err = new Error(`router read failed: ${p.error || 'unknown error'}${p.detail ? ` (${p.detail})` : ''}`);
|
|
52
|
+
err.toolError = p.error;
|
|
53
|
+
throw err;
|
|
54
|
+
}
|
|
55
|
+
return p;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const dash = (v) => (v === null || v === undefined || v === '' ? '-' : String(v));
|
|
59
|
+
const mib = (b) => (b === null || b === undefined ? '-' : `${Math.round(Number(b) / 1048576)} MiB`);
|
|
60
|
+
const pad = (rows) => {
|
|
61
|
+
const w = [];
|
|
62
|
+
for (const r of rows) r.forEach((c, i) => { w[i] = Math.max(w[i] || 0, String(c).length); });
|
|
63
|
+
return rows.map((r) => r.map((c, i) => (i === r.length - 1 ? String(c) : String(c).padEnd(w[i]))).join(' ').trimEnd());
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export function healthLines(p) {
|
|
67
|
+
const s = p.summary || {};
|
|
68
|
+
return [
|
|
69
|
+
`Router health score: ${dash(p.score)}/100`,
|
|
70
|
+
`CPU load: ${s.cpu_load_pct === null || s.cpu_load_pct === undefined ? '-' : `${s.cpu_load_pct}%`}${s.cpu_count ? ` (${s.cpu_count} cpu)` : ''}`,
|
|
71
|
+
`Memory: ${s.memory_used_pct === null || s.memory_used_pct === undefined ? '-' : `${s.memory_used_pct}% used`} (${mib(s.memory_free_bytes)} free of ${mib(s.memory_total_bytes)})`,
|
|
72
|
+
`Temperature: ${s.temperature_c === null || s.temperature_c === undefined ? 'unknown (no sensor reported)' : `${s.temperature_c} C`}`,
|
|
73
|
+
`Uptime: ${dash(s.uptime)}`,
|
|
74
|
+
`RouterOS: ${dash(s.version)} board ${dash(s.board_name)} ${dash(s.architecture)}`,
|
|
75
|
+
...(p.issues || []).map((i) => `! ${i}`),
|
|
76
|
+
];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function leaseLines(p) {
|
|
80
|
+
const rows = (p.leases || []).map((l) => [dash(l.address), dash(l['mac-address']), dash(l['host-name']), dash(l.status), l.dynamic === 'true' || l.dynamic === true ? 'dynamic' : 'static']);
|
|
81
|
+
return [`${p.total ?? rows.length} DHCP lease(s), ${p.bound ?? 0} bound`, ...(rows.length ? pad([['ADDRESS', 'MAC', 'HOST', 'STATUS', 'TYPE'], ...rows]) : [])];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function poolLines(p) {
|
|
85
|
+
const rows = (p.pools || []).map((x) => [dash(x.name), dash(x.ranges), x.size, x.used, x.free, x.used_pct === null || x.used_pct === undefined ? '-' : `${x.used_pct}%`]);
|
|
86
|
+
return [
|
|
87
|
+
`${p.pool_count ?? rows.length} DHCP pool(s): ${p.total_used ?? 0} of ${p.total_size ?? 0} addresses used, ${p.total_avail ?? 0} free`,
|
|
88
|
+
...(rows.length ? pad([['POOL', 'RANGES', 'SIZE', 'USED', 'FREE', 'USED%'], ...rows]) : []),
|
|
89
|
+
];
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function conflictLines(p) {
|
|
93
|
+
const out = [p.plain_english || (p.healthy ? 'No IP address conflicts found.' : 'IP address conflicts found.')];
|
|
94
|
+
for (const c of p.conflicts || []) {
|
|
95
|
+
out.push(` ${c.address}: ${(c.claims || []).map((k) => `${k.mac} (${(k.seen_in || []).join(', ')})`).join(' vs ') || (c.macs || []).join(' vs ')}`);
|
|
96
|
+
}
|
|
97
|
+
if (p.limits) out.push(`Limit: ${p.limits}`);
|
|
98
|
+
for (const i of p.issues || []) out.push(`! ${i}`);
|
|
99
|
+
return out;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function freeIpLines(p) {
|
|
103
|
+
const ips = p.free_ips || [];
|
|
104
|
+
return [
|
|
105
|
+
`${ips.length} free address(es) in pool ${dash(p.pool_searched)} (checked against DHCP leases${p.arp_checked ? ' and ARP' : ', ARP unavailable'}). Nothing was reserved.`,
|
|
106
|
+
...ips.map((f) => ` ${f.address} (${dash(f.pool)})`),
|
|
107
|
+
...(p.note ? [`! ${p.note}`] : []),
|
|
108
|
+
];
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function peerRow(x) {
|
|
112
|
+
return [dash(x.name || x.public_key), dash(x.interface), dash(x.endpoint), dash(x.allowed_address), dash(x.last_handshake), x.disabled ? 'disabled' : x.connected ? 'connected' : 'not connected'];
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function wireguardLines(p) {
|
|
116
|
+
const rows = (p.peers || []).map(peerRow);
|
|
117
|
+
return [
|
|
118
|
+
`${p.total ?? rows.length} WireGuard peer(s), ${p.connected ?? 0} connected`,
|
|
119
|
+
...(rows.length ? pad([['PEER', 'IFACE', 'ENDPOINT', 'ALLOWED', 'HANDSHAKE', 'STATE'], ...rows]) : []),
|
|
120
|
+
...(p.connected_rule ? [`Rule: ${p.connected_rule}`] : []),
|
|
121
|
+
];
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function vpnLines(p) {
|
|
125
|
+
const h = p.health || {};
|
|
126
|
+
const out = [p.plain_english || `${p.tunnels_up ?? 0} of ${p.tunnels_total ?? 0} VPN tunnel(s) up.`, `Score: ${p.score === null || p.score === undefined ? '- (no tunnels)' : `${p.score}/100`}`];
|
|
127
|
+
for (const x of h.wireguard || []) out.push(` wg peer ${peerRow(x)[0]}: ${peerRow(x)[5]} (handshake ${dash(x.last_handshake)})`);
|
|
128
|
+
for (const x of h.ipsec || []) out.push(` ipsec ${dash(x.remote_address)}: ${dash(x.state)}, up ${dash(x.uptime)}`);
|
|
129
|
+
for (const i of p.issues || []) out.push(`! ${i}`);
|
|
130
|
+
return out;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function inventoryLines(p) {
|
|
134
|
+
const routers = (p.routers || []).map((r) => [dash(r.identity || [r.platform, r.board_name].filter(Boolean).join(' ')), dash(r.role), dash(r.address), dash(r.version), dash(r.board_name)]);
|
|
135
|
+
const devices = (p.devices || []).map((d) => [dash(d.address), dash(d.mac), dash(d.host_name), dash(d.interface), d.seen_in_arp ? 'arp' : 'lease only']);
|
|
136
|
+
return [
|
|
137
|
+
`${p.router_count ?? routers.length} MikroTik router(s)`,
|
|
138
|
+
...pad([['ROUTER', 'ROLE', 'ADDRESS', 'VERSION', 'BOARD'], ...routers]),
|
|
139
|
+
`${p.total ?? devices.length} connected device(s)`,
|
|
140
|
+
...(devices.length ? pad([['ADDRESS', 'MAC', 'HOST', 'IFACE', 'SEEN'], ...devices]) : []),
|
|
141
|
+
];
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export const ROUTER_FORMATTERS = Object.freeze({
|
|
145
|
+
health: healthLines, leases: leaseLines, pools: poolLines, conflicts: conflictLines,
|
|
146
|
+
'free-ip': freeIpLines, vpn: vpnLines, wireguard: wireguardLines, inventory: inventoryLines,
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
/** `quox router trust --show`: the fingerprint to compare out of band. Pure. */
|
|
150
|
+
export function formatTrust(d) {
|
|
151
|
+
if (!d || d.success === false) {
|
|
152
|
+
return [`Could not read the router certificate: ${(d && (d.error || d.detail)) || 'unknown error'}`, 'No credentials were sent.'];
|
|
153
|
+
}
|
|
154
|
+
const lines = [
|
|
155
|
+
`Router endpoint: ${d.host}:${d.port}`,
|
|
156
|
+
`Certificate SHA-256: ${d.cert_sha256_formatted}`,
|
|
157
|
+
`Public key SHA-256: ${d.spki_sha256_formatted}`,
|
|
158
|
+
`Subject: ${d.subject ? String(d.subject).replace(/\n/g, ', ') : '-'}`,
|
|
159
|
+
`Issuer: ${d.issuer ? String(d.issuer).replace(/\n/g, ', ') : '-'}${d.self_signed ? ' (self-signed)' : ''}`,
|
|
160
|
+
`Valid until: ${d.valid_to || '-'}`,
|
|
161
|
+
d.pin_configured
|
|
162
|
+
? (d.matches_configured_pin ? 'MIKROTIK_TLS_PIN is set and MATCHES this endpoint.' : 'MIKROTIK_TLS_PIN is set and does NOT match this endpoint. Do not trust it until you know why.')
|
|
163
|
+
: 'MIKROTIK_TLS_PIN is not set, so the collector sends nothing to this router yet.',
|
|
164
|
+
'No credentials were sent. Nothing was pinned.',
|
|
165
|
+
'Compare the fingerprint with the one on the router itself (System > Certificates) over a channel you trust, then set MIKROTIK_TLS_PIN to the SHA-256 value.',
|
|
166
|
+
];
|
|
167
|
+
return lines;
|
|
168
|
+
}
|