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,350 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prompt commands: user-owned prompts library (CRUD + versions + import/export)
|
|
3
|
+
*
|
|
4
|
+
* quox prompt list|get|create|update|delete|versions|restore|export|import
|
|
5
|
+
*
|
|
6
|
+
* Backend: services/auth/routes/prompts.js, mounted at plain `/api/prompts`
|
|
7
|
+
* (NOT org-scoped in the URL — server.js:726). Every operation still requires
|
|
8
|
+
* an org_id: GETs take it as a query param, writes take it as a body field.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { readFileSync } from 'node:fs';
|
|
12
|
+
import { formatOutput, formatDetail, printSuccess, printWarning, printInfo } from '../core/output.js';
|
|
13
|
+
import { resolveOrgId } from './helpers.js';
|
|
14
|
+
import { resolveEndpoint } from '../api/endpoints.js';
|
|
15
|
+
import { confirmDestructive } from '../core/confirm.js';
|
|
16
|
+
|
|
17
|
+
/** Parse a stored JSON-string field (tags/variables/deploy_targets) back to an array/object. */
|
|
18
|
+
function parseJsonField(value) {
|
|
19
|
+
if (Array.isArray(value) || (value !== null && typeof value === 'object')) return value;
|
|
20
|
+
if (typeof value !== 'string' || value.trim() === '') return [];
|
|
21
|
+
try {
|
|
22
|
+
return JSON.parse(value);
|
|
23
|
+
} catch {
|
|
24
|
+
return value;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Format a created_at/updated_at value for display. The value may be an
|
|
30
|
+
* epoch-millis number, an all-digits epoch-millis string, or an ISO string,
|
|
31
|
+
* depending on the path it came through. Falsy values pass through as-is
|
|
32
|
+
* rather than rendering "Invalid Date".
|
|
33
|
+
*/
|
|
34
|
+
function formatTimestamp(value) {
|
|
35
|
+
if (!value) return value;
|
|
36
|
+
const isEpochMillis = (typeof value === 'number' && Number.isFinite(value))
|
|
37
|
+
|| (typeof value === 'string' && /^\d+$/.test(value));
|
|
38
|
+
const date = isEpochMillis ? new Date(Number(value)) : new Date(value);
|
|
39
|
+
if (Number.isNaN(date.getTime())) return value;
|
|
40
|
+
return date.toISOString().replace('T', ' ').replace(/\.\d+Z$/, ' UTC');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Extract `{{identifier}}` template variables from prompt body text, in
|
|
45
|
+
* order of first appearance, deduped. Conservative identifier pattern
|
|
46
|
+
* (`[A-Za-z_][A-Za-z0-9_]*`) so arbitrary template expressions (helpers,
|
|
47
|
+
* filters, nested paths) aren't misparsed as variable names.
|
|
48
|
+
*/
|
|
49
|
+
function extractVariables(body) {
|
|
50
|
+
if (typeof body !== 'string' || body === '') return [];
|
|
51
|
+
const seen = new Set();
|
|
52
|
+
const variables = [];
|
|
53
|
+
const re = /\{\{\s*([A-Za-z_][A-Za-z0-9_]*)\s*\}\}/g;
|
|
54
|
+
let match;
|
|
55
|
+
while ((match = re.exec(body)) !== null) {
|
|
56
|
+
if (!seen.has(match[1])) {
|
|
57
|
+
seen.add(match[1]);
|
|
58
|
+
variables.push(match[1]);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return variables;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function normalizePrompt(p) {
|
|
65
|
+
if (!p) return p;
|
|
66
|
+
return {
|
|
67
|
+
...p,
|
|
68
|
+
tags: parseJsonField(p.tags),
|
|
69
|
+
variables: parseJsonField(p.variables),
|
|
70
|
+
deploy_targets: parseJsonField(p.deploy_targets),
|
|
71
|
+
created_at: formatTimestamp(p.created_at),
|
|
72
|
+
updated_at: formatTimestamp(p.updated_at),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Resolve prompt content from --content or --file. Throws if neither given. */
|
|
77
|
+
function resolveContent(options) {
|
|
78
|
+
if (options.file) {
|
|
79
|
+
return readFileSync(options.file, 'utf-8');
|
|
80
|
+
}
|
|
81
|
+
if (options.content !== undefined) {
|
|
82
|
+
return options.content;
|
|
83
|
+
}
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function registerPromptCommands(program, ctx) {
|
|
88
|
+
const { client, auth } = ctx;
|
|
89
|
+
|
|
90
|
+
const prompt = program
|
|
91
|
+
.command('prompt')
|
|
92
|
+
.description('Manage the user-owned prompts library');
|
|
93
|
+
|
|
94
|
+
// ── prompt list ─────────────────────────────────────────────────────────────
|
|
95
|
+
|
|
96
|
+
prompt
|
|
97
|
+
.command('list')
|
|
98
|
+
.description('List prompts (own + org-visible)')
|
|
99
|
+
.option('--search <text>', 'substring search on name/body')
|
|
100
|
+
.option('--tags <tags>', 'comma-separated tag filter (match any)')
|
|
101
|
+
.action(async (options) => {
|
|
102
|
+
auth.requireAuth();
|
|
103
|
+
const orgId = resolveOrgId(auth, program);
|
|
104
|
+
const query = { org_id: orgId };
|
|
105
|
+
if (options.search) query.q = options.search;
|
|
106
|
+
if (options.tags) query.tags = options.tags;
|
|
107
|
+
|
|
108
|
+
const data = await client.get(resolveEndpoint('prompts.list').path, {
|
|
109
|
+
service: resolveEndpoint('prompts.list').service,
|
|
110
|
+
query,
|
|
111
|
+
});
|
|
112
|
+
const prompts = (data.prompts || data || []).map(normalizePrompt);
|
|
113
|
+
|
|
114
|
+
formatOutput({
|
|
115
|
+
columns: ['id', 'name', 'category', 'visibility', 'tags', 'updated_at'],
|
|
116
|
+
headers: ['ID', 'Name', 'Category', 'Visibility', 'Tags', 'Updated'],
|
|
117
|
+
rows: prompts.map(p => ({
|
|
118
|
+
...p,
|
|
119
|
+
tags: Array.isArray(p.tags) ? p.tags.join(', ') : p.tags,
|
|
120
|
+
})),
|
|
121
|
+
}, { format: program.opts().output, emptyMessage: 'No prompts found.' });
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
// ── prompt get ──────────────────────────────────────────────────────────────
|
|
125
|
+
|
|
126
|
+
prompt
|
|
127
|
+
.command('get')
|
|
128
|
+
.description('Show prompt details')
|
|
129
|
+
.argument('<id>', 'prompt ID')
|
|
130
|
+
.action(async (id) => {
|
|
131
|
+
auth.requireAuth();
|
|
132
|
+
const orgId = resolveOrgId(auth, program);
|
|
133
|
+
const data = await client.get(resolveEndpoint('prompts.get', { id }).path, {
|
|
134
|
+
service: resolveEndpoint('prompts.get', { id }).service,
|
|
135
|
+
query: { org_id: orgId },
|
|
136
|
+
});
|
|
137
|
+
const p = normalizePrompt(data.prompt || data);
|
|
138
|
+
|
|
139
|
+
formatDetail(p, {
|
|
140
|
+
format: program.opts().output,
|
|
141
|
+
title: `Prompt: ${p.name || id}`,
|
|
142
|
+
fields: ['id', 'name', 'description', 'category', 'visibility', 'tags', 'body', 'variables', 'deploy_targets', 'created_at', 'updated_at'],
|
|
143
|
+
labels: {
|
|
144
|
+
id: 'ID', name: 'Name', description: 'Description', category: 'Category',
|
|
145
|
+
visibility: 'Visibility', tags: 'Tags', body: 'Body', variables: 'Variables',
|
|
146
|
+
deploy_targets: 'Deploy Targets', created_at: 'Created', updated_at: 'Updated',
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
// ── prompt create ───────────────────────────────────────────────────────────
|
|
152
|
+
|
|
153
|
+
prompt
|
|
154
|
+
.command('create')
|
|
155
|
+
.description('Create a prompt')
|
|
156
|
+
.requiredOption('--name <name>', 'prompt name')
|
|
157
|
+
.option('--content <text>', 'prompt body text')
|
|
158
|
+
.option('--file <path>', 'read prompt body from a file (overrides --content)')
|
|
159
|
+
.option('--description <text>', 'description')
|
|
160
|
+
.option('--tags <tags>', 'comma-separated tags')
|
|
161
|
+
.option('--category <category>', 'category')
|
|
162
|
+
.option('--visibility <vis>', 'private|org', 'private')
|
|
163
|
+
.action(async (options) => {
|
|
164
|
+
auth.requireAuth();
|
|
165
|
+
const orgId = resolveOrgId(auth, program);
|
|
166
|
+
|
|
167
|
+
const body = resolveContent(options);
|
|
168
|
+
if (body === undefined || body.trim() === '') {
|
|
169
|
+
throw new Error('Prompt content is required. Use --content <text> or --file <path>.');
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const payload = { org_id: orgId, name: options.name, body };
|
|
173
|
+
if (options.description) payload.description = options.description;
|
|
174
|
+
if (options.tags) payload.tags = options.tags.split(',').map(t => t.trim()).filter(Boolean);
|
|
175
|
+
if (options.category) payload.category = options.category;
|
|
176
|
+
if (options.visibility) payload.visibility = options.visibility;
|
|
177
|
+
// fleet #398: nothing extracted {{var}} tokens from the body, so
|
|
178
|
+
// `variables` was always stored as []. Only fill it in when the
|
|
179
|
+
// caller didn't explicitly pass one, and only when the body actually
|
|
180
|
+
// contains variables (keeps the payload identical to before for
|
|
181
|
+
// variable-less prompts).
|
|
182
|
+
const createdVariables = extractVariables(body);
|
|
183
|
+
if (payload.variables === undefined && createdVariables.length > 0) payload.variables = createdVariables;
|
|
184
|
+
|
|
185
|
+
const data = await client.post(resolveEndpoint('prompts.create').path, {
|
|
186
|
+
service: resolveEndpoint('prompts.create').service,
|
|
187
|
+
body: payload,
|
|
188
|
+
});
|
|
189
|
+
printSuccess(`Prompt "${options.name}" created.`);
|
|
190
|
+
formatDetail(normalizePrompt(data.prompt || data), { format: program.opts().output });
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
// ── prompt update ───────────────────────────────────────────────────────────
|
|
194
|
+
|
|
195
|
+
prompt
|
|
196
|
+
.command('update')
|
|
197
|
+
.description('Update a prompt (bumps version)')
|
|
198
|
+
.argument('<id>', 'prompt ID')
|
|
199
|
+
.option('--name <name>', 'new name')
|
|
200
|
+
.option('--content <text>', 'new body text')
|
|
201
|
+
.option('--file <path>', 'read new body from a file (overrides --content)')
|
|
202
|
+
.option('--description <text>', 'new description')
|
|
203
|
+
.option('--tags <tags>', 'new tags (comma-separated)')
|
|
204
|
+
.option('--category <category>', 'new category')
|
|
205
|
+
.option('--visibility <vis>', 'private|org')
|
|
206
|
+
.action(async (id, options) => {
|
|
207
|
+
auth.requireAuth();
|
|
208
|
+
const orgId = resolveOrgId(auth, program);
|
|
209
|
+
|
|
210
|
+
const newBody = resolveContent(options);
|
|
211
|
+
const payload = { org_id: orgId };
|
|
212
|
+
if (options.name) payload.name = options.name;
|
|
213
|
+
if (newBody !== undefined) payload.body = newBody;
|
|
214
|
+
if (options.description !== undefined) payload.description = options.description;
|
|
215
|
+
if (options.tags) payload.tags = options.tags.split(',').map(t => t.trim()).filter(Boolean);
|
|
216
|
+
if (options.category) payload.category = options.category;
|
|
217
|
+
if (options.visibility) payload.visibility = options.visibility;
|
|
218
|
+
// Re-extract {{var}} tokens whenever the body changes, so stale
|
|
219
|
+
// variables from the old body don't linger. Only when the caller
|
|
220
|
+
// didn't explicitly pass one.
|
|
221
|
+
if (newBody !== undefined && payload.variables === undefined) {
|
|
222
|
+
payload.variables = extractVariables(newBody);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (Object.keys(payload).length === 1) {
|
|
226
|
+
throw new Error('Specify at least one field to update (--name, --content/--file, --description, --tags, --category, --visibility).');
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const data = await client.patch(resolveEndpoint('prompts.update', { id }).path, {
|
|
230
|
+
service: resolveEndpoint('prompts.update', { id }).service,
|
|
231
|
+
body: payload,
|
|
232
|
+
});
|
|
233
|
+
printSuccess('Prompt updated.');
|
|
234
|
+
formatDetail(normalizePrompt(data.prompt || data), { format: program.opts().output });
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
// ── prompt delete ───────────────────────────────────────────────────────────
|
|
238
|
+
|
|
239
|
+
prompt
|
|
240
|
+
.command('delete')
|
|
241
|
+
.description('Delete a prompt (soft-delete)')
|
|
242
|
+
.argument('<id>', 'prompt ID')
|
|
243
|
+
.option('--force', 'skip confirmation')
|
|
244
|
+
.action(async (id, options) => {
|
|
245
|
+
auth.requireAuth();
|
|
246
|
+
const orgId = resolveOrgId(auth, program);
|
|
247
|
+
|
|
248
|
+
const confirmed = await confirmDestructive(program, options, `Delete prompt ${id}? This cannot be undone.`);
|
|
249
|
+
if (!confirmed) {
|
|
250
|
+
printInfo('Cancelled.');
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// NOTE: org_id MUST be sent in the body, not the query string. The
|
|
255
|
+
// handler's `req.body || req.query` fallback (prompts.js:431) never
|
|
256
|
+
// reaches req.query because express.json() leaves req.body as a
|
|
257
|
+
// truthy `{}` even on a request with no JSON body.
|
|
258
|
+
await client.delete(resolveEndpoint('prompts.delete', { id }).path, {
|
|
259
|
+
service: resolveEndpoint('prompts.delete', { id }).service,
|
|
260
|
+
body: { org_id: orgId },
|
|
261
|
+
});
|
|
262
|
+
printSuccess('Prompt deleted.');
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
// ── prompt versions ─────────────────────────────────────────────────────────
|
|
266
|
+
|
|
267
|
+
prompt
|
|
268
|
+
.command('versions')
|
|
269
|
+
.description('Show version history for a prompt')
|
|
270
|
+
.argument('<id>', 'prompt ID')
|
|
271
|
+
.action(async (id) => {
|
|
272
|
+
auth.requireAuth();
|
|
273
|
+
const orgId = resolveOrgId(auth, program);
|
|
274
|
+
const data = await client.get(resolveEndpoint('prompts.versions', { id }).path, {
|
|
275
|
+
service: resolveEndpoint('prompts.versions', { id }).service,
|
|
276
|
+
query: { org_id: orgId },
|
|
277
|
+
});
|
|
278
|
+
const versions = data.versions || data || [];
|
|
279
|
+
|
|
280
|
+
formatOutput({
|
|
281
|
+
columns: ['version', 'name', 'created_at', 'created_by'],
|
|
282
|
+
headers: ['Version', 'Name', 'Created', 'By'],
|
|
283
|
+
rows: versions,
|
|
284
|
+
}, { format: program.opts().output, emptyMessage: 'No version history found.' });
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
// ── prompt restore ──────────────────────────────────────────────────────────
|
|
288
|
+
|
|
289
|
+
prompt
|
|
290
|
+
.command('restore')
|
|
291
|
+
.description('Restore a prompt to a previous version')
|
|
292
|
+
.argument('<id>', 'prompt ID')
|
|
293
|
+
.argument('<version>', 'version number to restore')
|
|
294
|
+
.action(async (id, version) => {
|
|
295
|
+
auth.requireAuth();
|
|
296
|
+
const orgId = resolveOrgId(auth, program);
|
|
297
|
+
const data = await client.post(resolveEndpoint('prompts.restore', { id, version }).path, {
|
|
298
|
+
service: resolveEndpoint('prompts.restore', { id, version }).service,
|
|
299
|
+
body: { org_id: orgId },
|
|
300
|
+
});
|
|
301
|
+
printSuccess(`Prompt restored from version ${version}.`);
|
|
302
|
+
formatDetail(normalizePrompt(data.prompt || data), { format: program.opts().output });
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
// ── prompt export ───────────────────────────────────────────────────────────
|
|
306
|
+
|
|
307
|
+
prompt
|
|
308
|
+
.command('export')
|
|
309
|
+
.description('Export all of the caller\'s prompts as JSON')
|
|
310
|
+
.action(async () => {
|
|
311
|
+
auth.requireAuth();
|
|
312
|
+
const orgId = resolveOrgId(auth, program);
|
|
313
|
+
const data = await client.get(resolveEndpoint('prompts.export').path, {
|
|
314
|
+
service: resolveEndpoint('prompts.export').service,
|
|
315
|
+
query: { org_id: orgId },
|
|
316
|
+
});
|
|
317
|
+
console.log(JSON.stringify(data, null, 2));
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
// ── prompt import ───────────────────────────────────────────────────────────
|
|
321
|
+
|
|
322
|
+
prompt
|
|
323
|
+
.command('import')
|
|
324
|
+
.description('Bulk-import prompts from a JSON file (as produced by `quox prompt export`)')
|
|
325
|
+
.argument('<file>', 'path to a JSON file with an array of prompts or { prompts: [...] }')
|
|
326
|
+
.action(async (file) => {
|
|
327
|
+
auth.requireAuth();
|
|
328
|
+
const orgId = resolveOrgId(auth, program);
|
|
329
|
+
|
|
330
|
+
let parsed;
|
|
331
|
+
try {
|
|
332
|
+
parsed = JSON.parse(readFileSync(file, 'utf-8'));
|
|
333
|
+
} catch (err) {
|
|
334
|
+
throw new Error(`Could not read/parse ${file}: ${err.message}`);
|
|
335
|
+
}
|
|
336
|
+
const prompts = Array.isArray(parsed) ? parsed : (parsed.prompts || []);
|
|
337
|
+
if (!Array.isArray(prompts) || prompts.length === 0) {
|
|
338
|
+
throw new Error(`No prompts found in ${file}. Expected an array or { "prompts": [...] }.`);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const data = await client.post(resolveEndpoint('prompts.import').path, {
|
|
342
|
+
service: resolveEndpoint('prompts.import').service,
|
|
343
|
+
body: { org_id: orgId, prompts },
|
|
344
|
+
});
|
|
345
|
+
printSuccess(`Imported ${data.imported ?? 0} prompt(s).`);
|
|
346
|
+
if ((data.imported ?? 0) < prompts.length) {
|
|
347
|
+
printWarning(`${prompts.length - (data.imported ?? 0)} prompt(s) were skipped (missing name/body).`);
|
|
348
|
+
}
|
|
349
|
+
});
|
|
350
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* quox provider list | connect <kind> | test [<kind>] (quox#1281, CLI verb first)
|
|
3
|
+
*
|
|
4
|
+
* The SAME backend calls as the TUI's /connect: cli/lib/quoxon/connect.js flows (connectFlow, connectOllamaFlow,
|
|
5
|
+
* connectCompatFlow, connectionLines) over cli/lib/quoxon/api.js createApi (the auth vault create/update with
|
|
6
|
+
* confirm_private_origin, the server-side key test and Ollama / OpenAI-compatible probes, capabilities, consent).
|
|
7
|
+
* Nothing here talks to the backend on its own, so the TUI and the CLI cannot drift. The vault routes audit every
|
|
8
|
+
* write server side (integration.private_origin for a private address), org-scoped by the caller's session.
|
|
9
|
+
*
|
|
10
|
+
* Secrets: a key is never an argv flag or argument. It arrives on stdin (--key-from-stdin) or from a hidden prompt,
|
|
11
|
+
* is sent once to the server test and the vault, and is never printed (connectFlow scrubs it from any message).
|
|
12
|
+
*/
|
|
13
|
+
import { formatDetail, printInfo } from '../core/output.js';
|
|
14
|
+
import { resolveOrgId } from './helpers.js';
|
|
15
|
+
import { resolveEndpoint } from '../api/endpoints.js';
|
|
16
|
+
import { createApi, isInteractiveHuman } from '../quoxon/api.js';
|
|
17
|
+
import { PROVIDERS, providerFor, looksLikeKey, connectionLines, connectFlow, connectOllamaFlow, connectCompatFlow } from '../quoxon/connect.js';
|
|
18
|
+
import { needsConsent, consentCovers, connectConsentNote, providerLabel } from '../quoxon/consent.js';
|
|
19
|
+
import { readStdin } from '../quoxon/print.js';
|
|
20
|
+
|
|
21
|
+
const KINDS = Object.keys(PROVIDERS);
|
|
22
|
+
const SAFE_ROW = ['id', 'integration_type', 'label', 'is_default', 'test_status', 'last_tested_at', 'created_at'];
|
|
23
|
+
const CONTAINER_NOTE = 'Quox runs in containers: 127.0.0.1 or localhost means the Quox container, not your machine. Use the host\'s LAN IP, e.g. http://192.0.2.10:11434';
|
|
24
|
+
|
|
25
|
+
function defaultIO() {
|
|
26
|
+
return {
|
|
27
|
+
isTTY: () => isInteractiveHuman(),
|
|
28
|
+
readStdin: () => readStdin(),
|
|
29
|
+
promptSecret: async (message) => {
|
|
30
|
+
const { default: inquirer } = await import('inquirer');
|
|
31
|
+
const a = await inquirer.prompt([{ type: 'password', name: 'v', message, mask: '*' }]);
|
|
32
|
+
return a.v;
|
|
33
|
+
},
|
|
34
|
+
confirm: async (message) => {
|
|
35
|
+
const { default: inquirer } = await import('inquirer');
|
|
36
|
+
const a = await inquirer.prompt([{ type: 'confirm', name: 'v', message, default: false }]);
|
|
37
|
+
return Boolean(a.v);
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const say = (lines) => { for (const [t] of lines || []) printInfo(t); };
|
|
43
|
+
|
|
44
|
+
export function registerProviderCommands(program, ctx) {
|
|
45
|
+
const { client, auth, config } = ctx;
|
|
46
|
+
const io = ctx.providerIO || defaultIO();
|
|
47
|
+
const api = () => (ctx.makeApi ? ctx.makeApi() : createApi({ client, config, auth }));
|
|
48
|
+
|
|
49
|
+
const provider = program
|
|
50
|
+
.command('provider')
|
|
51
|
+
.description('Connect the AI providers your org uses (the same as /connect in the Quox terminal)');
|
|
52
|
+
|
|
53
|
+
// ── provider list ──────────────────────────────────────────────────────────
|
|
54
|
+
provider
|
|
55
|
+
.command('list')
|
|
56
|
+
.description('Show which AI providers this org can use, and how each is connected')
|
|
57
|
+
.action(async () => {
|
|
58
|
+
auth.requireAuth();
|
|
59
|
+
const a = api();
|
|
60
|
+
const [vault, caps] = await Promise.all([a.connections(), a.capabilities ? a.capabilities() : null]);
|
|
61
|
+
if (program.opts().output === 'json') {
|
|
62
|
+
const rows = vault?.ok ? vault.rows.map((r) => Object.fromEntries(SAFE_ROW.filter((k) => r[k] !== undefined).map((k) => [k, r[k]]))) : null;
|
|
63
|
+
formatDetail({ connections: rows, ...(vault?.ok ? {} : { error: vault?.error || 'vault not readable' }), capabilities: caps?.ok ? caps.capabilities : null }, { format: 'json' });
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
say(connectionLines(vault, caps));
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// ── provider connect ───────────────────────────────────────────────────────
|
|
70
|
+
const readKey = async (p, opts) => {
|
|
71
|
+
if (opts.keyFromStdin) return String(await io.readStdin() || '').replace(/\r?\n$/, '').trim();
|
|
72
|
+
if (io.isTTY()) return String(await io.promptSecret(`${p.name} key (hidden, never shown or stored on this machine):`) || '').trim();
|
|
73
|
+
throw new Error(`No key given. Pipe it in with --key-from-stdin (for example: quox provider connect ${p.id} --key-from-stdin < keyfile), or run this in a terminal to be asked. A key is never taken as an argument.`);
|
|
74
|
+
};
|
|
75
|
+
const consentAfter = async (type) => {
|
|
76
|
+
const a = api();
|
|
77
|
+
if (!needsConsent(type) || !a.consentStatus) return;
|
|
78
|
+
let rec = null; try { rec = await a.consentStatus(); } catch { rec = null; }
|
|
79
|
+
if (consentCovers(rec, type)) return;
|
|
80
|
+
// --yes never grants consent: it is this person's own OK to send their conversations to a third party
|
|
81
|
+
if (!io.isTTY() || !a.grantConsent) { printInfo(connectConsentNote(type)); return; }
|
|
82
|
+
const yes = await io.confirm(`Allow Quox to send your conversations to ${providerLabel(type)}? Your messages and tool results go to ${providerLabel(type)}, a third party, when it answers. Nothing is sent until you say yes.`);
|
|
83
|
+
if (!yes) { printInfo(`Left off: nothing goes to ${providerLabel(type)} until you say so. ${connectConsentNote(type)}`); return; }
|
|
84
|
+
try { await a.grantConsent(type); printInfo(`✔ Your OK is recorded: ${providerLabel(type)} can now answer. Withdraw it any time: quox consent list.`); }
|
|
85
|
+
catch (e) { printInfo(`Quox could not record your OK (${e?.message || e}). Try: quox consent grant ${type}`); }
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
provider
|
|
89
|
+
.command('connect')
|
|
90
|
+
.description('Connect an AI provider for this org: a keyed provider (openai, anthropic, groq...) takes its key on stdin or a hidden prompt; a self-hosted one (ollama, lmstudio, vllm, custom) takes a URL')
|
|
91
|
+
.argument('<kind>', `provider: ${KINDS.join(', ')}`)
|
|
92
|
+
.argument('[url]', 'server URL for ollama / lmstudio / vllm / custom (same as --url)')
|
|
93
|
+
.option('--url <url>', `server URL for a self-hosted provider. ${CONTAINER_NOTE}`)
|
|
94
|
+
.option('--model <model>', 'default model to use on that server')
|
|
95
|
+
.option('--key-from-stdin', 'read the API key from stdin (never pass a key as an argument)')
|
|
96
|
+
.option('--yes', 'confirm connecting to an address on your private network (the server still checks it and audits the decision)')
|
|
97
|
+
.addHelpText('after', `\n${CONTAINER_NOTE}\nKeys: --key-from-stdin, or run in a terminal to be asked with a hidden prompt.`)
|
|
98
|
+
.action(async (kind, urlArg, opts) => {
|
|
99
|
+
auth.requireAuth();
|
|
100
|
+
const p = providerFor(kind);
|
|
101
|
+
if (!p) throw new Error(`No provider called "${kind}". Use one of: ${KINDS.join(', ')}`);
|
|
102
|
+
if (urlArg && (looksLikeKey(urlArg) || (p.card && !p.url))) throw new Error('never pass a key as an argument (it would sit in your shell history and process list). Use --key-from-stdin, or run in a terminal to be asked.');
|
|
103
|
+
const a = api();
|
|
104
|
+
const url = opts.url || urlArg || '';
|
|
105
|
+
let r;
|
|
106
|
+
if (p.id === 'ollama') {
|
|
107
|
+
r = await connectOllamaFlow(a, [url, opts.model].filter(Boolean).join(' '));
|
|
108
|
+
say(r.lines);
|
|
109
|
+
if (r.privateAsk) {
|
|
110
|
+
const q = `Connect to ${r.privateAsk.host}? It's on your private network. Quox saves only ${r.privateAsk.origin}, after the server checks it answers as Ollama.`;
|
|
111
|
+
let yes = Boolean(opts.yes);
|
|
112
|
+
if (!yes && io.isTTY()) yes = await io.confirm(q);
|
|
113
|
+
if (!yes && !io.isTTY()) throw new Error(`${r.privateAsk.host} is on your private network. Run again with --yes to confirm this one address. Nothing was saved.`);
|
|
114
|
+
if (!yes) throw new Error(`Left blocked: ${r.privateAsk.host} was not connected. Nothing was saved.`);
|
|
115
|
+
r = await r.privateAsk.retry();
|
|
116
|
+
say(r.lines);
|
|
117
|
+
}
|
|
118
|
+
} else if (p.url) {
|
|
119
|
+
r = await connectCompatFlow(a, p.id, [url, opts.model].filter(Boolean).join(' '));
|
|
120
|
+
say(r.lines);
|
|
121
|
+
if (r.needsKey) { r = await connectFlow(a, 'custom', await readKey(PROVIDERS.custom, opts), r.extra); printInfo(r.text); if (r.disclosure) printInfo(r.disclosure); }
|
|
122
|
+
} else {
|
|
123
|
+
r = await connectFlow(a, p.id, await readKey(p, opts));
|
|
124
|
+
printInfo(r.text);
|
|
125
|
+
if (r.ok && r.disclosure) printInfo(r.disclosure);
|
|
126
|
+
}
|
|
127
|
+
if (r?.lanOffer) printInfo(' From the CLI an instance admin allows private targets with: quox server config lan-targets on then run this again.');
|
|
128
|
+
if (!r?.ok) throw new Error(r?.text || `${p.name} was not connected. Nothing was saved.`);
|
|
129
|
+
await consentAfter(p.type);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
// ── provider test ──────────────────────────────────────────────────────────
|
|
133
|
+
provider
|
|
134
|
+
.command('test')
|
|
135
|
+
.description('Test the stored connection for a provider (or every default connection) through the vault test')
|
|
136
|
+
.argument('[kind]', 'provider to test (default: every connected provider)')
|
|
137
|
+
.action(async (kind) => {
|
|
138
|
+
auth.requireAuth();
|
|
139
|
+
const p = kind ? providerFor(kind) : null;
|
|
140
|
+
if (kind && !p) throw new Error(`No provider called "${kind}". Use one of: ${KINDS.join(', ')}`);
|
|
141
|
+
const vault = await api().connections();
|
|
142
|
+
if (!vault?.ok) throw new Error(`Could not read the vault: ${vault?.error || 'no answer'}`);
|
|
143
|
+
const types = new Set(p ? [p.type] : Object.values(PROVIDERS).map((x) => x.type));
|
|
144
|
+
const rows = vault.rows.filter((r) => types.has(r.integration_type) && (r.is_default === 1 || r.is_default === true));
|
|
145
|
+
if (!rows.length) throw new Error(p ? `No ${p.id} connection is stored for this org. Connect one: quox provider connect ${p.id}` : 'No provider connection is stored for this org. Connect one: quox provider connect <kind>');
|
|
146
|
+
const orgId = resolveOrgId(auth, program);
|
|
147
|
+
let failed = 0;
|
|
148
|
+
for (const row of rows) {
|
|
149
|
+
const name = (Object.values(PROVIDERS).find((x) => x.type === row.integration_type) || {}).name || row.integration_type;
|
|
150
|
+
const ep = resolveEndpoint('vault.test', { orgId, id: row.id });
|
|
151
|
+
let res;
|
|
152
|
+
try { res = await client.post(ep.path, { service: ep.service, retries: 0 }); }
|
|
153
|
+
catch (e) { res = { success: false, message: e?.message || 'failed' }; }
|
|
154
|
+
if (res?.success === false) { failed++; printInfo(`✖ ${name} ${row.label || row.id} failed: ${res?.message || 'no reason given'}`); }
|
|
155
|
+
else printInfo(`✔ ${name} ${row.label || row.id} ok${res?.message ? `: ${res.message}` : ''}`);
|
|
156
|
+
}
|
|
157
|
+
if (failed) process.exitCode = 1;
|
|
158
|
+
});
|
|
159
|
+
}
|