quoxcli 0.0.0-stage → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +35 -0
- package/README.md +144 -2
- package/bin/quox.js +265 -0
- package/bin/quoxon.js +8 -0
- package/lib/api/endpoints/admin.js +151 -0
- package/lib/api/endpoints/aee.js +38 -0
- package/lib/api/endpoints/agent-attach.js +126 -0
- package/lib/api/endpoints/agent-schedules.js +26 -0
- package/lib/api/endpoints/agents.js +63 -0
- package/lib/api/endpoints/assistants.js +62 -0
- package/lib/api/endpoints/audit.js +14 -0
- package/lib/api/endpoints/auth.js +50 -0
- package/lib/api/endpoints/awareness.js +34 -0
- package/lib/api/endpoints/away.js +15 -0
- package/lib/api/endpoints/backup-schedules.js +20 -0
- package/lib/api/endpoints/backups.js +86 -0
- package/lib/api/endpoints/brain.js +140 -0
- package/lib/api/endpoints/budgets.js +62 -0
- package/lib/api/endpoints/calendarq.js +71 -0
- package/lib/api/endpoints/catalog.js +14 -0
- package/lib/api/endpoints/chat.js +61 -0
- package/lib/api/endpoints/consent.js +26 -0
- package/lib/api/endpoints/contactsq.js +68 -0
- package/lib/api/endpoints/conversations.js +46 -0
- package/lib/api/endpoints/custom-roles.js +92 -0
- package/lib/api/endpoints/divisions.js +56 -0
- package/lib/api/endpoints/domains.js +38 -0
- package/lib/api/endpoints/engagement.js +231 -0
- package/lib/api/endpoints/engine.js +11 -0
- package/lib/api/endpoints/entity.js +50 -0
- package/lib/api/endpoints/executions.js +45 -0
- package/lib/api/endpoints/fetchAllowlist.js +27 -0
- package/lib/api/endpoints/file.js +20 -0
- package/lib/api/endpoints/files.js +40 -0
- package/lib/api/endpoints/github.js +21 -0
- package/lib/api/endpoints/governance.js +43 -0
- package/lib/api/endpoints/hat.js +66 -0
- package/lib/api/endpoints/inbox.js +52 -0
- package/lib/api/endpoints/inboxq.js +120 -0
- package/lib/api/endpoints/index.js +242 -0
- package/lib/api/endpoints/invites.js +19 -0
- package/lib/api/endpoints/llm-policy.js +43 -0
- package/lib/api/endpoints/lucid.js +89 -0
- package/lib/api/endpoints/members.js +32 -0
- package/lib/api/endpoints/memory.js +94 -0
- package/lib/api/endpoints/mfa.js +32 -0
- package/lib/api/endpoints/mind.js +13 -0
- package/lib/api/endpoints/mon.js +184 -0
- package/lib/api/endpoints/notesq.js +43 -0
- package/lib/api/endpoints/objectives.js +68 -0
- package/lib/api/endpoints/orgs.js +38 -0
- package/lib/api/endpoints/plugins.js +152 -0
- package/lib/api/endpoints/powers.js +75 -0
- package/lib/api/endpoints/presence.js +33 -0
- package/lib/api/endpoints/prompts.js +69 -0
- package/lib/api/endpoints/qlarity.js +122 -0
- package/lib/api/endpoints/quoxbox.js +55 -0
- package/lib/api/endpoints/quoxchat.js +54 -0
- package/lib/api/endpoints/quoxflow.js +32 -0
- package/lib/api/endpoints/quoxplan.js +121 -0
- package/lib/api/endpoints/quoxseo.js +114 -0
- package/lib/api/endpoints/reach.js +47 -0
- package/lib/api/endpoints/router.js +17 -0
- package/lib/api/endpoints/runs.js +38 -0
- package/lib/api/endpoints/safety.js +53 -0
- package/lib/api/endpoints/scim.js +46 -0
- package/lib/api/endpoints/scopes.js +14 -0
- package/lib/api/endpoints/security.js +69 -0
- package/lib/api/endpoints/setup.js +90 -0
- package/lib/api/endpoints/skills.js +200 -0
- package/lib/api/endpoints/sso.js +67 -0
- package/lib/api/endpoints/stepup.js +20 -0
- package/lib/api/endpoints/tags.js +32 -0
- package/lib/api/endpoints/teams.js +56 -0
- package/lib/api/endpoints/telephony.js +46 -0
- package/lib/api/endpoints/tg.js +56 -0
- package/lib/api/endpoints/tools.js +50 -0
- package/lib/api/endpoints/traces.js +157 -0
- package/lib/api/endpoints/triggers.js +96 -0
- package/lib/api/endpoints/user-library.js +41 -0
- package/lib/api/endpoints/users.js +20 -0
- package/lib/api/endpoints/v1.js +585 -0
- package/lib/api/endpoints/vault.js +192 -0
- package/lib/api/endpoints/voice.js +22 -0
- package/lib/api/endpoints/volt.js +84 -0
- package/lib/api/endpoints/ward.js +47 -0
- package/lib/api/endpoints/webhooks.js +46 -0
- package/lib/api/endpoints/workflows.js +169 -0
- package/lib/api/endpoints/x.js +32 -0
- package/lib/api/endpoints.js +15 -0
- package/lib/commands/actions.js +61 -0
- package/lib/commands/admin.js +375 -0
- package/lib/commands/aee.js +99 -0
- package/lib/commands/agent.js +337 -0
- package/lib/commands/agentAttach.js +1528 -0
- package/lib/commands/approvals.js +119 -0
- package/lib/commands/aside.js +24 -0
- package/lib/commands/ask.js +144 -0
- package/lib/commands/assistant.js +169 -0
- package/lib/commands/auth.js +464 -0
- package/lib/commands/awareness.js +88 -0
- package/lib/commands/backup.js +377 -0
- package/lib/commands/brain.js +1085 -0
- package/lib/commands/budget.js +241 -0
- package/lib/commands/build.js +170 -0
- package/lib/commands/calendar.js +216 -0
- package/lib/commands/capabilities.js +34 -0
- package/lib/commands/chat.js +295 -0
- package/lib/commands/claude.js +380 -0
- package/lib/commands/config-cmd.js +106 -0
- package/lib/commands/consent.js +84 -0
- package/lib/commands/contacts.js +1016 -0
- package/lib/commands/contactsSelftest.js +132 -0
- package/lib/commands/conversation.js +105 -0
- package/lib/commands/dashboard.js +43 -0
- package/lib/commands/department.js +96 -0
- package/lib/commands/dev.js +620 -0
- package/lib/commands/devResident.js +326 -0
- package/lib/commands/devSupervisor.js +1062 -0
- package/lib/commands/discord.js +132 -0
- package/lib/commands/doctor.js +181 -0
- package/lib/commands/domain.js +114 -0
- package/lib/commands/done.js +347 -0
- package/lib/commands/engagement.js +597 -0
- package/lib/commands/entity.js +119 -0
- package/lib/commands/facebook.js +212 -0
- package/lib/commands/fetchAllowlist.js +71 -0
- package/lib/commands/file.js +165 -0
- package/lib/commands/fleet.js +1189 -0
- package/lib/commands/governance.js +647 -0
- package/lib/commands/gripe.js +355 -0
- package/lib/commands/hat.js +374 -0
- package/lib/commands/helpers.js +96 -0
- package/lib/commands/hyperion.js +58 -0
- package/lib/commands/inbox.js +173 -0
- package/lib/commands/instagram.js +264 -0
- package/lib/commands/integration.js +100 -0
- package/lib/commands/inventory.js +115 -0
- package/lib/commands/invites.js +45 -0
- package/lib/commands/licence.js +139 -0
- package/lib/commands/linkedin.js +178 -0
- package/lib/commands/llm-policy.js +117 -0
- package/lib/commands/llm.js +45 -0
- package/lib/commands/lucid.js +597 -0
- package/lib/commands/mail.js +531 -0
- package/lib/commands/memory.js +361 -0
- package/lib/commands/mind.js +71 -0
- package/lib/commands/mon.js +581 -0
- package/lib/commands/monitoring.js +86 -0
- package/lib/commands/motion.js +22 -0
- package/lib/commands/notes.js +1246 -0
- package/lib/commands/notesBatch.js +160 -0
- package/lib/commands/notesBridges.js +56 -0
- package/lib/commands/notesLinks.js +60 -0
- package/lib/commands/notification.js +57 -0
- package/lib/commands/nucleus.js +123 -0
- package/lib/commands/objective.js +240 -0
- package/lib/commands/offload.js +89 -0
- package/lib/commands/onboard.js +329 -0
- package/lib/commands/open.js +56 -0
- package/lib/commands/org.js +276 -0
- package/lib/commands/peek.js +28 -0
- package/lib/commands/plan.js +944 -0
- package/lib/commands/plugin-doctor.js +1091 -0
- package/lib/commands/plugin.js +649 -0
- package/lib/commands/policy.js +78 -0
- package/lib/commands/powers.js +296 -0
- package/lib/commands/presence.js +89 -0
- package/lib/commands/prompt.js +350 -0
- package/lib/commands/provider.js +159 -0
- package/lib/commands/qlarity.js +706 -0
- package/lib/commands/quoxbox.js +270 -0
- package/lib/commands/quoxchat.js +275 -0
- package/lib/commands/quoxseo.js +378 -0
- package/lib/commands/reach.js +159 -0
- package/lib/commands/role.js +286 -0
- package/lib/commands/router.js +77 -0
- package/lib/commands/rows.js +358 -0
- package/lib/commands/run.js +187 -0
- package/lib/commands/safety.js +107 -0
- package/lib/commands/scim.js +88 -0
- package/lib/commands/scope.js +58 -0
- package/lib/commands/security.js +157 -0
- package/lib/commands/server.js +292 -0
- package/lib/commands/service.js +89 -0
- package/lib/commands/settings.js +99 -0
- package/lib/commands/setup.js +569 -0
- package/lib/commands/skillManifest.js +113 -0
- package/lib/commands/skillRightsFormat.js +38 -0
- package/lib/commands/skills.js +687 -0
- package/lib/commands/slot.js +1451 -0
- package/lib/commands/sso.js +229 -0
- package/lib/commands/status.js +137 -0
- package/lib/commands/tag.js +91 -0
- package/lib/commands/team.js +142 -0
- package/lib/commands/tg.js +240 -0
- package/lib/commands/tiktok.js +218 -0
- package/lib/commands/tips.js +23 -0
- package/lib/commands/tool.js +304 -0
- package/lib/commands/trace.js +530 -0
- package/lib/commands/trigger.js +223 -0
- package/lib/commands/update.js +46 -0
- package/lib/commands/vault.js +1447 -0
- package/lib/commands/vm.js +237 -0
- package/lib/commands/voice.js +213 -0
- package/lib/commands/volt.js +570 -0
- package/lib/commands/walkthrough.js +32 -0
- package/lib/commands/watch.js +167 -0
- package/lib/commands/webhook.js +137 -0
- package/lib/commands/welcome.js +14 -0
- package/lib/commands/workflow.js +660 -0
- package/lib/commands/x.js +448 -0
- package/lib/core/actingAs.js +136 -0
- package/lib/core/activeSummary.js +186 -0
- package/lib/core/aee.js +53 -0
- package/lib/core/agentAlias.js +163 -0
- package/lib/core/approvalContext.js +61 -0
- package/lib/core/aside.js +54 -0
- package/lib/core/askDecision.js +60 -0
- package/lib/core/auth.js +501 -0
- package/lib/core/banner.js +143 -0
- package/lib/core/brainPrime.js +90 -0
- package/lib/core/callerSource.js +129 -0
- package/lib/core/claudeLs.js +94 -0
- package/lib/core/cliBuild.js +62 -0
- package/lib/core/client.js +693 -0
- package/lib/core/commandRegistrars.js +121 -0
- package/lib/core/completions.js +163 -0
- package/lib/core/config.js +669 -0
- package/lib/core/confirm.js +37 -0
- package/lib/core/customHats.js +168 -0
- package/lib/core/deeplinks.js +76 -0
- package/lib/core/doneEngineFetch.js +227 -0
- package/lib/core/errors.js +441 -0
- package/lib/core/global-flags.js +190 -0
- package/lib/core/governedSession.js +402 -0
- package/lib/core/hyperion.js +102 -0
- package/lib/core/inboxResolution.js +65 -0
- package/lib/core/lazyCommands.js +122 -0
- package/lib/core/liveSessionsReport.js +112 -0
- package/lib/core/offload.js +504 -0
- package/lib/core/org-override.js +52 -0
- package/lib/core/orgResolve.js +70 -0
- package/lib/core/output.js +239 -0
- package/lib/core/peek.js +126 -0
- package/lib/core/prodEnv.js +7 -0
- package/lib/core/profileUsers.js +51 -0
- package/lib/core/promptCancel.js +18 -0
- package/lib/core/quoxglyph.js +158 -0
- package/lib/core/repoPath.js +11 -0
- package/lib/core/residentAlarm.js +282 -0
- package/lib/core/residentAuth.js +243 -0
- package/lib/core/residentPump.js +229 -0
- package/lib/core/residentSeed.js +106 -0
- package/lib/core/rootOptions.js +39 -0
- package/lib/core/router.js +168 -0
- package/lib/core/selfUpdate.js +188 -0
- package/lib/core/sessionHeartbeat.js +192 -0
- package/lib/core/sessionsOverview.js +224 -0
- package/lib/core/slotKeeper.js +527 -0
- package/lib/core/slotWake.js +120 -0
- package/lib/core/supervisorEngine.js +933 -0
- package/lib/core/systemdInstaller.js +256 -0
- package/lib/core/version.js +87 -0
- package/lib/core/vm.js +361 -0
- package/lib/core/vmTui.js +86 -0
- package/lib/core/walkthrough.js +35 -0
- package/lib/hyperion/deck.js +29 -0
- package/lib/lucid/LucidCard.jsx +68 -0
- package/lib/lucid/LucidIntro.jsx +92 -0
- package/lib/lucid/blind.js +71 -0
- package/lib/lucid/cards.js +86 -0
- package/lib/lucid/deck.js +32 -0
- package/lib/lucid/fmt.js +139 -0
- package/lib/lucid/markdown.js +81 -0
- package/lib/lucid/motion.js +56 -0
- package/lib/lucid/sitrep.js +173 -0
- package/lib/plugins/loader.js +93 -0
- package/lib/quoxon/activity.js +116 -0
- package/lib/quoxon/aocl.js +117 -0
- package/lib/quoxon/api.js +952 -0
- package/lib/quoxon/app.js +4032 -0
- package/lib/quoxon/approvalFacts.js +94 -0
- package/lib/quoxon/approvalsView.js +325 -0
- package/lib/quoxon/asideView.js +40 -0
- package/lib/quoxon/ask.js +121 -0
- package/lib/quoxon/away.js +52 -0
- package/lib/quoxon/bang.js +224 -0
- package/lib/quoxon/bulkRisk.js +17 -0
- package/lib/quoxon/capabilities.js +82 -0
- package/lib/quoxon/cells.js +53 -0
- package/lib/quoxon/commandShort.js +112 -0
- package/lib/quoxon/commands.js +479 -0
- package/lib/quoxon/connect.js +654 -0
- package/lib/quoxon/connectCard.js +45 -0
- package/lib/quoxon/consent.js +86 -0
- package/lib/quoxon/context.js +68 -0
- package/lib/quoxon/copy.js +34 -0
- package/lib/quoxon/countdownLine.js +11 -0
- package/lib/quoxon/demo.autoplay.js +29 -0
- package/lib/quoxon/demo.fixtures.js +24 -0
- package/lib/quoxon/demo.js +76 -0
- package/lib/quoxon/devObserver.js +20 -0
- package/lib/quoxon/dividerAocl.js +191 -0
- package/lib/quoxon/dividerRule.js +57 -0
- package/lib/quoxon/dragonModes.js +150 -0
- package/lib/quoxon/dragonView.js +41 -0
- package/lib/quoxon/earlyInput.js +40 -0
- package/lib/quoxon/echo.js +15 -0
- package/lib/quoxon/egg.js +36 -0
- package/lib/quoxon/events.js +677 -0
- package/lib/quoxon/feedback.js +50 -0
- package/lib/quoxon/gallery.js +237 -0
- package/lib/quoxon/handoffOffer.js +78 -0
- package/lib/quoxon/hatSwitch.js +37 -0
- package/lib/quoxon/headless.js +161 -0
- package/lib/quoxon/hostUrls.js +41 -0
- package/lib/quoxon/indicator.js +197 -0
- package/lib/quoxon/intent.js +52 -0
- package/lib/quoxon/keys.js +164 -0
- package/lib/quoxon/launcher.js +103 -0
- package/lib/quoxon/main.js +176 -0
- package/lib/quoxon/markdown.js +341 -0
- package/lib/quoxon/markdownSettle.js +19 -0
- package/lib/quoxon/matrixDomains.json +46 -0
- package/lib/quoxon/mentions.js +90 -0
- package/lib/quoxon/mind.js +103 -0
- package/lib/quoxon/mode.js +46 -0
- package/lib/quoxon/model.js +311 -0
- package/lib/quoxon/modelPrefs.js +345 -0
- package/lib/quoxon/motion/clock.js +70 -0
- package/lib/quoxon/motion/color.js +35 -0
- package/lib/quoxon/motion/frame.js +14 -0
- package/lib/quoxon/motion/level.js +91 -0
- package/lib/quoxon/motion/reveal.js +54 -0
- package/lib/quoxon/motion/ring.js +79 -0
- package/lib/quoxon/mouse.js +211 -0
- package/lib/quoxon/noai.js +57 -0
- package/lib/quoxon/objects/agent.js +61 -0
- package/lib/quoxon/objects/away.js +72 -0
- package/lib/quoxon/objects/crystal.js +100 -0
- package/lib/quoxon/objects/fetch.js +182 -0
- package/lib/quoxon/objects/fleet.js +149 -0
- package/lib/quoxon/objects/health.js +186 -0
- package/lib/quoxon/objects/index.js +106 -0
- package/lib/quoxon/objects/ladder.js +71 -0
- package/lib/quoxon/objects/memory.js +44 -0
- package/lib/quoxon/objects/mirror.js +137 -0
- package/lib/quoxon/objects/model.js +118 -0
- package/lib/quoxon/objects/peek.js +17 -0
- package/lib/quoxon/objects/proof.js +110 -0
- package/lib/quoxon/objects/prove.js +130 -0
- package/lib/quoxon/objects/replay.js +125 -0
- package/lib/quoxon/objects/sitrep.js +58 -0
- package/lib/quoxon/objects/skill.js +105 -0
- package/lib/quoxon/objects/view.js +87 -0
- package/lib/quoxon/objects/workflow.js +119 -0
- package/lib/quoxon/observers.js +42 -0
- package/lib/quoxon/onboarding/copy.js +263 -0
- package/lib/quoxon/onboarding/credential.js +108 -0
- package/lib/quoxon/onboarding/detect.js +22 -0
- package/lib/quoxon/onboarding/dockerOffer.js +45 -0
- package/lib/quoxon/onboarding/failureCause.js +33 -0
- package/lib/quoxon/onboarding/finish.js +134 -0
- package/lib/quoxon/onboarding/index.js +94 -0
- package/lib/quoxon/onboarding/installRunner.js +284 -0
- package/lib/quoxon/onboarding/intent.js +53 -0
- package/lib/quoxon/onboarding/journey.js +73 -0
- package/lib/quoxon/onboarding/preflight.js +156 -0
- package/lib/quoxon/onboarding/probe.js +65 -0
- package/lib/quoxon/onboarding/runner.js +58 -0
- package/lib/quoxon/onboarding/view.js +682 -0
- package/lib/quoxon/orgLabel.js +10 -0
- package/lib/quoxon/orgSwitch.js +33 -0
- package/lib/quoxon/ownBroadcast.js +22 -0
- package/lib/quoxon/palette.js +43 -0
- package/lib/quoxon/print.js +98 -0
- package/lib/quoxon/profileSwitch.js +53 -0
- package/lib/quoxon/promptHistory.js +41 -0
- package/lib/quoxon/providers.js +48 -0
- package/lib/quoxon/qart.js +348 -0
- package/lib/quoxon/qdragon.js +410 -0
- package/lib/quoxon/qdragonFly.js +61 -0
- package/lib/quoxon/qdragonScenes.js +282 -0
- package/lib/quoxon/qeyes.js +213 -0
- package/lib/quoxon/qglyph.js +143 -0
- package/lib/quoxon/qhello.js +58 -0
- package/lib/quoxon/qmotion.js +132 -0
- package/lib/quoxon/render.js +1167 -0
- package/lib/quoxon/resend.js +72 -0
- package/lib/quoxon/roster.js +307 -0
- package/lib/quoxon/route.js +39 -0
- package/lib/quoxon/rows.js +711 -0
- package/lib/quoxon/rowsStore.js +80 -0
- package/lib/quoxon/rules.js +130 -0
- package/lib/quoxon/screenContext.js +150 -0
- package/lib/quoxon/screenFrames.js +254 -0
- package/lib/quoxon/session.js +238 -0
- package/lib/quoxon/sessionDead.js +17 -0
- package/lib/quoxon/signin.js +125 -0
- package/lib/quoxon/skills.js +30 -0
- package/lib/quoxon/slash/agents.js +112 -0
- package/lib/quoxon/slash/argSources.js +65 -0
- package/lib/quoxon/slash/args.js +65 -0
- package/lib/quoxon/slash/automate.js +89 -0
- package/lib/quoxon/slash/broadcast.js +140 -0
- package/lib/quoxon/slash/broadcastRun.js +108 -0
- package/lib/quoxon/slash/card.js +30 -0
- package/lib/quoxon/slash/challenge.js +114 -0
- package/lib/quoxon/slash/cliCommand.js +53 -0
- package/lib/quoxon/slash/cliLeaves.json +948 -0
- package/lib/quoxon/slash/compact.js +90 -0
- package/lib/quoxon/slash/confirm.js +30 -0
- package/lib/quoxon/slash/crystallize.js +69 -0
- package/lib/quoxon/slash/dashboard.js +136 -0
- package/lib/quoxon/slash/draft.js +54 -0
- package/lib/quoxon/slash/hatWalk.js +206 -0
- package/lib/quoxon/slash/inverses.js +77 -0
- package/lib/quoxon/slash/killer.js +24 -0
- package/lib/quoxon/slash/killerApi.js +16 -0
- package/lib/quoxon/slash/ledger.js +88 -0
- package/lib/quoxon/slash/listView.js +30 -0
- package/lib/quoxon/slash/modelCmd.js +261 -0
- package/lib/quoxon/slash/palette.js +118 -0
- package/lib/quoxon/slash/paletteController.js +169 -0
- package/lib/quoxon/slash/panels.js +116 -0
- package/lib/quoxon/slash/picker.js +56 -0
- package/lib/quoxon/slash/restart.js +202 -0
- package/lib/quoxon/slash/resume.js +59 -0
- package/lib/quoxon/slash/rowCmd.js +157 -0
- package/lib/quoxon/slash/rowWalk.js +175 -0
- package/lib/quoxon/slash/settings.js +60 -0
- package/lib/quoxon/slash/spawn.js +177 -0
- package/lib/quoxon/slash/stepUp.js +274 -0
- package/lib/quoxon/slash/stepUpCard.js +60 -0
- package/lib/quoxon/slash/stepUpCmds.js +78 -0
- package/lib/quoxon/slash/undoApi.js +38 -0
- package/lib/quoxon/slash/undoCmd.js +49 -0
- package/lib/quoxon/slash/undoWish.js +15 -0
- package/lib/quoxon/slash/watch.js +164 -0
- package/lib/quoxon/slash/wish.js +23 -0
- package/lib/quoxon/slash/wishCmd.js +32 -0
- package/lib/quoxon/slotChat.js +300 -0
- package/lib/quoxon/slotFormat.js +195 -0
- package/lib/quoxon/slotRun.js +131 -0
- package/lib/quoxon/sound/index.js +186 -0
- package/lib/quoxon/sound/mapping.js +120 -0
- package/lib/quoxon/sound/output.js +52 -0
- package/lib/quoxon/sound/synth.js +122 -0
- package/lib/quoxon/stray.js +22 -0
- package/lib/quoxon/suggestCommand.js +59 -0
- package/lib/quoxon/term.js +141 -0
- package/lib/quoxon/textBuffer.js +416 -0
- package/lib/quoxon/theme.js +286 -0
- package/lib/quoxon/tips.js +433 -0
- package/lib/quoxon/toolError.js +41 -0
- package/lib/quoxon/transcript.js +188 -0
- package/lib/quoxon/web/qface.js +89 -0
- package/lib/quoxon/welcome.js +52 -0
- package/lib/quoxon/whatsnew.js +136 -0
- package/lib/quoxon/whatsnew.jsonl +89 -0
- package/lib/quoxon/windowOrg.js +113 -0
- package/lib/server/diag.js +129 -0
- package/lib/server/doctor.js +71 -0
- package/lib/server/install.js +149 -0
- package/lib/server/pluginEnable.js +161 -0
- package/lib/server/skew.js +56 -0
- package/lib/server/snapshot.js +131 -0
- package/lib/server/status.js +128 -0
- package/lib/server/sys.js +65 -0
- package/lib/server/uninstall.js +63 -0
- package/lib/server/update.js +234 -0
- package/lib/toolOutputShape.js +315 -0
- package/lib/tui/app.jsx +289 -0
- package/lib/tui/components/Breadcrumb.jsx +42 -0
- package/lib/tui/components/ChatInput.jsx +68 -0
- package/lib/tui/components/Confirm.jsx +32 -0
- package/lib/tui/components/DataTable.jsx +270 -0
- package/lib/tui/components/DetailPanel.jsx +241 -0
- package/lib/tui/components/HelpOverlay.jsx +67 -0
- package/lib/tui/components/Layout.jsx +129 -0
- package/lib/tui/components/Palette.jsx +141 -0
- package/lib/tui/components/SearchInput.jsx +125 -0
- package/lib/tui/components/Sparkline.jsx +55 -0
- package/lib/tui/components/StatusBar.jsx +133 -0
- package/lib/tui/components/Toast.jsx +52 -0
- package/lib/tui/hooks/useApi.js +44 -0
- package/lib/tui/hooks/useHealth.js +90 -0
- package/lib/tui/hooks/usePolledApi.js +86 -0
- package/lib/tui/hooks/useTerminalSize.js +46 -0
- package/lib/tui/index.js +56 -0
- package/lib/tui/intro/Intro.jsx +332 -0
- package/lib/tui/jsx-loader.js +32 -0
- package/lib/tui/keys.js +87 -0
- package/lib/tui/registry.js +49 -0
- package/lib/tui/theme.js +103 -0
- package/lib/tui/views/AdminView.jsx +398 -0
- package/lib/tui/views/AgentView.jsx +204 -0
- package/lib/tui/views/BackupView.jsx +225 -0
- package/lib/tui/views/BrainView.jsx +223 -0
- package/lib/tui/views/ChatView.jsx +468 -0
- package/lib/tui/views/FilesView.jsx +218 -0
- package/lib/tui/views/FleetView.jsx +234 -0
- package/lib/tui/views/GovernanceView.jsx +453 -0
- package/lib/tui/views/HealthView.jsx +216 -0
- package/lib/tui/views/HyperionDeck.jsx +79 -0
- package/lib/tui/views/InboxView.jsx +228 -0
- package/lib/tui/views/LucidDeck.jsx +719 -0
- package/lib/tui/views/MainMenu.jsx +123 -0
- package/lib/tui/views/MemoryView.jsx +377 -0
- package/lib/tui/views/ObjectivesView.jsx +230 -0
- package/lib/tui/views/OrgView.jsx +229 -0
- package/lib/tui/views/ToolView.jsx +221 -0
- package/lib/tui/views/VaultView.jsx +422 -0
- package/lib/tui/views/WorkflowView.jsx +329 -0
- package/package.json +75 -4
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ROWS (owner 2026-10-17): `quox rows`, the CLI for the live lines above the quox window's status bar.
|
|
3
|
+
* CLI first: everything /row does in the window works here, from a shell or a script.
|
|
4
|
+
*
|
|
5
|
+
* quox rows list [--now] what you have, where each one lives, what it shows now
|
|
6
|
+
* quox rows add <id> --url|--quox|--plugin|--cmd one row (no --template = the status-row contract)
|
|
7
|
+
* quox rows edit <id> [...] change any field; --contract drops the template
|
|
8
|
+
* quox rows remove <id> (alias rm)
|
|
9
|
+
* quox rows test [id] | test --url ... fetch once and draw it here, nothing saved for an ad hoc test
|
|
10
|
+
* quox rows share [id...] a bundle others can import (command rows never leave)
|
|
11
|
+
* quox rows import <file|-> url, quox and plugin rows; command rows need --trust-commands
|
|
12
|
+
* AND a person answering y to each exact command
|
|
13
|
+
* quox rows contract the JSON shape a row endpoint returns
|
|
14
|
+
*
|
|
15
|
+
* Storage is the same as the window's (quoxon/rowsStore.js): synced rows in the user's server-side preferences,
|
|
16
|
+
* command rows in a 0600 local file. An open window picks changes up within a minute, or at once with /row reload.
|
|
17
|
+
*/
|
|
18
|
+
import chalk from 'chalk';
|
|
19
|
+
import { readFileSync } from 'node:fs';
|
|
20
|
+
import { createInterface } from 'node:readline/promises';
|
|
21
|
+
import { resolveEndpoint } from '../api/endpoints.js';
|
|
22
|
+
import { resolveOrgId } from './helpers.js';
|
|
23
|
+
import {
|
|
24
|
+
checkRow, secretInRow, syncable, createRowEngine, runShell, rowParts, clipParts, labelWidth, MAX_ROWS,
|
|
25
|
+
shareBundle, readBundle, cleanText, STATUS_ROW_TONES, themeRole, ROLES, fieldHints, dataFrom
|
|
26
|
+
} from '../quoxon/rows.js';
|
|
27
|
+
import { createRowsStore } from '../quoxon/rowsStore.js';
|
|
28
|
+
import { c as colourOf } from '../quoxon/theme.js';
|
|
29
|
+
|
|
30
|
+
export const CONTRACT = {
|
|
31
|
+
kind: 'quox.status-row',
|
|
32
|
+
version: 1,
|
|
33
|
+
fields: {
|
|
34
|
+
text: 'required (unless value is given). One line, 160 characters at most.',
|
|
35
|
+
label: 'optional. Shown first, 16 characters. The row\'s own label wins.',
|
|
36
|
+
value: 'optional. A number (shown compactly: 1.2k) or a short string (24), drawn bold before the text.',
|
|
37
|
+
tone: `optional. One of ${STATUS_ROW_TONES.join(', ')}. Colours the text and adds ✔ ⚠ ✖ ◆.`,
|
|
38
|
+
trend: 'optional. up, down, flat, or a number (the change: 3 shows ▲3, -2 shows ▼2).',
|
|
39
|
+
link: 'optional. An http(s) URL to look at for more; shown by quox rows test and /row test.'
|
|
40
|
+
},
|
|
41
|
+
example: { label: 'Approvals', value: 3, text: 'waiting for you', tone: 'warn', trend: 1, link: 'https://quox.example/approvals' },
|
|
42
|
+
rules: [
|
|
43
|
+
'Plain text output counts as {"text": "<the text>"}.',
|
|
44
|
+
'Terminal escapes and control characters are removed before anything is drawn.',
|
|
45
|
+
'A row definition never carries a secret: use a quox or plugin row (your session is used) or a local command that reads its own secret.',
|
|
46
|
+
'Command rows run on the machine that holds them only. They are never synced, shared or imported without a person saying yes to the exact command.'
|
|
47
|
+
]
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/** A theme role -> a chalk painter in the window's own colours (hex, ansi256, or plain under NO_COLOR). No grey. */
|
|
51
|
+
export function tint(name) {
|
|
52
|
+
const v = colourOf(name);
|
|
53
|
+
if (!v) return chalk.reset;
|
|
54
|
+
const m = /^ansi256\((\d+)\)$/.exec(v);
|
|
55
|
+
if (m) return chalk.ansi256(Number(m[1]));
|
|
56
|
+
return /^#[0-9a-f]{6}$/i.test(v) ? chalk.hex(v) : chalk.reset;
|
|
57
|
+
}
|
|
58
|
+
const paint = (role, bold, t) => { const f = tint(themeRole(role)); return bold ? f.bold(t) : f(t); };
|
|
59
|
+
export const drawRow = (state, width = process.stdout.columns || 100, labelW = 0) =>
|
|
60
|
+
' ' + clipParts(rowParts(state, { labelW }), Math.max(10, width - 2)).map(([t, role, bold]) => paint(role, bold, t)).join('');
|
|
61
|
+
|
|
62
|
+
const where = (r) => {
|
|
63
|
+
const s = r.source;
|
|
64
|
+
if (s.type === 'url') return s.url;
|
|
65
|
+
if (s.type === 'quox') return `${s.path}${s.service === 'collector' ? ' (collector)' : ''}`;
|
|
66
|
+
if (s.type === 'plugin') return `${s.plugin} ${s.path}`;
|
|
67
|
+
return s.cmd;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
/** Options -> a source (or null when none of --url/--quox/--plugin/--cmd was given). */
|
|
71
|
+
export function sourceFromOptions(o) {
|
|
72
|
+
const given = ['url', 'quox', 'plugin', 'cmd'].filter((k) => o[k] !== undefined);
|
|
73
|
+
if (given.length > 1) return { error: `Give one of --url, --quox, --plugin or --cmd (you gave ${given.map((g) => `--${g}`).join(' and ')}).` };
|
|
74
|
+
if (!given.length) return { source: null };
|
|
75
|
+
const k = given[0];
|
|
76
|
+
if (k === 'url') return { source: { type: 'url', url: String(o.url) } };
|
|
77
|
+
if (k === 'cmd') return { source: { type: 'cmd', cmd: String(o.cmd) } };
|
|
78
|
+
if (k === 'quox') return { source: { type: 'quox', service: o.service || (String(o.quox).startsWith('/api/v1/') ? 'collector' : 'auth'), path: String(o.quox) } };
|
|
79
|
+
return { source: { type: 'plugin', plugin: String(o.plugin), path: o.path ? String(o.path) : '' } };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* A plugin source with no --path -> the path its manifest's statusRows names. -> { source, label?, every? } | { error }.
|
|
84
|
+
* manifest.statusRows: [{ id, label, path, every?, description? }] (quox-plugin-sdk docs/status-rows.md).
|
|
85
|
+
*/
|
|
86
|
+
export async function resolvePluginRow(client, orgId, source, rowId) {
|
|
87
|
+
if (source.path) return { source };
|
|
88
|
+
let manifest;
|
|
89
|
+
try {
|
|
90
|
+
const ep = resolveEndpoint('plugins.get', { orgId, pluginId: source.plugin });
|
|
91
|
+
const d = await client.get(ep.path, { service: ep.service, retries: 0 });
|
|
92
|
+
manifest = d?.plugin?.manifest ?? d?.manifest;
|
|
93
|
+
if (typeof manifest === 'string') manifest = JSON.parse(manifest);
|
|
94
|
+
} catch (e) { return { error: `Could not read the ${source.plugin} plugin (${e?.message || 'failed'}). Is it installed in this org?` }; }
|
|
95
|
+
const rows = Array.isArray(manifest?.statusRows) ? manifest.statusRows : [];
|
|
96
|
+
if (!rows.length) return { error: `The ${source.plugin} plugin ships no status rows (its manifest has no statusRows). Give --path to read one of its routes.` };
|
|
97
|
+
const pick = rowId ? rows.find((r) => r?.id === rowId) : rows[0];
|
|
98
|
+
if (!pick) return { error: `${source.plugin} has no status row "${rowId}". It has: ${rows.map((r) => r?.id).filter(Boolean).join(', ')}.` };
|
|
99
|
+
return { source: { ...source, path: String(pick.path || '') }, label: typeof pick.label === 'string' ? pick.label : undefined, every: typeof pick.every === 'string' ? pick.every : undefined };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function registerRowsCommands(program, ctx) {
|
|
103
|
+
const { client, auth, config } = ctx;
|
|
104
|
+
const store = createRowsStore({ client, config, orgId: () => { try { return auth.getOrgId(program.opts().org); } catch { return null; } } });
|
|
105
|
+
const json = () => program.opts().output === 'json';
|
|
106
|
+
const fail = (msg, code = 1) => { console.error(tint('error')(msg)); process.exitCode = code; };
|
|
107
|
+
const say = (msg) => console.log(msg);
|
|
108
|
+
|
|
109
|
+
async function loadAll() {
|
|
110
|
+
const r = await store.rowPrefs();
|
|
111
|
+
return { synced: r.rows, cmd: store.localCmdRows(), offline: r.source !== 'server' ? (r.error || 'server unreachable') : null };
|
|
112
|
+
}
|
|
113
|
+
/** Write the synced list; -> true when the server took it. */
|
|
114
|
+
async function saveSynced(rows) {
|
|
115
|
+
const r = await store.setRowPrefs(rows);
|
|
116
|
+
if (!r.synced) fail(`Saved on this machine only: ${r.error || 'the server did not take it'}. Run the command again to retry.`);
|
|
117
|
+
return r.synced;
|
|
118
|
+
}
|
|
119
|
+
const engine = () => createRowEngine({ fetchUrl: (u) => store.rowFetch(u), fetchQuox: (s) => store.rowFetchQuox(s), runCmd: runShell });
|
|
120
|
+
|
|
121
|
+
/** Fetch once and print raw value, notes and the drawn row. -> the probe result. */
|
|
122
|
+
async function showOnce(row) {
|
|
123
|
+
const p = await engine().probe(row);
|
|
124
|
+
const raw = String(p.raw || '');
|
|
125
|
+
if (raw) {
|
|
126
|
+
say(tint('label')('Raw value:'));
|
|
127
|
+
const lines = raw.split('\n').filter((l) => l.trim()).slice(0, 6);
|
|
128
|
+
for (const l of lines) say(' ' + cleanText(l, (process.stdout.columns || 100) - 4));
|
|
129
|
+
if (raw.length > 600 || lines.length >= 6) say(' …');
|
|
130
|
+
}
|
|
131
|
+
if (!p.ok) {
|
|
132
|
+
fail(`⚠ ${row.label || row.id}: ${p.error}`);
|
|
133
|
+
const hints = raw ? fieldHints(dataFrom(raw)) : [];
|
|
134
|
+
if (hints.length && !row.template) {
|
|
135
|
+
say(tint('label')('It is JSON but not a status row. A template can read it; fields you can use:'));
|
|
136
|
+
say(' ' + hints.map((h) => tint('command')(`{{${h}}}`)).join(' '));
|
|
137
|
+
say(tint('label')(` e.g. --template '{{${hints.find((h) => !h.startsWith('count(')) || hints[0]}}} …'`));
|
|
138
|
+
}
|
|
139
|
+
return p;
|
|
140
|
+
}
|
|
141
|
+
for (const w of p.warnings || []) say(tint('warn')(`Note: ${w}`));
|
|
142
|
+
say(tint('label')(`Shape: ${row.template ? 'template' : 'status-row contract'}${p.seg?.link ? ` · link ${p.seg.link}` : ''}`));
|
|
143
|
+
say(tint('label')('The row:'));
|
|
144
|
+
say(drawRow({ row, status: 'ok', text: p.text, seg: p.seg, at: Date.now() }));
|
|
145
|
+
return p;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** Build a row from add/edit options on top of `base`. -> { row } | { error }. */
|
|
149
|
+
async function buildRow(id, o, base = null) {
|
|
150
|
+
const s = sourceFromOptions(o);
|
|
151
|
+
if (s.error) return { error: s.error };
|
|
152
|
+
let source = s.source || (base ? { ...base.source } : null);
|
|
153
|
+
if (!source) return { error: 'Say where the row reads from: --url <url>, --quox <path>, --plugin <id>, or --cmd "<command>".' };
|
|
154
|
+
let label = o.label ?? base?.label;
|
|
155
|
+
let every = o.every ?? base?.source?.every; // a new source keeps the row's interval
|
|
156
|
+
if (source.type === 'plugin' && !source.path) {
|
|
157
|
+
auth.requireAuth();
|
|
158
|
+
const r = await resolvePluginRow(client, resolveOrgId(auth, program), source, o.row);
|
|
159
|
+
if (r.error) return { error: r.error };
|
|
160
|
+
source = r.source; label = label ?? r.label; every = every ?? r.every;
|
|
161
|
+
}
|
|
162
|
+
if (every) source.every = every;
|
|
163
|
+
const draft = { id, source };
|
|
164
|
+
if (label) draft.label = label;
|
|
165
|
+
const colour = o.colour ?? o.color ?? base?.colour;
|
|
166
|
+
if (colour) draft.colour = colour;
|
|
167
|
+
const template = o.contract ? '' : (o.template ?? base?.template ?? '');
|
|
168
|
+
if (template) draft.template = template;
|
|
169
|
+
const c = checkRow(draft);
|
|
170
|
+
if (c.error) return { error: c.error };
|
|
171
|
+
const secret = secretInRow(c.row);
|
|
172
|
+
if (secret) return { error: secret };
|
|
173
|
+
return { row: c.row };
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async function put(row, { replace = null } = {}) {
|
|
177
|
+
const { synced, cmd } = await loadAll();
|
|
178
|
+
const all = [...synced, ...cmd];
|
|
179
|
+
const prev = replace ? all.find((r) => r.id === replace) : all.find((r) => r.id.toLowerCase() === row.id.toLowerCase());
|
|
180
|
+
if (!prev && all.length >= MAX_ROWS) return fail(`You have ${MAX_ROWS} rows already (the most the window has room for). quox rows remove <id> makes space.`);
|
|
181
|
+
const syncedNext = synced.filter((r) => r !== prev);
|
|
182
|
+
const cmdNext = cmd.filter((r) => r !== prev);
|
|
183
|
+
if (syncable(row)) syncedNext.push(row); else cmdNext.push(row);
|
|
184
|
+
if (prev && !syncable(prev) || !syncable(row)) if (!store.setLocalCmdRows(cmdNext)) return fail('Could not write the local rows file.');
|
|
185
|
+
if (prev && syncable(prev) || syncable(row)) if (!(await saveSynced(syncedNext))) return undefined;
|
|
186
|
+
if (json()) return say(JSON.stringify({ ok: true, row, where: syncable(row) ? 'synced' : 'this machine' }, null, 2));
|
|
187
|
+
say(tint('ok')(`✔ Row ${row.id} ${prev ? 'saved' : 'added'}. ${syncable(row) ? 'It follows your account to your other machines.' : 'It runs on THIS machine only and is never synced.'}`));
|
|
188
|
+
say(tint('label')(` Open windows show it within a minute (/row reload for now). quox rows test ${row.id} draws it here.`));
|
|
189
|
+
return undefined;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const rows = program.command('rows').description('Your live lines above the quox window\'s status bar: add, edit, remove, test, share, import (url, quox, plugin or local command rows)');
|
|
193
|
+
|
|
194
|
+
const sourceOpts = (cmd) => cmd
|
|
195
|
+
.option('--url <url>', 'read JSON or text from a URL (no credentials are sent to other hosts)')
|
|
196
|
+
.option('--quox <path>', 'read a path on your own Quox with your session, e.g. /api/orgs/{org}/inbox/stats')
|
|
197
|
+
.option('--service <name>', 'for --quox: auth (default) or collector')
|
|
198
|
+
.option('--plugin <id>', 'read a status row a plugin ships (its manifest statusRows)')
|
|
199
|
+
.option('--row <rowId>', 'for --plugin: which of its status rows (default: the first)')
|
|
200
|
+
.option('--path <path>', 'for --plugin: a route of the plugin backend instead of a declared row')
|
|
201
|
+
.option('--cmd <command>', 'run a local command (this machine only, never synced)')
|
|
202
|
+
.option('--template <template>', 'fill {{path}} from the data; leave out to read the status-row contract')
|
|
203
|
+
.option('--every <interval>', 'refresh interval: 30s, 5m, 1h (5 s to 1 day)')
|
|
204
|
+
.option('--label <text>', 'a short name drawn first')
|
|
205
|
+
.option('--colour <role>', `text colour: ${ROLES.join(', ')}`);
|
|
206
|
+
|
|
207
|
+
rows.command('list').alias('ls')
|
|
208
|
+
.description('List your rows: where each reads from, where it is stored, and (--now) what it shows now')
|
|
209
|
+
.option('--now', 'fetch each row once and draw it')
|
|
210
|
+
.action(async (o) => {
|
|
211
|
+
auth.requireAuth();
|
|
212
|
+
const { synced, cmd, offline } = await loadAll();
|
|
213
|
+
const all = [...synced, ...cmd];
|
|
214
|
+
if (offline) console.error(tint('warn')(`⚠ Server not reachable (${offline}); showing this machine's copy.`));
|
|
215
|
+
const states = [];
|
|
216
|
+
if (o.now) {
|
|
217
|
+
const e = engine();
|
|
218
|
+
for (const r of all) {
|
|
219
|
+
const p = await e.probe(r);
|
|
220
|
+
states.push(p.ok ? { row: r, status: 'ok', text: p.text, seg: p.seg, at: Date.now() } : { row: r, status: 'error', error: p.error, text: '', at: null });
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
if (json()) return say(JSON.stringify({ rows: all.map((r, i) => ({ ...r, stored: syncable(r) ? 'synced' : 'this machine', shape: r.template ? 'template' : 'contract', ...(states[i] ? { now: states[i].status === 'ok' ? states[i].text : null, error: states[i].error || undefined } : {}) })), max: MAX_ROWS }, null, 2));
|
|
224
|
+
if (!all.length) return say(`No rows yet. Try: quox rows add approvals --quox '/api/orgs/{org}/inbox/stats' --template '{{pending_approvals}} waiting' --label Approvals\n(or /row new in the quox window).`);
|
|
225
|
+
say(tint('heading').bold(`Rows (${all.length} of ${MAX_ROWS})`));
|
|
226
|
+
const lw = labelWidth(all.map((row) => ({ row })));
|
|
227
|
+
for (const [i, r] of all.entries()) {
|
|
228
|
+
say(` ${tint('id').bold(r.id.padEnd(12))} ${tint('label')(r.source.type.padEnd(6))} ${tint('text')(where(r))}`);
|
|
229
|
+
say(tint('label')(` ${''.padEnd(12)} every ${r.source.every} · ${syncable(r) ? 'synced to your account' : 'this machine only'} · ${r.template ? `template ${r.template}` : 'status-row contract'}${r.label ? ` · label ${r.label}` : ''}`));
|
|
230
|
+
if (states[i]) say(' ' + drawRow(states[i], (process.stdout.columns || 100) - 3, lw));
|
|
231
|
+
}
|
|
232
|
+
return undefined;
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
sourceOpts(rows.command('add <id>').description('Add a row. No --template = it reads the status-row contract (quox rows contract)')).option('--test', 'fetch it once and draw it after saving')
|
|
236
|
+
.action(async (id, o) => {
|
|
237
|
+
auth.requireAuth();
|
|
238
|
+
const b = await buildRow(id, o);
|
|
239
|
+
if (b.error) return fail(b.error);
|
|
240
|
+
const { synced, cmd } = await loadAll();
|
|
241
|
+
if ([...synced, ...cmd].some((r) => r.id.toLowerCase() === b.row.id.toLowerCase())) return fail(`A row called ${b.row.id} exists. quox rows edit ${b.row.id} changes it.`);
|
|
242
|
+
await put(b.row);
|
|
243
|
+
if (o.test && !process.exitCode) await showOnce(b.row);
|
|
244
|
+
return undefined;
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
sourceOpts(rows.command('edit <id>').description('Change a row: any of the add options; --contract drops the template')).option('--contract', 'read the status-row contract instead of a template')
|
|
248
|
+
.action(async (id, o) => {
|
|
249
|
+
auth.requireAuth();
|
|
250
|
+
const { synced, cmd } = await loadAll();
|
|
251
|
+
const cur = [...synced, ...cmd].find((r) => r.id.toLowerCase() === String(id).toLowerCase());
|
|
252
|
+
if (!cur) return fail(`No row "${id}". quox rows list shows them.`);
|
|
253
|
+
const b = await buildRow(cur.id, o, cur);
|
|
254
|
+
if (b.error) return fail(b.error);
|
|
255
|
+
if (JSON.stringify(b.row) === JSON.stringify(cur)) return say(`Nothing changed on ${cur.id}.`);
|
|
256
|
+
return put(b.row, { replace: cur.id });
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
rows.command('remove <id>').alias('rm').description('Remove a row')
|
|
260
|
+
.action(async (id) => {
|
|
261
|
+
auth.requireAuth();
|
|
262
|
+
const { synced, cmd } = await loadAll();
|
|
263
|
+
const cur = [...synced, ...cmd].find((r) => r.id.toLowerCase() === String(id).toLowerCase());
|
|
264
|
+
if (!cur) return fail(`No row "${id}". quox rows list shows them.`);
|
|
265
|
+
if (syncable(cur)) { if (!(await saveSynced(synced.filter((r) => r !== cur)))) return undefined; }
|
|
266
|
+
else if (!store.setLocalCmdRows(cmd.filter((r) => r !== cur))) return fail('Could not write the local rows file.');
|
|
267
|
+
return json() ? say(JSON.stringify({ ok: true, removed: cur.id })) : say(tint('ok')(`✔ Removed row ${cur.id}.`));
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
sourceOpts(rows.command('test [id]').description('Fetch a row once and draw it here. With --url/--quox/--plugin/--cmd instead of an id, try one before adding it (nothing is saved)'))
|
|
271
|
+
.action(async (id, o) => {
|
|
272
|
+
auth.requireAuth();
|
|
273
|
+
let row;
|
|
274
|
+
if (id) {
|
|
275
|
+
const { synced, cmd } = await loadAll();
|
|
276
|
+
row = [...synced, ...cmd].find((r) => r.id.toLowerCase() === String(id).toLowerCase());
|
|
277
|
+
if (!row) return fail(`No row "${id}". quox rows list shows them.`);
|
|
278
|
+
} else {
|
|
279
|
+
const b = await buildRow('test', o);
|
|
280
|
+
if (b.error) return fail(b.error);
|
|
281
|
+
row = b.row;
|
|
282
|
+
}
|
|
283
|
+
const p = await showOnce(row);
|
|
284
|
+
if (json()) say(JSON.stringify({ ok: p.ok, text: p.text ?? null, seg: p.seg ?? null, warnings: p.warnings ?? [], error: p.error ?? null }, null, 2));
|
|
285
|
+
return undefined;
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
rows.command('share [ids...]').description('Print a bundle of your rows for someone else to import. Command rows never leave this machine')
|
|
289
|
+
.action(async (ids) => {
|
|
290
|
+
auth.requireAuth();
|
|
291
|
+
const { synced, cmd } = await loadAll();
|
|
292
|
+
const all = [...synced, ...cmd];
|
|
293
|
+
const want = ids?.length ? ids.map((x) => String(x).toLowerCase()) : null;
|
|
294
|
+
const missing = want ? want.filter((x) => !all.some((r) => r.id.toLowerCase() === x)) : [];
|
|
295
|
+
if (missing.length) return fail(`No row ${missing.join(', ')}. quox rows list shows them.`);
|
|
296
|
+
const picked = want ? all.filter((r) => want.includes(r.id.toLowerCase())) : all;
|
|
297
|
+
const { bundle, skipped, refused } = shareBundle(picked);
|
|
298
|
+
for (const s of skipped) console.error(tint('warn')(`Not shared: ${s} is a command row (it runs a shell on this machine, so it never leaves it).`));
|
|
299
|
+
for (const r of refused) console.error(tint('warn')(`Not shared: ${r.id}: ${r.why}`));
|
|
300
|
+
if (!bundle.rows.length) return fail('Nothing to share.');
|
|
301
|
+
return say(JSON.stringify(bundle, null, 2));
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
rows.command('import <file>').description('Add rows from a share bundle (a file, or - for stdin). Command rows are never added without --trust-commands and a y for each exact command')
|
|
305
|
+
.option('--trust-commands', 'offer each command row for a yes/no, one by one (needs a person at the terminal)')
|
|
306
|
+
.option('--replace', 'replace rows that have the same id')
|
|
307
|
+
.action(async (file, o) => {
|
|
308
|
+
auth.requireAuth();
|
|
309
|
+
let text;
|
|
310
|
+
try { text = file === '-' ? readFileSync(0, 'utf8') : readFileSync(file, 'utf8'); } catch (e) { return fail(`Could not read ${file}: ${e.message}`); }
|
|
311
|
+
const b = readBundle(text);
|
|
312
|
+
for (const e of b.errors) console.error(tint('warn')(`Skipped ${e}`));
|
|
313
|
+
const take = [...b.rows];
|
|
314
|
+
if (b.commands.length) {
|
|
315
|
+
if (!o.trustCommands) {
|
|
316
|
+
for (const r of b.commands) console.error(tint('warn')(`Not added: ${r.id} would run \`${cleanText(r.source.cmd, 200)}\` on this machine. Command rows from a bundle need --trust-commands and your yes.`));
|
|
317
|
+
} else if (!process.stdin.isTTY || file === '-') {
|
|
318
|
+
return fail('A command row from a bundle needs a person at the terminal to say yes to it. Nothing was imported.');
|
|
319
|
+
} else {
|
|
320
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
321
|
+
try {
|
|
322
|
+
for (const r of b.commands) {
|
|
323
|
+
say(tint('warn').bold(`⚠ ${r.id} runs this on THIS machine every ${r.source.every}:`));
|
|
324
|
+
say(' ' + tint('command')(cleanText(r.source.cmd, 500)));
|
|
325
|
+
const a = (await rl.question(' Add it? y/N ')).trim();
|
|
326
|
+
if (/^y(es)?$/i.test(a)) take.push(r); else say(' Skipped.');
|
|
327
|
+
}
|
|
328
|
+
} finally { rl.close(); }
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
if (!take.length) return fail('Nothing to import.');
|
|
332
|
+
const { synced, cmd } = await loadAll();
|
|
333
|
+
const has = (id) => [...synced, ...cmd].find((r) => r.id.toLowerCase() === id.toLowerCase());
|
|
334
|
+
const clash = take.filter((r) => has(r.id));
|
|
335
|
+
if (clash.length && !o.replace) return fail(`You already have ${clash.map((r) => r.id).join(', ')}. --replace swaps them; nothing was imported.`);
|
|
336
|
+
const syncedNext = synced.filter((r) => !take.some((t) => t.id.toLowerCase() === r.id.toLowerCase()));
|
|
337
|
+
const cmdNext = cmd.filter((r) => !take.some((t) => t.id.toLowerCase() === r.id.toLowerCase()));
|
|
338
|
+
for (const r of take) (syncable(r) ? syncedNext : cmdNext).push(r);
|
|
339
|
+
if (syncedNext.length + cmdNext.length > MAX_ROWS) return fail(`That would make ${syncedNext.length + cmdNext.length} rows; the window has room for ${MAX_ROWS}. Remove some first. Nothing was imported.`);
|
|
340
|
+
if (take.some((r) => !syncable(r)) || cmdNext.length !== cmd.length) if (!store.setLocalCmdRows(cmdNext)) return fail('Could not write the local rows file.');
|
|
341
|
+
if (!(await saveSynced(syncedNext))) return undefined;
|
|
342
|
+
return say(tint('ok')(`✔ Imported ${take.map((r) => r.id).join(', ')}.`));
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
rows.command('contract').description('The JSON a row endpoint returns (the status-row contract), with an example')
|
|
346
|
+
.action(() => {
|
|
347
|
+
if (json()) return say(JSON.stringify(CONTRACT, null, 2));
|
|
348
|
+
say(tint('heading').bold('Status-row contract v1'));
|
|
349
|
+
for (const [k, v] of Object.entries(CONTRACT.fields)) say(` ${tint('id').bold(k.padEnd(6))} ${v}`);
|
|
350
|
+
say(tint('label')('Example:'));
|
|
351
|
+
say(' ' + JSON.stringify(CONTRACT.example));
|
|
352
|
+
say(tint('label')('Draws as:'));
|
|
353
|
+
const ex = { row: { id: 'approvals', source: {} }, status: 'ok', at: Date.now(), seg: { label: 'Approvals', value: '3', text: 'waiting for you', tone: 'warn', trend: '▲1' }, text: '3 waiting for you ▲1' };
|
|
354
|
+
say(' ' + drawRow(ex));
|
|
355
|
+
for (const r of CONTRACT.rules) say(tint('label')(` · ${r}`));
|
|
356
|
+
return undefined;
|
|
357
|
+
});
|
|
358
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Run commands: list, get, create, cancel, resume
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { formatOutput, formatDetail, printSuccess, printWarning } from '../core/output.js';
|
|
6
|
+
import { resolveOrgId, parseJSON } from './helpers.js';
|
|
7
|
+
import { resolveEndpoint } from '../api/endpoints.js';
|
|
8
|
+
|
|
9
|
+
export function registerRunCommands(program, ctx) {
|
|
10
|
+
const { client, auth } = ctx;
|
|
11
|
+
|
|
12
|
+
const run = program
|
|
13
|
+
.command('run')
|
|
14
|
+
.description('Manage workflow runs');
|
|
15
|
+
|
|
16
|
+
// fleet #465: runs live in two stores. The engine (quoxflow) is the
|
|
17
|
+
// execution store `workflow run`/apply-triggered runs actually land in;
|
|
18
|
+
// the auth-service 'runs.*' store is a separate model quoxflow never
|
|
19
|
+
// writes to. `list`/`get` below try the engine FIRST and fall back to the
|
|
20
|
+
// auth-service store, labeling which store answered.
|
|
21
|
+
|
|
22
|
+
run
|
|
23
|
+
.command('list')
|
|
24
|
+
.description('List runs')
|
|
25
|
+
.option('--status <status>', 'filter by status')
|
|
26
|
+
.option('--workflow <id>', 'filter by workflow')
|
|
27
|
+
.option('--limit <n>', 'max results', '50')
|
|
28
|
+
.action(async (options) => {
|
|
29
|
+
auth.requireAuth();
|
|
30
|
+
const orgId = resolveOrgId(auth, program);
|
|
31
|
+
|
|
32
|
+
let rows, source;
|
|
33
|
+
try {
|
|
34
|
+
// Journey #3 (2026-10-19): the engine path sent no query, so --workflow/--status/--limit were ignored and a
|
|
35
|
+
// filter for one workflow listed every workflow's runs. quoxflow's /api/executions takes all three.
|
|
36
|
+
const ep = resolveEndpoint('executions.list');
|
|
37
|
+
const engineQuery = { ...(options.workflow ? { workflowId: options.workflow } : {}), ...(options.status ? { status: options.status } : {}), limit: options.limit };
|
|
38
|
+
const data = await client.get(ep.path, { service: ep.service, query: engineQuery });
|
|
39
|
+
let items = data.data || data.items || (Array.isArray(data) ? data : []);
|
|
40
|
+
// an older engine that ignores the filter must still never show another workflow's runs
|
|
41
|
+
if (options.workflow) items = items.filter((it) => (it.workflowId ?? it.workflow_id) === options.workflow);
|
|
42
|
+
rows = items.map((item) => ({ ...item, source: 'engine' }));
|
|
43
|
+
source = 'engine';
|
|
44
|
+
} catch (err) {
|
|
45
|
+
printWarning(`Engine (quoxflow) execution list failed, falling back to the auth-service store: ${err.message}`);
|
|
46
|
+
const query = { limit: options.limit };
|
|
47
|
+
if (options.status) query.status = options.status;
|
|
48
|
+
if (options.workflow) query.workflow_id = options.workflow;
|
|
49
|
+
const ep = resolveEndpoint('runs.list', { orgId });
|
|
50
|
+
const data = await client.get(ep.path, { service: ep.service, query });
|
|
51
|
+
const items = data.items || data.runs || (Array.isArray(data) ? data : []);
|
|
52
|
+
rows = items.map((item) => ({ ...item, source: 'auth' }));
|
|
53
|
+
source = 'auth';
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
formatOutput({
|
|
57
|
+
// fleet #392: runs table has finished_at, not completed_at.
|
|
58
|
+
columns: ['id', 'workflow_name', 'status', 'source', 'started_at', 'finished_at'],
|
|
59
|
+
headers: ['ID', 'Workflow', 'Status', 'Source', 'Started', 'Completed'],
|
|
60
|
+
rows,
|
|
61
|
+
}, { format: program.opts().output, emptyMessage: `No results (${source} store).` });
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
run
|
|
65
|
+
.command('get')
|
|
66
|
+
.description('Get run details')
|
|
67
|
+
.argument('<runId>', 'run ID')
|
|
68
|
+
.action(async (runId) => {
|
|
69
|
+
auth.requireAuth();
|
|
70
|
+
const orgId = resolveOrgId(auth, program);
|
|
71
|
+
|
|
72
|
+
let data, source;
|
|
73
|
+
try {
|
|
74
|
+
const ep = resolveEndpoint('executions.get', { executionId: runId });
|
|
75
|
+
const res = await client.get(ep.path, { service: ep.service });
|
|
76
|
+
data = res.data || res;
|
|
77
|
+
source = 'engine';
|
|
78
|
+
} catch (err) {
|
|
79
|
+
if (err.status !== 404) throw err;
|
|
80
|
+
try {
|
|
81
|
+
const ep = resolveEndpoint('runs.get', { orgId, pollRunId: runId });
|
|
82
|
+
const res = await client.get(ep.path, { service: ep.service });
|
|
83
|
+
data = res.run || res;
|
|
84
|
+
source = 'auth';
|
|
85
|
+
} catch (err2) {
|
|
86
|
+
if (err2.status !== 404) throw err2;
|
|
87
|
+
throw new Error(
|
|
88
|
+
`Run "${runId}" was not found in either the engine (quoxflow) execution store or the auth-service run store.`
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
formatDetail({ ...data, source }, { format: program.opts().output });
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
run
|
|
97
|
+
.command('create')
|
|
98
|
+
.description('Start a new workflow run')
|
|
99
|
+
.requiredOption('--workflow <id>', 'workflow ID')
|
|
100
|
+
.option('--input <json>', 'input data as JSON')
|
|
101
|
+
.option('--wait', 'engine workflows: wait for the run to finish (up to 10 minutes) instead of returning its id at once')
|
|
102
|
+
.action(async (options) => {
|
|
103
|
+
auth.requireAuth();
|
|
104
|
+
const orgId = resolveOrgId(auth, program);
|
|
105
|
+
const body = { workflow_id: options.workflow };
|
|
106
|
+
if (options.input) body.context = parseJSON(options.input, '--input');
|
|
107
|
+
let data;
|
|
108
|
+
try {
|
|
109
|
+
data = await client.post(resolveEndpoint('runs.create', { orgId }).path, { service: resolveEndpoint('runs.create', { orgId }).service, body });
|
|
110
|
+
} catch (err) {
|
|
111
|
+
if (err.status !== 404) throw err;
|
|
112
|
+
// #997: the auth runs model only knows auth-store workflows; an engine workflow runs on the engine
|
|
113
|
+
// (same fallback as `workflow run`, quoxflow#7).
|
|
114
|
+
const getEp = resolveEndpoint('workflows.applyGet', { workflowId: options.workflow });
|
|
115
|
+
try { await client.get(getEp.path, { service: getEp.service }); } catch (engineErr) { if (engineErr.status === 404) throw err; throw engineErr; }
|
|
116
|
+
const exEp = resolveEndpoint('workflows.applyExecute', { workflowId: options.workflow });
|
|
117
|
+
// #1029: start async by default and return the id at once; the engine ran synchronously before, so any
|
|
118
|
+
// run past the 30 s client timeout printed 'timed out' while it kept going. --wait keeps the old shape.
|
|
119
|
+
const ran = await client.post(exEp.path, {
|
|
120
|
+
service: exEp.service,
|
|
121
|
+
body: { triggerData: body.context || {}, async: !options.wait },
|
|
122
|
+
...(options.wait ? { timeout: 600000 } : {}),
|
|
123
|
+
});
|
|
124
|
+
const runId = ran.executionId || ran.execution_id || ran.id || ran.execution?.id || ran.data?.executionId || 'ok';
|
|
125
|
+
const st = ran.status || ran.execution?.status || ran.data?.status;
|
|
126
|
+
printSuccess(`Run started (engine): ${runId}${st ? ` [${st}]` : ''}`);
|
|
127
|
+
if (!options.wait && runId !== 'ok') console.log(` Follow it: quox run get ${runId}`);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
printSuccess(`Run started: ${data.run?.id || data.id || 'ok'}`);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
run
|
|
134
|
+
.command('cancel')
|
|
135
|
+
.description('Cancel a running workflow')
|
|
136
|
+
.argument('<runId>', 'run ID')
|
|
137
|
+
.option('--reason <text>', 'cancellation reason')
|
|
138
|
+
.action(async (runId, options) => {
|
|
139
|
+
auth.requireAuth();
|
|
140
|
+
const orgId = resolveOrgId(auth, program);
|
|
141
|
+
const body = {};
|
|
142
|
+
if (options.reason) body.reason = options.reason;
|
|
143
|
+
// #997: engine first (the runs `run list` shows), auth-store fallback on a 404 only
|
|
144
|
+
const source = await engineFirst(
|
|
145
|
+
() => { const ep = resolveEndpoint('executions.cancel', { executionId: runId }); return client.post(ep.path, { service: ep.service, body }); },
|
|
146
|
+
() => client.post(resolveEndpoint('runs.cancel', { orgId, runId }).path, { service: resolveEndpoint('runs.cancel', { orgId, runId }).service, body }),
|
|
147
|
+
);
|
|
148
|
+
printSuccess(`Run cancelled (${source}).`);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
run
|
|
152
|
+
.command('resume')
|
|
153
|
+
.description('Resume a paused run')
|
|
154
|
+
.argument('<runId>', 'run ID')
|
|
155
|
+
.option('--response <json>', 'human response payload as JSON')
|
|
156
|
+
.option('--wait', 'engine runs: wait for the resumed run to finish (up to 10 minutes) instead of answering at once')
|
|
157
|
+
.action(async (runId, options) => {
|
|
158
|
+
auth.requireAuth();
|
|
159
|
+
const orgId = resolveOrgId(auth, program);
|
|
160
|
+
const response = options.response ? parseJSON(options.response, '--response') : undefined;
|
|
161
|
+
const body = response === undefined ? {} : { response };
|
|
162
|
+
// #997: the engine's resume body IS the decision ({approved, approver, reason?}; approver must be you);
|
|
163
|
+
// the auth model wraps it as { response }.
|
|
164
|
+
const source = await engineFirst(
|
|
165
|
+
// #1037: async by default; the engine checks the approver synchronously, then runs the rest in the background
|
|
166
|
+
() => {
|
|
167
|
+
const ep = resolveEndpoint('executions.resume', { executionId: runId });
|
|
168
|
+
return client.post(ep.path, {
|
|
169
|
+
service: ep.service,
|
|
170
|
+
body: { ...(response || {}), async: !options.wait },
|
|
171
|
+
...(options.wait ? { timeout: 600000 } : {}),
|
|
172
|
+
});
|
|
173
|
+
},
|
|
174
|
+
() => client.post(resolveEndpoint('runs.resume', { orgId, runId }).path, { service: resolveEndpoint('runs.resume', { orgId, runId }).service, body }),
|
|
175
|
+
);
|
|
176
|
+
printSuccess(`Run resumed (${source}).`);
|
|
177
|
+
if (source === 'engine' && !options.wait) console.log(` Follow it: quox run get ${runId}`);
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// #997: try the engine (quoxflow) store, fall back to the auth-service store only when the engine says 404.
|
|
182
|
+
// Any other engine answer (409 already terminal, 400 not paused) is the real outcome and is thrown.
|
|
183
|
+
async function engineFirst(engineCall, authCall) {
|
|
184
|
+
try { await engineCall(); return 'engine'; } catch (err) { if (err.status !== 404) throw err; }
|
|
185
|
+
await authCall();
|
|
186
|
+
return 'auth';
|
|
187
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Safety commands: kill switch (per-agent E-STOP) + instance DEFCON.
|
|
3
|
+
*
|
|
4
|
+
* quox safety status org halt state: DEFCON + active E-STOPs
|
|
5
|
+
* quox safety gate <agentId> the enforcement verdict a dispatcher sees
|
|
6
|
+
* quox safety estop <agentId> activate the per-agent kill switch
|
|
7
|
+
* quox safety release <agentId> release it
|
|
8
|
+
* quox safety defcon <LEVEL> set instance DEFCON (GREEN..RED)
|
|
9
|
+
* quox safety history append-only audit trail of every flip
|
|
10
|
+
*
|
|
11
|
+
* Backend: collector routes/safetyGate.js (quox#503, PR #505; dispatch
|
|
12
|
+
* chokepoint coverage quox#506). Mutations REQUIRE a human admin login:
|
|
13
|
+
* the server rejects agent/resident/service tokens by design, so if
|
|
14
|
+
* `estop`/`release`/`defcon` return 403 admin_required under a resident
|
|
15
|
+
* profile, that is the governance boundary working: log in as a human
|
|
16
|
+
* (`quox login`), do not look for a bypass.
|
|
17
|
+
*
|
|
18
|
+
* Closes the quox#507 parity gap: the 2026-09-02 live E-STOP proof drive
|
|
19
|
+
* had to be scripted with raw curl because these verbs did not exist.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { formatOutput, formatDetail, printSuccess, printWarning } from '../core/output.js';
|
|
23
|
+
import { resolveEndpoint } from '../api/endpoints.js';
|
|
24
|
+
|
|
25
|
+
export function registerSafetyCommands(program, ctx) {
|
|
26
|
+
const { client, auth } = ctx;
|
|
27
|
+
|
|
28
|
+
const safety = program
|
|
29
|
+
.command('safety')
|
|
30
|
+
.description('Kill switch (per-agent E-STOP) and instance DEFCON (human-admin gated mutations)');
|
|
31
|
+
|
|
32
|
+
safety
|
|
33
|
+
.command('status')
|
|
34
|
+
.description('Org halt state: DEFCON level + active per-agent E-STOPs')
|
|
35
|
+
.action(async () => {
|
|
36
|
+
auth.requireAuth();
|
|
37
|
+
const ep = resolveEndpoint('safety.status');
|
|
38
|
+
const data = await client.get(ep.path, { service: ep.service });
|
|
39
|
+
formatDetail(data, { format: program.opts().output });
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
safety
|
|
43
|
+
.command('gate')
|
|
44
|
+
.description('The enforcement verdict a dispatcher gets for this agent right now')
|
|
45
|
+
.argument('<agentId>', 'agent id, e.g. quox, sentinel')
|
|
46
|
+
.action(async (agentId) => {
|
|
47
|
+
auth.requireAuth();
|
|
48
|
+
const ep = resolveEndpoint('safety.gate', { agentId });
|
|
49
|
+
const data = await client.get(ep.path, { service: ep.service });
|
|
50
|
+
formatDetail(data, { format: program.opts().output });
|
|
51
|
+
if (data.blocked) printWarning(`Dispatch for ${agentId} is BLOCKED: ${data.reason}`);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
safety
|
|
55
|
+
.command('estop')
|
|
56
|
+
.description('Activate the per-agent kill switch (HUMAN admin only, enforced server-side)')
|
|
57
|
+
.argument('<agentId>', 'agent id to halt')
|
|
58
|
+
.option('--reason <text>', 'why (lands in the audit trail)')
|
|
59
|
+
.action(async (agentId, options) => {
|
|
60
|
+
auth.requireAuth();
|
|
61
|
+
const ep = resolveEndpoint('safety.estop', { agentId });
|
|
62
|
+
const data = await client.post(ep.path, { service: ep.service, body: { reason: options.reason || null } });
|
|
63
|
+
printSuccess(`E-STOP ACTIVE for ${agentId}${data.was_active ? ' (was already active)' : ''}: new dispatch is blocked at every chokepoint; live loop sessions end at the next turn boundary.`);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
safety
|
|
67
|
+
.command('release')
|
|
68
|
+
.description('Release a per-agent kill switch (HUMAN admin only, enforced server-side)')
|
|
69
|
+
.argument('<agentId>', 'agent id to release')
|
|
70
|
+
.action(async (agentId) => {
|
|
71
|
+
auth.requireAuth();
|
|
72
|
+
const ep = resolveEndpoint('safety.release', { agentId });
|
|
73
|
+
await client.delete(ep.path, { service: ep.service });
|
|
74
|
+
printSuccess(`E-STOP released for ${agentId}.`);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
safety
|
|
78
|
+
.command('defcon')
|
|
79
|
+
.description('Set instance DEFCON: GREEN | BLUE | AMBER | ORANGE (escalate) | RED (full halt)')
|
|
80
|
+
.argument('<level>', 'GREEN, BLUE, AMBER, ORANGE or RED')
|
|
81
|
+
.option('--reason <text>', 'why (lands in the audit trail)')
|
|
82
|
+
.action(async (level, options) => {
|
|
83
|
+
auth.requireAuth();
|
|
84
|
+
const ep = resolveEndpoint('safety.defcon');
|
|
85
|
+
const data = await client.post(ep.path, { service: ep.service, body: { level: level.toUpperCase(), reason: options.reason || null } });
|
|
86
|
+
printSuccess(`DEFCON ${data.old_level} -> ${data.new_level}.`);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
safety
|
|
90
|
+
.command('history')
|
|
91
|
+
.description('Append-only audit trail of every E-STOP and DEFCON change')
|
|
92
|
+
.option('--agent <agentId>', 'filter to one agent')
|
|
93
|
+
.option('--limit <n>', 'max rows', '50')
|
|
94
|
+
.action(async (options) => {
|
|
95
|
+
auth.requireAuth();
|
|
96
|
+
const ep = resolveEndpoint('safety.history');
|
|
97
|
+
const qs = new URLSearchParams();
|
|
98
|
+
if (options.agent) qs.set('agent_id', options.agent);
|
|
99
|
+
qs.set('limit', options.limit);
|
|
100
|
+
const data = await client.get(`${ep.path}?${qs}`, { service: ep.service });
|
|
101
|
+
formatOutput({
|
|
102
|
+
columns: ['created_at', 'scope', 'action', 'agent_id', 'new_value', 'actor_label', 'reason'],
|
|
103
|
+
headers: ['At', 'Scope', 'Action', 'Agent', 'Value', 'Actor', 'Reason'],
|
|
104
|
+
rows: data.history || [],
|
|
105
|
+
}, { format: program.opts().output, emptyMessage: 'No safety state changes recorded.' });
|
|
106
|
+
});
|
|
107
|
+
}
|