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
|
* D3: a tool that ended effect_unknown is reconciled against the server's own record of the call
|
|
11
11
|
* (the ops.tool envelope's payload.success). No envelope id means it cannot be reconciled from here.
|
|
12
12
|
*/
|
|
13
|
-
import { seg, fit,
|
|
13
|
+
import { seg, fit, plural, wrapHang } from './model.js';
|
|
14
14
|
|
|
15
15
|
const LINKS = ['AEE', 'AOCL', 'TOOL', 'WARD'];
|
|
16
16
|
|
|
@@ -19,7 +19,7 @@ export function chainLinks(d) {
|
|
|
19
19
|
const t = d.tool || {};
|
|
20
20
|
const env = d.envelope?.ok ? d.envelope.env : null;
|
|
21
21
|
const out = [];
|
|
22
|
-
out.push(env ? { name: 'AEE', ok: true, sub:
|
|
22
|
+
out.push(env ? { name: 'AEE', ok: true, sub: 'envelope read, id above' }
|
|
23
23
|
: { name: 'AEE', ok: d.envelope?.status === 404 ? false : null, sub: d.envelope?.status === 404 ? 'envelope not found' : `envelope not read${d.envelope?.error ? ` (${fit(d.envelope.error, 24)})` : ''}` });
|
|
24
24
|
const aocl = d.aocl?.ok ? d.aocl.list || [] : null;
|
|
25
25
|
if (aocl && aocl.length) {
|
|
@@ -52,7 +52,13 @@ const markOf = (ok) => (ok === true ? ['✔', 'ok'] : ok === false ? ['✖', 'er
|
|
|
52
52
|
export function proofChain(d, cols) {
|
|
53
53
|
const links = chainLinks(d);
|
|
54
54
|
const rows = [];
|
|
55
|
-
|
|
55
|
+
const envId = d.tool?.envelopeId || (d.envelope?.ok ? d.envelope.env?.id : null);
|
|
56
|
+
rows.push([seg('proof ', 'muted'), seg(fit(d.tool?.tool || 'tool call', cols - 10), 'value', true)]);
|
|
57
|
+
// The full id on its own row, never clipped, so it can be selected and copied whole.
|
|
58
|
+
if (envId) {
|
|
59
|
+
wrapHang(envId, cols - 7, 0).forEach((l, i) => rows.push([seg(i ? ' ' : 'id ', 'muted'), seg(l, 'value', true)]));
|
|
60
|
+
for (const t of [`read it back: quox aee get ${envId}`, 'check a bundle: quox evidence verify <file>']) for (const l of wrapHang(t, cols - 4, 2)) rows.push([seg(l, 'muted')]);
|
|
61
|
+
}
|
|
56
62
|
if (cols >= 90) {
|
|
57
63
|
const head = [], sub = [], cw = Math.max(23, Math.floor((cols - 4) / 4));
|
|
58
64
|
links.forEach((l, i) => {
|
|
@@ -69,7 +75,9 @@ export function proofChain(d, cols) {
|
|
|
69
75
|
links.forEach((l, i) => {
|
|
70
76
|
const role = `proto:${l.name.toLowerCase() === 'tool' ? 'volt' : l.name.toLowerCase()}`;
|
|
71
77
|
const [m, mr] = markOf(l.ok);
|
|
72
|
-
|
|
78
|
+
const subs = wrapHang(l.sub, cols - 11, 0);
|
|
79
|
+
rows.push([seg(m, mr), seg(' '), seg(l.name.padEnd(5), l.name === 'TOOL' ? 'value' : role, true), seg(subs[0], l.ok === false ? 'error' : 'muted')]);
|
|
80
|
+
for (const more of subs.slice(1)) rows.push([seg(' '.repeat(7)), seg(more, l.ok === false ? 'error' : 'muted')]);
|
|
73
81
|
if (i < 3) rows.push([seg(l.ok === true && links[i + 1].ok === true ? '┃' : '╎', l.ok === true && links[i + 1].ok === true ? 'indigo' : 'faint')]);
|
|
74
82
|
});
|
|
75
83
|
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* /prove: independently verify the last turn's tool calls. Two independent checks per call:
|
|
3
|
+
* 1. the receipt chain (the same reads as /proof: AEE envelope, the server's own record of the call,
|
|
4
|
+
* the WARD inclusion receipt), and
|
|
5
|
+
* 2. a TRUTH read: for a write whose effect we know how to read back (VERIFIERS below), read it back
|
|
6
|
+
* from the system of record and see whether it is really there.
|
|
7
|
+
* Per call: proven (chain agrees and, for a mapped write, the effect is there), unproven (cannot be shown:
|
|
8
|
+
* an unmapped write, a read that failed, no receipt) or contradicts (the stream said ok and the server or
|
|
9
|
+
* the truth read disagrees, or the call failed). An unmapped write is never called proven.
|
|
10
|
+
*
|
|
11
|
+
* Extending: push an entry on VERIFIERS: { id, tools: [tool names], keys: [arg names to find in the call
|
|
12
|
+
* summary], read: (reads, target) -> true | false | null, describe: (target, found) -> text }.
|
|
13
|
+
*/
|
|
14
|
+
import { seg, fit, fitRow, short, boxWidth } from './model.js';
|
|
15
|
+
import { chainLinks } from './proof.js';
|
|
16
|
+
|
|
17
|
+
export const VERIFIERS = [
|
|
18
|
+
{ id: 'slot_create', tools: ['slot_create'], keys: ['name', 'slot'], read: (r, t) => r.slotExists(t), describe: (t, f) => (f ? `slot "${t}" exists` : `slot "${t}" not found`) },
|
|
19
|
+
{ id: 'memory_save', tools: ['memory_save'], keys: ['content', 'text', 'fact'], read: (r, t) => r.memoryFind(t), describe: (t, f) => (f ? `memory "${fit(t, 24)}" is stored` : `memory "${fit(t, 24)}" not found`) }
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
export const verifierFor = (tool) => VERIFIERS.find(v => v.tools.includes(tool)) || null;
|
|
23
|
+
|
|
24
|
+
/** Tools that only read: their receipt chain is the whole proof (nothing changed to read back). */
|
|
25
|
+
const READ_RE = /_(list|read|get|search|stats|history|usage|brief|notes|roadmaps|streams|folders|zones|pending|accounts|blocked|proposals|latest|query|status|show)$/;
|
|
26
|
+
export const isReadTool = (tool) => READ_RE.test(String(tool || ''));
|
|
27
|
+
|
|
28
|
+
/** The thing to check, from the call summary: key=value, key: "value", else the first quoted string. */
|
|
29
|
+
export function targetOf(tool, summary) {
|
|
30
|
+
const s = String(summary || '');
|
|
31
|
+
const v = verifierFor(tool);
|
|
32
|
+
for (const k of v?.keys || ['name']) {
|
|
33
|
+
const m = new RegExp(`\\b${k}\\s*[=:]\\s*(?:"([^"]+)"|'([^']+)'|([\\w.\\-]+))`, 'i').exec(s);
|
|
34
|
+
if (m) return m[1] || m[2] || m[3];
|
|
35
|
+
}
|
|
36
|
+
const q = /["“]([^"”]+)["”]/.exec(s);
|
|
37
|
+
return q ? q[1] : null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** -> { mapped:false } | { mapped:true, found: true|false|null, what?, error?, noTarget? }. Never throws. */
|
|
41
|
+
export async function runTruth(call, reads) {
|
|
42
|
+
const v = verifierFor(call.tool);
|
|
43
|
+
if (!v) return { mapped: false };
|
|
44
|
+
const target = targetOf(call.tool, call.summary);
|
|
45
|
+
if (!target) return { mapped: true, found: null, noTarget: true };
|
|
46
|
+
try {
|
|
47
|
+
const found = await v.read(reads, target);
|
|
48
|
+
return { mapped: true, found: found === true ? true : found === false ? false : null, what: v.describe(target, found === true) };
|
|
49
|
+
} catch (e) {
|
|
50
|
+
return { mapped: true, found: null, error: e?.status ? `HTTP ${e.status}` : e?.message || 'failed' };
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Settled tool calls after the last user message of `items` (history + live blocks). */
|
|
55
|
+
export function lastTurnCalls(items) {
|
|
56
|
+
const list = items || [];
|
|
57
|
+
let from = 0;
|
|
58
|
+
for (let i = list.length - 1; i >= 0; i--) if (list[i]?.kind === 'user' && !list[i].echo) { from = i + 1; break; }
|
|
59
|
+
return list.slice(from).filter(b => b && b.kind === 'tool' && b.status !== 'running');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const V = (state, why, call) => ({ state, mark: state === 'proven' ? '✔' : state === 'contradicts' ? '✖' : '⚠', why, envelopeId: call.envelopeId || null });
|
|
63
|
+
|
|
64
|
+
/** { call, proof, truth } -> { state, mark, why, envelopeId }. Pure. */
|
|
65
|
+
export function verdictFor({ call, proof, truth }) {
|
|
66
|
+
const bad = !['ok', 'effect_unknown'].includes(call.status);
|
|
67
|
+
if (bad) return V('contradicts', `the call ${call.status || 'did not finish'}, so the task did not get done`, call);
|
|
68
|
+
if (!call.envelopeId) return V('unproven', 'no receipt id on this call', call);
|
|
69
|
+
const links = proof ? chainLinks(proof) : [];
|
|
70
|
+
const by = Object.fromEntries(links.map(l => [l.name, l]));
|
|
71
|
+
if (by.TOOL?.ok === false) return V('contradicts', by.TOOL.sub, call);
|
|
72
|
+
if (by.WARD?.ok === false) return V('contradicts', `WARD: ${by.WARD.sub}`, call);
|
|
73
|
+
if (by.AEE?.ok !== true) return V('unproven', `receipt not confirmed (${by.AEE?.sub || 'not read'})`, call);
|
|
74
|
+
if (by.TOOL?.ok !== true) return V('unproven', 'no server record of the call outcome', call);
|
|
75
|
+
if (truth?.mapped) {
|
|
76
|
+
if (truth.found === true) return V('proven', `${truth.what || 'effect found'} · server recorded success`, call);
|
|
77
|
+
if (truth.found === false) return V('contradicts', `${truth.what || 'effect not found'}, though the call reported ok`, call);
|
|
78
|
+
return V('unproven', truth.noTarget ? 'could not tell which thing to check from the call' : `effect not read back (${truth.error || 'no answer'})`, call);
|
|
79
|
+
}
|
|
80
|
+
if (isReadTool(call.tool)) return V('proven', 'read call · receipt chain checked', call);
|
|
81
|
+
return V('unproven', `no read mapping for ${call.tool}: receipt checked, effect not independently read`, call);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** d: { items: [{ call, proof, truth }] } -> view { box, rows } */
|
|
85
|
+
export function proveView(d, cols) {
|
|
86
|
+
const w = boxWidth(cols, 90), inner = w - 4, nw = w < 60;
|
|
87
|
+
const rows = [];
|
|
88
|
+
const tally = { proven: 0, unproven: 0, contradicts: 0 };
|
|
89
|
+
const items = d.items || [];
|
|
90
|
+
for (const it of items) {
|
|
91
|
+
const v = verdictFor(it);
|
|
92
|
+
tally[v.state]++;
|
|
93
|
+
const role = v.state === 'proven' ? 'ok' : v.state === 'contradicts' ? 'error' : 'warn';
|
|
94
|
+
rows.push([seg(v.mark, role, true), seg(' '), seg(fit(it.call.tool || 'tool', 14).padEnd(15), 'value', true), seg(v.state.padEnd(12), role, true), ...(nw ? [] : [seg(fit(v.why, inner - 30), 'muted')])]);
|
|
95
|
+
rows.push([seg(' '), ...(nw ? [seg(fit(v.why, inner - 2), 'muted')] : []), seg(v.envelopeId ? `${nw ? '' : ' '}receipt ${short(v.envelopeId, nw ? 12 : 22)}` : ' no receipt', 'muted')]);
|
|
96
|
+
}
|
|
97
|
+
if (!items.length) rows.push([seg('no tool calls to check', 'muted')]);
|
|
98
|
+
rows.push([seg(`${tally.proven} proven · ${tally.unproven} unproven · ${tally.contradicts} contradicts`, tally.contradicts ? 'error' : tally.unproven ? 'warn' : 'ok', true)]);
|
|
99
|
+
return { box: { width: w, title: 'Prove · last turn', titleRole: 'indigo', border: 'indigo' }, rows: rows.map(r => fitRow(r, inner)), tally };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Fetchers for createObjectsApi. deps: { self (the objects api, for proof), get, listOf, resolveEndpoint } */
|
|
103
|
+
export function proveFetchers({ self, get, listOf, resolveEndpoint }) {
|
|
104
|
+
const reads = {
|
|
105
|
+
async slotExists(name) {
|
|
106
|
+
const ep = resolveEndpoint('v1.slots.list');
|
|
107
|
+
const rows = listOf(await get('collector', ep.path, undefined, 10000), 'slots');
|
|
108
|
+
return rows.some(s => String(s.name || '').toLowerCase() === String(name).toLowerCase());
|
|
109
|
+
},
|
|
110
|
+
async memoryFind(text) {
|
|
111
|
+
const d = await get('memory', '/api/memory/search', { q: text, limit: 10 }, 10000);
|
|
112
|
+
const want = String(text).toLowerCase().slice(0, 60);
|
|
113
|
+
return listOf(d, 'results', 'memories', 'data').some(m => JSON.stringify(m).toLowerCase().includes(want));
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
return {
|
|
117
|
+
reads,
|
|
118
|
+
/** calls: tool blocks { tool, status, summary, envelopeId, callId } -> { data: { items } } */
|
|
119
|
+
async prove(calls) {
|
|
120
|
+
const items = await Promise.all((calls || []).slice(-12).map(async (call) => {
|
|
121
|
+
const [proof, truth] = await Promise.all([
|
|
122
|
+
call.envelopeId ? self.proof({ tool: call.tool, status: call.status, envelopeId: call.envelopeId, callId: call.callId }).then(r => r.data).catch(() => null) : null,
|
|
123
|
+
runTruth(call, reads)
|
|
124
|
+
]);
|
|
125
|
+
return { call: { tool: call.tool, status: call.status, summary: call.summary, envelopeId: call.envelopeId || null }, proof, truth };
|
|
126
|
+
}));
|
|
127
|
+
return { data: { items } };
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* /replay [run | conversation id] [page]: reconstruct a previous agent run from the audit trail, in order:
|
|
3
|
+
* the decisions (turn messages), each tool with an args summary, its output and its receipt. Read-only:
|
|
4
|
+
* nothing is re-executed (sandbox re-execution is not available). Default is the last turn of this
|
|
5
|
+
* conversation. Sources: the conversation's saved turns, this session's tool calls, and AEE envelopes
|
|
6
|
+
* (GET /aee/:id, GET /aee/query?corr=). A conversation id replays the messages only: the server does not link
|
|
7
|
+
* tool envelopes to a conversation, so the view says so and points at /replay <envelope id> for a tool run.
|
|
8
|
+
*/
|
|
9
|
+
import { seg, fit, fitRow, short, dur, boxWidth, parseTs } from './model.js';
|
|
10
|
+
|
|
11
|
+
export const PAGE = 6;
|
|
12
|
+
|
|
13
|
+
/** '' | '2' | 'abc' | 'abc 3' | 'abc page 3' | 'page 3' -> { ref, page } */
|
|
14
|
+
export function parseReplayArg(arg) {
|
|
15
|
+
let t = String(arg || '').trim();
|
|
16
|
+
let page = 1;
|
|
17
|
+
const m = /(?:^|\s)(?:page\s+)?(\d{1,4})$/i.exec(t);
|
|
18
|
+
const pm = /(?:^|\s)page\s+(\d{1,4})$/i.exec(t);
|
|
19
|
+
if (pm) { page = Number(pm[1]); t = t.slice(0, pm.index).trim(); }
|
|
20
|
+
else if (m && (m.index > 0 || t === m[1]) && !/^\d{5,}$/.test(t)) { page = Number(m[1]); t = t.slice(0, m.index).trim(); }
|
|
21
|
+
return { ref: t || null, page: Math.max(1, page) };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const receiptOf = (c) => `${c.envelopeId ? `receipt ${c.envelopeId}` : 'no receipt id'}${c.receipt?.verified && c.receipt.ward_seq != null ? ` · WARD seq ${c.receipt.ward_seq}` : ''}`;
|
|
25
|
+
|
|
26
|
+
/** { messages: [{ role, content }], calls: [tool blocks] } -> steps: the question, each tool, then the answer. */
|
|
27
|
+
export function buildTurnSteps({ messages = [], calls = [] }) {
|
|
28
|
+
const steps = [];
|
|
29
|
+
const users = messages.filter(m => m.role === 'user'), agents = messages.filter(m => m.role !== 'user');
|
|
30
|
+
for (const m of users) steps.push({ kind: 'user', text: String(m.content ?? '') });
|
|
31
|
+
for (const c of calls) steps.push({ kind: 'tool', tool: c.tool, args: c.summary || '', status: c.status, out: c.resultSummary || '', ms: c.durationMs, receipt: receiptOf(c), id: c.envelopeId || null });
|
|
32
|
+
for (const m of agents) steps.push({ kind: 'agent', text: String(m.content ?? '') });
|
|
33
|
+
return steps;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** AEE envelopes of one correlation id -> steps in time order. */
|
|
37
|
+
export function buildEnvelopeSteps(envs = []) {
|
|
38
|
+
const sorted = [...envs].sort((a, b) => (parseTs(a.ts) ?? 0) - (parseTs(b.ts) ?? 0));
|
|
39
|
+
return sorted.map((e) => {
|
|
40
|
+
const p = e.payload || {};
|
|
41
|
+
const intent = String(e.intent || '');
|
|
42
|
+
if (/^ops\.tool\./.test(intent)) {
|
|
43
|
+
const ok = p.success !== false && !/\.failed$|\.denied$/.test(intent);
|
|
44
|
+
return { kind: 'tool', id: e.id, tool: p.tool_name || intent, args: p.input_summary || p.summary || '', status: /\.denied$/.test(intent) ? 'denied' : ok ? 'ok' : 'failed', out: ok ? (p.result_summary || '') : (p.error || intent), ms: p.duration_ms, receipt: `receipt ${e.id}` };
|
|
45
|
+
}
|
|
46
|
+
if (intent === 'aocl.policy.evaluated') return { kind: 'policy', id: e.id, text: p.allPassed === false ? 'policy blocked' : 'policy passed', receipt: `receipt ${e.id}` };
|
|
47
|
+
if (intent === 'aocl.identity.verified') return { kind: 'policy', id: e.id, text: 'identity verified', receipt: `receipt ${e.id}` };
|
|
48
|
+
return { kind: 'event', id: e.id, text: `${intent || 'envelope'}${e.from ? ` from ${e.from}` : ''}`, receipt: `receipt ${e.id}` };
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const MARK = { ok: ['✔', 'ok'], failed: ['✖', 'error'], denied: ['✖', 'error'] };
|
|
53
|
+
|
|
54
|
+
/** d: { steps, page, size, title, notes } -> view { box, rows } */
|
|
55
|
+
export function replayView(d, cols) {
|
|
56
|
+
const w = boxWidth(cols, 96), inner = w - 4, nw = w < 60;
|
|
57
|
+
const size = d.size || PAGE;
|
|
58
|
+
const steps = d.steps || [];
|
|
59
|
+
const pages = Math.max(1, Math.ceil(steps.length / size));
|
|
60
|
+
const page = Math.min(Math.max(1, d.page || 1), pages);
|
|
61
|
+
const rows = [];
|
|
62
|
+
if (!steps.length) rows.push([seg('nothing recorded for this run', 'muted')]);
|
|
63
|
+
const base = (page - 1) * size;
|
|
64
|
+
steps.slice(base, base + size).forEach((s, i) => {
|
|
65
|
+
const n = String(base + i + 1).padStart(2);
|
|
66
|
+
const label = s.kind === 'user' ? 'you' : s.kind === 'agent' ? 'agent' : s.kind === 'tool' ? 'tool' : s.kind === 'policy' ? 'guard' : 'event';
|
|
67
|
+
const lrole = s.kind === 'user' ? 'sky' : s.kind === 'agent' ? 'violet' : s.kind === 'tool' ? 'indigo' : 'muted';
|
|
68
|
+
if (s.kind === 'tool') {
|
|
69
|
+
const [m, mr] = MARK[s.status] || ['○', 'muted'];
|
|
70
|
+
rows.push([seg(n + ' ', 'muted'), seg(label.padEnd(6), lrole, true), seg(`${s.tool} `, 'value', true), seg(m, mr), seg(` ${s.status || 'unknown'}${s.ms != null ? ` ${dur(s.ms)}` : ''}`, mr)]);
|
|
71
|
+
if (s.args) rows.push([seg(' args ', 'muted'), seg(s.args, 'value')]);
|
|
72
|
+
if (s.out) rows.push([seg(' output ', 'muted'), seg(s.out, s.status === 'ok' ? 'value' : 'warn')]);
|
|
73
|
+
rows.push([seg(' ', 'muted'), seg(s.receipt || 'no receipt id', 'muted')]);
|
|
74
|
+
} else {
|
|
75
|
+
rows.push([seg(n + ' ', 'muted'), seg(label.padEnd(6), lrole, true), seg(s.text || '', s.kind === 'event' ? 'muted' : 'value')]);
|
|
76
|
+
if (s.receipt && s.kind !== 'user' && s.kind !== 'agent') rows.push([seg(' ', 'muted'), seg(s.receipt, 'muted')]);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
for (const n of d.notes || []) rows.push([seg(`note: ${n}`, 'warn')]);
|
|
80
|
+
rows.push([seg(`page ${page} of ${pages}`, 'value', true), seg(page < pages ? `${nw ? '' : ' · '}${nw ? ' ' : ''}/replay${d.ref ? ` ${short(d.ref, 12)}` : ''} ${page + 1} for the next` : '', 'muted')]);
|
|
81
|
+
rows.push([seg('read-only: rebuilt from the audit trail, nothing is re-run', 'muted')]);
|
|
82
|
+
return { box: { width: w, title: `Replay · ${d.title || 'last turn'}`, titleRole: 'violet', border: 'violet' }, rows: rows.map(r => fitRow(r, inner)) };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Fetchers for createObjectsApi. deps: { get, listOf, why, verifyReceipt, aeeGet } */
|
|
86
|
+
export function replayFetchers({ get, listOf, why, verifyReceipt, aeeGet }) {
|
|
87
|
+
const turns = async (id) => listOf(await get('collector', `/api/v1/conversations/${encodeURIComponent(id)}/turns`, { limit: 200 }, 12000), 'turns');
|
|
88
|
+
const corrList = async (corr) => listOf(await get('collector', '/aee/query', { corr, limit: 100 }, 12000), 'envelopes');
|
|
89
|
+
return {
|
|
90
|
+
/** a: { ref, page, conversationId, calls } -> { data } | { error, tone } */
|
|
91
|
+
async replay(a) {
|
|
92
|
+
const page = a.page || 1;
|
|
93
|
+
if (!a.ref) {
|
|
94
|
+
const calls = await Promise.all((a.calls || []).slice(-12).map(async (c) => {
|
|
95
|
+
if (!c.envelopeId || (c.receipt?.verified && c.receipt.ward_seq != null)) return c;
|
|
96
|
+
const r = await verifyReceipt(c.envelopeId, 6);
|
|
97
|
+
const res = r.ok ? r.result : null;
|
|
98
|
+
return res?.status === 'WITNESSED' ? { ...c, receipt: { verified: true, ward_seq: res.entry?.seq } } : c;
|
|
99
|
+
}));
|
|
100
|
+
let messages = [], notes = [];
|
|
101
|
+
try {
|
|
102
|
+
const rows = await turns(a.conversationId);
|
|
103
|
+
let from = 0;
|
|
104
|
+
for (let i = rows.length - 1; i >= 0; i--) if (rows[i].role === 'user') { from = i; break; }
|
|
105
|
+
messages = rows.slice(from).filter(r => r.role === 'user' || r.role === 'assistant');
|
|
106
|
+
} catch (e) { notes.push(`the saved turn messages could not be read (${why(e)}); showing tool calls only`); }
|
|
107
|
+
return { data: { steps: buildTurnSteps({ messages, calls }), page, title: 'last turn', notes } };
|
|
108
|
+
}
|
|
109
|
+
let rows = null;
|
|
110
|
+
try { rows = await turns(a.ref); } catch (e) { if (e?.status && e.status !== 404 && e.status !== 400) return { error: `replay: ${why(e)}`, tone: 'warn' }; }
|
|
111
|
+
if (rows && rows.length) {
|
|
112
|
+
const steps = rows.filter(r => r.role === 'user' || r.role === 'assistant').map(r => ({ kind: r.role === 'user' ? 'user' : 'agent', text: String(r.content ?? '') }));
|
|
113
|
+
return { data: { steps, page, ref: a.ref, title: `conversation ${short(a.ref, 12)}`, notes: ['tool calls are not linked to a conversation id on the server: /replay <envelope id> replays a tool run'] } };
|
|
114
|
+
}
|
|
115
|
+
let corr = a.ref, seen = null;
|
|
116
|
+
const env = await aeeGet(a.ref);
|
|
117
|
+
if (env.ok && env.env) { seen = env.env; corr = env.env.corr || a.ref; }
|
|
118
|
+
let list = [];
|
|
119
|
+
try { list = await corrList(corr); } catch (e) { return { error: `replay: ${why(e)}`, tone: 'warn' }; }
|
|
120
|
+
if (seen && !list.some(e => e.id === seen.id)) list = [...list, seen];
|
|
121
|
+
if (!list.length) return { error: `No run, conversation or envelope called "${a.ref}".`, tone: 'warn' };
|
|
122
|
+
return { data: { steps: buildEnvelopeSteps(list), page, ref: a.ref, title: `run ${short(corr, 14)}`, notes: [] } };
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* /sitrep: the situation NOW, from persisted data only (no model call): approvals waiting (auth inbox),
|
|
3
|
+
* what needs you (LUCID needs_you, by urgency), running work (LUCID in_motion), and what Quox cannot see
|
|
4
|
+
* (LUCID silent / failing / unconnected feeds, plus any source this card could not read). /away is the
|
|
5
|
+
* "since you were last here" digest; this is the present tense.
|
|
6
|
+
*/
|
|
7
|
+
import { seg, fit, plural, boxWidth, parseTs, ago } from './model.js';
|
|
8
|
+
|
|
9
|
+
const URG = { now: 0, today: 1, 'this week': 2, whenever: 3, fyi: 4 };
|
|
10
|
+
const hrs = (ms) => { const m = Math.round((ms || 0) / 60000); return m < 60 ? `${m}m` : m < 2880 ? `${Math.round(m / 60)}h` : `${Math.round(m / 1440)}d`; };
|
|
11
|
+
|
|
12
|
+
/** sit/appr: { ok, v | error } parts. -> { approvals: [..]|null, needs, running, blind } */
|
|
13
|
+
export function sitrepData(sit, appr, now = Date.now()) {
|
|
14
|
+
const blind = [];
|
|
15
|
+
let needs = [], running = [];
|
|
16
|
+
if (sit?.ok) {
|
|
17
|
+
const s = sit.v?.situation || sit.v || {};
|
|
18
|
+
needs = (s.needs_you || []).filter(n => n.kind !== 'approval')
|
|
19
|
+
.map(n => ({ title: n.L0 || n.ref, urgency: n.urgency, ts: n.L1?.when?.since ?? n.status_since ?? null, ref: n.ref }))
|
|
20
|
+
.sort((a, b) => (URG[a.urgency] ?? 5) - (URG[b.urgency] ?? 5) || (parseTs(b.ts) ?? 0) - (parseTs(a.ts) ?? 0));
|
|
21
|
+
running = (s.in_motion || []).map(m => ({ title: m.L0 || m.ref, ref: m.ref }));
|
|
22
|
+
const cs = s.cannot_see || {};
|
|
23
|
+
for (const x of cs.silent || []) blind.push({ source: `lucid:${x.feed}`, reason: `silent ${hrs(x.silent_ms)}` });
|
|
24
|
+
for (const x of cs.failing || []) blind.push({ source: `lucid:${x.feed}`, reason: `failing: ${x.error}` });
|
|
25
|
+
for (const n of cs.unwired || []) blind.push({ source: `lucid:${n}`, reason: 'not connected' });
|
|
26
|
+
} else blind.push({ source: 'lucid', reason: sit?.error || 'not read' });
|
|
27
|
+
let approvals = null;
|
|
28
|
+
if (appr?.ok) approvals = (appr.v || []).map(a => ({ title: a.title || a.summary || a.id, ts: a.created_at, ref: a.id }));
|
|
29
|
+
else blind.push({ source: 'approvals', reason: appr?.error || 'not read' });
|
|
30
|
+
return { approvals, needs, running, blind, now };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** d: sitrepData(...) + now */
|
|
34
|
+
export function sitrepCard(d, cols, { maxNeeds = 5, maxRunning = 3, maxApprovals = 3 } = {}) {
|
|
35
|
+
const now = d.now ?? Date.now();
|
|
36
|
+
const w = boxWidth(cols, 96), inner = w - 4;
|
|
37
|
+
const rows = [];
|
|
38
|
+
const ap = d.approvals || [], needs = d.needs || [], run = d.running || [];
|
|
39
|
+
const line = (g, role, title, meta) => {
|
|
40
|
+
const m = meta ? ` ${meta}` : '';
|
|
41
|
+
return [seg(g, role), seg(fit(title, Math.max(8, inner - 2 - m.length)), 'value'), seg(m, 'muted')];
|
|
42
|
+
};
|
|
43
|
+
for (const a of ap.slice(0, maxApprovals)) rows.push(line('⚠ ', 'warn', `Approval: ${a.title}`, a.ts ? `waiting ${ago(a.ts, now)}` : ''));
|
|
44
|
+
if (ap.length > maxApprovals) rows.push([seg(`+${ap.length - maxApprovals} more approvals · /approvals`, 'muted')]);
|
|
45
|
+
for (const n of needs.slice(0, maxNeeds)) rows.push(line(n.urgency === 'now' ? '⚠ ' : '◐ ', n.urgency === 'now' ? 'warn' : 'muted', n.title, n.urgency || ''));
|
|
46
|
+
if (needs.length > maxNeeds) rows.push([seg(`+${needs.length - maxNeeds} more · quox lucid`, 'muted')]);
|
|
47
|
+
if (!ap.length && !needs.length) rows.push([seg('Nothing needs you right now.', 'muted')]);
|
|
48
|
+
if (run.length) {
|
|
49
|
+
rows.push([seg(' ')]);
|
|
50
|
+
for (const r of run.slice(0, maxRunning)) rows.push(line('◐ ', 'indigo', r.title, ''));
|
|
51
|
+
if (run.length > maxRunning) rows.push([seg(`+${run.length - maxRunning} more running · /roster all`, 'muted')]);
|
|
52
|
+
}
|
|
53
|
+
if ((d.blind || []).length) rows.push([seg(fit(`Can't see: ${d.blind.map(b => (b.reason ? `${b.source} (${b.reason})` : b.source)).join(', ')}`, inner), 'faint')]);
|
|
54
|
+
const right = w < 60
|
|
55
|
+
? [d.approvals ? `${ap.length} ap` : null, `${needs.length} need`, `${run.length} run`].filter(Boolean).join(' · ')
|
|
56
|
+
: [d.approvals ? plural(ap.length, 'approval') : null, `${needs.length} need you`, `${run.length} running`].filter(Boolean).join(' · ');
|
|
57
|
+
return { box: { width: w, title: 'Situation now', titleRole: 'value', right, rightRole: 'muted', border: ap.length || needs.some(n => n.urgency === 'now') ? 'warn' : 'muted' }, rows };
|
|
58
|
+
}
|
|
@@ -62,6 +62,17 @@ export function ObjectView({ item, cols, fx = null, t = fx && fx.e != null ? fx.
|
|
|
62
62
|
return h(Box, { flexDirection: 'column', marginTop: 1, paddingLeft: 1 }, top, ...body, bottom);
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
+
/**
|
|
66
|
+
* TUI-UX #5: would this card (rows + border + margin) be taller than the terminal? Ink cannot erase rows that already scrolled
|
|
67
|
+
* off the top of a live region, so a decode-in on such a card leaves a half-decoded copy in scrollback. Callers skip the
|
|
68
|
+
* animation for it and commit it settled.
|
|
69
|
+
*/
|
|
70
|
+
export function cardTallerThanScreen(item, cols, termRows, now) {
|
|
71
|
+
if (!Number.isFinite(termRows) || termRows <= 0) return false;
|
|
72
|
+
const view = buildObject(item.obj || item, cols, now);
|
|
73
|
+
return (view.rows || []).length + (view.box ? 3 : 1) + 2 >= termRows; // top + bottom border, marginTop, plus the composer line
|
|
74
|
+
}
|
|
75
|
+
|
|
65
76
|
/** Plain text of a view at a width (tests, transcripts, --print): the box drawn with the same geometry. */
|
|
66
77
|
export function objectText(item, cols, now) {
|
|
67
78
|
const view = buildObject(item.obj || item, cols, now);
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Functions take the facts they print (a URL, a size, a provider name); nothing here computes anything.
|
|
7
7
|
*/
|
|
8
|
+
import { askText } from '../ask.js';
|
|
8
9
|
|
|
9
10
|
export const LABEL = 'Getting started';
|
|
10
11
|
export const LABEL_SUB = 'setup guide · no AI yet, every line here is scripted';
|
|
@@ -12,7 +13,7 @@ export const LABEL_SUB = 'setup guide · no AI yet, every line here is scripted'
|
|
|
12
13
|
// The Q says HELLO itself, decoded in beside the mark right after the intro (qmotion.js HelloIntro); the
|
|
13
14
|
// guide then carries on with GREETING_REST. GREETING is the whole sentence, for anywhere that shows it at once.
|
|
14
15
|
export const HELLO = "Hi, I'm Quox.";
|
|
15
|
-
export const GREETING_REST = 'I run AI agents for you on your own machine, and I keep
|
|
16
|
+
export const GREETING_REST = 'I run AI agents for you on your own machine, and I keep receipts you can check for what they do.';
|
|
16
17
|
export const GREETING = `${HELLO} ${GREETING_REST}`;
|
|
17
18
|
export const QUESTION = 'What would you like help with?';
|
|
18
19
|
export const EXAMPLES = ['watch my servers', 'automate something', 'code with agents', 'sort my email', 'just look around'];
|
|
@@ -32,7 +33,7 @@ export const DEFERRAL = 'I can answer that properly once your Quox is running.';
|
|
|
32
33
|
|
|
33
34
|
// The install time is NOT promised as a number until it has been measured on a clean machine (C2 exit).
|
|
34
35
|
export const OFFER = "To do that I need a QuoxCORE, the engine your agents run in. It runs here in Docker, it's free for your own use, and the first install builds from source, so it takes a while.";
|
|
35
|
-
export const OFFER_ASK = 'Set it up now? (Y/n)';
|
|
36
|
+
export const OFFER_ASK = askText('Set it up now?', { keys: '(Y/n)', who: 'Quox', action: 'connect an AI and install the Quox server, step by step' });
|
|
36
37
|
export const ALREADY = 'Already have one? /connect server <url>';
|
|
37
38
|
export const DECLINED = 'No problem. Run quox any time and we will pick up here.';
|
|
38
39
|
export const DECLINED_HINT = 'Type yes to set up now, /connect server <url> to use one you have, or /exit.';
|
|
@@ -45,10 +46,22 @@ export const CONNECT_USAGE = 'Usage: /connect server <url> for example /connec
|
|
|
45
46
|
export const connectBadUrl = (u) => `That does not look like a server address: ${u}. Use the full URL, starting with https://`;
|
|
46
47
|
export const connectHandoff = (url) => `Connecting to ${url}. Sign in with your QuoxCORE email and password.`;
|
|
47
48
|
export const CONNECT_DONE = 'Signed in. Starting Quox.';
|
|
49
|
+
export const connectChecking = (url) => `Checking ${url} is a QuoxCORE server...`;
|
|
50
|
+
export const CONNECT_CANCELLED = 'Cancelled. Nothing was saved: the previous server address is back.';
|
|
48
51
|
export const connectFailed = (url) => `Sign-in to ${url} did not complete. Check the address, then try /connect server again.`;
|
|
49
52
|
export const CONNECT_INTENT_LINE = 'Great. Point me at it with /connect server <url> and sign in, and we go straight to your agents.';
|
|
50
53
|
|
|
51
54
|
// ---- preflight ---------------------------------------------------------------------------------
|
|
55
|
+
// FIRSTRUN: the guide offers to fix Docker itself (dockerOffer.js), so nobody has to leave it to do so.
|
|
56
|
+
export const DOCKER_OFFER_QUESTION = {
|
|
57
|
+
install: 'Install Docker for you now?',
|
|
58
|
+
group: 'Add you to the docker group now?',
|
|
59
|
+
relaunch: 'Carry on now with the docker group, without logging out?'
|
|
60
|
+
};
|
|
61
|
+
export const DOCKER_OFFER_HINT = 'y yes · r check again · q stop';
|
|
62
|
+
export const DOCKER_OFFER_RUNNING = 'Running (sudo may ask for your password):';
|
|
63
|
+
export const DOCKER_OFFER_FAILED = 'That did not finish. Fix what it said, then run quox to pick up where you left off.';
|
|
64
|
+
export const DOCKER_OFFER_RELAUNCH = 'Starting quox again with the docker group.';
|
|
52
65
|
export const PREFLIGHT_TITLE = 'Checking this machine';
|
|
53
66
|
export const PREFLIGHT_RETRY = 'r to check again · q to stop';
|
|
54
67
|
export const PREFLIGHT_RETRY_DRY = 'r to check again · c to continue anyway (dry run) · q to stop';
|
|
@@ -66,6 +79,8 @@ export const CHECK = {
|
|
|
66
79
|
missingLink: 'https://docs.docker.com/get-docker/',
|
|
67
80
|
perm: 'Docker is installed, but this user cannot reach the daemon',
|
|
68
81
|
permFix: 'Run: sudo usermod -aG docker $USER && newgrp docker then press r.',
|
|
82
|
+
permStale: 'Docker is installed, and you are in the docker group, but this terminal started before that',
|
|
83
|
+
permStaleFix: 'Pressing r cannot fix this: a running terminal never gains a new group. Run: newgrp docker (or open a new terminal), then run: quox',
|
|
69
84
|
down: 'Docker is installed, but the daemon is not running',
|
|
70
85
|
downFix: 'Start Docker (Docker Desktop, or: sudo systemctl start docker), then press r.',
|
|
71
86
|
link: 'https://docs.docker.com/engine/install/'
|
|
@@ -128,6 +143,12 @@ export const CRED_OPTIONS = {
|
|
|
128
143
|
export const credPrompt = (label) => `Paste your ${label}. It stays in memory only: never written to disk, history or the transcript.`;
|
|
129
144
|
export const CRED_INPUT_HINT = 'Enter to use it · Esc to go back';
|
|
130
145
|
export const credHeld = (label, fp) => `${label} held in memory (fingerprint ${fp}). It is stored in your Quox vault after install, never on disk here.`;
|
|
146
|
+
export const credConsent = (label) => `${label} is a cloud AI: your messages and tool results are sent to it, so Quox needs your OK once. Say no and nothing goes to it until you give it later with /consent grant openai (or Settings > Privacy in the dashboard). A local model (Ollama) needs no consent.`;
|
|
147
|
+
// QUOXON-Z2H lane R: the one consent question at the credential step. Default No: cloud processing stays opt-in.
|
|
148
|
+
export const credConsentAsk = (who) => askText(`Allow Quox to send your conversations to ${who}?`, { keys: 'y/N', who: 'Quox', action: `send your messages and any tool results to ${who}, a third party, when it answers` });
|
|
149
|
+
export const CRED_CONSENT_HINT = 'y yes · n or Enter no';
|
|
150
|
+
export const credConsentYes = (who) => `Yes. Your OK for ${who} is recorded as soon as your owner account exists. Withdraw it any time in Settings > Privacy.`;
|
|
151
|
+
export const credConsentNo = (who, provider) => `No. ${who} receives nothing until you give your OK: /consent grant ${provider}, or Settings > Privacy in the dashboard.`;
|
|
131
152
|
export const CRED_EMPTY = 'Nothing was pasted. Paste the key, or Esc to choose again.';
|
|
132
153
|
export const CRED_OLLAMA = 'Ollama it is. The Quox server runs in Docker and cannot reach "localhost", so after install connect it by this machine\'s LAN address: start Ollama with OLLAMA_HOST=0.0.0.0, then /connect ollama http://<lan-ip>:11434';
|
|
133
154
|
export const OLLAMA_URL_PROMPT = 'The Ollama URL on your network, for example http://<lan-ip>:11434. Not a secret. On that machine, start Ollama with OLLAMA_HOST=0.0.0.0 and allow port 11434.';
|
|
@@ -148,6 +169,10 @@ export const STAGES = {
|
|
|
148
169
|
start: 'Start the services',
|
|
149
170
|
health: 'Wait until healthy'
|
|
150
171
|
};
|
|
172
|
+
/** Z2H-IMAGES: the build stage relabels itself once setup.sh says whether it is downloading or building. */
|
|
173
|
+
export const BUILD_LABELS = { download: 'Download the images', build: 'Build the images' };
|
|
174
|
+
export const INSTALL_RERUN_HINT = 'It is safe to run the installer again: finished steps are kept and the build is cached.';
|
|
175
|
+
export const INSTALL_LOG_PATH = (p) => `Full install log: ${p}`;
|
|
151
176
|
export const INSTALL_FAILED = (stage) => `The install stopped at: ${stage}. Press v for the log, r to try again.`;
|
|
152
177
|
export const INSTALL_NOT_YET = 'Installing from here arrives in the next release. For now, run this in another terminal:';
|
|
153
178
|
export const INSTALL_COMMAND = 'curl -fsSL https://get.quox.ai | sh';
|
|
@@ -156,7 +181,7 @@ export const INSTALL_KEY_GONE = 'Your key was not kept: it only ever lived in me
|
|
|
156
181
|
export const installWhere = (dir) => `Installing into ${dir}`;
|
|
157
182
|
export const installScriptLead = (url, bytes) => `Installer ${url} (${Math.max(1, Math.round(bytes / 1024))} KB), SHA256`;
|
|
158
183
|
export const installScript = (url, sha, bytes) => `${installScriptLead(url, bytes)} ${sha}`;
|
|
159
|
-
export const INSTALL_STOP_ASK = 'Stop the install? You can pick it up next time.
|
|
184
|
+
export const INSTALL_STOP_ASK = askText('Stop the install?', { reason: 'You can pick it up next time.', keys: 'y/N' });
|
|
160
185
|
export const INSTALL_STOP_KEEP = 'Carrying on.';
|
|
161
186
|
export const INSTALL_STOPPED = 'Install stopped. Run quox to pick up where you left off.';
|
|
162
187
|
export const INSTALL_EARLIER = 'done earlier';
|
|
@@ -184,6 +209,7 @@ export const FIN = {
|
|
|
184
209
|
name: 'AI KEY', running: (label) => `testing your ${label}, then storing it in your vault`,
|
|
185
210
|
skipped: 'no key to store', ollama: 'Ollama: add it to your vault with quox vault create --type ollama'
|
|
186
211
|
},
|
|
212
|
+
consent: { name: 'CONSENT', running: (who) => `recording your OK for ${who}`, ok: (who) => `OK recorded for ${who}`, fail: (e, provider) => `not recorded (${e}), later: /consent grant ${provider}` },
|
|
187
213
|
link: { name: 'DASHBOARD', running: 'making your one-time sign-in link', ok: 'one-time link ready (10 minutes, single use)', fail: (e) => `no link (${e}), sign in with your owner account instead` }
|
|
188
214
|
};
|
|
189
215
|
export const KEY_GONE_AFTER = 'The key is gone from this terminal\'s memory now.';
|
|
@@ -214,13 +240,14 @@ TIPS.offscript = TIPS.explore;
|
|
|
214
240
|
export const FINALE_TIPS = TIPS.explore; // the dry run and any intent without its own set
|
|
215
241
|
// K2: one gentle, honest next connection that fits what they said they wanted.
|
|
216
242
|
export const K2 = {
|
|
217
|
-
servers: 'You said you want to watch your servers. None are connected yet
|
|
243
|
+
servers: 'You said you want to watch your servers. None are connected yet. Add your first with /hosts add (it needs no AI), or on the dashboard Fleet page.',
|
|
218
244
|
automate: 'You said you want to automate something. Already use n8n? Bring a workflow across with: quox workflow import-n8n --file <path>',
|
|
219
245
|
code: 'You said you want to code with agents. Connect GitHub so they can see your repos: quox vault create --type github',
|
|
220
246
|
email: 'You said you want help with email. Connect your mailbox: quox mail connect --provider gmail (or outlook, icloud)',
|
|
221
247
|
explore: 'Want your agents to do more? /connect shows what is connected and what you can add.'
|
|
222
248
|
};
|
|
223
249
|
K2.offscript = K2.explore;
|
|
250
|
+
export const K2_SERVERS_NO_INSTALLER = 'You said you want to watch your servers. None are connected yet, and this install cannot enrol hosts yet because its host installer is not available.';
|
|
224
251
|
export const k2NoAi = (provider) => `Your agents have no AI yet, so they cannot answer for themselves. Connect one now? /connect ${provider}`;
|
|
225
252
|
export const FINALE_EXIT = 'Enter to start talking to your Quox';
|
|
226
253
|
export const FINALE_EXIT_DRY = 'Enter to finish';
|
|
@@ -233,3 +260,4 @@ export const HELP = [
|
|
|
233
260
|
export const SLASH_UNKNOWN = 'Only /connect server, /help and /exit work until your Quox is running.';
|
|
234
261
|
export const BYE = 'See you soon. Run quox to pick up where you left off.';
|
|
235
262
|
export const NO_SERVER_PRINT = 'Run quox (without --print) to get started.';
|
|
263
|
+
export const FINALE_HOSTS_NO_AI = 'No AI needed to add a host: /hosts add shows the one command to run on that machine.';
|
|
@@ -15,18 +15,31 @@ export const KEY_PROVIDERS = new Set(['openai', 'anthropic']);
|
|
|
15
15
|
export const CHATGPT_ENABLED = false;
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
|
-
* Options in display order.
|
|
19
|
-
*
|
|
18
|
+
* Options in display order. #1288: the choices that work come first. Sign in with ChatGPT is not built
|
|
19
|
+
* yet (waiting on OpenAI), so while it is off it is listed LAST as "coming soon", never first; once it
|
|
20
|
+
* is on it leads (OpenAI-first). Ollama is always offered (B3): "on this machine" first when
|
|
21
|
+
* localhost:11434 answered, else "on another machine" (asks for its LAN URL) before Skip.
|
|
20
22
|
*/
|
|
21
23
|
export function credentialOptions({ ollama = false, chatgpt = CHATGPT_ENABLED } = {}) {
|
|
22
|
-
|
|
23
|
-
const order =
|
|
24
|
+
const working = ollama ? ['ollama', 'openai', 'anthropic', 'skip'] : ['openai', 'anthropic', 'ollama', 'skip'];
|
|
25
|
+
const order = chatgpt ? ['chatgpt', ...working] : [...working, 'chatgpt'];
|
|
24
26
|
return order.map(id => {
|
|
25
27
|
if (id === 'ollama') return { id, ...(ollama ? CRED_OPTIONS.ollama : CRED_OPTIONS.ollamaLan), detected: Boolean(ollama), disabled: false };
|
|
26
28
|
return { id, ...CRED_OPTIONS[id], disabled: id === 'chatgpt' && !chatgpt };
|
|
27
29
|
});
|
|
28
30
|
}
|
|
29
31
|
|
|
32
|
+
/** The next selectable row from `sel` in direction dir (1 down, -1 up), wrapping; disabled rows are skipped. */
|
|
33
|
+
export function nextSelectable(options, sel, dir) {
|
|
34
|
+
const n = options.length;
|
|
35
|
+
if (!n) return 0;
|
|
36
|
+
for (let k = 1; k <= n; k++) {
|
|
37
|
+
const i = (((sel + dir * k) % n) + n) % n;
|
|
38
|
+
if (!options[i].disabled) return i;
|
|
39
|
+
}
|
|
40
|
+
return sel;
|
|
41
|
+
}
|
|
42
|
+
|
|
30
43
|
/** A typed Ollama URL -> normalised http(s) URL (bare host gets http:// and :11434) | null. */
|
|
31
44
|
export function normaliseOllamaUrl(text) {
|
|
32
45
|
let v = String(text || '').trim();
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FIRSTRUN (2026-10-19): the guide OFFERS to fix Docker instead of saying "install Docker, then press r".
|
|
3
|
+
*
|
|
4
|
+
* install Docker missing (Linux): Docker's official script, then add this user to the docker group
|
|
5
|
+
* group Docker installed, this user not in the docker group: add them
|
|
6
|
+
* relaunch in the group, but this terminal started before that: nothing to run with sudo
|
|
7
|
+
*
|
|
8
|
+
* A yes leaves the Ink view (the journey is saved at preflight), runs the command with the real terminal
|
|
9
|
+
* attached so sudo can ask for a password, then starts quox again under `sg docker`, so the new group applies
|
|
10
|
+
* without logging out. The relaunched quox picks up where the guide left off.
|
|
11
|
+
*/
|
|
12
|
+
import { spawnSync } from 'node:child_process';
|
|
13
|
+
import { askText } from '../ask.js';
|
|
14
|
+
import * as T from './copy.js';
|
|
15
|
+
|
|
16
|
+
export const DOCKER_INSTALL_CMD = 'curl -fsSL https://get.docker.com | sudo sh && sudo usermod -aG docker "$USER"';
|
|
17
|
+
export const DOCKER_GROUP_CMD = 'sudo usermod -aG docker "$USER"';
|
|
18
|
+
const COMMANDS = { install: DOCKER_INSTALL_CMD, group: DOCKER_GROUP_CMD, relaunch: null };
|
|
19
|
+
|
|
20
|
+
/** rows -> { kind, command, ask } for the first row the guide can fix, or null. */
|
|
21
|
+
export function dockerOffer(rows) {
|
|
22
|
+
const r = (rows || []).find(x => x?.status === 'fail' && x.offer && x.offer in COMMANDS);
|
|
23
|
+
if (!r) return null;
|
|
24
|
+
const kind = r.offer;
|
|
25
|
+
const command = COMMANDS[kind];
|
|
26
|
+
const q = T.DOCKER_OFFER_QUESTION[kind];
|
|
27
|
+
const action = command ? `run: ${command} then start quox again with the docker group` : 'start quox again with the docker group (sg docker), no sudo';
|
|
28
|
+
return { kind, command, ask: askText(q, { keys: 'y/N', who: 'Quox', action }) };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const shq = (s) => `'${String(s).replace(/'/g, `'\\''`)}'`;
|
|
32
|
+
|
|
33
|
+
/** Runs the offer with the terminal attached. -> { action: 'exit', status } */
|
|
34
|
+
export function runDockerOffer(kind, { spawn = spawnSync, out = (s) => process.stdout.write(s), node = process.execPath, bin } = {}) {
|
|
35
|
+
const command = COMMANDS[kind];
|
|
36
|
+
if (command) {
|
|
37
|
+
out(`\n${T.DOCKER_OFFER_RUNNING}\n ${command}\n\n`);
|
|
38
|
+
const r = spawn('sh', ['-c', command], { stdio: 'inherit' });
|
|
39
|
+
if (r?.status !== 0) { out(`\n${T.DOCKER_OFFER_FAILED}\n`); return { action: 'exit', status: r?.status ?? 1 }; }
|
|
40
|
+
}
|
|
41
|
+
out(`\n${T.DOCKER_OFFER_RELAUNCH}\n`);
|
|
42
|
+
const again = spawn('sg', ['docker', '-c', `exec ${shq(node)} ${shq(bin)}`], { stdio: 'inherit' });
|
|
43
|
+
if (again?.error) { out(`${T.CHECK.docker.permStaleFix}\n`); return { action: 'exit', status: 1 }; }
|
|
44
|
+
return { action: 'exit', status: again?.status ?? 0 };
|
|
45
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FIRSTRUN (2026-10-19): a failed install says WHY in one plain line, for the causes a stranger can act on.
|
|
3
|
+
* Input: installer output plus the tail of the build log setup.sh names. Output: { kind, host?, message } | null.
|
|
4
|
+
* Prebuilt images being unavailable ("error from registry: denied") is never a cause: the installer builds instead.
|
|
5
|
+
*/
|
|
6
|
+
const RETRY = 'then press r to try again.';
|
|
7
|
+
|
|
8
|
+
const NETWORK = [
|
|
9
|
+
/Could not connect to ([a-z0-9.-]+\.[a-z]{2,}):\d+/i,
|
|
10
|
+
/Temporary failure resolving '([^']+)'/i,
|
|
11
|
+
/Failed to fetch https?:\/\/([a-z0-9.-]+\.[a-z]{2,})\/\S*\s+(?:Could not connect|Unable to connect|Connection failed|Temporary failure)/i,
|
|
12
|
+
/request to https?:\/\/([a-z0-9.-]+\.[a-z]{2,})\/\S* failed, reason: (?:connect )?(?:ETIMEDOUT|ECONNRESET|ECONNREFUSED|EAI_AGAIN|ENOTFOUND)/i,
|
|
13
|
+
/getaddrinfo (?:EAI_AGAIN|ENOTFOUND) ([a-z0-9.-]+\.[a-z]{2,})/i
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
export function explainInstallFailure(text) {
|
|
17
|
+
const t = String(text ?? '');
|
|
18
|
+
if (!t.trim()) return null;
|
|
19
|
+
for (const re of NETWORK) {
|
|
20
|
+
const m = re.exec(t);
|
|
21
|
+
if (m) {
|
|
22
|
+
const host = m[1].toLowerCase();
|
|
23
|
+
return { kind: 'network', host, message: `The build could not reach ${host}, a package server. Check this machine's internet connection, firewall or proxy, ${RETRY}` };
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
if (/no space left on device/i.test(t)) {
|
|
27
|
+
return { kind: 'disk', message: `The disk filled up during the build. Free some space (docker system prune -f frees old build cache), ${RETRY}` };
|
|
28
|
+
}
|
|
29
|
+
if (/heap out of memory|exit code: 137\b|\bOOMKilled\b|Cannot allocate memory/i.test(t)) {
|
|
30
|
+
return { kind: 'memory', message: `The build ran out of memory. Close other programs or give this machine more RAM (8 GB is recommended), ${RETRY}` };
|
|
31
|
+
}
|
|
32
|
+
return null;
|
|
33
|
+
}
|