quoxcli 0.2.0 → 0.4.0
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 +1 -1
- package/README.md +2 -0
- package/bin/quox.js +99 -126
- package/lib/api/endpoints/actions.js +15 -0
- package/lib/api/endpoints/admin.js +46 -0
- package/lib/api/endpoints/agent-schedules.js +26 -0
- package/lib/api/endpoints/agents.js +7 -0
- package/lib/api/endpoints/assistants.js +12 -0
- package/lib/api/endpoints/away.js +15 -0
- package/lib/api/endpoints/blog.js +16 -0
- package/lib/api/endpoints/chat.js +14 -0
- package/lib/api/endpoints/contactsq.js +6 -0
- package/lib/api/endpoints/conversations.js +14 -0
- package/lib/api/endpoints/devServerRegistry.js +25 -0
- package/lib/api/endpoints/engagement.js +26 -0
- package/lib/api/endpoints/evidence.js +20 -0
- package/lib/api/endpoints/executions.js +13 -0
- package/lib/api/endpoints/fetchAllowlist.js +27 -0
- package/lib/api/endpoints/files.js +14 -0
- package/lib/api/endpoints/hat.js +44 -0
- package/lib/api/endpoints/inbox.js +20 -0
- package/lib/api/endpoints/index.js +26 -0
- package/lib/api/endpoints/loops.js +26 -0
- package/lib/api/endpoints/memory.js +19 -0
- package/lib/api/endpoints/mind.js +13 -0
- package/lib/api/endpoints/mon.js +179 -0
- package/lib/api/endpoints/objectives.js +12 -0
- package/lib/api/endpoints/plugins.js +18 -0
- package/lib/api/endpoints/presence.js +33 -0
- package/lib/api/endpoints/quoxplan.js +7 -0
- package/lib/api/endpoints/quoxseo.js +12 -0
- package/lib/api/endpoints/router.js +17 -0
- package/lib/api/endpoints/safety.js +7 -0
- package/lib/api/endpoints/server.js +21 -0
- package/lib/api/endpoints/setup.js +6 -0
- package/lib/api/endpoints/skills.js +20 -0
- package/lib/api/endpoints/stepup.js +20 -0
- package/lib/api/endpoints/teams.js +2 -2
- package/lib/api/endpoints/triggers.js +26 -0
- package/lib/api/endpoints/v1.js +120 -0
- package/lib/api/endpoints/ward.js +7 -0
- package/lib/api/endpoints/workflows.js +7 -0
- package/lib/api/endpoints/x.js +8 -0
- package/lib/commands/actions.js +131 -0
- package/lib/commands/admin.js +16 -0
- package/lib/commands/agent.js +170 -27
- package/lib/commands/agentAttach.js +140 -13
- package/lib/commands/approvals.js +78 -1
- package/lib/commands/aside.js +24 -0
- package/lib/commands/ask.js +146 -0
- package/lib/commands/assistant.js +59 -4
- package/lib/commands/auth.js +8 -2
- package/lib/commands/backup.js +8 -4
- package/lib/commands/blog.js +110 -0
- package/lib/commands/brain.js +105 -14
- package/lib/commands/build.js +19 -5
- package/lib/commands/chat.js +180 -26
- package/lib/commands/chatCompanion.js +27 -0
- package/lib/commands/chatState.js +45 -0
- package/lib/commands/claude.js +35 -38
- package/lib/commands/contacts.js +134 -4
- package/lib/commands/contactsSendAt.js +21 -0
- package/lib/commands/conversation.js +35 -4
- package/lib/commands/dashboard.js +43 -0
- package/lib/commands/department.js +28 -7
- package/lib/commands/devResident.js +15 -3
- package/lib/commands/devSupervisor.js +13 -1
- package/lib/commands/discord.js +75 -39
- package/lib/commands/done.js +98 -28
- package/lib/commands/engagement.js +107 -6
- package/lib/commands/evidence.js +154 -0
- package/lib/commands/ext.js +23 -0
- package/lib/commands/fetchAllowlist.js +71 -0
- package/lib/commands/file.js +38 -1
- package/lib/commands/fleet.js +336 -4
- package/lib/commands/governance.js +12 -3
- package/lib/commands/gripe.js +44 -13
- package/lib/commands/hat.js +254 -9
- package/lib/commands/helpers.js +18 -1
- package/lib/commands/hyperion.js +58 -0
- package/lib/commands/image.js +68 -0
- package/lib/commands/inbox.js +74 -23
- package/lib/commands/kuma.js +1050 -0
- package/lib/commands/kumaSla.js +174 -0
- package/lib/commands/licence.js +52 -155
- package/lib/commands/loop.js +291 -0
- package/lib/commands/lucid.js +10 -4
- package/lib/commands/memory.js +85 -8
- package/lib/commands/mind.js +71 -0
- package/lib/commands/mon.js +69 -2
- package/lib/commands/monitoring.js +1 -1
- package/lib/commands/motion.js +2 -2
- package/lib/commands/nucleus.js +48 -17
- package/lib/commands/objective.js +59 -1
- package/lib/commands/offload.js +89 -0
- package/lib/commands/onboard.js +2 -1
- package/lib/commands/org.js +30 -3
- package/lib/commands/peek.js +28 -0
- package/lib/commands/plan.js +38 -0
- package/lib/commands/plugin.js +110 -0
- package/lib/commands/presence.js +89 -0
- package/lib/commands/provider.js +217 -0
- package/lib/commands/qlarity.js +19 -3
- package/lib/commands/quoxchat.js +13 -4
- package/lib/commands/quoxseo.js +69 -0
- package/lib/commands/reach.js +2 -1
- package/lib/commands/research.js +79 -0
- package/lib/commands/router.js +77 -0
- package/lib/commands/rows.js +367 -0
- package/lib/commands/run.js +82 -10
- package/lib/commands/scope.js +2 -2
- package/lib/commands/server.js +405 -0
- package/lib/commands/setup.js +33 -7
- package/lib/commands/shortcut.js +41 -0
- package/lib/commands/skills.js +68 -9
- package/lib/commands/slot.js +260 -7
- package/lib/commands/status.js +1 -1
- package/lib/commands/team.js +3 -1
- package/lib/commands/tips.js +23 -0
- package/lib/commands/tool.js +133 -45
- package/lib/commands/trace.js +12 -7
- package/lib/commands/trigger.js +95 -0
- package/lib/commands/update.js +46 -0
- package/lib/commands/vault.js +31 -8
- package/lib/commands/vm.js +237 -0
- package/lib/commands/voice.js +34 -3
- package/lib/commands/volt.js +45 -3
- package/lib/commands/walkthrough.js +32 -0
- package/lib/commands/watch.js +73 -21
- package/lib/commands/welcome.js +14 -0
- package/lib/commands/workflow.js +189 -32
- package/lib/commands/x.js +13 -4
- package/lib/commands/youtube.js +172 -0
- package/lib/core/actingAs.js +2 -2
- package/lib/core/actionItems.js +115 -0
- package/lib/core/activeSummary.js +186 -0
- package/lib/core/agentAlias.js +2 -0
- package/lib/core/agentFamilies.js +7 -0
- package/lib/core/aside.js +54 -0
- package/lib/core/askDecision.js +60 -0
- package/lib/core/auth.js +164 -13
- package/lib/core/callerSource.js +1 -1
- package/lib/core/cliBuild.js +62 -0
- package/lib/core/client.js +152 -35
- package/lib/core/commandRegistrars.js +130 -0
- package/lib/core/config.js +143 -25
- package/lib/core/customHats.js +168 -0
- package/lib/core/doneEngineFetch.js +8 -0
- package/lib/core/errors.js +329 -20
- package/lib/core/findingReconcile.js +90 -0
- package/lib/core/global-flags.js +105 -5
- package/lib/core/governedSession.js +1 -3
- package/lib/core/hyperion.js +102 -0
- package/lib/core/inboxResolution.js +65 -0
- package/lib/core/lazyCommands.js +4 -1
- package/lib/core/liveSessionsReport.js +1 -1
- package/lib/core/loopGlyph.js +97 -0
- package/lib/core/offload.js +533 -0
- package/lib/core/org-override.js +1 -1
- package/lib/core/orgResolve.js +70 -0
- package/lib/core/output.js +11 -11
- package/lib/core/peek.js +126 -0
- package/lib/core/profileUsers.js +51 -0
- package/lib/core/promptCancel.js +18 -0
- package/lib/core/repoPath.js +11 -0
- package/lib/core/residentSeed.js +3 -1
- 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 +12 -8
- package/lib/core/sessionsOverview.js +64 -0
- package/lib/core/shortcut.js +83 -0
- package/lib/core/slotKeeper.js +248 -3
- package/lib/core/slotWake.js +120 -0
- package/lib/core/supervisorEngine.js +71 -2
- package/lib/core/version.js +1 -1
- package/lib/core/vm.js +366 -0
- package/lib/core/vmTui.js +86 -0
- package/lib/core/walkthrough.js +35 -0
- package/lib/dragon/companionView.js +34 -0
- package/lib/dragon/frames.js +118 -0
- package/lib/dragon/jobs.js +123 -0
- package/lib/dragon/renderWorker.js +15 -0
- package/lib/dragon/select.js +72 -0
- package/lib/evidence/bundleExport.js +47 -0
- package/lib/evidence/bundleVerify.js +77 -0
- package/lib/hyperion/deck.js +29 -0
- package/lib/lucid/LucidCard.jsx +68 -0
- package/lib/lucid/LucidIntro.jsx +45 -58
- package/lib/lucid/cards.js +86 -0
- package/lib/lucid/markdown.js +81 -0
- package/lib/lucid/motion.js +56 -0
- package/lib/quoxon/EXTENSIONS.md +23 -0
- package/lib/quoxon/activity.js +14 -2
- package/lib/quoxon/aocl.js +117 -0
- package/lib/quoxon/api.js +690 -25
- package/lib/quoxon/app.js +3586 -250
- package/lib/quoxon/approvalFacts.js +96 -0
- package/lib/quoxon/approvalSentence.js +44 -0
- package/lib/quoxon/approvalsView.js +384 -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 +13 -6
- package/lib/quoxon/cells.js +53 -0
- package/lib/quoxon/commandShort.js +124 -0
- package/lib/quoxon/commands.js +429 -39
- package/lib/quoxon/connect.js +210 -23
- package/lib/quoxon/connectCard.js +19 -2
- package/lib/quoxon/consent.js +86 -0
- package/lib/quoxon/copy.js +4 -3
- package/lib/quoxon/countdownLine.js +20 -0
- package/lib/quoxon/deck.js +30 -0
- package/lib/quoxon/demo.autoplay.js +29 -0
- package/lib/quoxon/demo.fixtures.js +1 -1
- package/lib/quoxon/demo.js +46 -3
- package/lib/quoxon/devObserver.js +20 -0
- package/lib/quoxon/dividerAocl.js +192 -0
- package/lib/quoxon/dividerRule.js +57 -0
- package/lib/quoxon/dragonCompanion.js +255 -0
- package/lib/quoxon/dragonModes.js +151 -0
- package/lib/quoxon/dragonMoments.js +82 -0
- package/lib/quoxon/dragonReads.js +68 -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 +2 -2
- package/lib/quoxon/events.js +266 -26
- package/lib/quoxon/extensions.js +276 -0
- package/lib/quoxon/feedback.js +50 -0
- package/lib/quoxon/gallery.js +21 -7
- 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/inboxView.js +79 -0
- package/lib/quoxon/indicator.js +6 -2
- package/lib/quoxon/installCli.js +79 -0
- package/lib/quoxon/intent.js +12 -1
- package/lib/quoxon/keys.js +51 -8
- package/lib/quoxon/launcher.js +103 -0
- package/lib/quoxon/loopCard.js +456 -0
- package/lib/quoxon/loopCards.js +180 -0
- package/lib/quoxon/loopStream.js +87 -0
- package/lib/quoxon/main.js +101 -160
- package/lib/quoxon/markdown.js +45 -21
- 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 +132 -26
- package/lib/quoxon/modelPrefs.js +345 -0
- package/lib/quoxon/motion/clock.js +3 -2
- package/lib/quoxon/motion/level.js +91 -0
- package/lib/quoxon/mouse.js +222 -0
- package/lib/quoxon/noai.js +57 -0
- package/lib/quoxon/objectiveOffer.js +82 -0
- package/lib/quoxon/objects/away.js +72 -0
- package/lib/quoxon/objects/fetch.js +17 -5
- package/lib/quoxon/objects/fleet.js +149 -0
- package/lib/quoxon/objects/health.js +186 -0
- package/lib/quoxon/objects/index.js +46 -7
- package/lib/quoxon/objects/mirror.js +137 -0
- package/lib/quoxon/objects/model.js +33 -0
- package/lib/quoxon/objects/peek.js +17 -0
- package/lib/quoxon/objects/proof.js +12 -4
- 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/view.js +11 -0
- package/lib/quoxon/onboarding/copy.js +32 -4
- package/lib/quoxon/onboarding/credential.js +17 -4
- package/lib/quoxon/onboarding/dockerOffer.js +45 -0
- package/lib/quoxon/onboarding/failureCause.js +33 -0
- package/lib/quoxon/onboarding/finish.js +15 -0
- package/lib/quoxon/onboarding/index.js +34 -7
- package/lib/quoxon/onboarding/installRunner.js +64 -5
- package/lib/quoxon/onboarding/preflight.js +17 -3
- package/lib/quoxon/onboarding/probe.js +65 -0
- package/lib/quoxon/onboarding/shortcutOffer.js +28 -0
- package/lib/quoxon/onboarding/view.js +76 -19
- package/lib/quoxon/orgLabel.js +10 -0
- package/lib/quoxon/orgSwitch.js +59 -0
- package/lib/quoxon/ownBroadcast.js +22 -0
- package/lib/quoxon/palette.js +43 -0
- package/lib/quoxon/planSteps.js +51 -0
- package/lib/quoxon/print.js +12 -5
- 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 +20 -51
- package/lib/quoxon/qdragon.js +410 -0
- package/lib/quoxon/qdragonFly.js +62 -0
- package/lib/quoxon/qdragonScenes.js +282 -0
- package/lib/quoxon/qeyes.js +213 -0
- package/lib/quoxon/qmotion.js +75 -37
- package/lib/quoxon/render.js +740 -124
- package/lib/quoxon/resend.js +72 -0
- package/lib/quoxon/roster.js +117 -17
- package/lib/quoxon/route.js +6 -5
- package/lib/quoxon/rows.js +954 -0
- package/lib/quoxon/rowsStore.js +80 -0
- package/lib/quoxon/screenContext.js +150 -0
- package/lib/quoxon/screenFrames.js +254 -0
- package/lib/quoxon/serverChip.js +42 -0
- package/lib/quoxon/session.js +182 -2
- package/lib/quoxon/sessionDead.js +17 -0
- package/lib/quoxon/signin.js +125 -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 +94 -0
- package/lib/quoxon/slash/cliLeaves.json +1023 -0
- package/lib/quoxon/slash/compact.js +1 -1
- 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/evidenceCmd.js +34 -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 +13 -8
- package/lib/quoxon/slash/listView.js +3 -1
- package/lib/quoxon/slash/modelCmd.js +261 -0
- package/lib/quoxon/slash/palette.js +22 -8
- package/lib/quoxon/slash/paletteController.js +117 -7
- package/lib/quoxon/slash/panels.js +21 -6
- package/lib/quoxon/slash/picker.js +20 -2
- package/lib/quoxon/slash/qpeCmd.js +35 -0
- package/lib/quoxon/slash/restart.js +95 -13
- package/lib/quoxon/slash/resume.js +18 -1
- package/lib/quoxon/slash/rowCmd.js +157 -0
- package/lib/quoxon/slash/rowWalk.js +175 -0
- package/lib/quoxon/slash/settings.js +16 -6
- 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/todoCmd.js +122 -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/mapping.js +2 -1
- package/lib/quoxon/stray.js +22 -0
- package/lib/quoxon/suggestCommand.js +90 -0
- package/lib/quoxon/term.js +46 -11
- package/lib/quoxon/textBuffer.js +176 -2
- package/lib/quoxon/theme.js +58 -25
- package/lib/quoxon/tips.js +433 -0
- package/lib/quoxon/toolCards.js +30 -0
- package/lib/quoxon/toolError.js +41 -0
- package/lib/quoxon/transcript.js +42 -4
- package/lib/quoxon/web/qface.js +18 -10
- package/lib/quoxon/welcome.js +1 -1
- package/lib/quoxon/whatsnew.js +136 -0
- package/lib/quoxon/whatsnew.jsonl +134 -0
- package/lib/quoxon/windowOrg.js +123 -0
- package/lib/server/devServers.js +137 -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/views/HyperionDeck.jsx +79 -0
- package/lib/tui/views/LucidDeck.jsx +209 -217
- package/package.json +5 -1
- package/vendor/dragonlab/SOURCE +4 -0
- package/vendor/dragonlab/design/cipher/v1/cipher-master.svg +46 -0
- package/vendor/dragonlab/design/cipher/v2/character.mjs +49 -0
- package/vendor/dragonlab/design/cipher/v2/review.mjs +33 -0
- package/vendor/dragonlab/design/cipher/v2/watch.mjs +56 -0
- package/vendor/dragonlab/design/drago/v1/character.mjs +19 -0
- package/vendor/dragonlab/design/drago/v1/drago-master.svg +56 -0
- package/vendor/dragonlab/design/drago/v2/character.mjs +87 -0
- package/vendor/dragonlab/design/drago/v4/character.mjs +67 -0
- package/vendor/dragonlab/design/drago/v4/profile.mjs +35 -0
- package/vendor/dragonlab/design/drago/v4/review.mjs +33 -0
- package/vendor/dragonlab/design/drago/v4/watch.mjs +56 -0
- package/vendor/dragonlab/design/hyperion/v1/hyperion-master.svg +56 -0
- package/vendor/dragonlab/design/hyperion/v2/character.mjs +79 -0
- package/vendor/dragonlab/design/hyperion/v2/review.mjs +33 -0
- package/vendor/dragonlab/design/hyperion/v2/watch.mjs +56 -0
- package/vendor/dragonlab/design/lucid/recovery/arrival.mjs +22 -0
- package/vendor/dragonlab/design/lucid/recovery/character.mjs +106 -0
- package/vendor/dragonlab/design/lucid/recovery/layout.mjs +50 -0
- package/vendor/dragonlab/design/lucid/recovery/lucid-master.svg +78 -0
- package/vendor/dragonlab/design/lucid/recovery/motion.mjs +42 -0
- package/vendor/dragonlab/design/lucid/recovery/render.mjs +121 -0
- package/vendor/dragonlab/design/lucid/recovery/review.mjs +68 -0
- package/vendor/dragonlab/design/lucid/recovery/verify.mjs +84 -0
- package/vendor/dragonlab/design/lucid/recovery/watch.mjs +56 -0
- package/vendor/dragonlab/design/seer/v1/seer-master.svg +53 -0
- package/vendor/dragonlab/design/seer/v2/character.mjs +88 -0
- package/vendor/dragonlab/design/seer/v3/character.mjs +33 -0
- package/vendor/dragonlab/design/seer/v4/character.mjs +66 -0
- package/vendor/dragonlab/design/seer/v4/review.mjs +33 -0
- package/vendor/dragonlab/design/seer/v4/watch.mjs +56 -0
- package/vendor/dragonlab/design/stage3/companion-review.mjs +28 -0
- package/vendor/dragonlab/design/stage3/sentinel-art.js +39 -0
- package/vendor/dragonlab/design/stage3/sentinel-braille.js +216 -0
- package/vendor/dragonlab/design/stage3/sentinel-character.js +257 -0
- package/vendor/dragonlab/design/stage3/sentinel-icon.js +103 -0
- package/vendor/dragonlab/design/stage3/sentinel-warden-eyes.js +95 -0
- package/vendor/dragonlab/design/stage3/svg-coverage.js +148 -0
- package/vendor/dragonlab/factory/svg-rig.mjs +63 -0
- package/vendor/dragonlab/integration/companions-v1/characters.mjs +88 -0
- package/vendor/dragonlab/integration/companions-v1/cipher.mjs +2 -0
- package/vendor/dragonlab/integration/companions-v1/companion.mjs +63 -0
- package/vendor/dragonlab/integration/companions-v1/drago.mjs +2 -0
- package/vendor/dragonlab/integration/companions-v1/hyperion.mjs +2 -0
- package/vendor/dragonlab/integration/companions-v1/lucid.mjs +2 -0
- package/vendor/dragonlab/integration/companions-v1/seer.mjs +2 -0
- package/vendor/dragonlab/integration/companions-v1/sentinel.mjs +2 -0
- package/vendor/dragonlab/integration/companions-v1/view.mjs +28 -0
- package/vendor/dragonlab/integration/hyperion-v3/activity.mjs +76 -0
- package/vendor/dragonlab/lib/power-effects.js +294 -0
- package/vendor/dragonlab/lib/power-registry.js +74 -0
- package/vendor/dragonlab/vendor/quox-dragon/qart.js +348 -0
- package/vendor/dragonlab/vendor/quox-dragon/qeyes.js +213 -0
- package/lib/core/websiteSession.js +0 -148
package/lib/quoxon/model.js
CHANGED
|
@@ -7,15 +7,27 @@
|
|
|
7
7
|
* differs from what was asked, the client says so once (never a silent fallback).
|
|
8
8
|
* null choice = auto (no provider in the body; the server's policy picks).
|
|
9
9
|
*
|
|
10
|
-
* Availability is the same source as /connect: the
|
|
10
|
+
* Availability is the same source as /connect, /doctor and /capabilities (providers.js): the server's
|
|
11
|
+
* capabilities, with the org vault for detail.
|
|
11
12
|
* Model ids mirror src/config/modelCatalog.js (openai + anthropic rows); an id not listed is passed
|
|
12
13
|
* through with a note, the server decides whether it exists.
|
|
13
14
|
*/
|
|
14
|
-
import { PROVIDERS as CONNECT,
|
|
15
|
+
import { PROVIDERS as CONNECT, offerText } from './connect.js';
|
|
16
|
+
import { providerState, SUBSCRIPTION_NAME } from './providers.js';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* No hardcoded model lists for the hosted providers: OpenAI and the Claude API are listed live by the server
|
|
20
|
+
* (GET /api/v1/providers/<p>/models, 1 h server cache, refreshed by /model). The Claude SUBSCRIPTION has no
|
|
21
|
+
* key to list with, so it takes the Claude CLI's family aliases, which the CLI resolves to the newest model.
|
|
22
|
+
*/
|
|
23
|
+
export const CLAUDE_FAMILIES = ['fable', 'opus', 'sonnet', 'haiku'];
|
|
15
24
|
|
|
16
25
|
export const CHOICES = [
|
|
17
|
-
{ id: 'openai', provider: 'openai', name: 'OpenAI', vault: 'openai', connect: 'openai',
|
|
18
|
-
{ id: 'anthropic', provider: 'anthropic', name: 'Claude API', vault: 'anthropic', connect: 'anthropic',
|
|
26
|
+
{ id: 'openai', provider: 'openai', name: 'OpenAI', vault: 'openai', connect: 'openai', discover: true, models: [] },
|
|
27
|
+
{ id: 'anthropic', provider: 'anthropic', name: 'Claude API', vault: 'anthropic', connect: 'anthropic', discover: true, models: [] },
|
|
28
|
+
// R3: the server's signed-in Claude subscription (dev only). The stream asks for anthropic; with no Claude API
|
|
29
|
+
// key the server runs it through the claude CLI on that sign-in. `via` only tells the client which row it is.
|
|
30
|
+
{ id: 'claude-cli', provider: 'anthropic', via: 'subscription', cap: 'claude_subscription', name: SUBSCRIPTION_NAME, aliases: CLAUDE_FAMILIES, models: [] },
|
|
19
31
|
// B3: served by the chat stream; models are whatever that Ollama has installed (listed by the server).
|
|
20
32
|
{ id: 'ollama', provider: 'ollama', name: 'Ollama', vault: 'ollama', connect: 'ollama', dynamic: true, hint: 'http://<host>:11434', models: [] },
|
|
21
33
|
// B5: OpenAI-compatible presets. Models come from the provider's own /models (api.compatModels).
|
|
@@ -27,13 +39,16 @@ export const CHOICES = [
|
|
|
27
39
|
['custom', 'Custom', 'openai_compatible', false, ' https://<host>/v1']
|
|
28
40
|
].map(([id, name, vault, hosted, hint]) => ({ id, provider: id, name, vault, connect: id, dynamic: true, compat: true, hosted, selfHosted: !hosted, hint: hint.trim(), models: [] }))
|
|
29
41
|
];
|
|
30
|
-
const ALIASES = { gpt: 'openai', chatgpt: 'openai', claude: 'anthropic', 'claude-api': 'anthropic', 'anthropic-api': 'anthropic', local: 'ollama', hf: 'huggingface', 'lm-studio': 'lmstudio', 'openai-compatible': 'custom', openai_compatible: 'custom', default: 'auto', policy: 'auto' };
|
|
42
|
+
const ALIASES = { 'claude-subscription': 'claude-cli', subscription: 'claude-cli', 'claude-code': 'claude-cli', gpt: 'openai', chatgpt: 'openai', claude: 'anthropic', 'claude-api': 'anthropic', 'anthropic-api': 'anthropic', local: 'ollama', hf: 'huggingface', 'lm-studio': 'lmstudio', 'openai-compatible': 'custom', openai_compatible: 'custom', default: 'auto', policy: 'auto' };
|
|
31
43
|
const byProvider = (p) => CHOICES.find(c => c.provider === p) || null;
|
|
44
|
+
/** The CHOICES row a stored choice is (the subscription row shares provider anthropic). */
|
|
45
|
+
const rowOf = (ch) => (ch?.via === 'subscription' ? CHOICES.find(c => c.via === 'subscription') : byProvider(ch?.provider));
|
|
32
46
|
|
|
33
47
|
/** Stored or flag value -> { provider, model? } | null. Only the two known fields survive. */
|
|
34
48
|
export function normChoice(v) {
|
|
35
49
|
if (!v || typeof v !== 'object' || typeof v.provider !== 'string' || !byProvider(v.provider)) return null;
|
|
36
50
|
const out = typeof v.model === 'string' && v.model.trim() ? { provider: v.provider, model: v.model.trim() } : { provider: v.provider };
|
|
51
|
+
if (v.provider === 'anthropic' && v.via === 'subscription') out.via = 'subscription';
|
|
37
52
|
// B3/B5: the Ollama / self-hosted host, for the status line before the first turn (turn.started.endpoint wins after).
|
|
38
53
|
if ((v.provider === 'ollama' || byProvider(v.provider).selfHosted) && typeof v.host === 'string' && v.host.trim()) out.host = v.host.trim().slice(0, 120);
|
|
39
54
|
// D3: prompt profile (compact | full); absent = the server default (compact on ollama / local presets, full elsewhere).
|
|
@@ -53,31 +68,46 @@ export function parseModelArg(arg) {
|
|
|
53
68
|
if (id === 'auto') return { ok: true, choice: null };
|
|
54
69
|
const c = CHOICES.find(x => x.id === id);
|
|
55
70
|
if (!c) return { ok: false, error: `No provider called "${rawName}". Use ${CHOICES.map(x => x.id).join(', ')} or auto (/model lists them).` };
|
|
56
|
-
const
|
|
71
|
+
const resolved = model;
|
|
72
|
+
const choice = resolved ? { provider: c.provider, model: resolved } : { provider: c.provider };
|
|
73
|
+
if (c.via) choice.via = c.via;
|
|
57
74
|
if (profile) choice.profile = profile;
|
|
58
75
|
return { ok: true, choice };
|
|
59
76
|
}
|
|
60
77
|
|
|
61
|
-
/** ->
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
78
|
+
/** -> { state, row?, reason? } for one choice, from the shared provider status (providers.js). */
|
|
79
|
+
const availability = (res, c, caps) => providerState({ cap: c.cap || c.id, vault: c.vault }, caps, res);
|
|
80
|
+
|
|
81
|
+
/** QUOXON-FW6: a typed provider name or alias -> the CHOICES id, or null. */
|
|
82
|
+
export const providerId = (name) => {
|
|
83
|
+
const n = String(name || '').trim().toLowerCase();
|
|
84
|
+
const id = ALIASES[n] || n;
|
|
85
|
+
return CHOICES.some(c => c.id === id) ? id : null;
|
|
86
|
+
};
|
|
87
|
+
/** The CHOICES id a stored choice is (the subscription row shares provider anthropic). */
|
|
88
|
+
export const choiceId = (ch) => (ch ? rowOf(ch)?.id || null : null);
|
|
89
|
+
/** QUOXON-FW6: the CHOICES rows usable on this server now. Never lists a provider not connected. */
|
|
90
|
+
export const connectedChoices = (res, caps = null) =>
|
|
91
|
+
CHOICES.filter(c => c.served !== false && availability(res, c, caps).state === 'connected');
|
|
69
92
|
|
|
70
|
-
const same = (a, b) => (a?.provider || null) === (b?.provider || null);
|
|
93
|
+
const same = (a, b) => (a?.provider || null) === (b?.provider || null) && (a?.via || null) === (b?.via || null);
|
|
71
94
|
|
|
72
|
-
/** -> [[text, tone?]] lines for /model. */
|
|
73
|
-
export function modelLines(res, current) {
|
|
95
|
+
/** -> [[text, tone?]] lines for /model. `caps` = api.capabilities() (the server's word on each provider). */
|
|
96
|
+
export function modelLines(res, current, caps = null) {
|
|
74
97
|
const out = [['Model for your next turns (the server decides; turn.started shows what ran):']];
|
|
75
98
|
const mark = (on) => (on ? '▸' : ' ');
|
|
76
99
|
out.push([`${mark(!current)} auto server policy picks the provider and model · /model auto`]);
|
|
77
100
|
for (const c of CHOICES) {
|
|
78
|
-
const a = availability(res, c);
|
|
101
|
+
const a = availability(res, c, caps);
|
|
79
102
|
const picked = same(current, c);
|
|
80
103
|
const sel = picked && current.model ? ` · using ${current.model}` : '';
|
|
104
|
+
if (c.via === 'subscription') {
|
|
105
|
+
const api = availability(res, byProvider('anthropic'), caps).state === 'connected';
|
|
106
|
+
if (a.state === 'connected') out.push([`${mark(picked)} ● ${c.name} ready on this server${sel} · /model claude-cli${api ? ' (the Claude API key is used first while it is connected)' : ''}`, 'ok']);
|
|
107
|
+
else if (a.state === 'unknown') out.push([`${mark(picked)} ? ${c.name} unknown (the server did not say)`, 'warn']);
|
|
108
|
+
else out.push([`${mark(picked)} ○ ${c.name} ${a.reason || 'not signed in on this server'} · sign in from the dashboard Terminal`]);
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
81
111
|
if (c.served === false) {
|
|
82
112
|
out.push([`${mark(picked)} ${c.name} ${a.state === 'connected' ? 'connected, but ' : ''}not served by the chat stream yet`]);
|
|
83
113
|
continue;
|
|
@@ -85,17 +115,17 @@ export function modelLines(res, current) {
|
|
|
85
115
|
if (c.dynamic) {
|
|
86
116
|
if (a.state === 'connected') out.push([`${mark(picked)} ● ${c.name} connected · ${a.row.label || a.row.id}${sel} · /model ${c.id} lists its ${c.compat ? '' : 'installed '}models`, 'ok']);
|
|
87
117
|
else if (a.state === 'stored') out.push([`${mark(picked)} ◐ ${c.name} stored but not the default · quox vault set-default ${a.row.id}`, 'warn']);
|
|
88
|
-
else if (a.state === 'missing') out.push([`${mark(picked)} ○ ${c.name} not connected · /connect ${c.connect}${c.hint ? ` ${c.hint}` : ''}`]);
|
|
118
|
+
else if (a.state === 'missing' || a.state === 'unavailable') out.push([`${mark(picked)} ○ ${c.name} not connected · /connect ${c.connect}${c.hint ? ` ${c.hint}` : ''}`]);
|
|
89
119
|
else out.push([`${mark(picked)} ? ${c.name} unknown (vault not readable)${sel}`, 'warn']);
|
|
90
120
|
continue;
|
|
91
121
|
}
|
|
92
122
|
const models = c.models.join(', ');
|
|
93
|
-
if (a.state === 'connected') out.push([`${mark(picked)} ● ${c.name} connected${sel} · /model ${c.id} [${models}]`, 'ok']);
|
|
123
|
+
if (a.state === 'connected') out.push([`${mark(picked)} ● ${c.name} connected${sel} · /model ${c.id} [${models || 'live list'}]`, 'ok']);
|
|
124
|
+
else if (a.state === 'unavailable') out.push([`${mark(picked)} ○ ${c.name} ${a.reason || 'not available on this server'}`]);
|
|
94
125
|
else if (a.state === 'stored') out.push([`${mark(picked)} ◐ ${c.name} stored but not the default · quox vault set-default ${a.row.id}`, 'warn']);
|
|
95
126
|
else if (a.state === 'missing') out.push([`${mark(picked)} ○ ${c.name} not connected · /connect ${c.connect}`]);
|
|
96
127
|
else out.push([`${mark(picked)} ? ${c.name} unknown (vault not readable)${sel}`, 'warn']);
|
|
97
128
|
}
|
|
98
|
-
out.push([' ? Claude (subscription) dev-only · reached via auto or anthropic when no Claude API key is stored; no status route']);
|
|
99
129
|
if (!res?.ok) out.push([` could not read the vault: ${res?.error || 'no answer'}`, 'warn']);
|
|
100
130
|
return out;
|
|
101
131
|
}
|
|
@@ -104,22 +134,76 @@ export function modelLines(res, current) {
|
|
|
104
134
|
* The /model <arg> decision. -> { ok, choice?, text, tone? }. A provider the vault says is not usable is
|
|
105
135
|
* refused (with the /connect offer), never quietly swapped for another.
|
|
106
136
|
*/
|
|
107
|
-
export function selectModel(res, arg) {
|
|
137
|
+
export function selectModel(res, arg, caps = null) {
|
|
108
138
|
const p = parseModelArg(arg);
|
|
109
139
|
if (!p.ok) return { ok: false, text: p.error, tone: 'warn' };
|
|
110
140
|
if (!p.choice) return { ok: true, choice: null, text: 'Model: auto. The server policy picks the provider and model.', tone: 'ok' };
|
|
111
|
-
const c =
|
|
141
|
+
const c = rowOf(p.choice);
|
|
112
142
|
if (c.served === false) return { ok: false, text: `${c.name} is not served by the chat stream yet, so I can't use it here. /model lists what can.`, tone: 'warn' };
|
|
113
|
-
const a = availability(res, c);
|
|
143
|
+
const a = availability(res, c, caps);
|
|
144
|
+
const label0 = p.choice.model ? `${c.id} ${p.choice.model}` : c.id;
|
|
145
|
+
if (c.via === 'subscription') {
|
|
146
|
+
if (a.state === 'unknown') return { ok: true, choice: p.choice, text: `Model: ${label0}. Could not confirm the ${c.name} is signed in; the server will say if it is not.`, tone: 'warn' };
|
|
147
|
+
if (a.state !== 'connected') return { ok: false, text: `${c.name} is not ready on this server (${a.reason || 'not signed in'}). Sign in from the dashboard Terminal.`, tone: 'warn' };
|
|
148
|
+
const api = availability(res, byProvider('anthropic'), caps).state === 'connected';
|
|
149
|
+
return { ok: true, choice: p.choice, text: `Model: ${c.name}${p.choice.model ? ` · ${p.choice.model}` : ''}. ${api ? 'The Claude API key is connected, so the server uses it first.' : 'Your next turns ask for it.'}`, tone: api ? 'warn' : 'ok' };
|
|
150
|
+
}
|
|
151
|
+
if (c.id === 'anthropic' && a.state === 'missing' && availability(res, rowOf({ via: 'subscription' }), caps).state === 'connected') {
|
|
152
|
+
// what --provider anthropic does: no API key, so the server runs Claude on its signed-in subscription
|
|
153
|
+
return { ok: true, choice: { ...p.choice, via: 'subscription' }, text: `Model: ${label0}. The Claude API isn't connected, so your next turns run on the ${SUBSCRIPTION_NAME}.`, tone: 'warn' };
|
|
154
|
+
}
|
|
114
155
|
if (c.dynamic && a.state === 'missing') return { ok: false, text: `${c.name} isn't connected, so I can't use it yet. Connect it: /connect ${c.connect}${c.hint ? ` ${c.hint}` : ''}`, tone: 'warn' };
|
|
115
156
|
if (a.state === 'missing') return { ok: false, text: offerText(CONNECT[c.connect]), tone: 'warn' };
|
|
157
|
+
if (a.state === 'unavailable') return { ok: false, text: `${c.name} isn't available on this server${a.reason ? `: ${a.reason}` : ''}.`, tone: 'warn' };
|
|
116
158
|
if (a.state === 'stored') return { ok: false, text: `${c.name} has a key stored, but not as the org default, so the server will not use it. Fix: quox vault set-default ${a.row.id}`, tone: 'warn' };
|
|
117
159
|
const label = p.choice.model ? `${c.id} ${p.choice.model}` : c.id;
|
|
118
|
-
const extra = p.choice.model && !c.dynamic && !c.models.includes(p.choice.model) ? ` ${p.choice.model} is not in this client's list; the server decides whether it exists.` : '';
|
|
119
|
-
if (a.state === 'unknown') return { ok: true, choice: p.choice, text: `Model: ${label}. Could not confirm ${c.name} is connected (
|
|
160
|
+
const extra = p.choice.model && !c.dynamic && !c.discover && !c.models.includes(p.choice.model) ? ` ${p.choice.model} is not in this client's list; the server decides whether it exists.` : '';
|
|
161
|
+
if (a.state === 'unknown') return { ok: true, choice: p.choice, text: `Model: ${label}. Could not confirm ${c.name} is connected (the server did not say); the server will say if it is not.${extra}`, tone: 'warn' };
|
|
120
162
|
return { ok: true, choice: p.choice, text: `Model: ${label}. Your next turns ask for it.${extra}`, tone: extra ? 'warn' : 'ok' };
|
|
121
163
|
}
|
|
122
164
|
|
|
165
|
+
/** Newest-first discovered rows -> ids. */
|
|
166
|
+
const ids = (res) => (res?.ok && Array.isArray(res.models) ? res.models.map(modelId).filter(Boolean) : []);
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* /model openai|anthropic [model] against the live list (res from api.providerModels(provider)).
|
|
170
|
+
* Match order: exact id, "latest" (newest listed), a family (opus, sonnet, mini, ...) or any prefix/substring ->
|
|
171
|
+
* the NEWEST match. No match -> a clear error with the list. Discovery failed -> any typed id is allowed with a
|
|
172
|
+
* plain note (the provider validates it on first use). -> { ok, choice?, text, tone, models?, current? }
|
|
173
|
+
*/
|
|
174
|
+
export function selectDiscoveredModel(res, choice, row) {
|
|
175
|
+
const name = row.name;
|
|
176
|
+
const model = choice.model || null;
|
|
177
|
+
const list = ids(res);
|
|
178
|
+
const rows = res?.ok ? res.models : [];
|
|
179
|
+
if (!res?.ok || !list.length) {
|
|
180
|
+
const why = res?.message || res?.error || 'no answer';
|
|
181
|
+
if (!model) return { ok: true, choice, text: `Model: ${row.id}. Could not list ${name}'s models (${why}); the server uses its default.`, tone: 'warn' };
|
|
182
|
+
return { ok: true, choice, text: `Model: ${row.id} ${model}. Could not list ${name}'s models (${why}); ${model} is sent as typed and the provider will say if it does not exist.`, tone: 'warn' };
|
|
183
|
+
}
|
|
184
|
+
const newest = res.default_model || list[0];
|
|
185
|
+
if (!model) return { ok: true, choice, text: `Model: ${name} · ${newest} (the newest listed). Models below; pick one with /model ${row.id} <model|family|latest>.`, tone: 'ok', models: rows, current: newest };
|
|
186
|
+
const q = model.toLowerCase();
|
|
187
|
+
const hit = q === 'latest' ? list[0]
|
|
188
|
+
: list.find(i => i.toLowerCase() === q)
|
|
189
|
+
|| rows.find(r => r.family && r.family === q)?.id
|
|
190
|
+
|| list.find(i => i.toLowerCase().startsWith(q))
|
|
191
|
+
|| list.find(i => i.toLowerCase().includes(q));
|
|
192
|
+
if (!hit) return { ok: false, text: `${model} is not in ${name}'s live list. Pick an id, a family (${[...new Set(rows.map(r => r.family).filter(Boolean))].slice(0, 6).join(', ') || 'see below'}) or latest.`, tone: 'warn', models: rows };
|
|
193
|
+
return { ok: true, choice: { ...choice, model: hit }, text: `Model: ${name} · ${hit}${hit !== model ? ` (from "${model}")` : ''}. Your next turns ask for it.`, tone: 'ok' };
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/** Subscription path: a Claude family alias (the CLI resolves the newest) or a full claude-* id; anything else is an error with the aliases. */
|
|
197
|
+
export function selectSubscriptionModel(choice) {
|
|
198
|
+
const m = choice.model;
|
|
199
|
+
if (!m) return { ok: true, choice, text: 'Model: claude-cli. The server default applies (the newest Sonnet unless your org set CLAUDE_CLI_DEFAULT_MODEL).', tone: 'ok' };
|
|
200
|
+
const q = m.toLowerCase();
|
|
201
|
+
if (CLAUDE_FAMILIES.includes(q)) return { ok: true, choice: { ...choice, model: q }, text: `Model: claude-cli ${q} (the Claude CLI resolves the newest ${q}; turn.started shows what ran).`, tone: 'ok' };
|
|
202
|
+
if (q === 'latest') return { ok: true, choice: { provider: choice.provider, via: choice.via }, text: 'Model: claude-cli, the server default.', tone: 'ok' };
|
|
203
|
+
if (/^claude-[a-z0-9.\-]+$/i.test(m)) return { ok: true, choice, text: `Model: claude-cli ${m}. The CLI will say if it does not exist.`, tone: 'ok' };
|
|
204
|
+
return { ok: false, text: `Unknown Claude model "${m}". Use ${CLAUDE_FAMILIES.join(', ')} (the CLI picks the newest) or a full claude-* id.`, tone: 'warn' };
|
|
205
|
+
}
|
|
206
|
+
|
|
123
207
|
/**
|
|
124
208
|
* B3: /model ollama [model] against the server's installed-model list (res from api.ollamaModels()).
|
|
125
209
|
* -> { ok, choice?, text, tone, rows? } ; rows = the installed list to show.
|
|
@@ -179,13 +263,35 @@ export function selectCompatModel(res, presetId, model) {
|
|
|
179
263
|
return { ok: true, choice: withHost({ provider: c.provider, model }), text: `Model: ${c.name}${host ? ` · ${host}` : ''} · ${model}. Your next turns ask for it.`, tone: 'ok' };
|
|
180
264
|
}
|
|
181
265
|
|
|
266
|
+
/**
|
|
267
|
+
* Z2H-3B: after a successful /connect, should the new provider become the active choice? Yes when the current
|
|
268
|
+
* selection is not a working connected provider (nothing connected, or a saved/phantom choice that is not connected),
|
|
269
|
+
* or when auto has no OTHER connected provider to pick. No provider is a silent default: with another working one
|
|
270
|
+
* active, nothing changes and the caller keeps its "Use it: /model <id>" hint.
|
|
271
|
+
* -> { adopt:true, choice, line } | { adopt:false }. `vault`/`caps` are re-read AFTER the connect.
|
|
272
|
+
*/
|
|
273
|
+
export function adoptAfterConnect({ vault, caps, current, id, host = null, model = null }) {
|
|
274
|
+
const row = CHOICES.find(c => c.id === id);
|
|
275
|
+
if (!row) return { adopt: false };
|
|
276
|
+
const connected = (c) => availability(vault, c, caps).state === 'connected';
|
|
277
|
+
const cur = current ? rowOf(current) : null;
|
|
278
|
+
const curWorks = cur && cur.id !== row.id && connected(cur);
|
|
279
|
+
const othersConnected = CHOICES.some(c => c.id !== row.id && connected(c));
|
|
280
|
+
if (curWorks) return { adopt: false };
|
|
281
|
+
if (!current && othersConnected) return { adopt: false };
|
|
282
|
+
const choice = normChoice({ provider: row.provider, ...(model ? { model } : {}), ...(host ? { host } : {}) });
|
|
283
|
+
return { adopt: true, choice, line: `Using ${row.name}${model ? ` ${model}` : ''} now. /model to change.` };
|
|
284
|
+
}
|
|
285
|
+
|
|
182
286
|
/** Status-line text: what the server said it ran, else what is selected; null for auto before any turn (keeps the line short). */
|
|
183
287
|
export function modelLabel(ctx, choice) {
|
|
288
|
+
if (ctx?.model && ctx.provider === 'anthropic' && ctx.auth === 'subscription') return `claude-cli ${ctx.model}`;
|
|
184
289
|
if (ctx?.model && ctx.provider === 'ollama') return `Ollama · ${ctx.endpoint || choice?.host || '?'} · ${ctx.model}`;
|
|
185
290
|
const cc = ctx?.provider ? byProvider(ctx.provider) : null;
|
|
186
291
|
if (ctx?.model && cc?.compat) return cc.hosted ? `${cc.name} · ${ctx.model}` : `${cc.name} · ${ctx.endpoint || choice?.host || '?'} · ${ctx.model}`;
|
|
187
292
|
if (ctx?.model) return ctx.provider ? `${ctx.provider} ${ctx.model}` : ctx.model;
|
|
188
293
|
if (!choice) return null;
|
|
294
|
+
if (choice.via === 'subscription') return choice.model ? `claude-cli ${choice.model}` : 'claude-cli';
|
|
189
295
|
if (choice.provider === 'ollama') return `Ollama · ${choice.host || '?'} · ${choice.model || 'default model'}`;
|
|
190
296
|
const ch = byProvider(choice.provider);
|
|
191
297
|
if (ch?.compat) return ch.hosted ? `${ch.name} · ${choice.model || 'pick a model'}` : `${ch.name} · ${choice.host || '?'} · ${choice.model || 'default model'}`;
|
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QUOXON-FW6: model favourites and the default model. Pure helpers plus one quiet live-list reader.
|
|
3
|
+
*
|
|
4
|
+
* The preference is { favourites: [{ provider, model }], default: { provider, model } | null }.
|
|
5
|
+
* `provider` is a /model provider id (openai, anthropic, claude-cli, ollama, a compat preset).
|
|
6
|
+
* Persistence is the caller's (api.modelPrefs / api.setModelPrefs: the user's server-side
|
|
7
|
+
* preferences, with the profile as a cache). There is no model list in this file: ids come from
|
|
8
|
+
* the provider's own live list (liveModels) or from what the user typed. A typed id that is not on
|
|
9
|
+
* the live list is kept with a warning, never refused.
|
|
10
|
+
*/
|
|
11
|
+
import { CHOICES, CLAUDE_FAMILIES, providerId } from './model.js';
|
|
12
|
+
|
|
13
|
+
export const MAX_FAVS = 20;
|
|
14
|
+
const nameOf = (id) => CHOICES.find((c) => c.id === id)?.name || id;
|
|
15
|
+
|
|
16
|
+
const cleanOne = (v) => {
|
|
17
|
+
const provider = providerId(v?.provider);
|
|
18
|
+
const model = typeof v?.model === 'string' ? v.model.trim().slice(0, 200) : '';
|
|
19
|
+
return provider && model && !/\s/.test(model) ? { provider, model } : null;
|
|
20
|
+
};
|
|
21
|
+
export const sameModel = (a, b) =>
|
|
22
|
+
Boolean(a && b) && a.provider === b.provider && a.model === b.model;
|
|
23
|
+
|
|
24
|
+
/** Anything stored or received -> a clean prefs object (bad rows dropped, duplicates removed). */
|
|
25
|
+
export function normPrefs(v) {
|
|
26
|
+
const favs = [];
|
|
27
|
+
for (const f of Array.isArray(v?.favourites) ? v.favourites : []) {
|
|
28
|
+
const c = cleanOne(f);
|
|
29
|
+
if (c && !favs.some((x) => sameModel(x, c)) && favs.length < MAX_FAVS) favs.push(c);
|
|
30
|
+
}
|
|
31
|
+
return { favourites: favs, default: cleanOne(v?.default) };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const label = (m) => `${m.provider} ${m.model}`;
|
|
35
|
+
|
|
36
|
+
/** -> { prefs, added, text, tone }. */
|
|
37
|
+
export function addFav(prefs, provider, model) {
|
|
38
|
+
const c = cleanOne({ provider, model });
|
|
39
|
+
if (!c)
|
|
40
|
+
return { prefs, added: false, text: 'Usage: /model fav add <provider> <model>', tone: 'warn' };
|
|
41
|
+
const at = prefs.favourites.findIndex((f) => sameModel(f, c));
|
|
42
|
+
if (at >= 0)
|
|
43
|
+
return {
|
|
44
|
+
prefs,
|
|
45
|
+
added: false,
|
|
46
|
+
text: `${label(c)} is already favourite ${at + 1}.`,
|
|
47
|
+
tone: 'plain',
|
|
48
|
+
};
|
|
49
|
+
if (prefs.favourites.length >= MAX_FAVS)
|
|
50
|
+
return {
|
|
51
|
+
prefs,
|
|
52
|
+
added: false,
|
|
53
|
+
text: `You have ${MAX_FAVS} favourites already. /model fav rm <n> makes room.`,
|
|
54
|
+
tone: 'warn',
|
|
55
|
+
};
|
|
56
|
+
const next = { ...prefs, favourites: [...prefs.favourites, c] };
|
|
57
|
+
const n = next.favourites.length;
|
|
58
|
+
return {
|
|
59
|
+
prefs: next,
|
|
60
|
+
added: true,
|
|
61
|
+
text: `Added ${label(c)} as favourite ${n}. /model ${n} switches to it.`,
|
|
62
|
+
tone: 'ok',
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* ref = a 1-based number, or a model id (exact, "provider model", "provider/model", or a
|
|
68
|
+
* unique substring).
|
|
69
|
+
*/
|
|
70
|
+
export function rmFav(prefs, ref) {
|
|
71
|
+
const q = String(ref ?? '')
|
|
72
|
+
.trim()
|
|
73
|
+
.toLowerCase();
|
|
74
|
+
const favs = prefs.favourites;
|
|
75
|
+
if (!q) return { prefs, removed: null, text: 'Usage: /model fav rm <n|model>', tone: 'warn' };
|
|
76
|
+
let idx = -1;
|
|
77
|
+
if (/^\d+$/.test(q)) idx = Number(q) - 1;
|
|
78
|
+
else {
|
|
79
|
+
const exact = favs
|
|
80
|
+
.map((f, i) => i)
|
|
81
|
+
.filter(
|
|
82
|
+
(i) =>
|
|
83
|
+
favs[i].model.toLowerCase() === q ||
|
|
84
|
+
label(favs[i]).toLowerCase() === q ||
|
|
85
|
+
`${favs[i].provider}/${favs[i].model}`.toLowerCase() === q,
|
|
86
|
+
);
|
|
87
|
+
const part = favs.map((f, i) => i).filter((i) => favs[i].model.toLowerCase().includes(q));
|
|
88
|
+
const hits = exact.length ? exact : part;
|
|
89
|
+
if (hits.length > 1)
|
|
90
|
+
return {
|
|
91
|
+
prefs,
|
|
92
|
+
removed: null,
|
|
93
|
+
text:
|
|
94
|
+
`"${ref}" matches ${hits.map((i) => `${i + 1} ${label(favs[i])}`).join(', ')}. ` +
|
|
95
|
+
'Use the number.',
|
|
96
|
+
tone: 'warn',
|
|
97
|
+
};
|
|
98
|
+
idx = hits.length ? hits[0] : -1;
|
|
99
|
+
}
|
|
100
|
+
if (idx < 0 || idx >= favs.length)
|
|
101
|
+
return {
|
|
102
|
+
prefs,
|
|
103
|
+
removed: null,
|
|
104
|
+
text: favs.length
|
|
105
|
+
? `No favourite "${ref}". /model fav lists them.`
|
|
106
|
+
: 'You have no favourites yet. /model fav add <provider> <model>',
|
|
107
|
+
tone: 'warn',
|
|
108
|
+
};
|
|
109
|
+
const removed = favs[idx];
|
|
110
|
+
return {
|
|
111
|
+
prefs: { ...prefs, favourites: favs.filter((_, i) => i !== idx) },
|
|
112
|
+
removed,
|
|
113
|
+
text: `Removed ${label(removed)}. The numbers after it moved up by one.`,
|
|
114
|
+
tone: 'ok',
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function setDefault(prefs, provider, model) {
|
|
119
|
+
const c = cleanOne({ provider, model });
|
|
120
|
+
if (!c)
|
|
121
|
+
return { prefs, ok: false, text: 'Usage: /model default <provider> <model>', tone: 'warn' };
|
|
122
|
+
return {
|
|
123
|
+
prefs: { ...prefs, default: c },
|
|
124
|
+
ok: true,
|
|
125
|
+
text:
|
|
126
|
+
`Default model: ${label(c)}. New sessions start on it when ${nameOf(c.provider)} is ` +
|
|
127
|
+
`connected. /model ${label(c)} uses it now.`,
|
|
128
|
+
tone: 'ok',
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
export const clearDefault = (prefs) => ({
|
|
132
|
+
prefs: { ...prefs, default: null },
|
|
133
|
+
text: 'Default model cleared. New sessions use the last choice, else the server policy.',
|
|
134
|
+
tone: 'ok',
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
/** Toggle one model in the favourites -> { prefs, added }. */
|
|
138
|
+
export function toggleFav(prefs, m) {
|
|
139
|
+
const i = prefs.favourites.findIndex((f) => sameModel(f, m));
|
|
140
|
+
if (i >= 0)
|
|
141
|
+
return {
|
|
142
|
+
prefs: { ...prefs, favourites: prefs.favourites.filter((_, k) => k !== i) },
|
|
143
|
+
added: false,
|
|
144
|
+
};
|
|
145
|
+
return {
|
|
146
|
+
prefs: {
|
|
147
|
+
...prefs,
|
|
148
|
+
favourites: [...prefs.favourites, { provider: m.provider, model: m.model }].slice(
|
|
149
|
+
0,
|
|
150
|
+
MAX_FAVS,
|
|
151
|
+
),
|
|
152
|
+
},
|
|
153
|
+
added: true,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** /model fav lines. `current` = the stored choice ({ provider, model?, via? }) or null. */
|
|
158
|
+
export function favLines(prefs, currentKey = null) {
|
|
159
|
+
if (!prefs.favourites.length)
|
|
160
|
+
return [
|
|
161
|
+
[
|
|
162
|
+
'No favourites yet. Add one: /model fav add <provider> <model> ' +
|
|
163
|
+
'(or open /model and press f)',
|
|
164
|
+
],
|
|
165
|
+
];
|
|
166
|
+
const out = [['Favourite models (/model <n> switches):']];
|
|
167
|
+
prefs.favourites.forEach((f, i) => {
|
|
168
|
+
const marks = [
|
|
169
|
+
currentKey && sameModel(f, currentKey) ? 'current' : null,
|
|
170
|
+
prefs.default && sameModel(f, prefs.default) ? 'default' : null,
|
|
171
|
+
].filter(Boolean);
|
|
172
|
+
out.push([` ${i + 1} ${label(f)}${marks.length ? ` (${marks.join(', ')})` : ''}`, 'plain']);
|
|
173
|
+
});
|
|
174
|
+
out.push([
|
|
175
|
+
prefs.default
|
|
176
|
+
? `Default: ${label(prefs.default)}`
|
|
177
|
+
: 'No default model set. /model default <provider> <model>',
|
|
178
|
+
]);
|
|
179
|
+
return out;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const FAV_WORDS = new Set(['fav', 'favs', 'favourite', 'favourites', 'favorite', 'favorites']);
|
|
183
|
+
const SUBS_ONE = { list: 'list', status: 'list' };
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* The words after /model -> what to do. kinds: chooser list use fav-list fav-add fav-rm
|
|
187
|
+
* default-show default-set default-clear error pass.
|
|
188
|
+
*/
|
|
189
|
+
export function parseModelSub(arg) {
|
|
190
|
+
const w = String(arg || '')
|
|
191
|
+
.trim()
|
|
192
|
+
.split(/\s+/)
|
|
193
|
+
.filter(Boolean);
|
|
194
|
+
if (!w.length) return { kind: 'chooser' };
|
|
195
|
+
const first = w[0].toLowerCase();
|
|
196
|
+
if (SUBS_ONE[first] && w.length === 1) return { kind: SUBS_ONE[first] };
|
|
197
|
+
if (/^\d+$/.test(first) && w.length === 1) return { kind: 'use', n: Number(first) };
|
|
198
|
+
if (FAV_WORDS.has(first)) {
|
|
199
|
+
const sub = (w[1] || '').toLowerCase();
|
|
200
|
+
if (!sub) return { kind: 'fav-list' };
|
|
201
|
+
if (/^\d+$/.test(sub) && w.length === 2) return { kind: 'use', n: Number(sub) };
|
|
202
|
+
if (sub === 'add') {
|
|
203
|
+
if (w.length !== 4)
|
|
204
|
+
return {
|
|
205
|
+
kind: 'error',
|
|
206
|
+
text:
|
|
207
|
+
'Usage: /model fav add <provider> <model> ' +
|
|
208
|
+
'e.g. /model fav add openai gpt-5.6-terra',
|
|
209
|
+
};
|
|
210
|
+
return { kind: 'fav-add', provider: w[2], model: w[3] };
|
|
211
|
+
}
|
|
212
|
+
if (['rm', 'remove', 'del', 'delete'].includes(sub))
|
|
213
|
+
return w.length < 3
|
|
214
|
+
? { kind: 'error', text: 'Usage: /model fav rm <n|model>' }
|
|
215
|
+
: { kind: 'fav-rm', ref: w.slice(2).join(' ') };
|
|
216
|
+
return {
|
|
217
|
+
kind: 'error',
|
|
218
|
+
text: 'Usage: /model fav [add <provider> <model> | rm <n|model> | <n>]',
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
if (first === 'default') {
|
|
222
|
+
if (w.length === 1) return { kind: 'default-show' };
|
|
223
|
+
if (w.length === 2 && ['clear', 'none', 'off', 'unset'].includes(w[1].toLowerCase()))
|
|
224
|
+
return { kind: 'default-clear' };
|
|
225
|
+
if (w.length === 3) return { kind: 'default-set', provider: w[1], model: w[2] };
|
|
226
|
+
return { kind: 'error', text: 'Usage: /model default [<provider> <model> | clear]' };
|
|
227
|
+
}
|
|
228
|
+
return { kind: 'pass' };
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* A provider's list response (the shape differs per provider) -> ids, newest first as the
|
|
233
|
+
* server gave them.
|
|
234
|
+
*/
|
|
235
|
+
export function idsOf(choiceId, res) {
|
|
236
|
+
if (choiceId === 'claude-cli') return [...CLAUDE_FAMILIES];
|
|
237
|
+
if (!res?.ok || !Array.isArray(res.models)) return [];
|
|
238
|
+
const raw =
|
|
239
|
+
choiceId === 'ollama'
|
|
240
|
+
? res.models.map((m) => (typeof m === 'string' ? m : m?.name))
|
|
241
|
+
: res.models.map((m) => (typeof m === 'string' ? m : m?.id));
|
|
242
|
+
return raw.filter((x) => typeof x === 'string' && x);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* The live model ids for one provider, never throws. -> { ok, ids, def }. ok=false means
|
|
247
|
+
* "could not list" (the caller decides how quiet to be); ids is [] then.
|
|
248
|
+
*/
|
|
249
|
+
export async function liveModels(api, id, { refresh = false } = {}) {
|
|
250
|
+
try {
|
|
251
|
+
if (id === 'claude-cli') return { ok: true, ids: [...CLAUDE_FAMILIES], def: null };
|
|
252
|
+
const row = CHOICES.find((c) => c.id === id);
|
|
253
|
+
if (!row) return { ok: false, ids: [], def: null };
|
|
254
|
+
let res = null;
|
|
255
|
+
if (id === 'ollama') res = await api.ollamaModels?.();
|
|
256
|
+
else if (row.compat) res = await api.compatModels?.(id);
|
|
257
|
+
else if (row.discover)
|
|
258
|
+
res = await api.providerModels?.(row.provider, refresh ? { refresh: true } : undefined);
|
|
259
|
+
if (!res?.ok) return { ok: false, ids: [], def: null };
|
|
260
|
+
return { ok: true, ids: idsOf(id, res), def: res.default_model || null };
|
|
261
|
+
} catch {
|
|
262
|
+
return { ok: false, ids: [], def: null };
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Typed id vs the live list -> { model, known: true|false|null, note? }. null = no list to
|
|
268
|
+
* check against.
|
|
269
|
+
*/
|
|
270
|
+
export function checkModelId(ids, typed, providerName = '') {
|
|
271
|
+
if (!ids?.length) return { model: typed, known: null };
|
|
272
|
+
if (ids.includes(typed)) return { model: typed, known: true };
|
|
273
|
+
const ci =
|
|
274
|
+
ids.find((i) => i.toLowerCase() === typed.toLowerCase()) ||
|
|
275
|
+
ids.find((i) => i === `${typed}:latest`);
|
|
276
|
+
if (ci) return { model: ci, known: true };
|
|
277
|
+
return {
|
|
278
|
+
model: typed,
|
|
279
|
+
known: false,
|
|
280
|
+
note:
|
|
281
|
+
`${typed} is not on ${providerName || 'the provider'}'s live list right now; ` +
|
|
282
|
+
'kept as typed in case it is new.',
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/* ---- the /model chooser ---------------------------------------------------------- */
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Selectable items, in display order: favourites first (numbered by their place in the full
|
|
290
|
+
* list; ones on a provider that is not connected are left out), then each connected provider's
|
|
291
|
+
* models.
|
|
292
|
+
* groups: [{ id, name, ids, note? }] for CONNECTED providers only.
|
|
293
|
+
*/
|
|
294
|
+
export function chooserItems(prefs, groups) {
|
|
295
|
+
const connected = new Set(groups.map((g) => g.id));
|
|
296
|
+
const items = [];
|
|
297
|
+
prefs.favourites.forEach((f, i) => {
|
|
298
|
+
if (connected.has(f.provider))
|
|
299
|
+
items.push({ kind: 'fav', n: i + 1, provider: f.provider, model: f.model });
|
|
300
|
+
});
|
|
301
|
+
for (const g of groups) {
|
|
302
|
+
if (!g.ids.length)
|
|
303
|
+
items.push({
|
|
304
|
+
kind: 'provider',
|
|
305
|
+
provider: g.id,
|
|
306
|
+
model: null,
|
|
307
|
+
name: g.name,
|
|
308
|
+
note: g.note || 'no model list; the server picks',
|
|
309
|
+
});
|
|
310
|
+
else
|
|
311
|
+
for (const id of g.ids)
|
|
312
|
+
items.push({ kind: 'model', provider: g.id, model: id, name: g.name });
|
|
313
|
+
}
|
|
314
|
+
return items;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* One picker row ({ key, text, note? }) for an item. `current` = { provider, model } of the
|
|
319
|
+
* stored choice, or null.
|
|
320
|
+
*/
|
|
321
|
+
export function chooserRow(item, prefs, current) {
|
|
322
|
+
if (item.kind === 'provider')
|
|
323
|
+
return { key: item.provider, text: item.name, note: item.note, dim: false };
|
|
324
|
+
const here = current && current.model ? sameModel(current, item) : false;
|
|
325
|
+
const marks = [
|
|
326
|
+
here ? 'current' : null,
|
|
327
|
+
prefs.default && sameModel(prefs.default, item) ? 'default' : null,
|
|
328
|
+
item.kind === 'model' && prefs.favourites.some((f) => sameModel(f, item)) ? '★' : null,
|
|
329
|
+
].filter(Boolean);
|
|
330
|
+
const body = item.kind === 'fav' ? label(item) : item.model;
|
|
331
|
+
return {
|
|
332
|
+
key: item.kind === 'fav' ? `★ ${item.n}` : item.provider,
|
|
333
|
+
text: body + (marks.length ? ` ${marks.join(' · ')}` : ''),
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* The stored choice as { provider: <CHOICES id>, model } for marking; null when it carries no
|
|
339
|
+
* model.
|
|
340
|
+
*/
|
|
341
|
+
export function currentKey(choice, idOf) {
|
|
342
|
+
if (!choice?.model) return null;
|
|
343
|
+
const provider = idOf(choice);
|
|
344
|
+
return provider ? { provider, model: choice.model } : null;
|
|
345
|
+
}
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* Demand shapes: { every: ms } (repeat while declared) or { at: epochMs } (one wake at that time).
|
|
8
8
|
*/
|
|
9
|
+
import { normaliseLevel } from './level.js';
|
|
9
10
|
export const FRAME_MS = 83; // ~12 fps for anything that moves
|
|
10
11
|
export const SECOND_MS = 1000;
|
|
11
12
|
|
|
@@ -49,8 +50,8 @@ export class Clock {
|
|
|
49
50
|
get armed() { return this.id != null; }
|
|
50
51
|
}
|
|
51
52
|
|
|
52
|
-
/** Motion is decoration: off
|
|
53
|
-
export const reducedMotion = (env = process.env) => env.QUOX_REDUCED_MOTION === '1' || Boolean(env.QUOX_TUI_LITE);
|
|
53
|
+
/** Motion is decoration: off at the quiet level (QUOX_MOTION=quiet, QUOX_REDUCED_MOTION=1, QUOX_TUI_LITE), or when not on a real terminal. Colour is separate (NO_COLOR). */
|
|
54
|
+
export const reducedMotion = (env = process.env) => env.QUOX_REDUCED_MOTION === '1' || Boolean(env.QUOX_TUI_LITE) || normaliseLevel(env.QUOX_MOTION) === 'quiet';
|
|
54
55
|
export const motionAllowed = (isTTY, env = process.env) => Boolean(isTTY) && !reducedMotion(env);
|
|
55
56
|
|
|
56
57
|
/**
|