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
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import React from 'react';
|
|
12
12
|
import { Box, Static, Text, useStdin, useStdout } from 'ink';
|
|
13
|
-
import { HistoryItem, Row, introAnimates, clampCols } from '../render.js';
|
|
13
|
+
import { HistoryItem, Row, introAnimates, clampCols, AskCard } from '../render.js';
|
|
14
14
|
import { c, clip } from '../theme.js';
|
|
15
15
|
import { parseChunk, initialKeyState, PASTE_ON, PASTE_OFF } from '../keys.js';
|
|
16
16
|
import { packageVersion } from '../welcome.js';
|
|
@@ -18,10 +18,15 @@ import * as T from './copy.js';
|
|
|
18
18
|
import { matchIntent, yesNo, parseConnect } from './intent.js';
|
|
19
19
|
import { loadJourney, saveJourney, clearJourney, resumeStep } from './journey.js';
|
|
20
20
|
import { pendingRows, runPreflight, preflightPassed, detectOllama, defaultDeps } from './preflight.js';
|
|
21
|
-
import { credentialOptions, KEY_PROVIDERS, normaliseOllamaUrl } from './credential.js';
|
|
21
|
+
import { credentialOptions, KEY_PROVIDERS, normaliseOllamaUrl, nextSelectable } from './credential.js';
|
|
22
|
+
import { needsConsent, providerLabel } from '../consent.js';
|
|
22
23
|
import { HelloIntro, helloIntroMs } from '../qmotion.js';
|
|
23
24
|
import { launchEgg } from '../egg.js';
|
|
24
25
|
import { passwordProblem, emailOk, generatePassword } from './finish.js';
|
|
26
|
+
import { dockerOffer } from './dockerOffer.js';
|
|
27
|
+
|
|
28
|
+
// FIRSTRUN: a number pick means its example ("1" is "watch my servers"); the request is replayed as the first real turn.
|
|
29
|
+
const requestText = (text) => { const n = /^\s*([1-9])\s*$/.exec(String(text ?? '')); return n && T.EXAMPLES[Number(n[1]) - 1] ? T.EXAMPLES[Number(n[1]) - 1] : text; };
|
|
25
30
|
|
|
26
31
|
const h = React.createElement;
|
|
27
32
|
const GLYPH = { ok: '✔', fail: '✖', warn: '⚠', running: '◐', pending: '·' };
|
|
@@ -55,6 +60,7 @@ function Item({ item, cols }) {
|
|
|
55
60
|
case 'header': return h(Box, { flexDirection: 'column', marginTop: 1 },
|
|
56
61
|
h(Text, null, h(Text, { color: c('violet'), bold: true }, T.LABEL), h(Text, { color: c('dim') }, ' ' + '─'.repeat(Math.max(1, cols - T.LABEL.length - 1)))),
|
|
57
62
|
h(Text, { color: c('dim') }, clip(T.LABEL_SUB, cols)));
|
|
63
|
+
case 'ask': return h(AskCard, { text: item.text, cols }); // QUOXON-ASK: the same card as an approval
|
|
58
64
|
case 'guide': return h(Box, { marginTop: item.gap === false ? 0 : 1, paddingLeft: 2 }, h(Para, { width: cols - 2, color: c(item.tone || 'text'), bold: Boolean(item.bold), text: item.text }));
|
|
59
65
|
case 'dim': return h(Box, { paddingLeft: 2 }, h(Para, { width: cols - 2, color: c(item.tone || 'dim'), text: item.text }));
|
|
60
66
|
case 'cmd': return h(Box, { paddingLeft: 4 }, h(Para, { width: cols - 4, color: c('sky'), text: item.text }));
|
|
@@ -95,7 +101,7 @@ const Hint = ({ text, cols }) => h(Box, { paddingLeft: 2 }, h(Text, { color: c('
|
|
|
95
101
|
|
|
96
102
|
// ---- the view ---------------------------------------------------------------------------------
|
|
97
103
|
export function GettingStarted({
|
|
98
|
-
columns, version = '0.
|
|
104
|
+
columns, version = '0.4.0', cwd = '', welcome = false, animateIntro, earlyInput = null,
|
|
99
105
|
journeyPath, dryRun = false, runner = null, preflightDeps, runChecks = runPreflight, findOllama = detectOllama,
|
|
100
106
|
requestCredential, broker = null, finisher = null, onExit = () => {}, now = () => Date.now()
|
|
101
107
|
}) {
|
|
@@ -114,7 +120,7 @@ export function GettingStarted({
|
|
|
114
120
|
const items = [introItem, ...(welcome ? [{ id: 'welcome', kind: 'welcome', version: packageVersion() }] : []), { id: 'hdr', kind: 'header' }];
|
|
115
121
|
if (resumeAt) {
|
|
116
122
|
items.push({ id: 'resume', kind: 'guide', text: T.RESUME });
|
|
117
|
-
if (saved.request) items.push({ id: 'resumeReq', kind: 'dim', text: T.resumeRequest(saved.request) });
|
|
123
|
+
if (saved.request) items.push({ id: 'resumeReq', kind: 'dim', text: T.resumeRequest(requestText(saved.request)) });
|
|
118
124
|
} else {
|
|
119
125
|
items.push({ id: 'greet', kind: 'guide', text: T.GREETING_REST }, { id: 'q', kind: 'guide', text: T.QUESTION }, { id: 'ex', kind: 'list', items: T.EXAMPLES, numbered: true });
|
|
120
126
|
}
|
|
@@ -122,7 +128,7 @@ export function GettingStarted({
|
|
|
122
128
|
};
|
|
123
129
|
|
|
124
130
|
const S = React.useRef({
|
|
125
|
-
history: [], step: 'ask', input: '', intent: saved?.intent || null, request: saved?.request
|
|
131
|
+
history: [], step: 'ask', input: '', intent: saved?.intent || null, request: saved?.request ? requestText(saved.request) : null, provider: saved?.provider === 'skip' ? 'skip' : null,
|
|
126
132
|
rows: [], fixNote: null, options: [], sel: 1, flash: null, install: null, logs: [], showLog: false, done: false, secret: null,
|
|
127
133
|
installStage: saved?.installStage || null, installDir: saved?.installDir || null, dashboardUrl: saved?.dashboardUrl || null,
|
|
128
134
|
ownerEmail: saved?.ownerEmail || null, finRows: [], stopping: false, vault: null
|
|
@@ -139,7 +145,7 @@ export function GettingStarted({
|
|
|
139
145
|
|
|
140
146
|
// ---- intro (Orbit & Lock), then the opening lines move into <Static> --------------------------
|
|
141
147
|
const animating = animateIntro ?? introAnimates(stdout?.isTTY);
|
|
142
|
-
// Orbit & Lock, then the Q says hello (T.HELLO decodes in beside it); rarely the
|
|
148
|
+
// Orbit & Lock, then the Q says hello (T.HELLO decodes in beside it); rarely the dragon's googly-eye greeting after that.
|
|
143
149
|
const eye = React.useMemo(() => (animating && animateIntro === undefined ? launchEgg({ tty: Boolean(stdout?.isTTY) }) : false), []);
|
|
144
150
|
const introMs = helloIntroMs({ eye, text: T.HELLO });
|
|
145
151
|
const introStart = React.useRef(Date.now());
|
|
@@ -173,7 +179,7 @@ export function GettingStarted({
|
|
|
173
179
|
|
|
174
180
|
function offerLines() {
|
|
175
181
|
const items = [{ kind: 'guide', text: T.OFFER }];
|
|
176
|
-
items.push({ kind: '
|
|
182
|
+
items.push({ kind: 'ask', text: T.OFFER_ASK }, { kind: 'dim', text: T.ALREADY });
|
|
177
183
|
push(...items);
|
|
178
184
|
set({ step: 'offer' });
|
|
179
185
|
persist();
|
|
@@ -184,7 +190,7 @@ export function GettingStarted({
|
|
|
184
190
|
if (m.intent === 'empty') return;
|
|
185
191
|
push({ kind: 'user', text });
|
|
186
192
|
if (m.intent === 'connect') { push({ kind: 'guide', text: T.CONNECT_INTENT_LINE }); set({ step: 'ask' }); return; }
|
|
187
|
-
set({ intent: m.intent, request: text });
|
|
193
|
+
set({ intent: m.intent, request: requestText(text) });
|
|
188
194
|
push({ kind: 'guide', text: m.intent === 'offscript' ? T.DEFERRAL : T.INTENT_LINES[m.intent] });
|
|
189
195
|
offerLines();
|
|
190
196
|
}
|
|
@@ -197,7 +203,7 @@ export function GettingStarted({
|
|
|
197
203
|
if (conn) {
|
|
198
204
|
push({ kind: 'user', text: t });
|
|
199
205
|
if (!conn.ok) { push({ kind: 'dim', tone: 'warn', text: conn.reason === 'url' ? T.connectBadUrl(conn.value) : T.CONNECT_USAGE }); return true; }
|
|
200
|
-
push({ kind: 'guide', text: T.
|
|
206
|
+
push({ kind: 'guide', text: T.connectChecking(conn.url) });
|
|
201
207
|
exit({ action: 'connect', url: conn.url });
|
|
202
208
|
return true;
|
|
203
209
|
}
|
|
@@ -233,6 +239,11 @@ export function GettingStarted({
|
|
|
233
239
|
|
|
234
240
|
async function toCredential() {
|
|
235
241
|
set({ rows: [] });
|
|
242
|
+
if (S.current.provider === 'skip') { // Z2H-R2B: the owner already chose "Skip for now"; never re-ask on resume (/connect stays open)
|
|
243
|
+
push({ kind: 'guide', tone: 'warn', text: T.CRED_SKIPPED });
|
|
244
|
+
startInstall();
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
236
247
|
const ollama = await findOllama();
|
|
237
248
|
if (!live.current || S.current.done) return;
|
|
238
249
|
const options = credentialOptions({ ollama });
|
|
@@ -250,8 +261,10 @@ export function GettingStarted({
|
|
|
250
261
|
const held = await requestCredential(opt.id); // THE hook: K1 secure card once it merges
|
|
251
262
|
if (!live.current || S.current.done) { held?.drop?.(); return; }
|
|
252
263
|
if (!held) { set({ step: 'credential', input: '' }); return; }
|
|
253
|
-
push({ kind: 'user', text: opt.label }, { kind: 'guide', tone: 'ok', text: T.credHeld(opt.label, held.fingerprint) });
|
|
264
|
+
push({ kind: 'user', text: opt.label }, { kind: 'guide', tone: 'ok', text: T.credHeld(opt.label, held.fingerprint) }, ...(needsConsent(opt.id) ? [{ kind: 'guide', text: T.credConsent(opt.label) }] : []));
|
|
254
265
|
set({ secret: held, input: '' });
|
|
266
|
+
if (needsConsent(opt.id)) { set({ step: 'cloudConsent', cloudConsent: null }); return; } // ask once; the answer is the user's own
|
|
267
|
+
|
|
255
268
|
} else if (opt.id === 'ollama' && !opt.detected) {
|
|
256
269
|
set({ step: 'ollamaUrl', input: '' }); // B3: nothing answered locally; ask for the LAN URL
|
|
257
270
|
return;
|
|
@@ -261,6 +274,13 @@ export function GettingStarted({
|
|
|
261
274
|
startInstall();
|
|
262
275
|
}
|
|
263
276
|
|
|
277
|
+
function answerCloudConsent(yes) {
|
|
278
|
+
const p = S.current.provider; const who = providerLabel(p);
|
|
279
|
+
push({ kind: 'user', text: yes ? 'yes' : 'no' }, { kind: 'guide', tone: yes ? 'ok' : 'warn', text: yes ? T.credConsentYes(who) : T.credConsentNo(who, p) });
|
|
280
|
+
set({ cloudConsent: yes });
|
|
281
|
+
startInstall();
|
|
282
|
+
}
|
|
283
|
+
|
|
264
284
|
function submitOllamaUrl() {
|
|
265
285
|
const url = normaliseOllamaUrl(S.current.input);
|
|
266
286
|
if (!url) { set({ flash: T.OLLAMA_URL_BAD }); return; }
|
|
@@ -300,6 +320,7 @@ export function GettingStarted({
|
|
|
300
320
|
if (!live.current) return;
|
|
301
321
|
if (ev.type === 'log') set(s => ({ logs: [...s.logs, ev.line].slice(-8) }));
|
|
302
322
|
if (ev.type === 'script') push({ kind: 'long', tone: 'dim', gap: false, text: T.installScript(ev.url, ev.sha256, ev.bytes), lead: T.installScriptLead(ev.url, ev.bytes), value: ev.sha256 });
|
|
323
|
+
if (ev.type === 'stage-label') set(s => ({ install: { ...s.install, stages: s.install.stages.map(x => (x.id === ev.id ? { ...x, body: ev.label } : x)) } }));
|
|
303
324
|
if (ev.type === 'stage') set(s => ({ install: { ...s.install, stages: s.install.stages.map(x => (x.id === ev.id ? { ...x, status: ev.status, startedAt: ev.status === 'running' ? ev.at : x.startedAt, ms: ev.ms ?? x.ms, earlier: Boolean(ev.earlier) } : x)) } }));
|
|
304
325
|
}
|
|
305
326
|
});
|
|
@@ -308,9 +329,10 @@ export function GettingStarted({
|
|
|
308
329
|
if (res.interrupted || S.current.stopping) {
|
|
309
330
|
push({ kind: 'dim', text: T.INSTALL_STOPPED }); persist({ step: 'install' }); exit({ action: 'exit' }); return;
|
|
310
331
|
}
|
|
332
|
+
if (res.logPath) set({ installLogPath: res.logPath });
|
|
311
333
|
if (res.dashboardUrl) set({ dashboardUrl: res.dashboardUrl });
|
|
312
334
|
const rows = S.current.install.stages.map(st => ({ ...st, tail: st.earlier ? T.INSTALL_EARLIER : st.ms != null ? fmtS(st.ms) : '' }));
|
|
313
|
-
if (!res.ok) { set(s => ({ step: 'installFail', install: { ...s.install, failed: res.failedStage || '?', degraded: Boolean(res.degraded), error: res.error || null } })); persist({ step: 'install' }); return; }
|
|
335
|
+
if (!res.ok) { set(s => ({ step: 'installFail', install: { ...s.install, failed: res.failedStage || '?', degraded: Boolean(res.degraded), error: res.error || null, logPath: res.logPath || null, cause: res.cause?.message || null } })); persist({ step: 'install' }); return; }
|
|
314
336
|
push({ kind: 'guide', text: T.INSTALL_TITLE + (dry ? ` (${T.INSTALL_DRY})` : ''), bold: true }, { kind: 'rows', rows });
|
|
315
337
|
if (dry || !finisher) { finale({ dashboardUrl: res.dashboardUrl, dry }); return; }
|
|
316
338
|
toOwner();
|
|
@@ -351,6 +373,7 @@ export function GettingStarted({
|
|
|
351
373
|
{ id: 'account', name: F.account.name, status: 'running', body: F.account.running },
|
|
352
374
|
{ id: 'signin', name: F.signin.name, status: 'pending', body: F.signin.running },
|
|
353
375
|
{ id: 'vault', name: F.vault.name, status: 'pending', body: S.current.secret ? F.vault.running(opt?.label || 'key') : F.vault.skipped },
|
|
376
|
+
...(S.current.cloudConsent === true ? [{ id: 'consent', name: F.consent.name, status: 'pending', body: F.consent.running(providerLabel(S.current.provider)) }] : []),
|
|
354
377
|
{ id: 'link', name: F.link.name, status: 'pending', body: F.link.running }
|
|
355
378
|
];
|
|
356
379
|
const row = (id, patch) => set(s => ({ finRows: s.finRows.map(r => (r.id === id ? { ...r, ...patch } : r)) }));
|
|
@@ -385,15 +408,27 @@ export function GettingStarted({
|
|
|
385
408
|
}
|
|
386
409
|
set({ vault });
|
|
387
410
|
|
|
411
|
+
if (S.current.cloudConsent === true && finisher.grantConsent) { // the owner said yes at the credential step
|
|
412
|
+
row('consent', { status: 'running' });
|
|
413
|
+
const g = await finisher.grantConsent(S.current.provider);
|
|
414
|
+
if (!live.current || S.current.done) return;
|
|
415
|
+
const who = providerLabel(S.current.provider);
|
|
416
|
+
row('consent', g?.ok ? { status: 'ok', body: F.consent.ok(who) } : { status: 'warn', body: F.consent.fail(g?.error || 'unknown error', S.current.provider) });
|
|
417
|
+
}
|
|
418
|
+
|
|
388
419
|
row('link', { status: 'running' });
|
|
389
420
|
const link = await finisher.loginLink(S.current.dashboardUrl);
|
|
390
421
|
if (!live.current || S.current.done) return;
|
|
391
422
|
row('link', link.ok ? { status: 'ok', body: F.link.ok } : { status: 'warn', body: F.link.fail(link.error) });
|
|
392
423
|
|
|
424
|
+
// Z2H-R5 E: only mention adding a host where this install can serve its host installer (unknown = as before).
|
|
425
|
+
let hostsAvailable = true;
|
|
426
|
+
try { const hi = finisher.hostInstaller ? await finisher.hostInstaller() : null; if (hi && hi.ok === false && Number(hi.status) >= 400) hostsAvailable = false; } catch { /* unknown */ }
|
|
427
|
+
if (!live.current || S.current.done) return;
|
|
393
428
|
push({ kind: 'guide', text: T.FINISH_TITLE, bold: true }, { kind: 'rows', rows: S.current.finRows });
|
|
394
429
|
if (vault) push({ kind: 'dim', text: vault.ok ? T.KEY_GONE_AFTER : `${T.KEY_GONE_AFTER} ${T.keyNotStored(vault.provider || S.current.provider)}` });
|
|
395
430
|
set({ finRows: [] });
|
|
396
|
-
finale({ dashboardUrl: S.current.dashboardUrl, link: link.ok ? link.url : null, dry: false });
|
|
431
|
+
finale({ dashboardUrl: S.current.dashboardUrl, link: link.ok ? link.url : null, dry: false, hostsAvailable });
|
|
397
432
|
}
|
|
398
433
|
|
|
399
434
|
function replayText() {
|
|
@@ -402,7 +437,7 @@ export function GettingStarted({
|
|
|
402
437
|
return request;
|
|
403
438
|
}
|
|
404
439
|
|
|
405
|
-
function finale({ dashboardUrl, link = null, dry }) {
|
|
440
|
+
function finale({ dashboardUrl, link = null, dry, hostsAvailable = true }) {
|
|
406
441
|
const items = [];
|
|
407
442
|
const intent = S.current.intent && T.TIPS[S.current.intent] ? S.current.intent : 'explore';
|
|
408
443
|
if (dry) items.push({ kind: 'guide', tone: 'warn', text: T.FINALE_DRY_BANNER });
|
|
@@ -414,9 +449,11 @@ export function GettingStarted({
|
|
|
414
449
|
else items.push({ kind: 'long', text: T.finaleDashboardPlain(url), lead: T.FINALE_DASHBOARD_LEAD_PLAIN, value: url });
|
|
415
450
|
items.push({ kind: 'guide', gap: false, text: T.FINALE_PROOF });
|
|
416
451
|
if (!dry) items.push({ kind: 'dim', text: T.FINALE_CERT });
|
|
452
|
+
if (!dry && S.current.installLogPath) items.push({ kind: 'dim', text: T.INSTALL_LOG_PATH(S.current.installLogPath) });
|
|
417
453
|
items.push({ kind: 'guide', text: T.FINALE_TIPS_TITLE }, { kind: 'list', items: dry ? T.FINALE_TIPS : T.TIPS[intent] });
|
|
418
454
|
const noAi = !dry && (S.current.provider === 'skip' || (S.current.vault && !S.current.vault.ok));
|
|
419
|
-
if (
|
|
455
|
+
if (noAi && hostsAvailable) items.push({ kind: 'guide', text: T.FINALE_HOSTS_NO_AI });
|
|
456
|
+
if (!dry) items.push({ kind: 'guide', text: noAi ? T.k2NoAi(S.current.provider === 'anthropic' ? 'anthropic' : 'openai') : (!hostsAvailable && intent === 'servers' ? T.K2_SERVERS_NO_INSTALLER : T.K2[intent]) });
|
|
420
457
|
push(...items);
|
|
421
458
|
S.current.secret?.drop?.();
|
|
422
459
|
set({ step: 'finale', secret: null, finaleDry: dry });
|
|
@@ -455,15 +492,23 @@ export function GettingStarted({
|
|
|
455
492
|
toCredential();
|
|
456
493
|
}
|
|
457
494
|
else if (ev.type === 'text' && /^q$/i.test(ev.text)) { push({ kind: 'dim', text: T.BYE }); persist({ step: 'preflight' }); exit({ action: 'exit' }); }
|
|
495
|
+
else if (ev.type === 'text' && /^y$/i.test(ev.text) && !dryRun) { // FIRSTRUN: the guide fixes Docker itself
|
|
496
|
+
const offer = dockerOffer(s.rows);
|
|
497
|
+
if (offer) { push({ kind: 'ask', text: offer.ask }, { kind: 'user', text: 'yes' }); persist({ step: 'preflight' }); exit({ action: 'docker', kind: offer.kind }); }
|
|
498
|
+
}
|
|
458
499
|
return;
|
|
459
500
|
case 'credential': {
|
|
460
501
|
const n = s.options.length;
|
|
461
|
-
if (ev.type === 'key' && ev.name === 'up') set({ sel: (s.sel -
|
|
462
|
-
else if (ev.type === 'key' && (ev.name === 'down' || ev.name === 'tab')) set({ sel: (s.sel
|
|
502
|
+
if (ev.type === 'key' && ev.name === 'up') set({ sel: nextSelectable(s.options, s.sel, -1), flash: null }); // #1288: never land on a disabled row
|
|
503
|
+
else if (ev.type === 'key' && (ev.name === 'down' || ev.name === 'tab')) set({ sel: nextSelectable(s.options, s.sel, 1), flash: null });
|
|
463
504
|
else if (ev.type === 'key' && ev.name === 'enter') choose(s.options[s.sel]);
|
|
464
505
|
else if (ev.type === 'text' && /^[1-9]$/.test(ev.text) && Number(ev.text) <= n) { set({ sel: Number(ev.text) - 1 }); choose(s.options[Number(ev.text) - 1]); }
|
|
465
506
|
return;
|
|
466
507
|
}
|
|
508
|
+
case 'cloudConsent':
|
|
509
|
+
if (ev.type === 'text' && /^y(es)?$/i.test(ev.text.trim())) answerCloudConsent(true);
|
|
510
|
+
else if ((ev.type === 'text' && /^n(o)?$/i.test(ev.text.trim())) || (ev.type === 'key' && (ev.name === 'enter' || ev.name === 'escape'))) answerCloudConsent(false);
|
|
511
|
+
return;
|
|
467
512
|
case 'install': if (ev.type === 'text' && /^v$/i.test(ev.text)) set({ showLog: !s.showLog }); return;
|
|
468
513
|
case 'installStop':
|
|
469
514
|
if (ev.type === 'text' && /^y/i.test(ev.text)) stopInstall();
|
|
@@ -484,7 +529,7 @@ export function GettingStarted({
|
|
|
484
529
|
}
|
|
485
530
|
};
|
|
486
531
|
function stopInstall() {
|
|
487
|
-
push({ kind: '
|
|
532
|
+
push({ kind: 'ask', text: T.INSTALL_STOP_ASK }, { kind: 'user', text: 'yes' });
|
|
488
533
|
set({ stopping: true, step: 'install' });
|
|
489
534
|
S.current.abort?.abort?.(); // the runner stops the installer's process group; startInstall then exits
|
|
490
535
|
}
|
|
@@ -549,7 +594,11 @@ export function GettingStarted({
|
|
|
549
594
|
case 'preflight': case 'preflightFail':
|
|
550
595
|
liveParts.push(h(Box, { key: 'pf', marginTop: 1, paddingLeft: 2 }, h(Text, { bold: true, color: c('text') }, T.PREFLIGHT_TITLE)),
|
|
551
596
|
...s.rows.map((r) => h(CheckRow, { key: r.id, r, cols })));
|
|
552
|
-
if (s.step === 'preflightFail')
|
|
597
|
+
if (s.step === 'preflightFail') {
|
|
598
|
+
const offer = dryRun ? null : dockerOffer(s.rows); // FIRSTRUN: offer, never "go and do this"
|
|
599
|
+
if (offer) liveParts.push(h(Box, { key: 'pfo', marginTop: 1 }, h(AskCard, { text: offer.ask, cols })), h(Hint, { key: 'h', text: T.DOCKER_OFFER_HINT, cols }));
|
|
600
|
+
else liveParts.push(h(Box, { key: 'pfn', marginTop: 1, paddingLeft: 2, width: cols }, h(Para, { width: cols - 2, color: c('warn'), text: s.fixNote })), h(Hint, { key: 'h', text: dryRun ? T.PREFLIGHT_RETRY_DRY : T.PREFLIGHT_RETRY, cols }));
|
|
601
|
+
}
|
|
553
602
|
break;
|
|
554
603
|
case 'credential':
|
|
555
604
|
liveParts.push(h(Box, { key: 'opts', flexDirection: 'column', marginTop: 1 }, ...s.options.map((o, i) => {
|
|
@@ -573,6 +622,11 @@ export function GettingStarted({
|
|
|
573
622
|
h(Text, { color: c('violet') }, '─'.repeat(cols))));
|
|
574
623
|
break;
|
|
575
624
|
}
|
|
625
|
+
case 'cloudConsent':
|
|
626
|
+
liveParts.push(h(Box, { key: 'cc', flexDirection: 'column', marginTop: 1 },
|
|
627
|
+
h(AskCard, { text: T.credConsentAsk(providerLabel(s.provider)), cols }),
|
|
628
|
+
h(Hint, { text: T.CRED_CONSENT_HINT, cols })));
|
|
629
|
+
break;
|
|
576
630
|
case 'ollamaUrl':
|
|
577
631
|
liveParts.push(h(Box, { key: 'card', flexDirection: 'column', marginTop: 1 },
|
|
578
632
|
h(Text, { color: c('violet') }, '─'.repeat(cols)),
|
|
@@ -606,10 +660,13 @@ export function GettingStarted({
|
|
|
606
660
|
if (s.step === 'installFail') {
|
|
607
661
|
liveParts.push(h(Box, { key: 'if', paddingLeft: 2, width: cols, marginTop: 1, flexDirection: 'column' },
|
|
608
662
|
h(Para, { width: cols - 2, color: c('fail'), text: T.INSTALL_FAILED(T.STAGES[s.install.failed] || s.install.failed) }),
|
|
663
|
+
s.install.cause ? h(Para, { width: cols - 2, color: c('warn'), text: s.install.cause }) : null, // FIRSTRUN: why, in one line
|
|
609
664
|
s.install.error ? h(Para, { width: cols - 2, color: c('dim'), text: s.install.error }) : null,
|
|
665
|
+
s.install.logPath ? h(Para, { width: cols - 2, color: c('dim'), text: T.INSTALL_LOG_PATH(s.install.logPath) }) : null,
|
|
666
|
+
s.install.logPath ? h(Para, { width: cols - 2, color: c('dim'), text: T.INSTALL_RERUN_HINT }) : null,
|
|
610
667
|
s.install.degraded ? h(Para, { width: cols - 2, color: c('warn'), text: T.INSTALL_DEGRADED }) : null));
|
|
611
668
|
}
|
|
612
|
-
if (s.step === 'installStop') liveParts.push(h(Box, { key: 'stop',
|
|
669
|
+
if (s.step === 'installStop') liveParts.push(h(Box, { key: 'stop', width: cols }, h(AskCard, { text: T.INSTALL_STOP_ASK, cols })));
|
|
613
670
|
liveParts.push(h(Hint, { key: 'h', text: s.step === 'installFail' ? T.INSTALL_FAIL_HINT : s.step === 'installStop' ? 'y to stop · n or Enter to carry on' : T.INSTALL_HINT, cols }));
|
|
614
671
|
break;
|
|
615
672
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** The org as the status line shows it: its name, or a short "org …H398" until the name arrives. Never a raw ULID. */
|
|
2
|
+
const ULID = /^[0-9A-HJKMNP-TV-Z]{26}$/i;
|
|
3
|
+
export const isOrgId = (s) => ULID.test(String(s || '').trim());
|
|
4
|
+
export function orgLabel(name, id = null) {
|
|
5
|
+
const n = name == null ? '' : String(name).trim();
|
|
6
|
+
if (n && !isOrgId(n)) return n;
|
|
7
|
+
const raw = isOrgId(n) ? n : (id ? String(id).trim() : '');
|
|
8
|
+
if (!raw) return null;
|
|
9
|
+
return isOrgId(raw) ? `org …${raw.slice(-4)}` : raw; // only a real ULID is masked
|
|
10
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/** /org helpers (pure). Org list rows, fuzzy match, and the lines the command prints. QUOXON-FW3. */
|
|
2
|
+
const norm = (s) => String(s ?? '').toLowerCase().replace(/[^a-z0-9]+/g, ' ').trim();
|
|
3
|
+
|
|
4
|
+
export const orgRows = (list) => (Array.isArray(list) ? list : []).map((o) => ({ id: o.id, name: o.name || o.slug || o.id, slug: o.slug || null, role: o.role || null }));
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Resolve a typed name or id against the user's orgs. Exact id, exact slug/name, then every word of the
|
|
8
|
+
* query found in the name or slug (so "acme widgets" finds "Acme Widgets Ltd"). Never guesses between ties.
|
|
9
|
+
* -> { org } | { ambiguous: [orgs] } | { none: true }
|
|
10
|
+
*/
|
|
11
|
+
export function matchOrg(query, list) {
|
|
12
|
+
const rows = orgRows(list);
|
|
13
|
+
const q = norm(query);
|
|
14
|
+
if (!q) return { none: true };
|
|
15
|
+
// An exact slug wins outright, so a slug never ties with another org's look-alike name (#1141).
|
|
16
|
+
const bySlug = rows.filter((o) => o.slug && o.slug === String(query).trim());
|
|
17
|
+
if (bySlug.length === 1) return { org: bySlug[0] };
|
|
18
|
+
const exact = rows.filter((o) => String(o.id).toLowerCase() === String(query).trim().toLowerCase() || norm(o.slug) === q || norm(o.name) === q);
|
|
19
|
+
if (exact.length === 1) return { org: exact[0] };
|
|
20
|
+
if (exact.length > 1) return { ambiguous: exact };
|
|
21
|
+
const words = q.split(' ');
|
|
22
|
+
const fuzzy = rows.filter((o) => { const hay = `${norm(o.name)} ${norm(o.slug)}`; return words.every((w) => hay.includes(w)); });
|
|
23
|
+
if (fuzzy.length === 1) return { org: fuzzy[0] };
|
|
24
|
+
if (fuzzy.length > 1) return { ambiguous: fuzzy };
|
|
25
|
+
return { none: true };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** One line per org; the current one is marked. */
|
|
29
|
+
export function orgLines(list, currentId) {
|
|
30
|
+
const rows = orgRows(list);
|
|
31
|
+
const w = Math.min(36, Math.max(4, ...rows.map((o) => [...o.name].length)));
|
|
32
|
+
return rows.map((o) => `${o.id === currentId ? '* ' : ' '}${o.name.padEnd(w)} ${o.slug || o.id}${o.role ? ` (${o.role})` : ''}`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** `quox org switch` with no argument: the same list, numbered from 1, current org marked. */
|
|
36
|
+
export function numberedOrgLines(list, currentId) {
|
|
37
|
+
const rows = orgRows(list);
|
|
38
|
+
const w = Math.min(36, Math.max(4, ...rows.map((o) => [...o.name].length)));
|
|
39
|
+
return rows.map((o, i) => `${o.id === currentId ? '*' : ' '} ${String(i + 1).padStart(String(rows.length).length)}) ${o.name.padEnd(w)} ${o.slug || o.id}${o.role ? ` (${o.role})` : ''}`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** A typed answer to the numbered list: an exact slug/name/id first (so a numeric slug is never shadowed), then a 1-based number. */
|
|
43
|
+
export function pickOrgByInput(input, list) {
|
|
44
|
+
const rows = orgRows(list);
|
|
45
|
+
const t = String(input ?? '').trim();
|
|
46
|
+
if (!t) return { none: true };
|
|
47
|
+
const m = matchOrg(t, rows);
|
|
48
|
+
const exactly = m.org && (m.org.slug === t || String(m.org.id).toLowerCase() === t.toLowerCase() || norm(m.org.name) === norm(t));
|
|
49
|
+
if (exactly) return m;
|
|
50
|
+
if (/^\d+$/.test(t)) { const n = Number(t); return n >= 1 && n <= rows.length ? { org: rows[n - 1] } : { none: true }; }
|
|
51
|
+
return m;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** After a profile-wide `quox org switch`: how the other windows on that profile pick the change up. */
|
|
55
|
+
export function sharedSwitchHint({ name, pids }) {
|
|
56
|
+
const n = pids.length;
|
|
57
|
+
return `${n} other quox session${n === 1 ? '' : 's'} ${n === 1 ? 'uses' : 'use'} profile "${name}" (pid ${pids.join(', ')}). `
|
|
58
|
+
+ 'They pick this org up on their next token refresh; run /restart in a window to pick it up now. /org <name> moves one window only.';
|
|
59
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Your own broadcast is not news to you: the fan-out reaches every held slot in the org, including this window's, and
|
|
3
|
+
* the "BROADCAST sent" card already told the sender. Match by the sender's identity, never by text.
|
|
4
|
+
*/
|
|
5
|
+
import { parseBroadcastText } from './slash/broadcast.js';
|
|
6
|
+
|
|
7
|
+
const norm = (s) => String(s || '').toLowerCase().replace(/\s*\(.*?\)\s*/g, ' ').replace(/\s+/g, ' ').trim();
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @param {object} m the slot message
|
|
11
|
+
* @param {object} me { label: this window's sender label, display: the signed-in display name, userId }
|
|
12
|
+
*/
|
|
13
|
+
export function isOwnBroadcast(m, me = {}) {
|
|
14
|
+
const bc = parseBroadcastText(m?.text);
|
|
15
|
+
if (!bc) return false;
|
|
16
|
+
const uid = me.userId ? String(me.userId) : null;
|
|
17
|
+
if (uid && [m.user_id, m.from_user_id, m.author_user_id, m.author_id].some((x) => x != null && String(x) === uid)) return true;
|
|
18
|
+
const sender = norm(bc.sender), from = norm(m.from);
|
|
19
|
+
const mine = [me.label, me.display].map(norm).filter(Boolean);
|
|
20
|
+
if (!mine.length) return false;
|
|
21
|
+
return mine.some((x) => x === sender || x === from) || (me.label && m.from === me.label) || false;
|
|
22
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The semantic palette (QUOXON-FW11). One role per MEANING, so a reader can see where prose stops and a thing
|
|
3
|
+
* they can type starts:
|
|
4
|
+
*
|
|
5
|
+
* prose default text. White on dark, ink on light. Never grey.
|
|
6
|
+
* command anything the user can type: /slash commands, `quox ...`, !bang, flags and args INCLUDED. The whole
|
|
7
|
+
* command is ONE span in ONE colour (never split into differently coloured parts).
|
|
8
|
+
* value ids, names, numbers, paths, URLs (theme role `id`; theme's `value` stays the near-white card text).
|
|
9
|
+
* ok / warn / error state. Carried by a glyph or label ("⚠"), never by tinting the sentence, so commands and
|
|
10
|
+
* values inside keep their own colours.
|
|
11
|
+
* brand Quox purple: chrome only (logo, status-bar labels, headers). Never a command inside prose.
|
|
12
|
+
* quiet secondary text and rules: a tinted, readable shade (never a mid-grey).
|
|
13
|
+
*
|
|
14
|
+
* Both tables: [truecolor hex, ansi256 index]. DARK >= 4.5:1 on #000, LIGHT >= 4.5:1 on #fff (tests).
|
|
15
|
+
*/
|
|
16
|
+
export const SEMANTIC_DARK = {
|
|
17
|
+
prose: ['#f1f5f9', 255], command: ['#2dd4bf', 80], value: ['#93c5fd', 117],
|
|
18
|
+
ok: ['#4ade80', 78], warn: ['#f59e0b', 214], error: ['#f87171', 210],
|
|
19
|
+
brand: ['#a855f7', 135], quiet: ['#bcc8e8', 189], rule: ['#7c8fe0', 105]
|
|
20
|
+
};
|
|
21
|
+
export const SEMANTIC_LIGHT = {
|
|
22
|
+
prose: ['#0f172a', 234], command: ['#0f766e', 30], value: ['#1d4ed8', 26],
|
|
23
|
+
ok: ['#15803d', 28], warn: ['#b45309', 130], error: ['#b91c1c', 124],
|
|
24
|
+
brand: ['#7e22ce', 91], quiet: ['#3b4f87', 60], rule: ['#7280c8', 104]
|
|
25
|
+
};
|
|
26
|
+
export const SEMANTIC_ROLES = Object.keys(SEMANTIC_DARK);
|
|
27
|
+
|
|
28
|
+
/** Is this hex a mid-grey? Low saturation in the middle of the lightness range (white and ink are allowed). */
|
|
29
|
+
export function isGrey(hex) {
|
|
30
|
+
const [r, g, b] = [1, 3, 5].map(i => parseInt(hex.slice(i, i + 2), 16) / 255);
|
|
31
|
+
const max = Math.max(r, g, b), min = Math.min(r, g, b), l = (max + min) / 2, d = max - min;
|
|
32
|
+
const s = d === 0 ? 0 : d / (1 - Math.abs(2 * l - 1));
|
|
33
|
+
return s < 0.25 && l > 0.2 && l < 0.85;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// ---- inline markup used by every notice producer --------------------------------------------------
|
|
37
|
+
// ⟦text⟧ = a command (one span, markers dropped on screen). ⟪text⟫ = a value. Helpers build them. (Not backticks:
|
|
38
|
+
// plain backticks already appear literally in notices.)
|
|
39
|
+
export const cmd = (s) => `⟦${s}⟧`;
|
|
40
|
+
export const val = (s) => `⟪${s}⟫`;
|
|
41
|
+
export const MARK = /⟦([^⟧\n]+)⟧|⟪([^⟫\n]+)⟫/g;
|
|
42
|
+
/** The on-screen characters of marked-up text (markers dropped). Use for width and wrapping. */
|
|
43
|
+
export const stripMarks = (s) => String(s ?? '').replace(MARK, (_, a, b) => a ?? b);
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// QUOXON plan approval card (owner 2026-10-26 18:05: "i would deny based on the fact it's not even presenting a plan"):
|
|
2
|
+
// an objective's "approve plan" item must list the plan's steps, and show the org the item really lives in.
|
|
3
|
+
// Pure helpers plus one async enrich step; the card renders what this returns and nothing else.
|
|
4
|
+
|
|
5
|
+
export const PLAN_STEPS_MAX = 8;
|
|
6
|
+
/** Verbs that change things. Anything else reads as read-only (inspect, list, audit, report). */
|
|
7
|
+
const CHANGES = /\b(upgrad\w*|updat\w*|restart\w*|redeploy\w*|deploy\w*|writ\w*|delet\w*|remov\w*|install\w*|migrat\w*|patch\w*|rollback|roll back|reboot\w*|creat\w*|apply|applies|rotat\w*|scal\w*|provision\w*|stop|start|kill|drop|reset|modify|modifies|change\w*|configur\w*)\b/i;
|
|
8
|
+
export const changesThings = (title) => CHANGES.test(String(title || ''));
|
|
9
|
+
|
|
10
|
+
/** Does this inbox item ask for approval of an objective's plan? */
|
|
11
|
+
export const isPlanApproval = (it) => it?.payload?.source === 'objective_checkpoint'
|
|
12
|
+
&& it?.payload?.checkpoint_type === 'plan_approval' && typeof it?.payload?.objective_id === 'string';
|
|
13
|
+
|
|
14
|
+
/** Steps from an objective read: [{ n, title, agent, writes }]. Agent display name from `names` (id -> name), else the id. */
|
|
15
|
+
export function stepsOf(objective, names = new Map()) {
|
|
16
|
+
const tasks = Array.isArray(objective?.tasks) ? objective.tasks : [];
|
|
17
|
+
return tasks.filter(t => t && t.title).map((t, i) => ({
|
|
18
|
+
n: i + 1, title: String(t.title), agent: t.agent_id ? String(names.get(t.agent_id) || t.agent_id) : null, writes: changesThings(t.title)
|
|
19
|
+
}));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Rows for the card: numbered lines plus "+N more" past the cap. Each row is a plain string. */
|
|
23
|
+
export function planLines(steps, max = PLAN_STEPS_MAX) {
|
|
24
|
+
const list = Array.isArray(steps) ? steps : [];
|
|
25
|
+
const lines = list.slice(0, max).map(s => `${s.n}. ${s.writes ? '[changes]' : '[read-only]'} ${s.title}${s.agent ? ` · ${s.agent}` : ''}`);
|
|
26
|
+
if (list.length > max) lines.push(`+${list.length - max} more`);
|
|
27
|
+
return lines;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Attach { steps, orgId, orgName } as item._plan for a plan-approval item. Reads the objective once (cached by objective id
|
|
32
|
+
* in `cache`). Never throws: when the read fails the item is returned as it came and the card says the plan could not be loaded.
|
|
33
|
+
*/
|
|
34
|
+
export async function enrichPlanItem(item, { objectiveGet, agents = null, orgs = null, cache = new Map() } = {}) {
|
|
35
|
+
if (!isPlanApproval(item) || typeof objectiveGet !== 'function') return item;
|
|
36
|
+
const id = item.payload.objective_id;
|
|
37
|
+
if (!cache.has(id)) {
|
|
38
|
+
cache.set(id, (async () => {
|
|
39
|
+
try {
|
|
40
|
+
const o = await objectiveGet(id);
|
|
41
|
+
let names = new Map();
|
|
42
|
+
try { for (const a of (agents ? await agents() : []) || []) if (a?.agent_id && a.name) names.set(a.agent_id, a.name); } catch { /* ids stand in */ }
|
|
43
|
+
const orgId = item.org_id || o?.org_id || null;
|
|
44
|
+
let orgName = null;
|
|
45
|
+
try { orgName = orgId && orgs ? ((await orgs()) || []).find(x => x?.id === orgId)?.name || null : null; } catch { /* the short id stands in */ }
|
|
46
|
+
return { steps: stepsOf(o, names), orgId, orgName, loaded: true };
|
|
47
|
+
} catch (e) { cache.delete(id); return { steps: [], orgId: item.org_id || null, orgName: null, loaded: false, error: e?.message || 'failed' }; }
|
|
48
|
+
})());
|
|
49
|
+
}
|
|
50
|
+
return { ...item, _plan: await cache.get(id) };
|
|
51
|
+
}
|
package/lib/quoxon/print.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import { textGate } from './events.js';
|
|
8
8
|
import { streamFields, fallbackNote } from './model.js';
|
|
9
9
|
import { connectOfferFor } from './connect.js';
|
|
10
|
+
import { isConsentError, consentPrintMessage } from './consent.js';
|
|
10
11
|
|
|
11
12
|
export const EXIT = { ok: 0, error: 1, approval: 2, interrupted: 3, auth: 4, unconfirmed: 5 };
|
|
12
13
|
|
|
@@ -28,7 +29,7 @@ const isAuth = (e) => e?.status === 401 || /valid jwt|service key|unauthor|not a
|
|
|
28
29
|
const newTurnId = () => `QXN_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
29
30
|
|
|
30
31
|
/** Runs one turn. out/err are write(string) sinks. Returns the exit code. */
|
|
31
|
-
export async function runPrint({ api, prompt, output = 'text', conversation = null, turnId = newTurnId(), timeoutS = 0, color = false, stopGraceMs = 8000, context = undefined, choice = null, promptProfile = undefined, out, err }) {
|
|
32
|
+
export async function runPrint({ api, prompt, agent = 'quox', output = 'text', conversation = null, turnId = newTurnId(), timeoutS = 0, color = false, stopGraceMs = 8000, context = undefined, choice = null, promptProfile = undefined, profile = null, out, err }) {
|
|
32
33
|
if (!prompt || !prompt.trim()) { err('quox: empty prompt (pass --print "<prompt>" or pipe it on stdin)\n'); return EXIT.error; }
|
|
33
34
|
const ctrl = new AbortController();
|
|
34
35
|
let abandoned = false, graceTimer = null;
|
|
@@ -45,10 +46,10 @@ export async function runPrint({ api, prompt, output = 'text', conversation = nu
|
|
|
45
46
|
const sum = { tools: 0, approvals: 0, receipts: [] };
|
|
46
47
|
let convo = conversation, doneSession = null, status = null, text = '', sawApproval = false, errorMsg = null, authFail = false;
|
|
47
48
|
const seen = new Set();
|
|
48
|
-
let offer = null;
|
|
49
|
+
let offer = null, handoff = null, objective = null;
|
|
49
50
|
let gate = null;
|
|
50
51
|
try {
|
|
51
|
-
for await (const ev of api.stream({ turnId, message: prompt, agent: 'quox', conversationId: conversation, context, ...streamFields(choice), ...(promptProfile ? { promptProfile } : {}) }, ctrl.signal)) {
|
|
52
|
+
for await (const ev of api.stream({ turnId, message: prompt, agent: agent || 'quox', conversationId: conversation, context, ...streamFields(choice), ...(promptProfile ? { promptProfile } : {}), ...(timeoutS > 0 ? { turnBudgetMs: timeoutS * 1000 } : {}) }, ctrl.signal)) {
|
|
52
53
|
if (output === 'json') out(JSON.stringify(ev) + '\n');
|
|
53
54
|
const fb = fallbackNote(choice, ev);
|
|
54
55
|
if (fb) err(`quox: ${fb}\n`);
|
|
@@ -61,8 +62,10 @@ export async function runPrint({ api, prompt, output = 'text', conversation = nu
|
|
|
61
62
|
else if (ev.type === 'tool.finished') { if (!seen.has(ev.call_id)) { seen.add(ev.call_id); sum.tools++; } if (ev.receipt?.ward_seq != null) sum.receipts.push(ev.receipt.ward_seq); }
|
|
62
63
|
else if (ev.type === 'approval.requested') { sawApproval = true; sum.approvals++; }
|
|
63
64
|
else if (ev.type === 'approval.resolved') sawApproval = false;
|
|
65
|
+
else if (ev.type === 'turn.objective_suggested' && ev.objective_suggested?.goal) objective = ev.objective_suggested; // QUOXON objective offer
|
|
66
|
+
else if (ev.type === 'turn.handoff_suggested' && ev.handoff_suggested?.agent) handoff = ev.handoff_suggested; // QUOXON handoff offer
|
|
64
67
|
else if (ev.type === 'turn.finished') status = ev.status || 'completed';
|
|
65
|
-
else if (ev.type === 'error') errorMsg = ev.error || 'stream error';
|
|
68
|
+
else if (ev.type === 'error') errorMsg = isConsentError(ev) ? consentPrintMessage(ev, profile) : (ev.error || 'stream error');
|
|
66
69
|
}
|
|
67
70
|
} catch (e) {
|
|
68
71
|
if (ctrl.signal.aborted) { status = 'abandoned'; errorMsg = `timed out after ${timeoutS}s; the server was not confirmed stopped`; }
|
|
@@ -83,12 +86,16 @@ export async function runPrint({ api, prompt, output = 'text', conversation = nu
|
|
|
83
86
|
if (!status) status = authFail ? 'auth' : 'failed';
|
|
84
87
|
|
|
85
88
|
if (output === 'json') {
|
|
86
|
-
out(JSON.stringify({ type: 'summary', status, turn_id: turnId, conversation_id: convo, duration_ms: Date.now() - t0, server_stopped: serverStopped, ...sum }) + '\n');
|
|
89
|
+
out(JSON.stringify({ type: 'summary', status, turn_id: turnId, conversation_id: convo, duration_ms: Date.now() - t0, server_stopped: serverStopped, ...sum, ...(handoff ? { handoff_suggested: handoff } : {}), ...(objective ? { objective_suggested: objective } : {}) }) + '\n');
|
|
87
90
|
} else if (text) out(text.endsWith('\n') ? text : text + '\n');
|
|
88
91
|
if (authFail) err(`Not signed in for chat. Run: quox login -p <profile>\n`);
|
|
89
92
|
else if (code === EXIT.approval) err('Held for approval: approve it in the dashboard or with `quox` /approvals.\n');
|
|
90
93
|
else if (code === EXIT.unconfirmed) err('quox: Ended without confirmation from the server\n');
|
|
91
94
|
else if (errorMsg) err(`quox: ${errorMsg}\n`);
|
|
92
95
|
if (offer && code !== EXIT.ok) err(`${offer}\n`);
|
|
96
|
+
// QUOXON handoff offer: no y/N here, so name the exact one-turn command (same question, same conversation).
|
|
97
|
+
// QUOXON objective offer: no y/N here, so name the exact command that makes it an objective.
|
|
98
|
+
if (objective && output !== 'json' && code === EXIT.ok) err(`quox: ${objective.reason ? `${String(objective.reason).replace(/\s+$/, '')} ` : ''}Run it until done: quox objective create ${JSON.stringify(objective.goal)} --checkpoint\n`);
|
|
99
|
+
else if (handoff && output !== 'json' && code === EXIT.ok) err(`quox: ${handoff.reason ? `${String(handoff.reason).replace(/\s+$/, '')} ` : ''}${handoff.name || handoff.agent} can answer it: quox --print --agent ${handoff.agent}${convo ? ` --conversation ${convo}` : ''} ${JSON.stringify(prompt)}\n`);
|
|
93
100
|
return code;
|
|
94
101
|
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// PROFILE-SWITCH (owner 2026-10-16): switch this window between logged-in profiles with one y, never automatically.
|
|
2
|
+
import { askText } from './ask.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The App's api, swappable in place: every reader (and every controller built at mount) goes through this proxy,
|
|
6
|
+
* so a profile switch replaces ref.current.live and the whole window follows. Methods are bound to the live api.
|
|
7
|
+
*/
|
|
8
|
+
export function liveApi(ref) {
|
|
9
|
+
return new Proxy({}, {
|
|
10
|
+
get(_, k) {
|
|
11
|
+
const a = ref.current?.live;
|
|
12
|
+
if (a == null) return undefined;
|
|
13
|
+
const v = a[k];
|
|
14
|
+
return typeof v === 'function' ? v.bind(a) : v;
|
|
15
|
+
},
|
|
16
|
+
has(_, k) { const a = ref.current?.live; return a != null && k in Object(a); },
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** The ask card offering to switch an agent-login window to a human login. */
|
|
21
|
+
export const switchAskLine = (profile, human) => askText('Switch?', {
|
|
22
|
+
title: `This window is an agent login (${profile}).`,
|
|
23
|
+
action: `switch this window to your login '${human}' (same conversation, attach, slot and groups)`,
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
/** The ask card for a /slot name that does not exist (the near misses are said on the line above). */
|
|
27
|
+
export const slotCreateAskLine = (nm) => askText('Create it?', { title: `No slot ${nm}.`, action: `create and hold slot ${nm}` });
|
|
28
|
+
|
|
29
|
+
/** A local run request (the shared run card): sign a profile in with the real `quox login`, terminal handed over. */
|
|
30
|
+
export function loginRunRequest(name, why, now = Date.now()) {
|
|
31
|
+
return { id: null, cmd: `quox login -p ${name}`, why, who: 'Quox', runnable: true, refuse: null, danger: null, expiresAt: now + 120000 };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** A safe profile name to put on a command line (letters, digits, . _ -). */
|
|
35
|
+
export const okProfileName = (n) => /^[A-Za-z0-9._-]{1,64}$/.test(String(n || ''));
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* /profile chooser rows (PROFILE-CHOOSER): signed-in profiles first (this window's marked), then profiles on this
|
|
39
|
+
* machine that are not signed in, then the current login's orgs (the window's org marked). Each row carries `pick`:
|
|
40
|
+
* { profile } switches this window in place, { login } offers `quox login -p`, { org } goes through /org switch
|
|
41
|
+
* (window-scoped since #1034, confirmed first). Nothing here writes the shared default profile.
|
|
42
|
+
*/
|
|
43
|
+
export function profileChooserRows({ current, humanProfiles = [], loginCandidates = [], orgs = [], orgId = null }) {
|
|
44
|
+
const names = [...new Set([current, ...humanProfiles].filter(okProfileName))];
|
|
45
|
+
const later = loginCandidates.filter(n => okProfileName(n) && !names.includes(n));
|
|
46
|
+
const orgList = (Array.isArray(orgs) ? orgs : []).map(o => ({ id: o.id, name: o.name || o.slug || o.id, slug: o.slug || null, role: o.role || null }));
|
|
47
|
+
const w = Math.min(28, Math.max(8, ...[...names, ...later].map(n => n.length + 8), ...orgList.map(o => [...o.name].length + 4)));
|
|
48
|
+
const rows = [];
|
|
49
|
+
for (const n of names) rows.push({ key: `${n === current ? '* ' : ' '}${`profile ${n}`.padEnd(w)}`, text: n === current ? 'this window' : 'signed in: switch this window to it', pick: { profile: n } });
|
|
50
|
+
for (const n of later) rows.push({ key: ` ${`profile ${n}`.padEnd(w)}`, text: 'not signed in: offers quox login', pick: { login: n } });
|
|
51
|
+
for (const o of orgList) rows.push({ key: `${o.id === orgId ? '* ' : ' '}${`org ${o.name}`.padEnd(w)}`, text: `${o.slug || o.id}${o.role ? ` (${o.role})` : ''}${o.id === orgId ? ' this window' : ''}`, pick: { org: o } });
|
|
52
|
+
return rows;
|
|
53
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QUOXON-HISTORY: what the user typed, kept across quox runs so Up recalls it (most recent first), like a shell.
|
|
3
|
+
* Local only (~/.quox/history.jsonl, mode 0600), capped. The caller never offers it anything that looks like a key:
|
|
4
|
+
* keys, PINs and /connect secrets are refused before they reach history.
|
|
5
|
+
*/
|
|
6
|
+
import { readFileSync, appendFileSync, writeFileSync, mkdirSync, statSync } from 'node:fs';
|
|
7
|
+
import { homedir } from 'node:os';
|
|
8
|
+
import { join, dirname } from 'node:path';
|
|
9
|
+
|
|
10
|
+
export const HISTORY_CAP = 500;
|
|
11
|
+
export const defaultHistoryPath = (home = homedir()) => join(home, '.quox', 'history.jsonl');
|
|
12
|
+
|
|
13
|
+
/** -> { load(): string[] oldest first, add(text) } on the given file. Every failure is quiet: history is a convenience. */
|
|
14
|
+
export function fileHistory(path = defaultHistoryPath(), { cap = HISTORY_CAP } = {}) {
|
|
15
|
+
const load = () => {
|
|
16
|
+
try {
|
|
17
|
+
const out = [];
|
|
18
|
+
for (const ln of readFileSync(path, 'utf8').split('\n')) {
|
|
19
|
+
if (!ln) continue;
|
|
20
|
+
try { const t = JSON.parse(ln)?.t; if (typeof t === 'string' && t) out.push(t); } catch { /* a torn line */ }
|
|
21
|
+
}
|
|
22
|
+
return out.slice(-cap);
|
|
23
|
+
} catch { return []; }
|
|
24
|
+
};
|
|
25
|
+
const add = (text) => {
|
|
26
|
+
try {
|
|
27
|
+
mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
|
|
28
|
+
appendFileSync(path, JSON.stringify({ t: text }) + '\n', { mode: 0o600 });
|
|
29
|
+
if (statSync(path).size > cap * 400) { const keep = load(); writeFileSync(path, keep.map(t => JSON.stringify({ t })).join('\n') + '\n', { mode: 0o600 }); }
|
|
30
|
+
} catch { /* read-only home: this run still has it in memory */ }
|
|
31
|
+
};
|
|
32
|
+
return { load, add };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Push onto the in-memory list, skipping an exact repeat of the last entry (shell ignoredups). -> true when added. */
|
|
36
|
+
export function pushHistory(list, text) {
|
|
37
|
+
if (typeof text !== 'string' || !text.trim()) return false;
|
|
38
|
+
if (list.length && list[list.length - 1] === text) return false;
|
|
39
|
+
list.push(text);
|
|
40
|
+
return true;
|
|
41
|
+
}
|