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,240 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Objective commands: create, list, get, start, approve, plus the HITL
|
|
3
|
+
* checkpoint verbs (checkpoints, approve-checkpoint, deny-checkpoint).
|
|
4
|
+
*
|
|
5
|
+
* Objectives are served by the tasks service (port 3104), not the auth
|
|
6
|
+
* service. Org/user scoping comes from the JWT (org_id/sub claims) rather
|
|
7
|
+
* than a URL path segment — see services/tasks/middleware/auth.js.
|
|
8
|
+
*
|
|
9
|
+
* Two approval gates exist and must not be confused:
|
|
10
|
+
* `approve` resolves a decomposed objective's DAG plan.
|
|
11
|
+
* `approve-checkpoint` resolves one HITL checkpoint (pre-start gate or a
|
|
12
|
+
* live loop gate: budget, risk, interval).
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { formatOutput, formatDetail, printSuccess } from '../core/output.js';
|
|
16
|
+
import { parseJSON } from './helpers.js';
|
|
17
|
+
import { resolveEndpoint } from '../api/endpoints.js';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Format a timestamp as a short relative-time string ("5m ago").
|
|
21
|
+
*
|
|
22
|
+
* Checkpoint rows carry SQLite `datetime('now')` values ("2026-08-14
|
|
23
|
+
* 04:00:00"), which are UTC but have no zone marker, so Node would read them
|
|
24
|
+
* as local time and skew the age by the offset. Normalise that shape to ISO
|
|
25
|
+
* UTC first; anything already ISO passes straight through.
|
|
26
|
+
*/
|
|
27
|
+
function relativeTime(ts) {
|
|
28
|
+
if (!ts) return '—';
|
|
29
|
+
const iso = /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/.test(ts)
|
|
30
|
+
? `${ts.replace(' ', 'T')}Z`
|
|
31
|
+
: ts;
|
|
32
|
+
const then = new Date(iso).getTime();
|
|
33
|
+
if (Number.isNaN(then)) return '—';
|
|
34
|
+
const diffSec = Math.max(0, Math.floor((Date.now() - then) / 1000));
|
|
35
|
+
if (diffSec < 60) return `${diffSec}s ago`;
|
|
36
|
+
const diffMin = Math.floor(diffSec / 60);
|
|
37
|
+
if (diffMin < 60) return `${diffMin}m ago`;
|
|
38
|
+
const diffHr = Math.floor(diffMin / 60);
|
|
39
|
+
if (diffHr < 24) return `${diffHr}h ago`;
|
|
40
|
+
const diffDay = Math.floor(diffHr / 24);
|
|
41
|
+
return `${diffDay}d ago`;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Accumulate repeated `--capability-scope` flags into one array of raw
|
|
46
|
+
* strings. Each value may itself be a comma-separated list; splitting and
|
|
47
|
+
* trimming happen in the action, alongside the other body fields.
|
|
48
|
+
*/
|
|
49
|
+
function collectScope(value, previous) {
|
|
50
|
+
return (previous || []).concat(value);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function registerObjectiveCommands(program, ctx) {
|
|
54
|
+
const { client, auth } = ctx;
|
|
55
|
+
|
|
56
|
+
const objective = program
|
|
57
|
+
.command('objective')
|
|
58
|
+
.description('Manage objectives (autonomous multi-step goals)');
|
|
59
|
+
|
|
60
|
+
objective
|
|
61
|
+
.command('create')
|
|
62
|
+
.description('Create a new objective (DAG plan by default, or a QLoop loop with --execution-mode loop)')
|
|
63
|
+
.argument('<goal>', 'objective title / goal statement')
|
|
64
|
+
.option('--description <text>', 'longer description')
|
|
65
|
+
.option('--team <id>', 'team ID to run the objective under')
|
|
66
|
+
.option('--max-tokens <n>', 'max token budget')
|
|
67
|
+
.option('--max-tool-calls <n>', 'max tool call budget')
|
|
68
|
+
.option('--max-duration-ms <n>', 'max wall-clock duration in ms')
|
|
69
|
+
.option('--require-approval', 'require approval before starting')
|
|
70
|
+
.option('--checkpoint', 'checkpoint after each task')
|
|
71
|
+
.option('--goal-condition <text>', 're-observe loop exit condition')
|
|
72
|
+
.option('--max-iterations <n>', 'max re-observe loop iterations')
|
|
73
|
+
.option('--metadata <json>', 'arbitrary metadata as JSON')
|
|
74
|
+
.option('--execution-mode <mode>', 'dag|loop. A loop runs a QLoop backend instead of a reviewable DAG plan, so it ALWAYS requires a before_start approval (see `quox objective checkpoints` and `approve-checkpoint`)')
|
|
75
|
+
.option('--loop-backend <backend>', 'claude|codex, loop mode only (default claude)')
|
|
76
|
+
.option('--capability-scope <tools>', 'comma-separated tool names the loop may call (repeatable); empty scope escalates every tool', collectScope)
|
|
77
|
+
.option('--system-prompt <text>', 'per-objective system prompt, carried verbatim to the run')
|
|
78
|
+
.action(async (goal, options) => {
|
|
79
|
+
auth.requireAuth();
|
|
80
|
+
const body = { title: goal };
|
|
81
|
+
if (options.description) body.description = options.description;
|
|
82
|
+
if (options.team) body.team_id = options.team;
|
|
83
|
+
if (options.maxTokens) body.max_tokens = Number(options.maxTokens);
|
|
84
|
+
if (options.maxToolCalls) body.max_tool_calls = Number(options.maxToolCalls);
|
|
85
|
+
if (options.maxDurationMs) body.max_duration_ms = Number(options.maxDurationMs);
|
|
86
|
+
if (options.requireApproval) body.require_approval_before_start = true;
|
|
87
|
+
if (options.checkpoint) body.checkpoint_after_each_task = true;
|
|
88
|
+
if (options.goalCondition) body.goal_condition = options.goalCondition;
|
|
89
|
+
if (options.maxIterations) body.max_iterations = Number(options.maxIterations);
|
|
90
|
+
if (options.metadata) body.metadata = parseJSON(options.metadata, '--metadata');
|
|
91
|
+
if (options.executionMode) body.execution_mode = options.executionMode;
|
|
92
|
+
if (options.loopBackend) body.loop_backend = options.loopBackend;
|
|
93
|
+
if (options.capabilityScope) {
|
|
94
|
+
const scope = options.capabilityScope
|
|
95
|
+
.flatMap((raw) => raw.split(','))
|
|
96
|
+
.map((t) => t.trim())
|
|
97
|
+
.filter(Boolean);
|
|
98
|
+
if (scope.length > 0) body.capability_scope = scope;
|
|
99
|
+
}
|
|
100
|
+
if (options.systemPrompt) body.system_prompt = options.systemPrompt;
|
|
101
|
+
|
|
102
|
+
const data = await client.post(resolveEndpoint('objectives.create').path, { service: resolveEndpoint('objectives.create').service, body });
|
|
103
|
+
printSuccess(`Objective created: ${data.id}`);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
objective
|
|
107
|
+
.command('list')
|
|
108
|
+
.description('List objectives')
|
|
109
|
+
.option('--status <status>', 'filter by status')
|
|
110
|
+
.option('--team <id>', 'filter by team ID')
|
|
111
|
+
.option('--limit <n>', 'max results', '50')
|
|
112
|
+
.action(async (options) => {
|
|
113
|
+
auth.requireAuth();
|
|
114
|
+
const query = { limit: options.limit };
|
|
115
|
+
if (options.status) query.status = options.status;
|
|
116
|
+
if (options.team) query.team_id = options.team;
|
|
117
|
+
const data = await client.get(resolveEndpoint('objectives.list').path, { service: resolveEndpoint('objectives.list').service, query });
|
|
118
|
+
formatOutput({
|
|
119
|
+
columns: ['id', 'title', 'status', 'team_id', 'created_at'],
|
|
120
|
+
headers: ['ID', 'Title', 'Status', 'Team', 'Created'],
|
|
121
|
+
rows: data.objectives || data,
|
|
122
|
+
}, { format: program.opts().output });
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
objective
|
|
126
|
+
.command('get')
|
|
127
|
+
.description('Get objective details (tasks, checkpoints, DAG)')
|
|
128
|
+
.argument('<id>', 'objective ID')
|
|
129
|
+
.action(async (id) => {
|
|
130
|
+
auth.requireAuth();
|
|
131
|
+
const data = await client.get(resolveEndpoint('objectives.get', { id }).path, { service: resolveEndpoint('objectives.get', { id }).service });
|
|
132
|
+
formatDetail(data, { format: program.opts().output, title: data.title });
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
objective
|
|
136
|
+
.command('start')
|
|
137
|
+
.description('Start an objective (decompose into tasks)')
|
|
138
|
+
.argument('<id>', 'objective ID')
|
|
139
|
+
.option('--skip-checkpoint', 'bypass pre-start approval checkpoint')
|
|
140
|
+
.action(async (id, options) => {
|
|
141
|
+
auth.requireAuth();
|
|
142
|
+
const body = {};
|
|
143
|
+
if (options.skipCheckpoint) body.skip_checkpoint = true;
|
|
144
|
+
const data = await client.post(resolveEndpoint('objectives.start', { id }).path, { service: resolveEndpoint('objectives.start', { id }).service, body });
|
|
145
|
+
printSuccess(`Objective ${id}: ${data.status}${data.message ? ` — ${data.message}` : ''}`);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
objective
|
|
149
|
+
.command('approve')
|
|
150
|
+
.description('Approve a decomposed objective\'s plan (starts execution)')
|
|
151
|
+
.argument('<id>', 'objective ID')
|
|
152
|
+
.action(async (id) => {
|
|
153
|
+
auth.requireAuth();
|
|
154
|
+
const data = await client.post(resolveEndpoint('objectives.approve-plan', { id }).path, { service: resolveEndpoint('objectives.approve-plan', { id }).service });
|
|
155
|
+
printSuccess(`Objective ${id}: ${data.status}${data.message ? ` — ${data.message}` : ''}`);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
objective
|
|
159
|
+
.command('checkpoints')
|
|
160
|
+
.description('List pending HITL checkpoints across the org (approval queue)')
|
|
161
|
+
.option('--include-reviewed', 'include checkpoints that have already been reviewed')
|
|
162
|
+
.option('--limit <n>', 'max results', '100')
|
|
163
|
+
.action(async (options) => {
|
|
164
|
+
auth.requireAuth();
|
|
165
|
+
const query = { limit: options.limit };
|
|
166
|
+
if (options.includeReviewed) query.include_reviewed = 'true';
|
|
167
|
+
const data = await client.get(resolveEndpoint('objectives.checkpoints-pending').path, { service: resolveEndpoint('objectives.checkpoints-pending').service, query });
|
|
168
|
+
const rows = (data.checkpoints || data || []).map((cp) => ({
|
|
169
|
+
id: cp.id,
|
|
170
|
+
objective_title: cp.objectiveTitle,
|
|
171
|
+
checkpoint_type: cp.checkpointType,
|
|
172
|
+
age: relativeTime(cp.createdAt),
|
|
173
|
+
title: cp.title,
|
|
174
|
+
}));
|
|
175
|
+
formatOutput({
|
|
176
|
+
columns: ['id', 'objective_title', 'checkpoint_type', 'age', 'title'],
|
|
177
|
+
headers: ['Checkpoint', 'Objective', 'Type', 'Age', 'Title'],
|
|
178
|
+
rows,
|
|
179
|
+
}, { format: program.opts().output, emptyMessage: 'No pending checkpoints.' });
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
objective
|
|
183
|
+
.command('approve-checkpoint')
|
|
184
|
+
.description('Approve one HITL checkpoint (not the DAG plan, see `approve`)')
|
|
185
|
+
.argument('<objectiveId>', 'objective ID')
|
|
186
|
+
.argument('<checkpointId>', 'checkpoint ID')
|
|
187
|
+
.option('--notes <text>', 'review notes recorded against the decision')
|
|
188
|
+
.action(async (objectiveId, checkpointId, options) => {
|
|
189
|
+
auth.requireAuth();
|
|
190
|
+
const body = {};
|
|
191
|
+
if (options.notes) body.notes = options.notes;
|
|
192
|
+
const params = { id: objectiveId, checkpointId };
|
|
193
|
+
const data = await client.post(resolveEndpoint('objectives.checkpoint-approve', params).path, { service: resolveEndpoint('objectives.checkpoint-approve', params).service, body });
|
|
194
|
+
printSuccess(`Checkpoint ${checkpointId}: ${data.status}${data.objective_status ? ` (objective ${data.objective_status})` : ''}`);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
objective
|
|
198
|
+
.command('deny-checkpoint')
|
|
199
|
+
.description('Reject one HITL checkpoint (not the DAG plan, see `approve`)')
|
|
200
|
+
.argument('<objectiveId>', 'objective ID')
|
|
201
|
+
.argument('<checkpointId>', 'checkpoint ID')
|
|
202
|
+
.option('--notes <text>', 'review notes recorded against the decision')
|
|
203
|
+
.action(async (objectiveId, checkpointId, options) => {
|
|
204
|
+
auth.requireAuth();
|
|
205
|
+
const body = {};
|
|
206
|
+
if (options.notes) body.notes = options.notes;
|
|
207
|
+
const params = { id: objectiveId, checkpointId };
|
|
208
|
+
const data = await client.post(resolveEndpoint('objectives.checkpoint-reject', params).path, { service: resolveEndpoint('objectives.checkpoint-reject', params).service, body });
|
|
209
|
+
printSuccess(`Checkpoint ${checkpointId}: ${data.status}${data.objective_status ? ` (objective ${data.objective_status})` : ''}`);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
objective
|
|
213
|
+
.command('cancel')
|
|
214
|
+
.description('Cancel an objective (pending ones included, e.g. proof leftovers that never started); its open tasks are cancelled too. For a RUNNING loop prefer `halt`, which also kills the live session.')
|
|
215
|
+
.argument('<objectiveId>', 'objective ID')
|
|
216
|
+
.action(async (objectiveId) => {
|
|
217
|
+
auth.requireAuth();
|
|
218
|
+
const ep = resolveEndpoint('objectives.cancel', { id: objectiveId });
|
|
219
|
+
const data = await client.delete(ep.path, { service: ep.service });
|
|
220
|
+
printSuccess(`Objective ${objectiveId}: ${data?.status || 'cancelled'}`);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
objective
|
|
224
|
+
.command('halt')
|
|
225
|
+
.description('Halt a running QLoop loop ON DEMAND (unconditional human stop; no pending gate needed). Fires the same signed-kill as a gate deny.')
|
|
226
|
+
.argument('<objectiveId>', 'loop objective ID')
|
|
227
|
+
.option('--notes <text>', 'reason recorded against the halt')
|
|
228
|
+
.action(async (objectiveId, options) => {
|
|
229
|
+
auth.requireAuth();
|
|
230
|
+
const body = {};
|
|
231
|
+
if (options.notes) body.notes = options.notes;
|
|
232
|
+
const params = { id: objectiveId };
|
|
233
|
+
const data = await client.post(resolveEndpoint('objectives.halt', params).path, { service: resolveEndpoint('objectives.halt', params).service, body });
|
|
234
|
+
if (data.status === 'halted') {
|
|
235
|
+
printSuccess(`Loop ${objectiveId} halted (interrupted=${data.interrupted}, objective ${data.objective_status})`);
|
|
236
|
+
} else {
|
|
237
|
+
printSuccess(`Loop ${objectiveId}: ${data.status}${data.reason ? ` (${data.reason})` : ''}`);
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `quox offload` : run a heavy command on a pool worker VM and behave like running it locally.
|
|
3
|
+
*
|
|
4
|
+
* quox offload [--dir <path>] [--fresh] [--keep] [--cores N --memory-gb N --disk-gb N] -- <command...>
|
|
5
|
+
* quox offload status
|
|
6
|
+
* quox offload release
|
|
7
|
+
*
|
|
8
|
+
* Engine: lib/core/offload.js. The worker is leased through the human pool REST API
|
|
9
|
+
* (same as `quox vm pool lease`) with a dedicated key at ~/.config/quox/offload_ed25519.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import path from 'node:path';
|
|
13
|
+
import { printInfo } from '../core/output.js';
|
|
14
|
+
import { makePoolRest } from '../core/vm.js';
|
|
15
|
+
import { OffloadError, realDeps, runOffload, releaseWorker, workerStatus, gitToplevel, WORKER_NAME_RE } from '../core/offload.js';
|
|
16
|
+
|
|
17
|
+
export function registerOffloadCommands(program, ctx, { deps: injected } = {}) {
|
|
18
|
+
const { client, auth } = ctx;
|
|
19
|
+
const json = () => program.opts().output === 'json';
|
|
20
|
+
const base = () => injected || realDeps({ rest: makePoolRest({ client }) });
|
|
21
|
+
// --worker <name> / QUOX_OFFLOAD_WORKER: your own worker (state, lease, size), not the box's shared one.
|
|
22
|
+
const mk = (options = {}) => {
|
|
23
|
+
const d = base();
|
|
24
|
+
const name = options.worker ?? d.env?.QUOX_OFFLOAD_WORKER;
|
|
25
|
+
if (name !== undefined && name !== '' && !WORKER_NAME_RE.test(String(name))) {
|
|
26
|
+
throw new OffloadError(`worker name must be lowercase letters, digits and dashes (up to 32), got ${JSON.stringify(name)}`);
|
|
27
|
+
}
|
|
28
|
+
return name ? Object.assign(Object.create(Object.getPrototypeOf(d)), d, { worker: String(name) }) : d;
|
|
29
|
+
};
|
|
30
|
+
const WORKER_HELP = 'use your own named worker (its own lease and size) instead of the shared one; or set QUOX_OFFLOAD_WORKER';
|
|
31
|
+
|
|
32
|
+
const off = program
|
|
33
|
+
.command('offload')
|
|
34
|
+
.description('Run a heavy command on a pool worker VM (synced dir, cached npm ci, live output, same exit code)')
|
|
35
|
+
.argument('[command...]', 'command to run remotely (put it after --)')
|
|
36
|
+
.option('--dir <path>', 'directory to sync (default: git toplevel of the current directory)')
|
|
37
|
+
.option('--fresh', 'release the current worker, lease a new one and reinstall dependencies')
|
|
38
|
+
.option('--keep', 'leave the worker leased for reuse (the default; kept for compatibility)')
|
|
39
|
+
.option('--cores <n>', 'vCPU cores for a newly leased worker')
|
|
40
|
+
.option('--memory-gb <n>', 'memory in GB for a newly leased worker')
|
|
41
|
+
.option('--disk-gb <n>', 'disk in GB for a newly leased worker')
|
|
42
|
+
.option('--worker <name>', WORKER_HELP)
|
|
43
|
+
.option('--docker', 'also give the worker Docker (user hyp in the docker group) and build the quox-sandbox images from the synced repo when they are missing or changed')
|
|
44
|
+
.allowExcessArguments(true)
|
|
45
|
+
.action(async (command, options) => {
|
|
46
|
+
auth.requireAuth();
|
|
47
|
+
const cwd = process.cwd();
|
|
48
|
+
const dir = path.resolve(options.dir || gitToplevel(cwd));
|
|
49
|
+
try {
|
|
50
|
+
const code = await runOffload(mk(options), {
|
|
51
|
+
dir, cwd, command, fresh: !!options.fresh, docker: !!options.docker,
|
|
52
|
+
cores: options.cores, memoryGb: options.memoryGb, diskGb: options.diskGb,
|
|
53
|
+
});
|
|
54
|
+
process.exitCode = code;
|
|
55
|
+
} catch (e) {
|
|
56
|
+
if (!(e instanceof OffloadError)) throw e;
|
|
57
|
+
process.stderr.write(`offload: ${e.message}\n`);
|
|
58
|
+
process.exitCode = 1;
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
off.command('status')
|
|
63
|
+
.description('Show your offload worker: vmid, ip, lease expiry, synced dirs')
|
|
64
|
+
.option('--worker <name>', WORKER_HELP)
|
|
65
|
+
.action(async (options) => {
|
|
66
|
+
auth.requireAuth();
|
|
67
|
+
const s = await workerStatus(mk(options));
|
|
68
|
+
if (json()) { console.log(JSON.stringify(s, null, 2)); return; }
|
|
69
|
+
if (!s.worker) { printInfo('no offload worker leased (it is leased on the next `quox offload -- <command>`)'); return; }
|
|
70
|
+
const w = s.worker;
|
|
71
|
+
console.log(`worker ${w.vmid} ${w.ip || '?'} ${w.state} lessee ${w.lessee} lease expires ${w.lease_expires_at || '?'}`);
|
|
72
|
+
for (const d of s.dirs) console.log(` synced ${d.local} -> ${d.remote} ${d.synced_at || ''}`);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
off.command('release')
|
|
76
|
+
.description('Release (destroy) your offload worker via the pool')
|
|
77
|
+
.option('--worker <name>', WORKER_HELP)
|
|
78
|
+
.action(async (options) => {
|
|
79
|
+
auth.requireAuth();
|
|
80
|
+
try {
|
|
81
|
+
const vmid = await releaseWorker(mk(options));
|
|
82
|
+
if (json()) console.log(JSON.stringify({ released: vmid }));
|
|
83
|
+
else printInfo(vmid ? `released worker ${vmid}` : 'no offload worker to release');
|
|
84
|
+
} catch (e) {
|
|
85
|
+
process.stderr.write(`offload: release failed: ${e.message}\n`);
|
|
86
|
+
process.exitCode = 1;
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
}
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `quox onboard <host>`: guided, idempotent, resumable tenant-onboarding
|
|
3
|
+
* wizard.
|
|
4
|
+
*
|
|
5
|
+
* This is NET-NEW GLUE over verbs that already exist (dev doctor, org create,
|
|
6
|
+
* plan roadmap create, engagement create/target/verify/gripe-target/schedule,
|
|
7
|
+
* vault list). It does NOT reimplement any of their logic: every REST call
|
|
8
|
+
* goes through ctx.client + resolveEndpoint, exactly as the individual verbs
|
|
9
|
+
* do. The wizard's only job is to run the mechanical steps in the right order,
|
|
10
|
+
* skip anything already done, and STOP cleanly at the two human-auth gates
|
|
11
|
+
* (dev-enable + profile-verify, and the GitHub PAT) with the exact copy-paste
|
|
12
|
+
* command the operator must run before re-running the wizard.
|
|
13
|
+
*
|
|
14
|
+
* Design rules baked in here:
|
|
15
|
+
* - Idempotent: every step CHECKs before it acts, so a second run creates no
|
|
16
|
+
* duplicates and simply resumes at the first incomplete step.
|
|
17
|
+
* - Honest gates: a gated step is never reported as done. It prints a WAITING
|
|
18
|
+
* banner and the wizard returns (exit 0, since a gate is a pause, not an error).
|
|
19
|
+
* - Secret-free: the GitHub PAT is instruct-and-stop in v1. The token never
|
|
20
|
+
* enters this process; the operator adds it with `quox vault create
|
|
21
|
+
* --credentials-stdin` and re-runs.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import { resolveEndpoint } from '../api/endpoints.js';
|
|
25
|
+
import { line as glyphLine, printEvent } from '../core/quoxglyph.js';
|
|
26
|
+
|
|
27
|
+
// ── status line markers ────────────────────────────────────────────────────
|
|
28
|
+
// One line per step. [ok] already there, [+] created/changed, [wait] a human
|
|
29
|
+
// gate blocks progress. Kept as plain console.log (not the chalk helpers) so
|
|
30
|
+
// the markers are trivially assertable in tests and scannable in a wall of
|
|
31
|
+
// tool output.
|
|
32
|
+
function ok(msg) { console.log(` [ok] ${msg}`); }
|
|
33
|
+
function created(msg) { console.log(` [+] ${msg}`); }
|
|
34
|
+
function waiting(msg) { console.log(` [wait] needs you: ${msg}`); }
|
|
35
|
+
|
|
36
|
+
// ── small pure helpers (exported for unit tests) ───────────────────────────
|
|
37
|
+
|
|
38
|
+
/** Slug for a display name: lowercase, non-alphanumeric collapsed to '-'. */
|
|
39
|
+
export function slugify(name) {
|
|
40
|
+
return String(name || '')
|
|
41
|
+
.toLowerCase()
|
|
42
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
43
|
+
.replace(/^-+|-+$/g, '')
|
|
44
|
+
|| 'tenant';
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Infer an engagement target type from its value.
|
|
49
|
+
* - an ipv4 or ipv4/cidr -> 'cidr'
|
|
50
|
+
* - a scheme://... or host:port -> 'url'
|
|
51
|
+
* - a bare hostname -> 'domain'
|
|
52
|
+
* Default for a bare IP is 'cidr' (the engagement scope model treats a single
|
|
53
|
+
* host as a /32 cidr target).
|
|
54
|
+
*/
|
|
55
|
+
export function inferTargetType(value) {
|
|
56
|
+
const v = String(value || '').trim();
|
|
57
|
+
// ipv4 with optional /nn suffix
|
|
58
|
+
if (/^\d{1,3}(\.\d{1,3}){3}(\/\d{1,2})?$/.test(v)) return 'cidr';
|
|
59
|
+
// explicit scheme, or a host:port pair
|
|
60
|
+
if (/:\/\//.test(v) || /^[^\s/]+:\d+$/.test(v)) return 'url';
|
|
61
|
+
return 'domain';
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** curl install command for a host that is not yet a fleet-enrolled dev server. */
|
|
65
|
+
export function buildOnboardInstallCommand(collectorBase, host) {
|
|
66
|
+
const base = String(collectorBase || '').replace(/\/+$/, '');
|
|
67
|
+
// REL-423: fail loud, never `curl | sudo bash` (that pipe reports success when the download fails).
|
|
68
|
+
return `( f=$(mktemp) && trap 'rm -f "$f"' EXIT && curl -fsSL ${base}/install -o "$f" && sudo bash "$f" --host-id ${host} )`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ── the wizard ──────────────────────────────────────────────────────────────
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Run the onboarding orchestration. Returns { status: 'COMPLETE' | 'WAITING' }.
|
|
75
|
+
* Exported so it can be unit-tested directly with an injected fake client, and
|
|
76
|
+
* so a caller can branch on the outcome.
|
|
77
|
+
*/
|
|
78
|
+
export async function runOnboard(host, options, ctx) {
|
|
79
|
+
const { client, auth, config } = ctx;
|
|
80
|
+
auth.requireAuth();
|
|
81
|
+
|
|
82
|
+
const name = options.name;
|
|
83
|
+
const targetValue = options.target || host;
|
|
84
|
+
const envTier = options.environmentTier || 'dev';
|
|
85
|
+
const cron = options.scheduleCron || '0 2 * * 3';
|
|
86
|
+
const tool = options.tool || 'vuln_scan';
|
|
87
|
+
const slug = slugify(name);
|
|
88
|
+
|
|
89
|
+
printEvent(glyphLine('quox', `onboarding ${host}`));
|
|
90
|
+
|
|
91
|
+
// ── Step 1: AGENT + DEV-ENABLE gate ──────────────────────────────────────
|
|
92
|
+
// Read real registry state; never infer readiness from an enable having
|
|
93
|
+
// returned 200. Not enrolled -> install. Enrolled but not dev-enabled or not
|
|
94
|
+
// profile_verified -> the enable/seam-key gate.
|
|
95
|
+
const orgIdForDev = auth.getOrgId(ctx.program?.opts?.().org);
|
|
96
|
+
const serversEp = resolveEndpoint('v1.dev.servers.list');
|
|
97
|
+
const serversResp = await client.get(serversEp.path, {
|
|
98
|
+
service: serversEp.service,
|
|
99
|
+
query: orgIdForDev ? { org_id: orgIdForDev } : {},
|
|
100
|
+
});
|
|
101
|
+
const servers = Array.isArray(serversResp?.servers) ? serversResp.servers : [];
|
|
102
|
+
const server = servers.find((s) => s.host_id === host);
|
|
103
|
+
|
|
104
|
+
if (!server) {
|
|
105
|
+
const collectorBase = config?.getServiceUrl ? config.getServiceUrl('collector') : '<collector-url>';
|
|
106
|
+
waiting(`${host} is not a fleet-enrolled dev server. Install the agent, then re-run:`);
|
|
107
|
+
console.log(` ${buildOnboardInstallCommand(collectorBase, host)}`);
|
|
108
|
+
return stop(host);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const enabledCaps = Array.isArray(server.enabled_capabilities) ? server.enabled_capabilities : [];
|
|
112
|
+
if (!server.profile_verified || enabledCaps.length === 0) {
|
|
113
|
+
const why = !server.profile_verified ? 'profile not verified' : 'no capabilities enabled';
|
|
114
|
+
waiting(`${host} is enrolled but not ready (${why}). Run (as an owner/admin):`);
|
|
115
|
+
console.log(` quox dev enable ${host}`);
|
|
116
|
+
console.log(' profile_verified needs the seam key plus an agent restart.');
|
|
117
|
+
console.log(` Confirm with: quox dev doctor ${host}`);
|
|
118
|
+
return stop(host);
|
|
119
|
+
}
|
|
120
|
+
ok(`${host} is a dev-enabled, profile-verified dev server`);
|
|
121
|
+
|
|
122
|
+
// ── Step 2: ORG ──────────────────────────────────────────────────────────
|
|
123
|
+
const orgsEp = resolveEndpoint('orgs.list');
|
|
124
|
+
const orgsResp = await client.get(orgsEp.path, { service: orgsEp.service });
|
|
125
|
+
const orgs = orgsResp?.orgs || orgsResp?.organizations || orgsResp || [];
|
|
126
|
+
let org = (Array.isArray(orgs) ? orgs : []).find(
|
|
127
|
+
(o) => o.name === name || o.slug === slug || o.id === name,
|
|
128
|
+
);
|
|
129
|
+
let orgId;
|
|
130
|
+
if (org) {
|
|
131
|
+
orgId = org.id;
|
|
132
|
+
ok(`org "${name}" exists (${orgId})`);
|
|
133
|
+
} else {
|
|
134
|
+
const createEp = resolveEndpoint('orgs.create');
|
|
135
|
+
const createResp = await client.post(createEp.path, {
|
|
136
|
+
service: createEp.service,
|
|
137
|
+
body: { name, slug },
|
|
138
|
+
});
|
|
139
|
+
org = createResp?.organization || createResp?.org || createResp;
|
|
140
|
+
orgId = org.id;
|
|
141
|
+
created(`org "${name}" (${orgId})`);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// ── Step 3: ROADMAP ───────────────────────────────────────────────────────
|
|
145
|
+
const roadmapId = slug;
|
|
146
|
+
const roadmapsEp = resolveEndpoint('quoxplan.roadmaps.list');
|
|
147
|
+
const roadmapsResp = await client.get(roadmapsEp.path, { service: roadmapsEp.service });
|
|
148
|
+
const roadmaps = roadmapsResp?.roadmaps || [];
|
|
149
|
+
const existingRoadmap = roadmaps.find((r) => r.id === roadmapId || r.org_id === orgId);
|
|
150
|
+
if (existingRoadmap) {
|
|
151
|
+
ok(`roadmap "${existingRoadmap.id}" exists for this tenant`);
|
|
152
|
+
} else {
|
|
153
|
+
const rmCreateEp = resolveEndpoint('quoxplan.roadmaps.create');
|
|
154
|
+
await client.post(rmCreateEp.path, {
|
|
155
|
+
service: rmCreateEp.service,
|
|
156
|
+
body: { org_id: orgId, id: roadmapId, name: `${name} delivery`, actor: 'quox-cli' },
|
|
157
|
+
});
|
|
158
|
+
created(`roadmap "${roadmapId}" for org ${orgId}`);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// ── Step 4: ENGAGEMENT ────────────────────────────────────────────────────
|
|
162
|
+
const engagementName = `${name} security`;
|
|
163
|
+
const engListEp = resolveEndpoint('engagement.list');
|
|
164
|
+
const engListResp = await client.get(engListEp.path, { service: engListEp.service });
|
|
165
|
+
const engagements = engListResp?.engagements || [];
|
|
166
|
+
let engagement = engagements.find((e) => e.name === engagementName);
|
|
167
|
+
let engagementId;
|
|
168
|
+
if (engagement) {
|
|
169
|
+
engagementId = engagement.id;
|
|
170
|
+
ok(`engagement "${engagementName}" exists (${engagementId})`);
|
|
171
|
+
} else {
|
|
172
|
+
const engCreateEp = resolveEndpoint('engagement.create');
|
|
173
|
+
const engResp = await client.post(engCreateEp.path, {
|
|
174
|
+
service: engCreateEp.service,
|
|
175
|
+
body: { name: engagementName, environment_tier: envTier, risk_ceiling: 'safe_active' },
|
|
176
|
+
});
|
|
177
|
+
engagement = engResp.engagement;
|
|
178
|
+
engagementId = engagement.id;
|
|
179
|
+
created(`engagement "${engagementName}" (${engagementId})`);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// ── Step 5: TARGET ────────────────────────────────────────────────────────
|
|
183
|
+
const engGetEp = resolveEndpoint('engagement.get', { id: engagementId });
|
|
184
|
+
const engDetail = await client.get(engGetEp.path, { service: engGetEp.service });
|
|
185
|
+
const currentEngagement = engDetail?.engagement || engagement;
|
|
186
|
+
const targets = Array.isArray(engDetail?.targets) ? engDetail.targets : [];
|
|
187
|
+
let target = targets.find((t) => t.value === targetValue);
|
|
188
|
+
let targetVerified = false;
|
|
189
|
+
|
|
190
|
+
if (!target) {
|
|
191
|
+
const type = inferTargetType(targetValue);
|
|
192
|
+
const addEp = resolveEndpoint('engagement.target.create', { id: engagementId });
|
|
193
|
+
const addResp = await client.post(addEp.path, {
|
|
194
|
+
service: addEp.service,
|
|
195
|
+
body: { type, value: targetValue, verification_method: 'quoxagent_install' },
|
|
196
|
+
});
|
|
197
|
+
target = addResp.target;
|
|
198
|
+
created(`target ${targetValue} (${type}) added`);
|
|
199
|
+
const verEp = resolveEndpoint('engagement.target.verify', { id: engagementId, targetId: target.id });
|
|
200
|
+
const verResp = await client.post(verEp.path, { service: verEp.service, body: {} });
|
|
201
|
+
targetVerified = verResp?.target?.verification_status === 'verified';
|
|
202
|
+
if (targetVerified) created(`target ${targetValue} verified`);
|
|
203
|
+
else waiting(`target ${targetValue} did not verify (status: ${verResp?.target?.verification_status || 'unknown'})`);
|
|
204
|
+
} else if (target.verification_status === 'verified') {
|
|
205
|
+
targetVerified = true;
|
|
206
|
+
ok(`target ${targetValue} already verified`);
|
|
207
|
+
} else {
|
|
208
|
+
const verEp = resolveEndpoint('engagement.target.verify', { id: engagementId, targetId: target.id });
|
|
209
|
+
const verResp = await client.post(verEp.path, { service: verEp.service, body: {} });
|
|
210
|
+
targetVerified = verResp?.target?.verification_status === 'verified';
|
|
211
|
+
if (targetVerified) created(`target ${targetValue} verified`);
|
|
212
|
+
else waiting(`target ${targetValue} did not verify (status: ${verResp?.target?.verification_status || 'unknown'})`);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// ── Step 6: GITHUB PAT gate ───────────────────────────────────────────────
|
|
216
|
+
// The repo is only meaningful if a customer GitHub connector token exists to
|
|
217
|
+
// file into it. Check the org vault for a github credential; if absent, STOP
|
|
218
|
+
// with the exact instruct-and-stop command (secret via --credentials-stdin,
|
|
219
|
+
// NEVER argv), and do NOT touch gripe target or schedule.
|
|
220
|
+
const repo = options.repo;
|
|
221
|
+
if (repo) {
|
|
222
|
+
const vaultEp = resolveEndpoint('vault.list', { orgId });
|
|
223
|
+
const vaultResp = await client.get(vaultEp.path, {
|
|
224
|
+
service: vaultEp.service,
|
|
225
|
+
query: { type: 'github' },
|
|
226
|
+
});
|
|
227
|
+
const creds = vaultResp?.credentials || vaultResp || [];
|
|
228
|
+
const githubCred = (Array.isArray(creds) ? creds : []).find((c) => c.integration_type === 'github');
|
|
229
|
+
if (!githubCred) {
|
|
230
|
+
waiting(`no GitHub connector credential in org ${orgId}. Add the fine-grained PAT, then re-run:`);
|
|
231
|
+
console.log(` printf '%s' '<PAT>' | quox vault create --org ${orgId} --type github --label "${name} github" --credentials-stdin`);
|
|
232
|
+
console.log(` (the JSON on stdin is {"token":"<PAT>"}; scope the PAT to ${repo} with Issues read/write)`);
|
|
233
|
+
return stop(host, { orgId, roadmapId, engagementId, targetValue, targetVerified });
|
|
234
|
+
}
|
|
235
|
+
ok(`GitHub connector credential present in org ${orgId}`);
|
|
236
|
+
|
|
237
|
+
// ── Step 7: GRIPE TARGET ────────────────────────────────────────────────
|
|
238
|
+
const wantRepo = repo;
|
|
239
|
+
const wantConnector = orgId;
|
|
240
|
+
if (currentEngagement.gripe_repo === wantRepo && currentEngagement.gripe_connector_org_id === wantConnector) {
|
|
241
|
+
ok(`gripe target already ${wantRepo} via org ${wantConnector}`);
|
|
242
|
+
} else {
|
|
243
|
+
const gtEp = resolveEndpoint('engagement.gripe_target.set', { id: engagementId });
|
|
244
|
+
await client.patch(gtEp.path, {
|
|
245
|
+
service: gtEp.service,
|
|
246
|
+
body: { gripe_repo: wantRepo, gripe_connector_org_id: wantConnector },
|
|
247
|
+
});
|
|
248
|
+
created(`gripe target -> ${wantRepo} via org ${wantConnector}`);
|
|
249
|
+
}
|
|
250
|
+
} else {
|
|
251
|
+
ok('no --repo given; skipping GitHub connector and gripe target');
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// ── Step 8: SCHEDULE ──────────────────────────────────────────────────────
|
|
255
|
+
const schedListEp = resolveEndpoint('engagement.schedule.list', { id: engagementId });
|
|
256
|
+
const schedResp = await client.get(schedListEp.path, { service: schedListEp.service });
|
|
257
|
+
const schedules = Array.isArray(schedResp?.schedules) ? schedResp.schedules : [];
|
|
258
|
+
let schedule = schedules.find((s) => s.target_value === targetValue && s.tool === tool);
|
|
259
|
+
if (schedule) {
|
|
260
|
+
ok(`schedule for ${tool} on ${targetValue} exists (next: ${schedule.next_run_at || 'n/a'})`);
|
|
261
|
+
} else {
|
|
262
|
+
const schedCreateEp = resolveEndpoint('engagement.schedule.create', { id: engagementId });
|
|
263
|
+
const createResp = await client.post(schedCreateEp.path, {
|
|
264
|
+
service: schedCreateEp.service,
|
|
265
|
+
body: { target_value: targetValue, tool, schedule_kind: 'cron', cron_expression: cron },
|
|
266
|
+
});
|
|
267
|
+
schedule = createResp.schedule;
|
|
268
|
+
created(`schedule ${tool} on ${targetValue} @ "${cron}" (next: ${schedule?.next_run_at || 'n/a'})`);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// ── Step 9: SUMMARY ───────────────────────────────────────────────────────
|
|
272
|
+
// Every human gate that could stop the wizard has already returned by here,
|
|
273
|
+
// so the only thing left that can hold onboarding open is an unverified
|
|
274
|
+
// target (verification is best-effort and can legitimately not land yet).
|
|
275
|
+
const complete = targetVerified;
|
|
276
|
+
printSummary({
|
|
277
|
+
status: complete ? 'COMPLETE' : 'WAITING',
|
|
278
|
+
host,
|
|
279
|
+
orgId,
|
|
280
|
+
roadmapId,
|
|
281
|
+
engagementId,
|
|
282
|
+
targetValue,
|
|
283
|
+
targetVerified,
|
|
284
|
+
gripeRepo: repo || null,
|
|
285
|
+
schedule: schedule ? { cron, next: schedule.next_run_at } : null,
|
|
286
|
+
remainingGate: targetVerified ? null : 'target not verified',
|
|
287
|
+
});
|
|
288
|
+
return { status: complete ? 'COMPLETE' : 'WAITING' };
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/** Shared gate-stop: print the WAITING banner + resume hint, return WAITING. */
|
|
292
|
+
function stop(host, partial) {
|
|
293
|
+
console.log('');
|
|
294
|
+
console.log(' ===== WAITING on a human gate =====');
|
|
295
|
+
console.log(` Do the step above, then re-run: quox onboard ${host} ... to continue.`);
|
|
296
|
+
if (partial) {
|
|
297
|
+
console.log(` Progress so far: org ${partial.orgId}, roadmap ${partial.roadmapId}, engagement ${partial.engagementId}, target ${partial.targetValue} (verified: ${partial.targetVerified}).`);
|
|
298
|
+
}
|
|
299
|
+
return { status: 'WAITING' };
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function printSummary(s) {
|
|
303
|
+
console.log('');
|
|
304
|
+
console.log(` ===== onboarding ${s.status} =====`);
|
|
305
|
+
console.log(` host: ${s.host}`);
|
|
306
|
+
console.log(` org: ${s.orgId}`);
|
|
307
|
+
console.log(` roadmap: ${s.roadmapId}`);
|
|
308
|
+
console.log(` engagement: ${s.engagementId}`);
|
|
309
|
+
console.log(` target: ${s.targetValue} (verified: ${s.targetVerified})`);
|
|
310
|
+
console.log(` gripe repo: ${s.gripeRepo || '(none)'}`);
|
|
311
|
+
if (s.schedule) console.log(` schedule: "${s.schedule.cron}" (next: ${s.schedule.next || 'n/a'})`);
|
|
312
|
+
if (s.remainingGate) console.log(` remaining: ${s.remainingGate}`);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
export function registerOnboardCommands(program, ctx) {
|
|
316
|
+
program
|
|
317
|
+
.command('onboard <host>')
|
|
318
|
+
.description('Guided, idempotent, resumable wizard to onboard a customer site as a governed Quox tenant')
|
|
319
|
+
.requiredOption('--name <customerName>', 'the org display name')
|
|
320
|
+
.option('--repo <owner/name>', 'the customer GitHub repo for auto-filed security issues')
|
|
321
|
+
.option('--target <hostOrIp>', 'the scan target (defaults to <host>)')
|
|
322
|
+
.option('--environment-tier <tier>', 'dev | staging | production', 'dev')
|
|
323
|
+
.option('--schedule-cron <expr>', 'cron cadence for the recurring scan', '0 2 * * 3')
|
|
324
|
+
.option('--tool <name>', 'capability tool to schedule', 'vuln_scan')
|
|
325
|
+
.option('--yes', 'run non-interactively: do every automatable step and print gate instructions without pausing')
|
|
326
|
+
.action(async (host, options) => {
|
|
327
|
+
await runOnboard(host, options, ctx);
|
|
328
|
+
});
|
|
329
|
+
}
|