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,706 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `quox qlarity` — governed browser inspection from the terminal.
|
|
3
|
+
*
|
|
4
|
+
* House gate: nothing ships without CLI parity, and it matters more here than
|
|
5
|
+
* usual. Qlarity exists so an agent stops guessing at runtime state and stops
|
|
6
|
+
* asking a human to paste the console. An agent reaches for a CLI, not a
|
|
7
|
+
* dashboard panel, so this IS the primary surface and the view mirrors it.
|
|
8
|
+
*
|
|
9
|
+
* Every call goes through the collector proxy, which is the governed boundary:
|
|
10
|
+
* it resolves the org, enforces the licence gate and emits the AEE envelope.
|
|
11
|
+
* There is no direct-to-service flag, deliberately — BLUEPRINT defect 5.1 is
|
|
12
|
+
* exactly a standalone CLI that defaults to the ungoverned path, and this
|
|
13
|
+
* command was written after that finding rather than before it.
|
|
14
|
+
*
|
|
15
|
+
* The one-shot form is the one people will actually use:
|
|
16
|
+
*
|
|
17
|
+
* quox qlarity read http://localhost:5173/ --selector .card --inspector pseudo_style
|
|
18
|
+
*
|
|
19
|
+
* It opens a session, reads, prints, and closes. A leaked session is a browser
|
|
20
|
+
* context sitting on the server until the reaper takes it, so the convenient
|
|
21
|
+
* path is also the tidy one.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import fs from 'node:fs';
|
|
25
|
+
import path from 'node:path';
|
|
26
|
+
import os from 'node:os';
|
|
27
|
+
import { resolveEndpoint } from '../api/endpoints.js';
|
|
28
|
+
import { resolveOrgId } from './helpers.js';
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Rethrow so bin/quox.js's handleError sets the house exit code (3 auth,
|
|
32
|
+
* 7 network, statusToExitCode otherwise). An earlier version caught
|
|
33
|
+
* everything and exited 1, which looked fine and quietly broke the contract
|
|
34
|
+
* every other command keeps: `quox plan` exits 3 on an expired session and
|
|
35
|
+
* this exited 1 for the same cause.
|
|
36
|
+
*
|
|
37
|
+
* The added value here is the EXPLANATION, not the exit code, so annotate and
|
|
38
|
+
* rethrow rather than swallow.
|
|
39
|
+
*/
|
|
40
|
+
function annotate(err) {
|
|
41
|
+
const msg = err?.message || String(err);
|
|
42
|
+
if (/not licensed/i.test(msg)) {
|
|
43
|
+
err.message = `${msg}\nThe service is running and refusing you: that is the licence gate working, not a fault.`;
|
|
44
|
+
} else if (/egress_denied|not_in_allowlist/i.test(msg)) {
|
|
45
|
+
err.message = `${msg}\nThe origin is not on this deployment's allowlist. Default-deny is the policy; widen it deliberately.`;
|
|
46
|
+
} else if (/org_context_required/i.test(msg)) {
|
|
47
|
+
err.message = `${msg}\nNo organisation context. Run \`quox auth login\` or select an org first.`;
|
|
48
|
+
}
|
|
49
|
+
throw err;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// quox#907: a browser login is slower than a read; well above the client's 30s default.
|
|
53
|
+
const LOGIN_TIMEOUT_MS = 120000;
|
|
54
|
+
|
|
55
|
+
async function call(client, id, params = {}, body, extra = {}) {
|
|
56
|
+
const ep = resolveEndpoint(id, params);
|
|
57
|
+
const opts = { service: ep.service, ...extra };
|
|
58
|
+
if (body) opts.body = body;
|
|
59
|
+
switch (ep.method) {
|
|
60
|
+
case 'POST': return client.post(ep.path, opts);
|
|
61
|
+
case 'DELETE': return client.delete(ep.path, opts);
|
|
62
|
+
default: return client.get(ep.path, opts);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Local bookkeeping for `quox qlarity login` (issue #15): which server-side
|
|
68
|
+
* session, per origin, THIS CLI created and successfully logged in. This is
|
|
69
|
+
* deliberately client-side rather than a qlarity/collector concept — the
|
|
70
|
+
* server has no notion of "a session the CLI is allowed to reuse", only
|
|
71
|
+
* sessions and their org. A one-shot verb cross-checks an entry here against
|
|
72
|
+
* the server's live session list (see findReusableSessionId) before ever
|
|
73
|
+
* trusting it, so a stale or server-reaped entry is never silently reused.
|
|
74
|
+
*/
|
|
75
|
+
function qlaritySessionsFile() {
|
|
76
|
+
return path.join(os.homedir(), '.local', 'share', 'quox', 'qlarity-sessions.json');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function readLoggedInSessions() {
|
|
80
|
+
try {
|
|
81
|
+
const parsed = JSON.parse(fs.readFileSync(qlaritySessionsFile(), 'utf8'));
|
|
82
|
+
return (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) ? parsed : {};
|
|
83
|
+
} catch {
|
|
84
|
+
return {};
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function writeLoggedInSessions(map) {
|
|
89
|
+
const file = qlaritySessionsFile();
|
|
90
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
91
|
+
fs.writeFileSync(file, JSON.stringify(map, null, 2));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Pure matcher, exported for unit tests: given the CLI's own origin ->
|
|
96
|
+
* session bookkeeping and the ORG's live session list (from
|
|
97
|
+
* `qlarity.sessions.list`), decide which session id (if any) is safe to
|
|
98
|
+
* reuse for `targetOrigin`. A recorded session that the server no longer
|
|
99
|
+
* lists (reaped, closed, or belonging to a different org entirely) is never
|
|
100
|
+
* reused — that would either 404 or silently attach to the wrong context.
|
|
101
|
+
* @param {Record<string, {sessionId: string}>} loggedInMap
|
|
102
|
+
* @param {Array<{id: string}>} liveSessions
|
|
103
|
+
* @param {string} targetOrigin
|
|
104
|
+
* @returns {string|null}
|
|
105
|
+
*/
|
|
106
|
+
export function findReusableSessionId(loggedInMap, liveSessions, targetOrigin) {
|
|
107
|
+
const entry = loggedInMap?.[targetOrigin];
|
|
108
|
+
if (!entry?.sessionId) return null;
|
|
109
|
+
const alive = Array.isArray(liveSessions) && liveSessions.some((s) => s.id === entry.sessionId);
|
|
110
|
+
return alive ? entry.sessionId : null;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* The one-shot lifecycle every single-page command shares: reuse a session
|
|
115
|
+
* `quox qlarity login` already authenticated for this origin when one is
|
|
116
|
+
* still live, otherwise create a fresh one, then open the url on it.
|
|
117
|
+
*
|
|
118
|
+
* Callers are responsible for closing a FRESH session on every exit path
|
|
119
|
+
* (see `read` below) — that half is NOT folded in here, because a command
|
|
120
|
+
* that fails between open and its own work still needs the session id to
|
|
121
|
+
* close it, AND a reused/explicit session must never be auto-closed by a
|
|
122
|
+
* one-shot verb (that would silently log the caller back out). The
|
|
123
|
+
* `reused` flag on the return value is what a caller uses to decide.
|
|
124
|
+
*/
|
|
125
|
+
async function openSession(client, url, { waitUntil, extraWaitMs, sessionId: explicitSessionId } = {}) {
|
|
126
|
+
let sessionId = explicitSessionId || null;
|
|
127
|
+
let reused = Boolean(explicitSessionId);
|
|
128
|
+
|
|
129
|
+
if (!sessionId) {
|
|
130
|
+
try {
|
|
131
|
+
const origin = new URL(url).origin;
|
|
132
|
+
const loggedIn = readLoggedInSessions();
|
|
133
|
+
if (loggedIn[origin]) {
|
|
134
|
+
const live = await call(client, 'qlarity.sessions.list');
|
|
135
|
+
const reusable = findReusableSessionId(loggedIn, live.sessions || [], origin);
|
|
136
|
+
if (reusable) {
|
|
137
|
+
sessionId = reusable;
|
|
138
|
+
reused = true;
|
|
139
|
+
} else {
|
|
140
|
+
// Stale bookkeeping (the session was reaped or closed server-side):
|
|
141
|
+
// drop it so the next one-shot does not re-check a dead session id.
|
|
142
|
+
delete loggedIn[origin];
|
|
143
|
+
writeLoggedInSessions(loggedIn);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
} catch {
|
|
147
|
+
// Reuse is an optimisation, never a hard requirement — fall through to
|
|
148
|
+
// creating a fresh session exactly as before this feature existed.
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (!sessionId) {
|
|
153
|
+
const s = await call(client, 'qlarity.session.create', {}, {});
|
|
154
|
+
sessionId = s.id;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const openBody = { url };
|
|
158
|
+
// SPA pages finish rendering AFTER the load event, so a plain 'load' capture
|
|
159
|
+
// catches a splash/initialising screen. These pass through to qlarity's
|
|
160
|
+
// navigate (wait_until + extra_wait_ms) so one-shots can wait for the app.
|
|
161
|
+
if (waitUntil) openBody.wait_until = waitUntil;
|
|
162
|
+
if (extraWaitMs) openBody.extra_wait_ms = extraWaitMs;
|
|
163
|
+
let opened;
|
|
164
|
+
try {
|
|
165
|
+
opened = await call(client, 'qlarity.session.open', { sessionId }, openBody);
|
|
166
|
+
} catch (err) {
|
|
167
|
+
// quox#680: this throws BEFORE the caller learns the session id, so the
|
|
168
|
+
// caller's finally cannot close it. A FRESH session is closed right here
|
|
169
|
+
// (three failed opens used to fill the org's 3-session cap -> 429). A
|
|
170
|
+
// reused/logged-in session is left alone, same as the callers' rule.
|
|
171
|
+
if (!reused) {
|
|
172
|
+
try { await call(client, 'qlarity.session.close', { sessionId }); } catch { /* reaper will get it */ }
|
|
173
|
+
}
|
|
174
|
+
throw err;
|
|
175
|
+
}
|
|
176
|
+
return { sessionId, opened, reused };
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** Shared parse of the settle options common to the one-shot inspection verbs. */
|
|
180
|
+
function settleOpts(options) {
|
|
181
|
+
const out = {};
|
|
182
|
+
if (options.waitUntil) out.waitUntil = options.waitUntil;
|
|
183
|
+
if (options.settle) {
|
|
184
|
+
const ms = parseInt(options.settle, 10);
|
|
185
|
+
if (Number.isFinite(ms) && ms > 0) out.extraWaitMs = ms;
|
|
186
|
+
}
|
|
187
|
+
return out;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* quox#735: parse `--viewport WxH` and `--device-scale-factor n` for screenshot.
|
|
192
|
+
* Bounds mirror qlarity's server-side check (min 320x320, max 3840x2160, dsf
|
|
193
|
+
* 1-3) so a bad value fails here, before a session is created. Returns the
|
|
194
|
+
* body fragment to merge into the screenshot request ({} when neither given).
|
|
195
|
+
* @throws {Error} with a plain message on invalid input
|
|
196
|
+
*/
|
|
197
|
+
export function parseCaptureGeometryOptions({ viewport, deviceScaleFactor } = {}) {
|
|
198
|
+
const out = {};
|
|
199
|
+
if (viewport !== undefined) {
|
|
200
|
+
const m = /^(\d+)x(\d+)$/i.exec(String(viewport).trim());
|
|
201
|
+
if (!m) throw new Error(`Invalid --viewport "${viewport}": expected WxH, e.g. 1440x900`);
|
|
202
|
+
const width = parseInt(m[1], 10);
|
|
203
|
+
const height = parseInt(m[2], 10);
|
|
204
|
+
if (width < 320 || height < 320 || width > 3840 || height > 2160) {
|
|
205
|
+
throw new Error(`Invalid --viewport ${width}x${height}: must be between 320x320 and 3840x2160`);
|
|
206
|
+
}
|
|
207
|
+
out.viewport = { width, height };
|
|
208
|
+
}
|
|
209
|
+
if (deviceScaleFactor !== undefined) {
|
|
210
|
+
const n = Number(deviceScaleFactor);
|
|
211
|
+
if (!Number.isFinite(n) || n < 1 || n > 3) {
|
|
212
|
+
throw new Error(`Invalid --device-scale-factor "${deviceScaleFactor}": must be a number from 1 to 3`);
|
|
213
|
+
}
|
|
214
|
+
out.device_scale_factor = n;
|
|
215
|
+
}
|
|
216
|
+
return out;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/** Where one-shot screenshots land. Binary, so stdout is the wrong place. */
|
|
220
|
+
function screenshotDir() {
|
|
221
|
+
return path.join(os.homedir(), '.local', 'share', 'quox', 'qlarity-shots');
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function screenshotFilename(url) {
|
|
225
|
+
let host = 'page';
|
|
226
|
+
try { host = new URL(url).hostname || host; } catch { /* keep default */ }
|
|
227
|
+
const ts = new Date().toISOString().replace(/[:.]/g, '-');
|
|
228
|
+
return `${host}-${ts}.png`;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export function registerQlarityCommands(program, ctx) {
|
|
232
|
+
const { client } = ctx;
|
|
233
|
+
|
|
234
|
+
const q = program
|
|
235
|
+
.command('qlarity')
|
|
236
|
+
.description('Qlarity: open a page, ask it a question, keep the receipt');
|
|
237
|
+
|
|
238
|
+
// quox#560: `read`/`console`/`network` declare their own local `--json`
|
|
239
|
+
// flag (for `--json` without a preceding `-o`/`--output`), which
|
|
240
|
+
// `mergeShadowedGlobals` (lib/core/global-flags.js) correctly hands down
|
|
241
|
+
// when the root `--json` shorthand is used. But the root's OTHER, equally
|
|
242
|
+
// canonical way to request JSON — `-o json` / `--output json`, which every
|
|
243
|
+
// other command in the CLI honours via `program.opts().output` — is a
|
|
244
|
+
// DIFFERENT attribute name (`output`, not `json`) that these subcommands
|
|
245
|
+
// never declare locally, so there is nothing for the merge hook to hand
|
|
246
|
+
// down and `-o json qlarity read ...` silently fell back to table-ish
|
|
247
|
+
// text output. Checking the root's resolved output format here alongside
|
|
248
|
+
// the local flag makes both spellings equivalent, matching every other
|
|
249
|
+
// command.
|
|
250
|
+
const wantsJson = (options) => Boolean(options.json) || program.opts().output === 'json';
|
|
251
|
+
|
|
252
|
+
q.command('health')
|
|
253
|
+
.description('Service health through the governed proxy')
|
|
254
|
+
.action(async () => {
|
|
255
|
+
try {
|
|
256
|
+
const d = await call(client, 'qlarity.health');
|
|
257
|
+
process.stdout.write(`\u{1F50D} QLARITY: ${d.status} — ${d.sessions} session(s), egress ${d.egress}\n`);
|
|
258
|
+
} catch (err) {
|
|
259
|
+
annotate(err);
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
q.command('allowlist')
|
|
264
|
+
.description('Which origins this deployment may inspect')
|
|
265
|
+
.action(async () => {
|
|
266
|
+
try {
|
|
267
|
+
const d = await call(client, 'qlarity.allowlist');
|
|
268
|
+
process.stdout.write(`policy: ${d.policy}\n`);
|
|
269
|
+
if (!d.entries?.length) {
|
|
270
|
+
process.stdout.write('no origins declared — nothing can be inspected (safe default)\n');
|
|
271
|
+
} else {
|
|
272
|
+
for (const e of d.entries) process.stdout.write(` ${e}\n`);
|
|
273
|
+
}
|
|
274
|
+
} catch (err) {
|
|
275
|
+
annotate(err);
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
// NO-ENV-ONLY-CONFIG (2026-08-18). `allowlist` above reports the install-wide
|
|
280
|
+
// env list an operator set on the host. THIS group is the one a customer can
|
|
281
|
+
// change themselves, which is what makes QLarity usable by its own buyer:
|
|
282
|
+
// before it, a new org opened QLarity, was told nothing could be inspected,
|
|
283
|
+
// and had no way to declare an origin without shell access.
|
|
284
|
+
const al = q.command('origins')
|
|
285
|
+
.description("The origins THIS org may inspect (add/remove without touching the host)");
|
|
286
|
+
|
|
287
|
+
al.command('list', { isDefault: true })
|
|
288
|
+
.description('Show this org\'s declared origins, and the install-wide ones')
|
|
289
|
+
.action(async () => {
|
|
290
|
+
try {
|
|
291
|
+
const d = await call(client, 'qlarity.orgAllowlist.list');
|
|
292
|
+
process.stdout.write(`policy: ${d.policy}\n`);
|
|
293
|
+
if (d.origins?.length) {
|
|
294
|
+
process.stdout.write('this org:\n');
|
|
295
|
+
for (const e of d.origins) process.stdout.write(` ${e.origin}${e.note ? ` (${e.note})` : ''}\n`);
|
|
296
|
+
} else {
|
|
297
|
+
process.stdout.write("this org: none declared yet — add one with `quox qlarity origins add <origin>`\n");
|
|
298
|
+
}
|
|
299
|
+
if (d.install_wide?.length) {
|
|
300
|
+
process.stdout.write('install-wide (set by the operator, applies to every org):\n');
|
|
301
|
+
for (const e of d.install_wide) process.stdout.write(` ${e}\n`);
|
|
302
|
+
}
|
|
303
|
+
} catch (err) {
|
|
304
|
+
annotate(err);
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
al.command('add <origin>')
|
|
309
|
+
.description('Declare an origin this org may inspect, e.g. https://staging.example.com')
|
|
310
|
+
.option('--note <text>', 'why it is allowed, shown alongside it later')
|
|
311
|
+
.action(async (origin, opts) => {
|
|
312
|
+
try {
|
|
313
|
+
const d = await call(client, 'qlarity.orgAllowlist.add', {}, { origin, note: opts.note });
|
|
314
|
+
process.stdout.write(`\u{1F50D} QLARITY: added ${d.entry.origin}\n`);
|
|
315
|
+
process.stdout.write(` ${d.origins.length} origin(s) now declared for this org\n`);
|
|
316
|
+
} catch (err) {
|
|
317
|
+
// A refusal here is usually a wildcard or a typo, and the server sends
|
|
318
|
+
// the reason. Surface it instead of a bare 400.
|
|
319
|
+
if (err?.status === 400 && err?.message) {
|
|
320
|
+
process.stderr.write(`Not added: ${err.message}\n`);
|
|
321
|
+
process.exitCode = 1;
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
annotate(err);
|
|
325
|
+
}
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
al.command('remove <origin>')
|
|
329
|
+
.description('Stop allowing an origin')
|
|
330
|
+
.action(async (origin) => {
|
|
331
|
+
try {
|
|
332
|
+
const d = await call(client, 'qlarity.orgAllowlist.remove', { origin });
|
|
333
|
+
if (d.removed) process.stdout.write(`\u{1F50D} QLARITY: removed ${origin}\n`);
|
|
334
|
+
else process.stdout.write(`nothing to remove: ${origin} was not declared for this org\n`);
|
|
335
|
+
} catch (err) {
|
|
336
|
+
annotate(err);
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
q.command('inspectors')
|
|
341
|
+
.description('The named reads available (data in, never code)')
|
|
342
|
+
.action(async () => {
|
|
343
|
+
try {
|
|
344
|
+
const d = await call(client, 'qlarity.inspectors.list');
|
|
345
|
+
for (const i of d.inspectors || []) {
|
|
346
|
+
process.stdout.write(` ${i.name.padEnd(16)} ${i.describe}\n`);
|
|
347
|
+
}
|
|
348
|
+
} catch (err) {
|
|
349
|
+
annotate(err);
|
|
350
|
+
}
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
q.command('sessions')
|
|
354
|
+
.description('Open sessions for this org')
|
|
355
|
+
.action(async () => {
|
|
356
|
+
try {
|
|
357
|
+
const d = await call(client, 'qlarity.sessions.list');
|
|
358
|
+
if (!d.sessions?.length) { process.stdout.write('no open sessions\n'); return; }
|
|
359
|
+
for (const s of d.sessions) {
|
|
360
|
+
process.stdout.write(` ${s.id} ${s.url || '(no page)'} last used ${s.last_used_at}\n`);
|
|
361
|
+
}
|
|
362
|
+
} catch (err) {
|
|
363
|
+
annotate(err);
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
q.command('close <sessionId>')
|
|
368
|
+
.description('Close a session and free its browser context')
|
|
369
|
+
.action(async (sessionId) => {
|
|
370
|
+
try {
|
|
371
|
+
await call(client, 'qlarity.session.close', { sessionId });
|
|
372
|
+
process.stdout.write(`closed ${sessionId}\n`);
|
|
373
|
+
} catch (err) {
|
|
374
|
+
annotate(err);
|
|
375
|
+
}
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* The workhorse. Opens, reads, prints, closes — including on failure, so a
|
|
380
|
+
* bad selector does not strand a browser context.
|
|
381
|
+
*/
|
|
382
|
+
q.command('read <url>')
|
|
383
|
+
.description('One-shot: open a page, run an inspector, print the result, close')
|
|
384
|
+
.option('--inspector <name>', 'named inspector', 'computed_style')
|
|
385
|
+
.option('--selector <css>', 'CSS selector for the inspector')
|
|
386
|
+
.option('--properties <list>', 'comma-separated CSS properties to narrow the result')
|
|
387
|
+
.option('--key <key>', 'storage key (storage inspector)')
|
|
388
|
+
.option('--assert <type>', 'assertion for the `expect` inspector (exists|visible|text_contains|count_equals|attribute_equals|…)')
|
|
389
|
+
.option('--value <value>', 'expected value for the `expect` inspector')
|
|
390
|
+
.option('--name <attr>', 'attribute name (attribute / expect attribute_equals inspectors)')
|
|
391
|
+
.option('--widths <list>', 'comma-separated viewport widths for the `responsive_sweep` inspector')
|
|
392
|
+
.option('--depth <n>', 'tree depth for the `dom_summary` inspector')
|
|
393
|
+
.option('--wait-until <cond>', 'navigation wait: load | domcontentloaded | networkidle')
|
|
394
|
+
.option('--settle <ms>', 'extra settle time after navigation, ms')
|
|
395
|
+
.option('--session <id>', 'reuse an existing session (e.g. one left open by `quox qlarity login`) instead of opening a fresh one')
|
|
396
|
+
.option('--json', 'raw JSON output')
|
|
397
|
+
.action(async (url, options) => {
|
|
398
|
+
let sessionId = null;
|
|
399
|
+
let reused = false;
|
|
400
|
+
try {
|
|
401
|
+
const { sessionId: sid, opened, reused: wasReused } = await openSession(client, url, { ...settleOpts(options), sessionId: options.session });
|
|
402
|
+
sessionId = sid;
|
|
403
|
+
reused = wasReused;
|
|
404
|
+
|
|
405
|
+
const args = {};
|
|
406
|
+
if (options.selector) args.selector = options.selector;
|
|
407
|
+
if (options.key) args.key = options.key;
|
|
408
|
+
if (options.properties) args.properties = options.properties.split(',').map((x) => x.trim());
|
|
409
|
+
// clarify-parity inspectors: expect (assert/value/name), responsive_sweep
|
|
410
|
+
// (widths), dom_summary (depth). Passed as data; the fixed page-side body
|
|
411
|
+
// lives in qlarity/src/inspectors.js.
|
|
412
|
+
if (options.assert) args.assert = options.assert;
|
|
413
|
+
if (options.value !== undefined) args.value = options.value;
|
|
414
|
+
if (options.name) args.name = options.name;
|
|
415
|
+
if (options.widths) args.widths = options.widths.split(',').map((x) => x.trim());
|
|
416
|
+
if (options.depth !== undefined) args.depth = parseInt(options.depth, 10);
|
|
417
|
+
|
|
418
|
+
const r = await call(client, 'qlarity.session.inspect', { sessionId },
|
|
419
|
+
{ inspector: options.inspector, ...args });
|
|
420
|
+
|
|
421
|
+
const blocked = await call(client, 'qlarity.session.blocked', { sessionId });
|
|
422
|
+
|
|
423
|
+
if (wantsJson(options)) {
|
|
424
|
+
process.stdout.write(`${JSON.stringify({ page: opened, result: r.result, blocked: blocked.blocked }, null, 2)}\n`);
|
|
425
|
+
} else {
|
|
426
|
+
process.stdout.write(`\u{1F50D} QLARITY: ${opened.url} (${opened.status}) ${opened.title || ''}\n\n`);
|
|
427
|
+
process.stdout.write(`${options.inspector}:\n${JSON.stringify(r.result, null, 2)}\n`);
|
|
428
|
+
if (blocked.blocked?.length) {
|
|
429
|
+
// Surfaced even on success: a page that silently failed to load a
|
|
430
|
+
// subresource looks fine and is not.
|
|
431
|
+
process.stdout.write(`\nblocked by egress policy (${blocked.blocked.length}):\n`);
|
|
432
|
+
for (const b of blocked.blocked) process.stdout.write(` ${b.url} -> ${b.reason}\n`);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
} catch (err) {
|
|
436
|
+
annotate(err);
|
|
437
|
+
} finally {
|
|
438
|
+
// Always, including on failure — UNLESS the session was reused (a
|
|
439
|
+
// `quox qlarity login` session, or an explicit --session): closing
|
|
440
|
+
// one of those would silently log the caller back out. A stranded
|
|
441
|
+
// FRESH context lives until the reaper and counts against this
|
|
442
|
+
// org's concurrency cap meanwhile, so those are always closed.
|
|
443
|
+
if (sessionId && !reused) {
|
|
444
|
+
try { await call(client, 'qlarity.session.close', { sessionId }); } catch { /* reaper will get it */ }
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* Console and network are read-only recorders attached at page open (see
|
|
451
|
+
* the server's attachRecorders), so a one-shot here needs no `inspect`
|
|
452
|
+
* step: open, read the ring buffer, print, close.
|
|
453
|
+
*/
|
|
454
|
+
q.command('console <url>')
|
|
455
|
+
.description('One-shot: open a page, print its console log, close')
|
|
456
|
+
.option('--wait-until <cond>', 'navigation wait: load | domcontentloaded | networkidle')
|
|
457
|
+
.option('--settle <ms>', 'extra settle time after navigation, ms')
|
|
458
|
+
.option('--session <id>', 'reuse an existing session instead of opening a fresh one')
|
|
459
|
+
.option('--json', 'raw JSON output')
|
|
460
|
+
.action(async (url, options) => {
|
|
461
|
+
let sessionId = null;
|
|
462
|
+
let reused = false;
|
|
463
|
+
try {
|
|
464
|
+
const { sessionId: sid, opened, reused: wasReused } = await openSession(client, url, { ...settleOpts(options), sessionId: options.session });
|
|
465
|
+
sessionId = sid;
|
|
466
|
+
reused = wasReused;
|
|
467
|
+
|
|
468
|
+
const c = await call(client, 'qlarity.session.console', { sessionId });
|
|
469
|
+
|
|
470
|
+
if (wantsJson(options)) {
|
|
471
|
+
process.stdout.write(`${JSON.stringify({ page: opened, messages: c.messages, total: c.total, dropped: c.dropped }, null, 2)}\n`);
|
|
472
|
+
} else {
|
|
473
|
+
process.stdout.write(`\u{1F50D} QLARITY: ${opened.url} (${opened.status}) ${opened.title || ''}\n\n`);
|
|
474
|
+
process.stdout.write(`console (${c.total}):\n${JSON.stringify(c.messages, null, 2)}\n`);
|
|
475
|
+
if (c.dropped) {
|
|
476
|
+
// Reported, never hidden: a caller reading a truncated log would
|
|
477
|
+
// otherwise draw the wrong conclusion about what the page logged.
|
|
478
|
+
process.stdout.write(`\n${c.dropped} message(s) dropped (ring buffer full)\n`);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
} catch (err) {
|
|
482
|
+
annotate(err);
|
|
483
|
+
} finally {
|
|
484
|
+
if (sessionId && !reused) {
|
|
485
|
+
try { await call(client, 'qlarity.session.close', { sessionId }); } catch { /* reaper will get it */ }
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
q.command('network <url>')
|
|
491
|
+
.description('One-shot: open a page, print its network log, close')
|
|
492
|
+
.option('--wait-until <cond>', 'navigation wait: load | domcontentloaded | networkidle')
|
|
493
|
+
.option('--settle <ms>', 'extra settle time after navigation, ms')
|
|
494
|
+
.option('--session <id>', 'reuse an existing session instead of opening a fresh one')
|
|
495
|
+
.option('--json', 'raw JSON output')
|
|
496
|
+
.action(async (url, options) => {
|
|
497
|
+
let sessionId = null;
|
|
498
|
+
let reused = false;
|
|
499
|
+
try {
|
|
500
|
+
const { sessionId: sid, opened, reused: wasReused } = await openSession(client, url, { ...settleOpts(options), sessionId: options.session });
|
|
501
|
+
sessionId = sid;
|
|
502
|
+
reused = wasReused;
|
|
503
|
+
|
|
504
|
+
const n = await call(client, 'qlarity.session.network', { sessionId });
|
|
505
|
+
|
|
506
|
+
if (wantsJson(options)) {
|
|
507
|
+
process.stdout.write(`${JSON.stringify({ page: opened, events: n.events, total: n.total, dropped: n.dropped }, null, 2)}\n`);
|
|
508
|
+
} else {
|
|
509
|
+
process.stdout.write(`\u{1F50D} QLARITY: ${opened.url} (${opened.status}) ${opened.title || ''}\n\n`);
|
|
510
|
+
process.stdout.write(`network (${n.total}):\n${JSON.stringify(n.events, null, 2)}\n`);
|
|
511
|
+
if (n.dropped) {
|
|
512
|
+
process.stdout.write(`\n${n.dropped} event(s) dropped (ring buffer full)\n`);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
} catch (err) {
|
|
516
|
+
annotate(err);
|
|
517
|
+
} finally {
|
|
518
|
+
if (sessionId && !reused) {
|
|
519
|
+
try { await call(client, 'qlarity.session.close', { sessionId }); } catch { /* reaper will get it */ }
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
/**
|
|
525
|
+
* Screenshots come back as base64 JSON (see qlarity's app.js: the digest
|
|
526
|
+
* is what makes an image witnessable, and the governed proxy assumes
|
|
527
|
+
* JSON), so this writes the decoded bytes to a file and prints the path —
|
|
528
|
+
* stdout is the wrong place for binary.
|
|
529
|
+
*/
|
|
530
|
+
q.command('screenshot <url>')
|
|
531
|
+
.description('One-shot: open a page, capture a screenshot, save it to a file, close')
|
|
532
|
+
.option('--selector <css>', 'CSS selector to capture a single element instead of the viewport')
|
|
533
|
+
.option('--full-page', 'capture the full scrollable page, not just the viewport')
|
|
534
|
+
.option('--wait-until <cond>', "navigation wait: load | domcontentloaded | networkidle (SPAs want networkidle)")
|
|
535
|
+
.option('--settle <ms>', 'extra settle time after navigation, ms (for SPAs that hydrate after load)')
|
|
536
|
+
.option('--wait-for <css>', 'wait for this element to be visible before the shot (SPAs paint after load; capped 10s)')
|
|
537
|
+
.option('--wait-ms <n>', 'wait this many ms right before the shot, capped 10s (prefer --wait-for when you know the element)', (v) => parseInt(v, 10))
|
|
538
|
+
.option('--no-reduced-motion', 'do NOT emulate prefers-reduced-motion (default emulates it so animated heroes/reveals render for the capture)')
|
|
539
|
+
.option('--viewport <WxH>', 'capture viewport for this call, e.g. 1440x900 or 390x844 (320x320 to 3840x2160; default 1280x720)')
|
|
540
|
+
.option('--device-scale-factor <n>', 'device pixel ratio for this call, 1 to 3 (default 1)')
|
|
541
|
+
.option('--session <id>', 'reuse an existing session instead of opening a fresh one')
|
|
542
|
+
.action(async (url, options) => {
|
|
543
|
+
let sessionId = null;
|
|
544
|
+
let reused = false;
|
|
545
|
+
try {
|
|
546
|
+
// Validate before any session exists: nothing to leak on a typo.
|
|
547
|
+
let geometry;
|
|
548
|
+
try {
|
|
549
|
+
geometry = parseCaptureGeometryOptions(options);
|
|
550
|
+
} catch (e) {
|
|
551
|
+
process.stderr.write(`${e.message}\n`);
|
|
552
|
+
process.exitCode = 2;
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
const { sessionId: sid, opened, reused: wasReused } = await openSession(client, url, { ...settleOpts(options), sessionId: options.session });
|
|
556
|
+
sessionId = sid;
|
|
557
|
+
reused = wasReused;
|
|
558
|
+
|
|
559
|
+
const body = { ...geometry };
|
|
560
|
+
if (options.selector) body.selector = options.selector;
|
|
561
|
+
if (options.fullPage) body.full_page = true;
|
|
562
|
+
if (options.waitFor) body.wait_for = options.waitFor;
|
|
563
|
+
if (Number.isFinite(options.waitMs)) body.wait_ms = options.waitMs;
|
|
564
|
+
// commander sets reducedMotion=false only when --no-reduced-motion is passed.
|
|
565
|
+
if (options.reducedMotion === false) body.reduced_motion = false;
|
|
566
|
+
|
|
567
|
+
const shot = await call(client, 'qlarity.session.screenshot', { sessionId }, body);
|
|
568
|
+
|
|
569
|
+
const dir = screenshotDir();
|
|
570
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
571
|
+
const file = path.join(dir, screenshotFilename(opened.url || url));
|
|
572
|
+
fs.writeFileSync(file, Buffer.from(shot.data, 'base64'));
|
|
573
|
+
|
|
574
|
+
process.stdout.write(`\u{1F50D} QLARITY: ${opened.url} (${opened.status}) ${opened.title || ''}\n`);
|
|
575
|
+
process.stdout.write(`saved ${shot.bytes} bytes to ${file}\n`);
|
|
576
|
+
process.stdout.write(`sha256 ${shot.sha256}\n`);
|
|
577
|
+
if (shot.viewport) {
|
|
578
|
+
process.stdout.write(`viewport ${shot.viewport.width}x${shot.viewport.height} @${shot.device_scale_factor ?? 1}x\n`);
|
|
579
|
+
}
|
|
580
|
+
} catch (err) {
|
|
581
|
+
annotate(err);
|
|
582
|
+
} finally {
|
|
583
|
+
if (sessionId && !reused) {
|
|
584
|
+
try { await call(client, 'qlarity.session.close', { sessionId }); } catch { /* reaper will get it */ }
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
});
|
|
588
|
+
|
|
589
|
+
q.command('eval <url> <script>')
|
|
590
|
+
.description('Request approval to run arbitrary JS. Files an inbox item and exits; never runs inline.')
|
|
591
|
+
.action(async (url, script) => {
|
|
592
|
+
let sessionId = null;
|
|
593
|
+
try {
|
|
594
|
+
const { sessionId: sid } = await openSession(client, url);
|
|
595
|
+
sessionId = sid;
|
|
596
|
+
const r = await call(client, 'qlarity.session.eval', { sessionId }, { script });
|
|
597
|
+
process.stdout.write(`\u{1F50D} QLARITY: ${r.status} — inbox item ${r.inbox_item_id}\n`);
|
|
598
|
+
process.stdout.write('Arbitrary evaluation is always a human approval. Nothing has run.\n');
|
|
599
|
+
} catch (err) {
|
|
600
|
+
annotate(err);
|
|
601
|
+
} finally {
|
|
602
|
+
// The session is NOT closed here: the approved script needs it. The
|
|
603
|
+
// reaper reclaims it if nobody approves.
|
|
604
|
+
if (sessionId) process.stdout.write(`session ${sessionId} left open for the approved run\n`);
|
|
605
|
+
}
|
|
606
|
+
});
|
|
607
|
+
|
|
608
|
+
/**
|
|
609
|
+
* Governed authed-session bootstrap (issue #15). Only ever sends a
|
|
610
|
+
* `credential_id` to the collector — never a raw password — and the
|
|
611
|
+
* collector resolves it server-side against a stored `browser_login`
|
|
612
|
+
* vault credential (see server.js's resolveQlarityLoginCredential). The
|
|
613
|
+
* session is deliberately kept open on success and recorded locally so
|
|
614
|
+
* `read`/`console`/`network`/`screenshot` one-shots against the SAME
|
|
615
|
+
* origin pick it up automatically instead of opening a fresh, logged-out
|
|
616
|
+
* one (see openSession's reuse check above).
|
|
617
|
+
*/
|
|
618
|
+
q.command('login <origin>')
|
|
619
|
+
.description('Log in to <origin> via a stored browser_login vault credential, leaving the session open for other qlarity verbs to reuse')
|
|
620
|
+
.requiredOption('--credential <idOrName>', 'vault credential id or label (must be a browser_login credential)')
|
|
621
|
+
.option('--login-path <path>', 'login form path, if the credential does not already declare one')
|
|
622
|
+
.action(async (origin, options) => {
|
|
623
|
+
let normalizedOrigin;
|
|
624
|
+
try {
|
|
625
|
+
normalizedOrigin = new URL(origin).origin;
|
|
626
|
+
} catch {
|
|
627
|
+
process.stderr.write(`Not a valid origin: ${origin}\n`);
|
|
628
|
+
process.exitCode = 1;
|
|
629
|
+
return;
|
|
630
|
+
}
|
|
631
|
+
try {
|
|
632
|
+
const credentialId = await resolveBrowserLoginCredentialId(ctx, options.credential);
|
|
633
|
+
|
|
634
|
+
const loggedIn = readLoggedInSessions();
|
|
635
|
+
let sessionId = findReusableSessionId(
|
|
636
|
+
loggedIn,
|
|
637
|
+
(await call(client, 'qlarity.sessions.list')).sessions || [],
|
|
638
|
+
normalizedOrigin,
|
|
639
|
+
);
|
|
640
|
+
let fresh = false;
|
|
641
|
+
if (!sessionId) {
|
|
642
|
+
const s = await call(client, 'qlarity.session.create', {}, {});
|
|
643
|
+
sessionId = s.id;
|
|
644
|
+
fresh = true;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
const body = { origin: normalizedOrigin, credential_id: credentialId };
|
|
648
|
+
if (options.loginPath) body.login_path = options.loginPath;
|
|
649
|
+
|
|
650
|
+
// quox#907: a real login (page load, form fill, cookie wait) can outlast the CLI's 30s default on a loaded
|
|
651
|
+
// box, and the server kept finishing it, so every timed-out retry left a headless browser behind. Give login
|
|
652
|
+
// its own budget, and close a session THIS call created when the login call fails (as openSession does, #680).
|
|
653
|
+
let r;
|
|
654
|
+
try {
|
|
655
|
+
r = await call(client, 'qlarity.session.login', { sessionId }, body, { timeout: LOGIN_TIMEOUT_MS });
|
|
656
|
+
} catch (loginErr) {
|
|
657
|
+
if (fresh) {
|
|
658
|
+
try { await call(client, 'qlarity.session.close', { sessionId }); } catch { /* the server reaper will get it */ }
|
|
659
|
+
}
|
|
660
|
+
throw loginErr;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
if (r.logged_in) {
|
|
664
|
+
loggedIn[normalizedOrigin] = { sessionId, logged_in_at: new Date().toISOString() };
|
|
665
|
+
writeLoggedInSessions(loggedIn);
|
|
666
|
+
process.stdout.write(`\u{1F50D} QLARITY: logged in to ${normalizedOrigin} — session ${sessionId} kept open for reuse\n`);
|
|
667
|
+
process.stdout.write(`evidence: ${r.evidence}\n`);
|
|
668
|
+
} else {
|
|
669
|
+
// Do NOT record this session as reusable: a failed login left
|
|
670
|
+
// open and later "reused" would silently serve a logged-out page.
|
|
671
|
+
process.stdout.write(`\u{1F50D} QLARITY: no new session cookie appeared for ${normalizedOrigin} — login likely failed\n`);
|
|
672
|
+
process.stdout.write(`session ${sessionId} left open for inspection (not marked reusable); close it with \`quox qlarity close ${sessionId}\` when done\n`);
|
|
673
|
+
process.exitCode = 1;
|
|
674
|
+
}
|
|
675
|
+
} catch (err) {
|
|
676
|
+
annotate(err);
|
|
677
|
+
}
|
|
678
|
+
});
|
|
679
|
+
|
|
680
|
+
return q;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
/**
|
|
684
|
+
* Resolve a `--credential` value (a vault credential id, OR a case-
|
|
685
|
+
* insensitive match on its label) to a credential id, scoped to
|
|
686
|
+
* `browser_login` credentials only — the same integration type
|
|
687
|
+
* server.js's resolveQlarityLoginCredential requires.
|
|
688
|
+
*/
|
|
689
|
+
async function resolveBrowserLoginCredentialId({ client, auth, program }, idOrName) {
|
|
690
|
+
const orgId = resolveOrgId(auth, program);
|
|
691
|
+
const ep = resolveEndpoint('vault.list', { orgId });
|
|
692
|
+
const data = await client.get(ep.path, { service: ep.service, query: { type: 'browser_login' } });
|
|
693
|
+
const rows = data.credentials || data || [];
|
|
694
|
+
const match = rows.find((r) => r.id === idOrName)
|
|
695
|
+
|| rows.find((r) => (r.label || '').toLowerCase() === String(idOrName).toLowerCase());
|
|
696
|
+
if (!match) {
|
|
697
|
+
const err = new Error(
|
|
698
|
+
`No browser_login credential matched "${idOrName}". Run \`quox vault list --type browser_login\` to see stored credentials.`,
|
|
699
|
+
);
|
|
700
|
+
err.status = 404;
|
|
701
|
+
throw err;
|
|
702
|
+
}
|
|
703
|
+
return match.id;
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
export default registerQlarityCommands;
|