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
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* /fleet: computers, agents, missions and SubFleets in one compact block, with health. Read-only.
|
|
3
|
+
* Sources (the same routes `quox fleet ...` and `quox objective list` use): GET /api/v1/agents
|
|
4
|
+
* (heartbeats), GET /api/v1/fleet/hosts (Bastion registry), org agents, GET /api/objectives. A source
|
|
5
|
+
* that could not be read is named on the "Can't see" line, never counted as zero. SubFleets have an
|
|
6
|
+
* RFC but no API yet, so that row says so. Moving work is not here: the hint points at plain words.
|
|
7
|
+
*/
|
|
8
|
+
import { seg, fit, fitRow, ago, plural, boxWidth } from './model.js';
|
|
9
|
+
import { fromObjective } from '../roster.js';
|
|
10
|
+
|
|
11
|
+
const OFFLINE = /^(dead|offline|down|disconnected|unreachable)$/;
|
|
12
|
+
const HEALTHY = /^(healthy|ok|good)$/;
|
|
13
|
+
const DEGRADED = /^(warning|warn|degraded|stale)$/;
|
|
14
|
+
const UNHEALTHY = /^(critical|unhealthy|error|failed|fail)$/;
|
|
15
|
+
const FLEET_OVERVIEW_WORDS = new Set(['health', 'status', 'list', 'all', 'overview', 'summary', 'hosts', 'agents', 'quoxagents']);
|
|
16
|
+
|
|
17
|
+
export const ORDER = ['healthy', 'degraded', 'unhealthy', 'offline', 'unknown'];
|
|
18
|
+
const SEVERITY = { unhealthy: 0, offline: 1, degraded: 2, unknown: 3, healthy: 4 };
|
|
19
|
+
|
|
20
|
+
/** A heartbeat row or a Bastion host row -> healthy | degraded | unhealthy | offline | unknown. */
|
|
21
|
+
export function healthOf(row) {
|
|
22
|
+
const s = row?.status;
|
|
23
|
+
const st = String(typeof s === 'object' && s ? s.state ?? '' : s ?? '').toLowerCase();
|
|
24
|
+
const h = String(typeof s === 'object' && s ? s.health ?? '' : '').toLowerCase();
|
|
25
|
+
if (OFFLINE.test(st) || OFFLINE.test(h)) return 'offline';
|
|
26
|
+
if (UNHEALTHY.test(h) || UNHEALTHY.test(st)) return 'unhealthy';
|
|
27
|
+
if (DEGRADED.test(h) || DEGRADED.test(st)) return 'degraded';
|
|
28
|
+
if (HEALTHY.test(h) || HEALTHY.test(st)) return 'healthy';
|
|
29
|
+
return 'unknown';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const listOk = (s) => (s?.ok ? s.list || [] : null);
|
|
33
|
+
|
|
34
|
+
/** d: { agents, hosts, orgAgents, missions } each { ok, list } | { ok:false, error } -> merged view model. */
|
|
35
|
+
export function collectFleet(d, now = Date.now()) {
|
|
36
|
+
const byId = new Map();
|
|
37
|
+
const cant = [];
|
|
38
|
+
const sources = [['heartbeats', d.agents], ['hosts', d.hosts]];
|
|
39
|
+
for (const [label, src] of sources) {
|
|
40
|
+
if (!src) continue;
|
|
41
|
+
if (!src.ok) { cant.push(`${label} (${src.error || 'failed'})`); continue; }
|
|
42
|
+
for (const r of src.list || []) {
|
|
43
|
+
const id = r.host_id || r.id || r.hostname;
|
|
44
|
+
if (!id) continue;
|
|
45
|
+
const cur = byId.get(id) || { id };
|
|
46
|
+
if (label === 'heartbeats') {
|
|
47
|
+
const s = r.status;
|
|
48
|
+
byId.set(id, { ...cur, health: healthOf(r), version: r.version ?? cur.version, lastSeenS: typeof s === 'object' ? s?.last_seen_seconds : undefined, heartbeat: true });
|
|
49
|
+
} else {
|
|
50
|
+
byId.set(id, { ...cur, ip: r.ip ?? cur.ip, group: r.group ?? cur.group, registryHealth: healthOf(r), health: cur.heartbeat ? cur.health : healthOf(r) });
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const computers = [...byId.values()].sort((a, b) => SEVERITY[a.health] - SEVERITY[b.health] || a.id.localeCompare(b.id));
|
|
55
|
+
const counts = Object.fromEntries(ORDER.map(k => [k, 0]));
|
|
56
|
+
for (const c of computers) counts[c.health]++;
|
|
57
|
+
const org = listOk(d.orgAgents);
|
|
58
|
+
if (d.orgAgents && !d.orgAgents.ok) cant.push(`agents (${d.orgAgents.error || 'failed'})`);
|
|
59
|
+
const agents = org ? { active: org.filter(a => !a.status || a.status === 'active').length, other: org.filter(a => a.status && a.status !== 'active').length } : null;
|
|
60
|
+
let missions = null;
|
|
61
|
+
if (d.missions && !d.missions.ok) cant.push(`missions (${d.missions.error || 'failed'})`);
|
|
62
|
+
else if (d.missions) {
|
|
63
|
+
const rows = (d.missions.list || []).map(o => fromObjective(o, now)).filter(Boolean);
|
|
64
|
+
missions = { running: rows.filter(r => r.state === 'running').length, waiting: rows.filter(r => r.state === 'waiting').length, loops: rows.filter(r => r.kind === 'loop' && (r.state === 'running' || r.state === 'waiting')).length, rows: rows.filter(r => r.state === 'running' || r.state === 'waiting') };
|
|
65
|
+
}
|
|
66
|
+
return { computers, counts, agents, missions, cant };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const ROLE = { healthy: 'ok', degraded: 'warn', unhealthy: 'error', offline: 'error', unknown: 'muted' };
|
|
70
|
+
const HINT = 'ask in plain words to move work';
|
|
71
|
+
|
|
72
|
+
/** d: { agents, hosts, orgAgents, missions, now } -> view { box, rows } */
|
|
73
|
+
export function fleetCard(d, cols) {
|
|
74
|
+
const now = d.now ?? Date.now();
|
|
75
|
+
const f = collectFleet(d, now);
|
|
76
|
+
const w = boxWidth(cols, 78), inner = w - 4;
|
|
77
|
+
const L = (label, parts) => [seg(label.padEnd(12), 'muted'), ...parts];
|
|
78
|
+
const rows = [];
|
|
79
|
+
if (f.computers.length || (d.agents?.ok || d.hosts?.ok)) {
|
|
80
|
+
const bits = [];
|
|
81
|
+
for (const k of ORDER) if (f.counts[k]) { if (bits.length) bits.push(seg(' · ', 'muted')); bits.push(seg(`${f.counts[k]} ${k}`, ROLE[k], k !== 'healthy')); }
|
|
82
|
+
rows.push(L('Computers', [seg(`${f.computers.length} `, 'value', true), ...bits]));
|
|
83
|
+
const bad = f.computers.filter(c => c.health !== 'healthy');
|
|
84
|
+
if (bad.length) rows.push([seg('not healthy'.padEnd(12), 'muted'), seg(fit(bad.map(c => `${c.id} ${c.health}`).join(', '), inner - 12), 'warn')]);
|
|
85
|
+
} else rows.push(L('Computers', [seg('?', 'warn'), seg(' not read', 'muted')]));
|
|
86
|
+
rows.push(f.agents ? L('Agents', [seg(`${f.agents.active} active`, 'value'), ...(f.agents.other ? [seg(` · ${f.agents.other} other`, 'muted')] : [])]) : L('Agents', [seg('?', 'warn')]));
|
|
87
|
+
if (f.missions) {
|
|
88
|
+
const m = f.missions;
|
|
89
|
+
const extra = [m.waiting ? plural(m.waiting, 'waiting', 'waiting') : null, m.loops ? plural(m.loops, 'loop') : null].filter(Boolean);
|
|
90
|
+
rows.push(L('Missions', [seg(`${m.running} running`, 'value'), ...(extra.length ? [seg(` · ${extra.join(' · ')}`, 'muted')] : [])]));
|
|
91
|
+
for (const r of m.rows.slice(0, 3)) rows.push([seg(' ', 'muted'), seg(fit(`${r.name}${r.activity ? ` · ${r.activity}` : ''}`, inner - 2), 'muted')]);
|
|
92
|
+
if (m.rows.length > 3) rows.push([seg(` …and ${m.rows.length - 3} more`, 'muted')]);
|
|
93
|
+
} else rows.push(L('Missions', [seg('?', 'warn')]));
|
|
94
|
+
rows.push(L('SubFleets', [seg('none yet', 'muted'), seg(w < 60 ? '' : ' (designed, no API in this Quox)', 'muted')]));
|
|
95
|
+
if (f.cant.length) rows.push([seg("Can't see ", 'warn'), seg(fit(f.cant.join(', '), inner - 12), 'warn')]);
|
|
96
|
+
rows.push([seg(HINT, 'muted')]);
|
|
97
|
+
return { box: { width: w, title: 'Fleet', titleRole: 'sky', border: 'sky' }, rows: rows.map(r => fitRow(r, inner)) };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** d: { host, now } -> view for one computer. */
|
|
101
|
+
export function hostCard(d, cols) {
|
|
102
|
+
const h = d.host;
|
|
103
|
+
const w = boxWidth(cols, 66), inner = w - 4;
|
|
104
|
+
const L = (label, v, role = 'value') => [seg(label.padEnd(8), 'muted'), seg(fit(v, inner - 8), role)];
|
|
105
|
+
const rows = [L('health', h.health, ROLE[h.health])];
|
|
106
|
+
if (h.ip) rows.push(L('ip', h.ip));
|
|
107
|
+
if (h.group) rows.push(L('group', h.group));
|
|
108
|
+
if (h.version) rows.push(L('version', h.version));
|
|
109
|
+
if (typeof h.lastSeenS === 'number') rows.push(L('seen', `${ago(Date.now() - h.lastSeenS * 1000, Date.now()) || '0s'} ago`, 'muted'));
|
|
110
|
+
if (!h.heartbeat) rows.push(L('agent', 'no heartbeat (in the registry only)', 'muted'));
|
|
111
|
+
rows.push([seg(HINT, 'muted')]);
|
|
112
|
+
return { box: { width: w, title: h.id, titleRole: 'sky', right: h.health, rightRole: ROLE[h.health], border: 'sky' }, rows };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** Builder registered as BUILDERS.fleet: one host when d.host is set, else the whole fleet. */
|
|
116
|
+
export function fleetView(d, cols) { return d.host ? hostCard(d, cols) : fleetCard(d, cols); }
|
|
117
|
+
|
|
118
|
+
/** Fetchers for createObjectsApi (kept here so fetch.js only spreads them in). */
|
|
119
|
+
export function fleetFetchers({ get, org, listOf, why, resolveEndpoint }) {
|
|
120
|
+
const read = async (fn) => { try { return { ok: true, list: await fn() }; } catch (e) { return { ok: false, error: why(e) }; } };
|
|
121
|
+
return {
|
|
122
|
+
/** Host ids for /fleet <TAB>: [{ value, desc }]; a failed read suggests nothing. */
|
|
123
|
+
async fleetNames() {
|
|
124
|
+
const hb = resolveEndpoint('v1.agents.list');
|
|
125
|
+
const rows = listOf(await get(hb.service, hb.path, undefined, 6000), 'agents');
|
|
126
|
+
return rows.map(r => r.host_id || r.id).filter(Boolean).map(id => ({ value: String(id), desc: healthOf(rows.find(r => (r.host_id || r.id) === id)) }));
|
|
127
|
+
},
|
|
128
|
+
/** E-fleet: name '' = the whole fleet, else one computer by id (case-insensitive, then prefix). */
|
|
129
|
+
async fleet(name) {
|
|
130
|
+
const q = String(name || '').trim().toLowerCase();
|
|
131
|
+
const hb = resolveEndpoint('v1.agents.list'), hs = resolveEndpoint('v1.fleet.hosts.list'), ob = resolveEndpoint('objectives.list');
|
|
132
|
+
const [agents, hosts, orgAgents, missions] = await Promise.all([
|
|
133
|
+
read(async () => listOf(await get(hb.service, hb.path, undefined, 10000), 'agents')),
|
|
134
|
+
read(async () => listOf(await get(hs.service, hs.path, undefined, 10000), 'hosts')),
|
|
135
|
+
org() ? read(async () => { const ep = resolveEndpoint('agents.list', { orgId: org() }); return listOf(await get(ep.service, ep.path, { includeTools: 'false' }, 10000), 'agents', 'data'); }) : Promise.resolve(null),
|
|
136
|
+
read(async () => listOf(await get(ob.service, ob.path, { limit: 100 }, 10000), 'objectives'))
|
|
137
|
+
]);
|
|
138
|
+
const data = { agents, hosts, orgAgents, missions, now: Date.now() };
|
|
139
|
+
if (!q) return { data };
|
|
140
|
+
const f = collectFleet(data, data.now);
|
|
141
|
+
const host = f.computers.find(c => c.id.toLowerCase() === q) || f.computers.find(c => c.id.toLowerCase().startsWith(q));
|
|
142
|
+
if (host) return { data: { host, now: data.now } };
|
|
143
|
+
// owner 2026-10-15 typed "/fleet health": overview words mean the whole fleet (a real host by that name still wins above)
|
|
144
|
+
if (FLEET_OVERVIEW_WORDS.has(q)) return { data };
|
|
145
|
+
if (!agents.ok && !hosts.ok) return { error: `Couldn't check hosts: ${agents.error}.`, tone: 'warn' }; // a failed list is never "no such host"
|
|
146
|
+
return { error: `No host called "${name}". /fleet lists them.`, tone: 'warn' };
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* /health (and /core): a compact summary of what the Core dashboard's overview (/dashboard) shows.
|
|
3
|
+
* Read-only, no model call, server-side reads only (the same truth the dashboard reads):
|
|
4
|
+
* services GET collector /api/v1/health/aggregate (the dashboard's service ring)
|
|
5
|
+
* computers GET /api/v1/agents heartbeats + GET /api/v1/fleet/hosts (as /fleet)
|
|
6
|
+
* agents org agents (as /fleet)
|
|
7
|
+
* approvals GET auth /api/orgs/:org/inbox/stats (the dashboard's "Attention needed")
|
|
8
|
+
* missions GET tasks /api/objectives (the dashboard's live objectives)
|
|
9
|
+
* failures GET quoxflow /api/executions?status=failed&since=24h
|
|
10
|
+
* licence GET auth /api/setup/licensing/entitlements
|
|
11
|
+
* A section whose read failed says "unavailable: <reason>"; an error body is never shown as zero.
|
|
12
|
+
* /status stays the session panel; /collector-health still goes to /status.
|
|
13
|
+
*/
|
|
14
|
+
import { seg, fit, fitRow, plural, boxWidth } from './model.js';
|
|
15
|
+
import { collectFleet, fleetFetchers } from './fleet.js';
|
|
16
|
+
|
|
17
|
+
const DAY_MS = 24 * 3600 * 1000;
|
|
18
|
+
const SVC_ROLE = { ok: 'ok', degraded: 'warn', down: 'error' };
|
|
19
|
+
const reasons = (...srcs) => [...new Set(srcs.filter(s => s && !s.ok).map(s => s.error || 'failed'))].join(', ');
|
|
20
|
+
|
|
21
|
+
/** d: { aggregate, agents, hosts, orgAgents, missions, inbox, failures, licence, now } -> per-section model ({ error } when unread). */
|
|
22
|
+
export function collectHealth(d) {
|
|
23
|
+
const now = d.now ?? Date.now();
|
|
24
|
+
const out = {};
|
|
25
|
+
|
|
26
|
+
const a = d.aggregate;
|
|
27
|
+
if (!a?.ok) out.services = { error: a?.error || 'not read' };
|
|
28
|
+
else {
|
|
29
|
+
const all = Object.entries(a.data?.services || {});
|
|
30
|
+
// Z2H run-6: an optional service this install does not ship (collector says not_installed) is neither up nor down.
|
|
31
|
+
const entries = all.filter(([, s]) => s?.status !== 'not_installed');
|
|
32
|
+
out.services = {
|
|
33
|
+
up: entries.filter(([, s]) => s?.status === 'ok').length,
|
|
34
|
+
total: entries.length,
|
|
35
|
+
score: typeof a.data?.score === 'number' ? a.data.score : null,
|
|
36
|
+
bad: entries.filter(([, s]) => s?.status !== 'ok').map(([name, s]) => ({ name, status: s?.status || 'unknown' })),
|
|
37
|
+
notInstalled: all.filter(([, s]) => s?.status === 'not_installed').map(([name]) => name),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const f = collectFleet({ agents: d.agents, hosts: d.hosts, orgAgents: d.orgAgents, missions: d.missions }, now);
|
|
42
|
+
if (d.agents?.ok || d.hosts?.ok) {
|
|
43
|
+
out.computers = { healthy: f.counts.healthy, total: f.computers.length, bad: f.computers.filter(c => c.health !== 'healthy').map(c => ({ id: c.id, health: c.health })), partial: reasons(d.agents, d.hosts) || null };
|
|
44
|
+
} else out.computers = { error: reasons(d.agents, d.hosts) || 'not read' };
|
|
45
|
+
|
|
46
|
+
if (!d.orgAgents) out.agents = { error: 'no org selected' };
|
|
47
|
+
else out.agents = f.agents ? { ...f.agents } : { error: d.orgAgents.error || 'failed' };
|
|
48
|
+
|
|
49
|
+
out.missions = f.missions ? { running: f.missions.running, waiting: f.missions.waiting, loops: f.missions.loops } : { error: d.missions?.error || 'not read' };
|
|
50
|
+
|
|
51
|
+
const ib = d.inbox;
|
|
52
|
+
if (!ib?.ok) out.approvals = { error: ib?.error || 'not read' };
|
|
53
|
+
else {
|
|
54
|
+
const s = ib.data || {};
|
|
55
|
+
out.approvals = { approvals: Number(s.pending_approvals) || 0, active: Number(s.total_active) || 0, urgent: Number(s.urgent) || 0, overdue: Number(s.overdue) || 0 };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const fl = d.failures;
|
|
59
|
+
if (!fl?.ok) out.failures = { error: fl?.error || 'not read' };
|
|
60
|
+
else {
|
|
61
|
+
const list = fl.list || [];
|
|
62
|
+
const r = list[0];
|
|
63
|
+
out.failures = { count: typeof fl.total === 'number' ? fl.total : list.length, last: r ? (r.workflowName || r.workflow_name || r.name || r.workflowId || r.workflow_id || r.id || null) : null };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const li = d.licence;
|
|
67
|
+
if (!li?.ok) out.licence = { error: li?.error || 'not read' };
|
|
68
|
+
else {
|
|
69
|
+
const L = li.data || {};
|
|
70
|
+
const leases = Array.isArray(L.licenses) ? L.licenses : [];
|
|
71
|
+
const plugins = Array.isArray(L.entitlements) ? L.entitlements.length : 0;
|
|
72
|
+
const errors = Array.isArray(L.entitlement_errors) ? L.entitlement_errors.length : 0;
|
|
73
|
+
if (!leases.length) out.licence = { core: true, plugins, errors };
|
|
74
|
+
else {
|
|
75
|
+
const plans = [...new Set(leases.flatMap(x => Array.isArray(x.plans) ? x.plans : []))];
|
|
76
|
+
const exp = leases.map(x => Date.parse(x.expires_at)).filter(Number.isFinite).sort((x, y) => x - y)[0];
|
|
77
|
+
out.licence = { core: false, plans, plugins, errors, expiresAt: Number.isFinite(exp) ? exp : null, expired: Number.isFinite(exp) && exp < now };
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return out;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Overall verdict for the title: partial (a section unread) > needs attention > all good. */
|
|
84
|
+
function verdict(h) {
|
|
85
|
+
if (Object.values(h).some(s => s?.error)) return ['partial', 'warn'];
|
|
86
|
+
const attention = h.services.up < h.services.total || h.computers.bad.length || h.failures.count || h.approvals.approvals || h.approvals.urgent || h.approvals.overdue || h.licence.errors || h.licence.expired;
|
|
87
|
+
return attention ? ['needs attention', 'warn'] : ['all good', 'ok'];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const HINT = [seg('/fleet', 'key'), seg(' computers · ', 'label'), seg('/status', 'key'), seg(' this session · ', 'label'), seg('/dashboard', 'key'), seg(' open it', 'label')];
|
|
91
|
+
|
|
92
|
+
/** d -> view { box, rows }. */
|
|
93
|
+
export function healthCard(d, cols) {
|
|
94
|
+
const h = collectHealth(d);
|
|
95
|
+
const w = boxWidth(cols, 78), inner = w - 4;
|
|
96
|
+
const L = (label, parts) => [seg(label.padEnd(11), 'label'), ...parts];
|
|
97
|
+
const na = (s) => [seg('unavailable: ', 'warn'), seg(s.error, 'warn')];
|
|
98
|
+
const sep = () => seg(' · ', 'label');
|
|
99
|
+
const rows = [];
|
|
100
|
+
|
|
101
|
+
const s = h.services;
|
|
102
|
+
if (s.error) rows.push(L('Services', na(s)));
|
|
103
|
+
else {
|
|
104
|
+
const parts = [seg(`${s.up}/${s.total} up`, s.up === s.total ? 'ok' : 'warn', true)];
|
|
105
|
+
for (const b of s.bad) parts.push(sep(), seg(`${b.name} ${b.status}`, SVC_ROLE[b.status] || 'warn'));
|
|
106
|
+
for (const n of s.notInstalled || []) parts.push(sep(), seg(`${n} not installed`, 'value'));
|
|
107
|
+
rows.push(L('Services', parts));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const c = h.computers;
|
|
111
|
+
if (c.error) rows.push(L('Computers', na(c)));
|
|
112
|
+
else if (!c.total) rows.push(L('Computers', [seg('none connected', 'value')]));
|
|
113
|
+
else {
|
|
114
|
+
const parts = [seg(`${c.healthy}/${c.total} healthy`, c.healthy === c.total ? 'ok' : 'warn', true)];
|
|
115
|
+
if (c.bad.length) parts.push(sep(), seg(fit(c.bad.map(x => `${x.id} ${x.health}`).join(', '), inner), c.bad.some(x => x.health === 'offline' || x.health === 'unhealthy') ? 'error' : 'warn'));
|
|
116
|
+
if (c.partial) parts.push(sep(), seg(`partial: ${c.partial}`, 'warn'));
|
|
117
|
+
rows.push(L('Computers', parts));
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const ag = h.agents;
|
|
121
|
+
rows.push(L('Agents', ag.error ? na(ag) : [seg(`${ag.active} active`, 'value', true), ...(ag.other ? [sep(), seg(`${ag.other} other`, 'value')] : [])]));
|
|
122
|
+
|
|
123
|
+
const ap = h.approvals;
|
|
124
|
+
if (ap.error) rows.push(L('Approvals', na(ap)));
|
|
125
|
+
else {
|
|
126
|
+
const parts = [seg(ap.approvals ? `${ap.approvals} open` : 'none open', ap.approvals ? 'warn' : 'ok', true)];
|
|
127
|
+
if (ap.active) parts.push(sep(), seg(`${ap.active} in inbox`, 'value'));
|
|
128
|
+
if (ap.urgent) parts.push(sep(), seg(`${ap.urgent} urgent`, 'error'));
|
|
129
|
+
if (ap.overdue) parts.push(sep(), seg(`${ap.overdue} overdue`, 'error'));
|
|
130
|
+
rows.push(L('Approvals', parts));
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const m = h.missions;
|
|
134
|
+
if (m.error) rows.push(L('Missions', na(m)));
|
|
135
|
+
else {
|
|
136
|
+
const parts = [seg(`${m.running} running`, 'value', true)];
|
|
137
|
+
if (m.waiting) parts.push(sep(), seg(`${m.waiting} waiting`, 'warn'));
|
|
138
|
+
if (m.loops) parts.push(sep(), seg(plural(m.loops, 'loop'), 'value'));
|
|
139
|
+
rows.push(L('Missions', parts));
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const fl = h.failures;
|
|
143
|
+
if (fl.error) rows.push(L('Failures', na(fl)));
|
|
144
|
+
else if (!fl.count) rows.push(L('Failures', [seg('none in 24h', 'ok')]));
|
|
145
|
+
else rows.push(L('Failures', [seg(`${plural(fl.count, 'failed run')} in 24h`, 'error', true), ...(fl.last ? [sep(), seg(`last: ${fl.last}`, 'value')] : [])]));
|
|
146
|
+
|
|
147
|
+
const li = h.licence;
|
|
148
|
+
if (li.error) rows.push(L('Licence', na(li)));
|
|
149
|
+
else {
|
|
150
|
+
const parts = li.core ? [seg('Core (free)', 'value', true)] : [seg(li.plans.length ? li.plans.join(', ') : 'licensed', 'value', true)];
|
|
151
|
+
if (li.plugins) parts.push(sep(), seg(plural(li.plugins, 'plugin'), 'value'));
|
|
152
|
+
if (!li.core && li.expiresAt) parts.push(sep(), seg(li.expired ? `expired ${new Date(li.expiresAt).toISOString().slice(0, 10)}` : `until ${new Date(li.expiresAt).toISOString().slice(0, 10)}`, li.expired ? 'error' : 'value'));
|
|
153
|
+
if (li.errors) parts.push(sep(), seg(plural(li.errors, 'licence error'), 'error'));
|
|
154
|
+
rows.push(L('Licence', parts));
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
rows.push(HINT);
|
|
158
|
+
const [right, rightRole] = verdict(h);
|
|
159
|
+
return { box: { width: w, title: 'Health', titleRole: 'sky', right, rightRole, border: 'sky' }, rows: rows.map(r => fitRow(r, inner)) };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** Fetchers for createObjectsApi. Every read is bounded and every failure carries its reason. */
|
|
163
|
+
export function healthFetchers({ get, org, listOf, why, resolveEndpoint }) {
|
|
164
|
+
const fleet = fleetFetchers({ get, org, listOf, why, resolveEndpoint });
|
|
165
|
+
const read = async (fn) => { try { return { ok: true, ...(await fn()) }; } catch (e) { return { ok: false, error: why(e) }; } };
|
|
166
|
+
const noOrg = { ok: false, error: 'no org selected' };
|
|
167
|
+
return {
|
|
168
|
+
/** -> { data } for the health card (now injectable for tests). */
|
|
169
|
+
async health(now = Date.now()) {
|
|
170
|
+
const ag = resolveEndpoint('v1.health.aggregate.list'), ex = resolveEndpoint('executions.list'), lic = resolveEndpoint('setup.licensing.entitlements.list');
|
|
171
|
+
const o = org();
|
|
172
|
+
const [fl, aggregate, inbox, failures, licence] = await Promise.all([
|
|
173
|
+
fleet.fleet(''),
|
|
174
|
+
read(async () => ({ data: await get(ag.service, ag.path, undefined, 15000) })),
|
|
175
|
+
o ? read(async () => { const ep = resolveEndpoint('inbox.stats.list', { orgId: o }); return { data: await get(ep.service, ep.path, undefined, 8000) }; }) : Promise.resolve(noOrg),
|
|
176
|
+
read(async () => {
|
|
177
|
+
const d = await get(ex.service, ex.path, { status: 'failed', since: new Date(now - DAY_MS).toISOString(), limit: 5 }, 8000);
|
|
178
|
+
const total = d?.pagination?.total;
|
|
179
|
+
return { list: listOf(d, 'data', 'executions'), total: typeof total === 'number' ? total : undefined };
|
|
180
|
+
}),
|
|
181
|
+
read(async () => ({ data: await get(lic.service, lic.path, undefined, 8000) })),
|
|
182
|
+
]);
|
|
183
|
+
return { data: { ...fl.data, aggregate, inbox, failures, licence, now } };
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
}
|
|
@@ -11,12 +11,21 @@ import { agentCard } from './agent.js';
|
|
|
11
11
|
import { memoryFacts } from './memory.js';
|
|
12
12
|
import { rulesLadder } from './ladder.js';
|
|
13
13
|
import { proofChain, reconcileView } from './proof.js';
|
|
14
|
+
import { wrapHang } from './model.js';
|
|
15
|
+
import { awayCard } from './away.js';
|
|
16
|
+
import { sitrepCard } from './sitrep.js';
|
|
17
|
+
import { fleetView } from './fleet.js';
|
|
18
|
+
import { healthCard } from './health.js';
|
|
19
|
+
import { proveView, lastTurnCalls } from './prove.js';
|
|
20
|
+
import { replayView, parseReplayArg } from './replay.js';
|
|
21
|
+
import { mirrorCard } from './mirror.js';
|
|
22
|
+
import { peekCard } from './peek.js';
|
|
14
23
|
|
|
15
|
-
export { skillCard, stepFlow, crystalCard, agentCard, memoryFacts, rulesLadder, proofChain, reconcileView };
|
|
24
|
+
export { skillCard, stepFlow, crystalCard, agentCard, memoryFacts, rulesLadder, proofChain, reconcileView, awayCard, sitrepCard };
|
|
16
25
|
export { createObjectsApi } from './fetch.js';
|
|
17
26
|
export { decodeRow, arrivalSeconds } from './model.js';
|
|
18
27
|
|
|
19
|
-
export const BUILDERS = { skill: skillCard, workflow: stepFlow, crystal: crystalCard, agent: agentCard, memory: memoryFacts, rules: rulesLadder, proof: proofChain, reconcile: reconcileView };
|
|
28
|
+
export const BUILDERS = { skill: skillCard, workflow: stepFlow, crystal: crystalCard, agent: agentCard, memory: memoryFacts, rules: rulesLadder, proof: proofChain, reconcile: reconcileView, away: awayCard, sitrep: sitrepCard, fleet: fleetView, health: healthCard, prove: proveView, replay: replayView, mirror: mirrorCard, peek: peekCard };
|
|
20
29
|
|
|
21
30
|
/** -> view { box?, rows } for an object item at this width (now injected for tests). */
|
|
22
31
|
export function buildObject(obj, cols, now = Date.now()) {
|
|
@@ -46,7 +55,8 @@ export async function runObjectCommand(cmd, arg, ctx) {
|
|
|
46
55
|
const o = ctx.objects;
|
|
47
56
|
const a = String(arg || '').trim();
|
|
48
57
|
// A bare /skills or /agents is a list, not an object: leave it to its own handler.
|
|
49
|
-
if (!['/workflow', '/crystal', '/memory', '/rules', '/proof', '/reconcile'].includes(cmd) && !((cmd === '/skills' || cmd === '/agents') && a)) return null;
|
|
58
|
+
if (!['/workflow', '/crystal', '/memory', '/rules', '/proof', '/reconcile', '/fleet', '/prove', '/replay', '/health', '/core'].includes(cmd) && !((cmd === '/skills' || cmd === '/agents') && a)) return null;
|
|
59
|
+
if (!o?.health && (cmd === '/health' || cmd === '/core')) return null; // no objects api (demo, local): app.js shows /status instead
|
|
50
60
|
if (!o) return cmd === '/rules' || cmd === '/skills' ? null : { note: 'Object views are not available in this mode.', tone: 'warn' };
|
|
51
61
|
const wrap = async (type, fn) => {
|
|
52
62
|
try {
|
|
@@ -63,6 +73,18 @@ export async function runObjectCommand(cmd, arg, ctx) {
|
|
|
63
73
|
case '/workflow': return a ? wrap('workflow', () => o.workflowRun(a)) : { note: USAGE['/workflow'], tone: 'warn' };
|
|
64
74
|
case '/crystal': return a ? wrap('crystal', () => o.crystal(a)) : { note: USAGE['/crystal'], tone: 'warn' };
|
|
65
75
|
case '/memory': return a ? wrap('memory', () => o.memory(a)) : { note: USAGE['/memory'], tone: 'warn' };
|
|
76
|
+
case '/replay': {
|
|
77
|
+
const { ref, page } = parseReplayArg(a);
|
|
78
|
+
if (!ref && !ctx.conversationId && !toolCalls(ctx.items).length) return { note: 'Nothing to replay yet: no turn in this conversation. /replay <run or conversation id> replays an earlier one.' };
|
|
79
|
+
return wrap('replay', () => o.replay({ ref, page, conversationId: ctx.conversationId || null, calls: lastTurnCalls(ctx.items) }));
|
|
80
|
+
}
|
|
81
|
+
case '/prove': {
|
|
82
|
+
const calls = lastTurnCalls(ctx.items);
|
|
83
|
+
if (!calls.length) return { note: 'No tool calls in the last turn: nothing to prove.' };
|
|
84
|
+
return wrap('prove', () => o.prove(calls));
|
|
85
|
+
}
|
|
86
|
+
case '/fleet': return wrap('fleet', () => o.fleet(a));
|
|
87
|
+
case '/health': case '/core': return wrap('health', () => o.health()); // the Core dashboard summary (objects/health.js)
|
|
66
88
|
case '/rules': {
|
|
67
89
|
return wrap('rules', async () => {
|
|
68
90
|
const r = await o.rules();
|
|
@@ -70,10 +92,27 @@ export async function runObjectCommand(cmd, arg, ctx) {
|
|
|
70
92
|
});
|
|
71
93
|
}
|
|
72
94
|
case '/proof': {
|
|
73
|
-
const
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
95
|
+
const all = toolCalls(ctx.items);
|
|
96
|
+
const calls = all.filter(b => b.envelopeId);
|
|
97
|
+
const newest = [...calls].reverse(); // 1 = newest
|
|
98
|
+
const num = /^\d{1,3}$/.test(a) ? Number(a) : 0;
|
|
99
|
+
const pick = (b) => wrap('proof', () => o.proof({ tool: b.tool, status: b.status, envelopeId: b.envelopeId, callId: b.callId }));
|
|
100
|
+
if (num >= 1 && newest[num - 1]) return pick(newest[num - 1]);
|
|
101
|
+
const t = a && !num ? calls.find(b => b.envelopeId === a || b.callId === a) : (calls.length === 1 ? calls[0] : null);
|
|
102
|
+
if (t) return pick(t);
|
|
103
|
+
if (a) return { note: `No tool call "${a}" with an envelope in this session. /proof lists them; /evidence exports the whole conversation.`, tone: 'warn' };
|
|
104
|
+
if (!calls.length) {
|
|
105
|
+
const ran = all.length ? ` ${all.length} tool call${all.length === 1 ? '' : 's'} ran, but the server reported no envelope id for ${all.length === 1 ? 'it' : 'them'}.` : '';
|
|
106
|
+
return { note: `No tool call with an envelope id in this session yet.${ran} /evidence exports what the server filed for this conversation.`, tone: 'warn' };
|
|
107
|
+
}
|
|
108
|
+
// Several calls: list the envelope ids, newest first (ten), then how many more.
|
|
109
|
+
const shown = newest.slice(0, 10);
|
|
110
|
+
const W = Math.max(20, (ctx.cols || 1e9) - 4);
|
|
111
|
+
const lines = [...wrapHang(`${calls.length} tool call${calls.length === 1 ? '' : 's'} with an envelope in this conversation (newest first):`, W, 2),
|
|
112
|
+
...shown.flatMap((b, i) => wrapHang(` ${i + 1}. ${b.envelopeId} ${b.tool || 'tool'} ${b.status || ''}`.trimEnd(), W, 5))];
|
|
113
|
+
if (newest.length > shown.length) lines.push(` ${newest.length - shown.length} more (older)`);
|
|
114
|
+
lines.push(...wrapHang('/proof <number or id> shows one chain. /evidence exports the signed bundle of all of them.', W, 2));
|
|
115
|
+
return { note: lines.join('\n'), lines, tone: 'dim' };
|
|
77
116
|
}
|
|
78
117
|
case '/reconcile': {
|
|
79
118
|
const unk = toolCalls(ctx.items).filter(b => b.status === 'effect_unknown');
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* /mirror: the Mirror, what CommanderQ sees about this org right now. Read from GET /api/v1/mind/mirror,
|
|
3
|
+
* the org-scoped object the collector builds from each owning service and injects into CommanderQ's
|
|
4
|
+
* [SELF-KNOWLEDGE]. Sectioned: identity, health, implants, capabilities, plugins, memory, codebase,
|
|
5
|
+
* then every unknown with its code, service and message. `/mirror agents` lists the agents.
|
|
6
|
+
* Text wraps to width, never clipped. No grey roles: 'value' text, 'sky' headings, 'ok'/'warn' state.
|
|
7
|
+
*/
|
|
8
|
+
import { seg, boxWidth, len } from './model.js';
|
|
9
|
+
|
|
10
|
+
const age = (ms) => {
|
|
11
|
+
if (!Number.isFinite(ms)) return '';
|
|
12
|
+
const s = Math.max(0, Math.floor(ms / 1000));
|
|
13
|
+
if (s < 60) return `${s}s`;
|
|
14
|
+
const m = Math.floor(s / 60);
|
|
15
|
+
if (m < 60) return `${m}m`;
|
|
16
|
+
const h = Math.floor(m / 60);
|
|
17
|
+
return h < 48 ? `${h}h` : `${Math.floor(h / 24)}d`;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/** Word-wrap to n display chars; a word longer than n is hard-split. Nothing is dropped. */
|
|
21
|
+
export function wrapText(text, n) {
|
|
22
|
+
const out = [];
|
|
23
|
+
for (const para of String(text ?? '').split('\n')) {
|
|
24
|
+
let line = '';
|
|
25
|
+
for (let w of para.split(/\s+/).filter(Boolean)) {
|
|
26
|
+
while (len(w) > n) {
|
|
27
|
+
if (line) { out.push(line); line = ''; }
|
|
28
|
+
out.push([...w].slice(0, n).join('')); w = [...w].slice(n).join('');
|
|
29
|
+
}
|
|
30
|
+
if (!line) line = w;
|
|
31
|
+
else if (len(line) + 1 + len(w) <= n) line += ' ' + w;
|
|
32
|
+
else { out.push(line); line = w; }
|
|
33
|
+
}
|
|
34
|
+
out.push(line);
|
|
35
|
+
}
|
|
36
|
+
return out;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const ICON = { healthy: '✔', degraded: '⚠', weak: '⚠', collecting: '◐', 'license-required': '⛔', unreachable: '✖', 'not-reported': '◐' };
|
|
40
|
+
const unk = (u) => `unknown: ${u.code} (${u.service}: ${u.message})`;
|
|
41
|
+
const isUnk = (v) => v && typeof v === 'object' && v.unknown;
|
|
42
|
+
|
|
43
|
+
/** d: GET /api/v1/mind/mirror body (+ view: 'agents'). -> view { box, rows } */
|
|
44
|
+
export function mirrorCard(d, cols) {
|
|
45
|
+
const w = boxWidth(cols, 100), inner = w - 4;
|
|
46
|
+
const rows = [];
|
|
47
|
+
const put = (t, role = 'value', indent = 0) => { const pad = ' '.repeat(indent); for (const l of wrapText(t, inner - indent)) rows.push([seg(pad + l, role)]); };
|
|
48
|
+
const head = (t) => { if (rows.length) rows.push([seg(' ')]); rows.push([seg(t, 'sky', true)]); };
|
|
49
|
+
const m = d?.mirror;
|
|
50
|
+
const a = age(d?.age_ms);
|
|
51
|
+
const right = `${a ? `${a} old · ` : ''}${d?.fresh ? 'fresh' : 'stale'}`;
|
|
52
|
+
const box = (title) => ({ width: w, title, titleRole: 'value', right, rightRole: d?.fresh ? 'ok' : 'warn', border: d?.fresh ? 'indigo' : 'warn' });
|
|
53
|
+
if (!m) {
|
|
54
|
+
put(`The Mirror could not be read: ${d?.error || d?.reason || 'the server returned no mirror'}.`, 'warn'); // RESTORE-TUI: say the server's reason (an unbuilt mirror says why)
|
|
55
|
+
return { box: { ...box('The Mirror'), right: 'not available', rightRole: 'warn', border: 'warn' }, rows };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (d.view === 'agents') {
|
|
59
|
+
const ag = m.capabilities?.agents;
|
|
60
|
+
if (isUnk(ag)) put(`Agents ${unk(ag.unknown)}`, 'warn');
|
|
61
|
+
else {
|
|
62
|
+
put(`${ag.count} agents, ${ag.entitled} entitled for this org.`);
|
|
63
|
+
rows.push([seg(' ')]);
|
|
64
|
+
for (const x of ag.list || []) put(`${x.entitled === false ? '⛔' : '✔'} ${x.name || x.id} (${x.id})${x.role ? ` · ${x.role}` : ''}${x.level != null ? ` · level ${x.level}` : ''}${x.entitled === false ? ' · not entitled' : ''}`, x.entitled === false ? 'warn' : 'value');
|
|
65
|
+
}
|
|
66
|
+
return { box: box('The Mirror · agents'), rows };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
head('Identity');
|
|
70
|
+
put(`${m.identity?.name || 'CommanderQ'}: ${m.identity?.role || ''}. This is what CommanderQ is told about this org on every turn, built ${m.generatedAt || 'now'} from server reads.`);
|
|
71
|
+
|
|
72
|
+
head('Health');
|
|
73
|
+
if (isUnk(m.health)) put(unk(m.health.unknown), 'warn');
|
|
74
|
+
else {
|
|
75
|
+
const h = m.health;
|
|
76
|
+
put(`${h.score}% · ${h.activeServices}/${h.totalServices} services up${h.down?.length ? ` · down: ${h.down.join(', ')}` : ''}${h.degraded?.length ? ` · degraded: ${h.degraded.join(', ')}` : ''}`, h.score >= 90 ? 'ok' : 'warn');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
head('Implants');
|
|
80
|
+
if (isUnk(m.implants)) put(unk(m.implants.unknown), 'warn');
|
|
81
|
+
else {
|
|
82
|
+
const s = m.implantSummary;
|
|
83
|
+
if (s) put(`${s.healthy}/${s.total} healthy (${Object.entries(s.by_status || {}).map(([k, v]) => `${v} ${k}`).join(', ')})`);
|
|
84
|
+
for (const i of m.implants || []) {
|
|
85
|
+
const bad = i.status !== 'healthy';
|
|
86
|
+
put(`${ICON[i.status] || '◐'} ${i.name}${i.tier ? ` [${i.tier}]` : ''} · ${i.status}${typeof i.health === 'number' ? ` ${i.health}` : ''}${i.metric ? ` · ${i.metric}` : ''}`, bad ? 'warn' : 'value', 1);
|
|
87
|
+
if (i.reason && bad) put(`${i.reason.code} (${i.reason.service}: ${i.reason.message})`, 'warn', 4);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
head('Capabilities');
|
|
92
|
+
const c = m.capabilities || {};
|
|
93
|
+
put(isUnk(c.tools) ? `Tools ${unk(c.tools.unknown)}` : `Tools: ${c.tools.total} (${c.tools.platform} platform, ${c.tools.org} installed for this org)`, isUnk(c.tools) ? 'warn' : 'value');
|
|
94
|
+
put(isUnk(c.workflows) ? `Workflows ${unk(c.workflows.unknown)}` : `Workflows: ${c.workflows}`, isUnk(c.workflows) ? 'warn' : 'value');
|
|
95
|
+
put(isUnk(c.agents) ? `Agents ${unk(c.agents.unknown)}` : `Agents: ${c.agents.count} (${c.agents.entitled} entitled) · /mirror agents lists them`, isUnk(c.agents) ? 'warn' : 'value');
|
|
96
|
+
|
|
97
|
+
head('Plugins');
|
|
98
|
+
if (isUnk(m.plugins)) put(unk(m.plugins.unknown), 'warn');
|
|
99
|
+
else put(`${m.plugins.installed} installed, ${m.plugins.enabled} enabled${m.plugins.list?.length ? `: ${m.plugins.list.map((p) => p.id).join(', ')}` : ''}`);
|
|
100
|
+
|
|
101
|
+
head('Memory');
|
|
102
|
+
if (isUnk(m.memory)) put(unk(m.memory.unknown), 'warn');
|
|
103
|
+
else {
|
|
104
|
+
const mm = m.memory;
|
|
105
|
+
put(`${mm.total} memories, ${mm.entities} entities, ${mm.relations} relations, across ${mm.users} users of this org`);
|
|
106
|
+
const types = Object.entries(mm.by_type || {}).sort((x, y) => y[1] - x[1]);
|
|
107
|
+
if (types.length) put(types.map(([k, v]) => `${k} ${v}`).join(' · '), 'value', 1);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
head('Codebase');
|
|
111
|
+
const f = m.codebaseFabric || {};
|
|
112
|
+
if (isUnk(f)) put(unk(f.unknown), 'warn');
|
|
113
|
+
else {
|
|
114
|
+
if (f.repobrain_unknown) put(`RepoBrain ${unk(f.repobrain_unknown)}`, 'warn');
|
|
115
|
+
else {
|
|
116
|
+
put(`RepoBrain: ${f.repos} repos indexed${f.indexedFiles != null ? `, ${f.indexedFiles} files` : ''}`);
|
|
117
|
+
if (f.indexedFiles_reason) put(`files ${f.indexedFiles_reason.code} (${f.indexedFiles_reason.service}: ${f.indexedFiles_reason.message})`, 'warn', 1);
|
|
118
|
+
}
|
|
119
|
+
if (f.projects_unknown) put(`Scans ${unk(f.projects_unknown)}`, 'warn');
|
|
120
|
+
else if (f.projectCount != null) put(`Scans: ${f.projectCount} projects, ${f.totalRoutes} routes, ${f.totalServices} services, invariants ${f.invariantStatus}${f.lastScan ? `, last ${f.lastScan}` : ''}`);
|
|
121
|
+
if (f.lastScan_reason) put(`${f.lastScan_reason.code} (${f.lastScan_reason.service}: ${f.lastScan_reason.message})`, 'warn', 1);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const un = m.unknowns || [];
|
|
125
|
+
head(`Unknown (${un.length})`);
|
|
126
|
+
if (!un.length) put('Every section above was read from its service.', 'ok');
|
|
127
|
+
for (const u of un) put(`${u.section}: ${u.code} (${u.service}: ${u.message})`, 'warn');
|
|
128
|
+
return { box: box('The Mirror'), rows };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** A failed fetch as one plain line: the HTTP status, the error code and the server's own reason. */
|
|
132
|
+
export function mirrorErrorNote(e) {
|
|
133
|
+
const body = e?.body && typeof e.body === 'object' ? e.body : {};
|
|
134
|
+
const reason = body.error || body.message || e?.message || 'failed';
|
|
135
|
+
const code = body.code || (typeof e?.code === 'string' ? e.code : '');
|
|
136
|
+
return `mirror: could not read the Mirror (${e?.status ? `HTTP ${e.status}: ` : ''}${code ? `${code}: ` : ''}${reason}). Nothing is assumed.`;
|
|
137
|
+
}
|
|
@@ -21,6 +21,39 @@ export const fit = (s, n) => {
|
|
|
21
21
|
return g.length > n ? g.slice(0, Math.max(0, n - 1)).join('') + '…' : g.join('');
|
|
22
22
|
};
|
|
23
23
|
|
|
24
|
+
/**
|
|
25
|
+
* Wrap a string to `width` display chars, never clipping: breaks at spaces, indents each continuation by `hang`
|
|
26
|
+
* spaces beyond the line's own leading indent, and hard-splits a token longer than the line (an id stays whole
|
|
27
|
+
* across rows, no ellipsis). -> string[]
|
|
28
|
+
*/
|
|
29
|
+
export function wrapHang(text, width, hang = 4) {
|
|
30
|
+
const src = String(text ?? '');
|
|
31
|
+
const lead = (src.match(/^ */) || [''])[0];
|
|
32
|
+
const words = src.trim().split(/\s+/).filter(Boolean);
|
|
33
|
+
if (!words.length) return [src];
|
|
34
|
+
const w = Math.max(8, width);
|
|
35
|
+
const lines = [];
|
|
36
|
+
let cur = lead;
|
|
37
|
+
let curHas = false;
|
|
38
|
+
const pad = () => lead + ' '.repeat(hang);
|
|
39
|
+
const push = () => { lines.push(cur); cur = pad(); curHas = false; };
|
|
40
|
+
for (let word of words) {
|
|
41
|
+
while (len(cur) + (curHas ? 1 : 0) + len(word) > w) {
|
|
42
|
+
if (curHas) { push(); continue; }
|
|
43
|
+
const room = Math.max(1, w - len(cur));
|
|
44
|
+
const g = [...word];
|
|
45
|
+
if (g.length <= room) break;
|
|
46
|
+
cur += g.slice(0, room).join('');
|
|
47
|
+
word = g.slice(room).join('');
|
|
48
|
+
push();
|
|
49
|
+
}
|
|
50
|
+
cur += (curHas ? ' ' : '') + word;
|
|
51
|
+
curHas = true;
|
|
52
|
+
}
|
|
53
|
+
if (curHas) lines.push(cur);
|
|
54
|
+
return lines;
|
|
55
|
+
}
|
|
56
|
+
|
|
24
57
|
/** Clip a row of segments to n chars total; later segments shrink first, the last one gets the ellipsis. */
|
|
25
58
|
export function fitRow(row, n) {
|
|
26
59
|
const out = [];
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PEEK card: the hit of `/peek <id>` as an inline object (the box layout keeps the label column; a note would
|
|
3
|
+
* re-flow it). Fields come from core/peek.js ROWS, the same list `quox peek` prints. d: a peekId result.
|
|
4
|
+
*/
|
|
5
|
+
import { seg, fit, boxWidth } from './model.js';
|
|
6
|
+
import { KIND_LABEL, ROWS, MORE } from '../../core/peek.js';
|
|
7
|
+
|
|
8
|
+
export function peekCard(d, cols) {
|
|
9
|
+
const w = boxWidth(cols, 84), inner = w - 4;
|
|
10
|
+
const { kind, data } = d.hit || {};
|
|
11
|
+
const rows = [];
|
|
12
|
+
const L = (label, value, role = 'value') => [seg(label.padEnd(10), 'muted'), seg(fit(value, inner - 10), role)];
|
|
13
|
+
for (const [k, v] of (ROWS[kind] ? ROWS[kind](data || {}) : [])) if (v) rows.push(L(k, v));
|
|
14
|
+
if (d.also?.length) rows.push(L('also', `an id of the same value: ${d.also.map(k => KIND_LABEL[k]).join(', ')}`, 'muted'));
|
|
15
|
+
if (MORE[kind]) rows.push(L('more', MORE[kind](d.id), 'muted'));
|
|
16
|
+
return { box: { width: w, title: `◇ ${KIND_LABEL[kind] || 'id'} ${d.id}`, titleRole: 'sky', right: 'read only', rightRole: 'muted', border: 'sky' }, rows };
|
|
17
|
+
}
|