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,660 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workflow commands: CRUD, run, steps, versions, declarative apply/validate/import (WP2.5)
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { readFileSync } from 'node:fs';
|
|
6
|
+
import { extname } from 'node:path';
|
|
7
|
+
import yaml from 'js-yaml';
|
|
8
|
+
import { formatOutput, formatDetail, printWarning, printInfo, getOutputFormat } from '../core/output.js';
|
|
9
|
+
import { events, printEvent, line as glyphLine } from '../core/quoxglyph.js';
|
|
10
|
+
import { resolveOrgId, parseJSON } from './helpers.js';
|
|
11
|
+
import { resolveEndpoint } from '../api/endpoints.js';
|
|
12
|
+
import { confirmDestructive } from '../core/confirm.js';
|
|
13
|
+
|
|
14
|
+
// Top-level Workflow fields compared for `workflow apply`'s diff-preview
|
|
15
|
+
// (mirrors core/workflow.ts's WorkflowSchema field set, minus `id`).
|
|
16
|
+
const APPLY_DIFF_FIELDS = ['name', 'description', 'notes', 'version', 'nodes', 'edges', 'variables', 'credentials', 'settings', 'metadata'];
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Read a declarative workflow/template file (.yaml/.yml/.json) and parse it
|
|
20
|
+
* to a plain object. Falls back to trying both parsers when the extension
|
|
21
|
+
* is unrecognized (YAML is a superset of JSON, so this also covers a
|
|
22
|
+
* misnamed .json-shaped file).
|
|
23
|
+
*/
|
|
24
|
+
function readDeclarativeFile(filePath) {
|
|
25
|
+
let raw;
|
|
26
|
+
try {
|
|
27
|
+
raw = readFileSync(filePath, 'utf-8');
|
|
28
|
+
} catch (err) {
|
|
29
|
+
throw new Error(`Could not read ${filePath}: ${err.message}`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const ext = extname(filePath).toLowerCase();
|
|
33
|
+
try {
|
|
34
|
+
if (ext === '.json') return JSON.parse(raw);
|
|
35
|
+
if (ext === '.yaml' || ext === '.yml') return yaml.load(raw);
|
|
36
|
+
try {
|
|
37
|
+
return JSON.parse(raw);
|
|
38
|
+
} catch {
|
|
39
|
+
return yaml.load(raw);
|
|
40
|
+
}
|
|
41
|
+
} catch (err) {
|
|
42
|
+
throw new Error(`Could not parse ${filePath} (expected YAML or JSON): ${err.message}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Short human-readable summary of a field value for the diff-preview. */
|
|
47
|
+
function summarizeValue(value) {
|
|
48
|
+
if (value === undefined) return '(unset)';
|
|
49
|
+
if (Array.isArray(value)) return `${value.length} item(s)`;
|
|
50
|
+
if (value !== null && typeof value === 'object') return `${Object.keys(value).length} key(s)`;
|
|
51
|
+
return JSON.stringify(value);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Build a field-level diff between the currently-stored workflow (from
|
|
56
|
+
* `workflows.applyGet`) and the local file, restricted to fields present in
|
|
57
|
+
* the local file — used by `workflow apply` to preview a PUT before firing it.
|
|
58
|
+
*/
|
|
59
|
+
function buildApplyDiff(existing, incoming) {
|
|
60
|
+
const existingDef = existing.definition || existing;
|
|
61
|
+
const changes = [];
|
|
62
|
+
for (const key of APPLY_DIFF_FIELDS) {
|
|
63
|
+
if (!(key in incoming)) continue;
|
|
64
|
+
const before = existingDef[key];
|
|
65
|
+
const after = incoming[key];
|
|
66
|
+
if (JSON.stringify(before) === JSON.stringify(after)) continue;
|
|
67
|
+
changes.push({ key, before, after });
|
|
68
|
+
}
|
|
69
|
+
return changes;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function printApplyDiff(workflowId, changes) {
|
|
73
|
+
console.log(`\nChanges to workflow ${workflowId}:`);
|
|
74
|
+
for (const { key, before, after } of changes) {
|
|
75
|
+
console.log(` ${key}: ${summarizeValue(before)} -> ${summarizeValue(after)}`);
|
|
76
|
+
}
|
|
77
|
+
console.log('');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Read one engine workflow by id, or by its exact name when no id matches (proof cases and people know
|
|
81
|
+
// names; engine ids are wf-<ms>-<6>). An ambiguous name is refused with the matching ids, never guessed.
|
|
82
|
+
async function getEngineWorkflow(client, ref) {
|
|
83
|
+
const ep = resolveEndpoint('workflows.applyGet', { workflowId: ref });
|
|
84
|
+
try {
|
|
85
|
+
const res = await client.get(ep.path, { service: ep.service });
|
|
86
|
+
return res.data || res;
|
|
87
|
+
} catch (err) {
|
|
88
|
+
if (err.status !== 404) throw err;
|
|
89
|
+
const listEp = resolveEndpoint('workflows.applyList');
|
|
90
|
+
const matches = [];
|
|
91
|
+
for (let offset = 0, guard = 0; guard < 200; guard++) {
|
|
92
|
+
const data = await client.get(listEp.path, { service: listEp.service, query: { limit: 100, offset } });
|
|
93
|
+
const items = data.data || data.items || data.workflows || (Array.isArray(data) ? data : []);
|
|
94
|
+
for (const w of items) if (w && w.name === ref) matches.push(w);
|
|
95
|
+
offset += items.length;
|
|
96
|
+
const pg = data.pagination;
|
|
97
|
+
if (!pg || !items.length || !(offset < pg.total)) break;
|
|
98
|
+
}
|
|
99
|
+
if (matches.length > 1) throw new Error(`Workflow name "${ref}" matches ${matches.length} workflows (${matches.map((m) => m.id).join(', ')}); give the id instead.`);
|
|
100
|
+
if (!matches.length) throw err;
|
|
101
|
+
const byId = resolveEndpoint('workflows.applyGet', { workflowId: matches[0].id });
|
|
102
|
+
const res = await client.get(byId.path, { service: byId.service });
|
|
103
|
+
return res.data || res;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function registerWorkflowCommands(program, ctx) {
|
|
108
|
+
const { client, auth } = ctx;
|
|
109
|
+
|
|
110
|
+
const workflow = program
|
|
111
|
+
.command('workflow')
|
|
112
|
+
.description('Manage workflows');
|
|
113
|
+
|
|
114
|
+
// fleet #465: workflows live in two stores. The engine (quoxflow) is the
|
|
115
|
+
// WRITE path for `workflow apply`/`activate`, but the auth-service store
|
|
116
|
+
// is a completely separate model quoxflow never writes to. `list` and
|
|
117
|
+
// `get` below read BOTH (or try engine-first with a fallback) and label
|
|
118
|
+
// which store answered, instead of silently missing engine-created
|
|
119
|
+
// workflows the way the auth-only read used to.
|
|
120
|
+
|
|
121
|
+
workflow
|
|
122
|
+
.command('list')
|
|
123
|
+
.description('List workflows')
|
|
124
|
+
.option('--status <status>', 'filter by status')
|
|
125
|
+
.action(async (options) => {
|
|
126
|
+
auth.requireAuth();
|
|
127
|
+
const orgId = resolveOrgId(auth, program);
|
|
128
|
+
const query = {};
|
|
129
|
+
if (options.status) query.status = options.status;
|
|
130
|
+
|
|
131
|
+
const rows = [];
|
|
132
|
+
|
|
133
|
+
try {
|
|
134
|
+
const ep = resolveEndpoint('workflows.applyList');
|
|
135
|
+
// The engine pages (default 20, max 100). Walk every page so the list is the
|
|
136
|
+
// whole store, not the first page (121 workflows read as 20 before).
|
|
137
|
+
const PAGE = 100;
|
|
138
|
+
for (let offset = 0, guard = 0; guard < 200; guard++) {
|
|
139
|
+
const data = await client.get(ep.path, { service: ep.service, query: { limit: PAGE, offset } });
|
|
140
|
+
const items = data.data || data.items || data.workflows || (Array.isArray(data) ? data : []);
|
|
141
|
+
for (const item of items) rows.push({ ...item, source: 'engine' });
|
|
142
|
+
const pg = data.pagination;
|
|
143
|
+
offset += items.length;
|
|
144
|
+
if (!pg || !items.length || !(offset < pg.total)) break;
|
|
145
|
+
}
|
|
146
|
+
} catch (err) {
|
|
147
|
+
printWarning(`Engine (quoxflow) workflow list failed: ${err.message}`);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
try {
|
|
151
|
+
const ep = resolveEndpoint('workflows.list', { orgId });
|
|
152
|
+
const data = await client.get(ep.path, { service: ep.service, query });
|
|
153
|
+
const items = data.items || data.workflows || (Array.isArray(data) ? data : []);
|
|
154
|
+
for (const item of items) rows.push({ ...item, source: 'auth' });
|
|
155
|
+
} catch (err) {
|
|
156
|
+
printWarning(`Auth-service workflow list failed: ${err.message}`);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
formatOutput({
|
|
160
|
+
columns: ['id', 'name', 'status', 'source', 'step_count', 'last_run', 'created_at'],
|
|
161
|
+
headers: ['ID', 'Name', 'Status', 'Source', 'Steps', 'Last Run', 'Created'],
|
|
162
|
+
rows,
|
|
163
|
+
}, { format: program.opts().output });
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
workflow
|
|
167
|
+
.command('get')
|
|
168
|
+
.description('Get workflow details')
|
|
169
|
+
.argument('<workflowId>', 'workflow ID or exact name')
|
|
170
|
+
.action(async (workflowId) => {
|
|
171
|
+
auth.requireAuth();
|
|
172
|
+
const orgId = resolveOrgId(auth, program);
|
|
173
|
+
|
|
174
|
+
let data, source;
|
|
175
|
+
try {
|
|
176
|
+
data = await getEngineWorkflow(client, workflowId);
|
|
177
|
+
source = 'engine';
|
|
178
|
+
} catch (err) {
|
|
179
|
+
if (err.status !== 404) throw err;
|
|
180
|
+
try {
|
|
181
|
+
const ep = resolveEndpoint('workflows.get', { orgId, workflowId });
|
|
182
|
+
const res = await client.get(ep.path, { service: ep.service });
|
|
183
|
+
data = res.workflow || res;
|
|
184
|
+
source = 'auth';
|
|
185
|
+
} catch (err2) {
|
|
186
|
+
if (err2.status !== 404) throw err2;
|
|
187
|
+
throw new Error(
|
|
188
|
+
`Workflow "${workflowId}" was not found in either the engine (quoxflow) or auth-service workflow store.`
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
formatDetail({ ...data, source }, { format: program.opts().output, title: data.name });
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
// ── workflow schedule <id> ──────────────────────────────────────────────────
|
|
197
|
+
// Is the schedule REALLY registered, and when does it fire next? Read from the live scheduler
|
|
198
|
+
// (GET /api/triggers/schedules), never inferred from the saved config (SITE-CLI 2026-10-17).
|
|
199
|
+
workflow
|
|
200
|
+
.command('schedule')
|
|
201
|
+
.description('Show whether a workflow\'s schedule trigger is registered with the live scheduler, and its next fire time')
|
|
202
|
+
.argument('<workflowId>', 'workflow ID or exact name')
|
|
203
|
+
.action(async (workflowId) => {
|
|
204
|
+
auth.requireAuth();
|
|
205
|
+
const wf = await getEngineWorkflow(client, workflowId);
|
|
206
|
+
const id = wf.id || workflowId;
|
|
207
|
+
const nodes = (wf.definition && wf.definition.nodes) || wf.nodes || [];
|
|
208
|
+
const schedNodes = nodes.filter((n) => n && n.type === 'trigger:schedule');
|
|
209
|
+
|
|
210
|
+
const liveEp = resolveEndpoint('triggers.schedules');
|
|
211
|
+
const liveRes = await client.get(liveEp.path, { service: liveEp.service });
|
|
212
|
+
const live = liveRes.data || liveRes;
|
|
213
|
+
const ownedByTasks = live.owned_by === 'tasks';
|
|
214
|
+
const rows = (live.schedules || []).filter((r) => r.workflowId === id);
|
|
215
|
+
|
|
216
|
+
const schedules = schedNodes.map((n) => {
|
|
217
|
+
const cfg = n.config || {};
|
|
218
|
+
const saved = cfg.expression || cfg.cron || null;
|
|
219
|
+
const reg = rows.find((r) => r.nodeId === n.id);
|
|
220
|
+
if (ownedByTasks) {
|
|
221
|
+
return { nodeId: n.id, registered: null, state: 'unknown', saved, expression: null, timezone: cfg.timezone || 'UTC', nextFireAt: null,
|
|
222
|
+
reason: 'Scheduling is owned by the tasks service on this instance; the workflow engine cannot see registrations.' };
|
|
223
|
+
}
|
|
224
|
+
if (!reg) {
|
|
225
|
+
return { nodeId: n.id, registered: false, state: 'not registered', saved, expression: null, timezone: cfg.timezone || 'UTC', nextFireAt: null,
|
|
226
|
+
reason: wf.active === false ? 'The workflow is not active, so its schedule is not registered. Activate it to register.' : 'Not registered with the scheduler.' };
|
|
227
|
+
}
|
|
228
|
+
return { nodeId: n.id, registered: true, state: reg.active ? 'active' : 'paused', saved, expression: reg.expression, timezone: reg.timezone || 'UTC', nextFireAt: reg.nextFireAt || null };
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
const result = { workflowId: id, name: wf.name, active: wf.active, ...(live.owned_by ? { owned_by: live.owned_by } : {}), schedules,
|
|
232
|
+
...(schedNodes.length ? {} : { note: 'This workflow has no schedule trigger.' }) };
|
|
233
|
+
if (getOutputFormat() === 'json' || program.opts().output === 'json') { formatDetail(result, { format: 'json' }); return; }
|
|
234
|
+
if (!schedNodes.length) { printInfo(`${id}: ${result.note}`); return; }
|
|
235
|
+
if (ownedByTasks) printInfo(live.note || 'Schedules are managed by the tasks service.');
|
|
236
|
+
formatOutput({
|
|
237
|
+
columns: ['nodeId', 'registeredLabel', 'state', 'expression', 'timezone', 'nextFireAt', 'reason'],
|
|
238
|
+
headers: ['Node', 'Registered', 'State', 'Cron (live)', 'Timezone', 'Next Fire', 'Note'],
|
|
239
|
+
rows: schedules.map((r) => ({ ...r, registeredLabel: r.registered === true ? 'yes' : r.registered === false ? 'no' : 'unknown', expression: r.expression || '-', nextFireAt: r.nextFireAt || '-', reason: r.reason || '' })),
|
|
240
|
+
}, { format: program.opts().output });
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
workflow
|
|
244
|
+
.command('create')
|
|
245
|
+
.description('Create a new workflow in the workflow engine (quoxflow). --steps takes a JSON array of nodes (chained in order) or an object with nodes and optional edges')
|
|
246
|
+
.requiredOption('--name <name>', 'workflow name')
|
|
247
|
+
.option('--description <desc>', 'description')
|
|
248
|
+
.option('--steps <json>', 'nodes as a JSON array (chained sequentially), or {"nodes":[...],"edges":[...]}')
|
|
249
|
+
.action(async (options) => {
|
|
250
|
+
auth.requireAuth();
|
|
251
|
+
// #1190: the engine is the one workflow store. Same id shape as `workflow apply`.
|
|
252
|
+
const id = `wf-${Date.now()}-${Math.random().toString(36).slice(2, 8).padEnd(6, '0')}`;
|
|
253
|
+
let nodes = [];
|
|
254
|
+
let edges = [];
|
|
255
|
+
if (options.steps) {
|
|
256
|
+
const steps = parseJSON(options.steps, '--steps');
|
|
257
|
+
if (Array.isArray(steps)) {
|
|
258
|
+
nodes = steps;
|
|
259
|
+
edges = steps.slice(1).map((n, i) => ({ id: `e${i + 1}`, source: steps[i].id ?? `n${i}`, target: n.id ?? `n${i + 1}` }));
|
|
260
|
+
} else if (steps && typeof steps === 'object' && Array.isArray(steps.nodes)) {
|
|
261
|
+
nodes = steps.nodes;
|
|
262
|
+
edges = Array.isArray(steps.edges) ? steps.edges : [];
|
|
263
|
+
} else {
|
|
264
|
+
throw new Error('--steps must be a JSON array of nodes, or an object with a "nodes" array (and optional "edges").');
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
const body = { id, name: options.name, nodes, edges };
|
|
268
|
+
if (options.description) body.description = options.description;
|
|
269
|
+
const ep = resolveEndpoint('workflows.applyCreate');
|
|
270
|
+
const res = await client.post(ep.path, { service: ep.service, body });
|
|
271
|
+
const created = res?.data || res;
|
|
272
|
+
printEvent(events.created('quox', 'workflow', `${options.name} (quoxflow) id:${created.id || id}`));
|
|
273
|
+
// #1000: --json gets the saved workflow (the event line is suppressed in json mode, which left nothing)
|
|
274
|
+
if (getOutputFormat() === 'json') formatDetail(created, { format: 'json' });
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
workflow
|
|
278
|
+
.command('update')
|
|
279
|
+
.description('Update a workflow')
|
|
280
|
+
.argument('<workflowId>', 'workflow ID')
|
|
281
|
+
.option('--name <name>', 'new name')
|
|
282
|
+
.option('--description <desc>', 'new description')
|
|
283
|
+
.option('--status <status>', 'new status')
|
|
284
|
+
.action(async (workflowId, options) => {
|
|
285
|
+
auth.requireAuth();
|
|
286
|
+
const orgId = resolveOrgId(auth, program);
|
|
287
|
+
const body = {};
|
|
288
|
+
if (options.name) body.name = options.name;
|
|
289
|
+
if (options.description) body.description = options.description;
|
|
290
|
+
if (options.status) body.status = options.status;
|
|
291
|
+
const data = await client.patch(resolveEndpoint('workflows.update', { orgId, workflowId }).path, { service: resolveEndpoint('workflows.update', { orgId, workflowId }).service, body });
|
|
292
|
+
printEvent(events.updated('quox', 'workflow', workflowId));
|
|
293
|
+
if (getOutputFormat() === 'json') formatDetail(data?.workflow || data, { format: 'json' });
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
workflow
|
|
297
|
+
.command('delete')
|
|
298
|
+
.description('Delete a workflow')
|
|
299
|
+
.argument('<workflowId>', 'workflow ID')
|
|
300
|
+
.action(async (workflowId) => {
|
|
301
|
+
auth.requireAuth();
|
|
302
|
+
const orgId = resolveOrgId(auth, program);
|
|
303
|
+
// #997: engine (quoxflow) first, the auth store only on an engine 404 (same order as `workflow get`)
|
|
304
|
+
let source = 'engine';
|
|
305
|
+
try {
|
|
306
|
+
const ep = resolveEndpoint('workflows.applyDelete', { workflowId });
|
|
307
|
+
await client.delete(ep.path, { service: ep.service });
|
|
308
|
+
} catch (err) {
|
|
309
|
+
if (err.status !== 404) throw err;
|
|
310
|
+
source = 'auth';
|
|
311
|
+
await client.delete(resolveEndpoint('workflows.delete', { orgId, workflowId }).path, { service: resolveEndpoint('workflows.delete', { orgId, workflowId }).service });
|
|
312
|
+
}
|
|
313
|
+
printEvent(glyphLine('quox', 'workflow deleted', { result: `${workflowId} (${source})`, status: 'ok' }));
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
workflow
|
|
317
|
+
.command('run')
|
|
318
|
+
.description('Execute a workflow (must be published/active — see `workflow publish`)')
|
|
319
|
+
.argument('<workflowId>', 'workflow ID')
|
|
320
|
+
.option('--input <json>', 'input data as JSON (passed as run context)')
|
|
321
|
+
.action(async (workflowId, options) => {
|
|
322
|
+
auth.requireAuth();
|
|
323
|
+
const orgId = resolveOrgId(auth, program);
|
|
324
|
+
const body = { workflow_id: workflowId };
|
|
325
|
+
if (options.input) body.context = parseJSON(options.input, '--input');
|
|
326
|
+
try {
|
|
327
|
+
const data = await client.post(resolveEndpoint('runs.create', { orgId }).path, { service: resolveEndpoint('runs.create', { orgId }).service, body });
|
|
328
|
+
printEvent(glyphLine('quox', 'workflow run', { result: `${workflowId} -> run ${data.run?.id || data.run_id || data.id || 'ok'}`, status: 'ok' }));
|
|
329
|
+
} catch (err) {
|
|
330
|
+
if (err.status === 404) {
|
|
331
|
+
// quoxflow#7: the auth runs route only knows auth-store workflows. If
|
|
332
|
+
// the engine store has it, execute it there instead.
|
|
333
|
+
let engineExists = false;
|
|
334
|
+
try {
|
|
335
|
+
const getEp = resolveEndpoint('workflows.applyGet', { workflowId });
|
|
336
|
+
await client.get(getEp.path, { service: getEp.service });
|
|
337
|
+
engineExists = true;
|
|
338
|
+
} catch (engineErr) {
|
|
339
|
+
if (engineErr.status !== 404) throw engineErr;
|
|
340
|
+
}
|
|
341
|
+
if (engineExists) {
|
|
342
|
+
const exEp = resolveEndpoint('workflows.applyExecute', { workflowId });
|
|
343
|
+
// #1029: the engine reads triggerData (a bare body lost --input), and async returns the run id at once
|
|
344
|
+
const run = await client.post(exEp.path, { service: exEp.service, body: { triggerData: body.context || {}, async: true } });
|
|
345
|
+
const runId = run.executionId || run.execution_id || run.runId || run.run_id || run.id || run.execution?.id || 'ok';
|
|
346
|
+
const st = run.status || run.execution?.status;
|
|
347
|
+
printEvent(glyphLine('quox', 'workflow run', { result: `${workflowId} (quoxflow) -> run ${runId}${st ? ` [${st}]` : ''}`, status: 'ok' }));
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
throw new Error(
|
|
351
|
+
`Workflow not found or not active. Only published workflows can be run — try \`quox workflow publish ${workflowId}\` first.`
|
|
352
|
+
);
|
|
353
|
+
}
|
|
354
|
+
throw err;
|
|
355
|
+
}
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
workflow
|
|
359
|
+
.command('publish')
|
|
360
|
+
.description('Publish a workflow')
|
|
361
|
+
.argument('<workflowId>', 'workflow ID')
|
|
362
|
+
.action(async (workflowId) => {
|
|
363
|
+
auth.requireAuth();
|
|
364
|
+
const orgId = resolveOrgId(auth, program);
|
|
365
|
+
try {
|
|
366
|
+
const ep = resolveEndpoint('workflows.publish', { orgId, workflowId });
|
|
367
|
+
await client.post(ep.path, { service: ep.service });
|
|
368
|
+
printEvent(glyphLine('quox', 'workflow published', { result: workflowId, status: 'ok' }));
|
|
369
|
+
} catch (err) {
|
|
370
|
+
if (err.status !== 404) throw err;
|
|
371
|
+
|
|
372
|
+
// fleet #465: publish operates on the auth-service model only. A
|
|
373
|
+
// workflow that exists solely in the engine (e.g. via `workflow
|
|
374
|
+
// apply`) 404s here even though it exists — check the engine before
|
|
375
|
+
// reporting a bare "not found".
|
|
376
|
+
let engineExists = false;
|
|
377
|
+
try {
|
|
378
|
+
const getEp = resolveEndpoint('workflows.applyGet', { workflowId });
|
|
379
|
+
await client.get(getEp.path, { service: getEp.service });
|
|
380
|
+
engineExists = true;
|
|
381
|
+
} catch (engineErr) {
|
|
382
|
+
if (engineErr.status !== 404) throw engineErr;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
if (engineExists) {
|
|
386
|
+
throw new Error(
|
|
387
|
+
`Workflow "${workflowId}" lives in the engine (quoxflow) store, not the auth-service store that `
|
|
388
|
+
+ `\`workflow publish\` operates on. Use \`quox trigger workflow-activate ${workflowId}\` to make it live — `
|
|
389
|
+
+ '`workflow publish` only applies to workflows created via `workflow create`.'
|
|
390
|
+
);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
throw new Error(
|
|
394
|
+
`Workflow "${workflowId}" was not found in either the engine (quoxflow) or auth-service workflow store.`
|
|
395
|
+
);
|
|
396
|
+
}
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
// Steps subcommand
|
|
400
|
+
const steps = workflow
|
|
401
|
+
.command('steps')
|
|
402
|
+
.description('Manage workflow steps');
|
|
403
|
+
|
|
404
|
+
steps
|
|
405
|
+
.command('list')
|
|
406
|
+
.description('List steps of a workflow')
|
|
407
|
+
.argument('<workflowId>', 'workflow ID')
|
|
408
|
+
.action(async (workflowId) => {
|
|
409
|
+
auth.requireAuth();
|
|
410
|
+
const orgId = resolveOrgId(auth, program);
|
|
411
|
+
// No dedicated GET .../steps route exists: steps are a field on the
|
|
412
|
+
// workflow object itself. quox#928: look in the same stores `workflow get`
|
|
413
|
+
// does, engine (quoxflow) first, so engine workflows (wf-obj-*) resolve too.
|
|
414
|
+
let rows;
|
|
415
|
+
try {
|
|
416
|
+
const ep = resolveEndpoint('workflows.applyGet', { workflowId });
|
|
417
|
+
const res = await client.get(ep.path, { service: ep.service });
|
|
418
|
+
const wf = res.data || res;
|
|
419
|
+
const nodes = wf?.definition?.nodes || wf?.nodes;
|
|
420
|
+
if (!Array.isArray(nodes)) throw Object.assign(new Error('not an engine workflow'), { status: 404 });
|
|
421
|
+
rows = nodes.map((n, idx) => ({ idx, label: n.name || n.id, step_type: n.type, ref_id: n.config?.tool || n.config?.toolName || '-' }));
|
|
422
|
+
} catch (err) {
|
|
423
|
+
if (err.status !== 404) throw err;
|
|
424
|
+
const data = await client.get(resolveEndpoint('workflows.get', { orgId, workflowId }).path, { service: resolveEndpoint('workflows.get', { orgId, workflowId }).service });
|
|
425
|
+
rows = (data.workflow || data).steps || [];
|
|
426
|
+
}
|
|
427
|
+
formatOutput({
|
|
428
|
+
// fleet #392: workflow_steps columns are idx/label/step_type/ref_id.
|
|
429
|
+
columns: ['idx', 'label', 'step_type', 'ref_id'],
|
|
430
|
+
headers: ['#', 'Name', 'Type', 'Tool'],
|
|
431
|
+
rows,
|
|
432
|
+
}, { format: program.opts().output });
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
steps
|
|
436
|
+
.command('set')
|
|
437
|
+
.description('Set workflow steps (replaces all)')
|
|
438
|
+
.argument('<workflowId>', 'workflow ID')
|
|
439
|
+
.requiredOption('--steps <json>', 'steps as JSON array')
|
|
440
|
+
.action(async (workflowId, options) => {
|
|
441
|
+
auth.requireAuth();
|
|
442
|
+
const orgId = resolveOrgId(auth, program);
|
|
443
|
+
const stepList = parseJSON(options.steps, '--steps');
|
|
444
|
+
try {
|
|
445
|
+
await client.put(resolveEndpoint('workflows.steps', { orgId, workflowId }).path, {
|
|
446
|
+
service: resolveEndpoint('workflows.steps', { orgId, workflowId }).service,
|
|
447
|
+
body: { steps: stepList },
|
|
448
|
+
});
|
|
449
|
+
} catch (err) {
|
|
450
|
+
if (err.status !== 404) throw err;
|
|
451
|
+
// #997: `steps list` reads engine workflows too, so an engine id 404'd here with a bare 'Workflow not found'.
|
|
452
|
+
// An engine workflow is a node/edge graph, not a step list: refuse it by name, like `workflow publish`.
|
|
453
|
+
let engineExists = false;
|
|
454
|
+
try {
|
|
455
|
+
const getEp = resolveEndpoint('workflows.applyGet', { workflowId });
|
|
456
|
+
await client.get(getEp.path, { service: getEp.service });
|
|
457
|
+
engineExists = true;
|
|
458
|
+
} catch (engineErr) {
|
|
459
|
+
if (engineErr.status !== 404) throw engineErr;
|
|
460
|
+
}
|
|
461
|
+
if (engineExists) {
|
|
462
|
+
throw new Error(
|
|
463
|
+
`Workflow "${workflowId}" lives in the engine (quoxflow) store, where it is a graph of nodes and edges, `
|
|
464
|
+
+ 'not the step list `workflow steps set` replaces. Edit it with `quox workflow apply -f <file>` '
|
|
465
|
+
+ `(start from \`quox -o json workflow get ${workflowId}\`).`
|
|
466
|
+
);
|
|
467
|
+
}
|
|
468
|
+
throw new Error(
|
|
469
|
+
`Workflow "${workflowId}" was not found in either the engine (quoxflow) or auth-service workflow store.`
|
|
470
|
+
);
|
|
471
|
+
}
|
|
472
|
+
printEvent(events.updated('quox', 'workflow steps', workflowId));
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
// ── workflow apply / validate / import-n8n / import-objective (WP2.5) ───────
|
|
476
|
+
//
|
|
477
|
+
// Declarative, file-based create/edit against quoxflow's own workflow
|
|
478
|
+
// engine — DIRECT to quoxflow (service: 'quoxflow'), NOT the auth-service
|
|
479
|
+
// 'workflows.create'/'workflows.update' used by the commands above. See
|
|
480
|
+
// the header comment in lib/api/endpoints/workflows.js for the full
|
|
481
|
+
// access-path + naming-collision rationale.
|
|
482
|
+
|
|
483
|
+
workflow
|
|
484
|
+
.command('apply')
|
|
485
|
+
.description('Create or update a workflow declaratively from a YAML/JSON file (quoxflow-direct: POST if the file has no id, PUT with a diff-preview if it does)')
|
|
486
|
+
.requiredOption('-f, --file <path>', 'path to a workflow definition file (.yaml/.yml/.json)')
|
|
487
|
+
.option('--force', 'skip the diff-preview confirmation on update')
|
|
488
|
+
.action(async (options) => {
|
|
489
|
+
auth.requireAuth();
|
|
490
|
+
const wf = readDeclarativeFile(options.file);
|
|
491
|
+
if (!wf || typeof wf !== 'object') {
|
|
492
|
+
throw new Error(`${options.file} did not parse to a workflow object.`);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
if (wf.id) {
|
|
496
|
+
let existing = null;
|
|
497
|
+
try {
|
|
498
|
+
const getEp = resolveEndpoint('workflows.applyGet', { workflowId: wf.id });
|
|
499
|
+
const data = await client.get(getEp.path, { service: getEp.service });
|
|
500
|
+
existing = data.data || data;
|
|
501
|
+
} catch (err) {
|
|
502
|
+
if (err.status !== 404) throw err;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
if (existing) {
|
|
506
|
+
const changes = buildApplyDiff(existing, wf);
|
|
507
|
+
if (changes.length === 0) {
|
|
508
|
+
printInfo(`No field-level changes detected for workflow ${wf.id}. Nothing to apply.`);
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
printApplyDiff(wf.id, changes);
|
|
512
|
+
|
|
513
|
+
// fleet #468 item 3: on a non-TTY shell (no --force/-y) the old
|
|
514
|
+
// unconditional inquirer.prompt() call printed "User force closed
|
|
515
|
+
// the prompt with 0 null" and returned silently WITHOUT applying,
|
|
516
|
+
// which looked like success but wasn't. confirmDestructive() checks
|
|
517
|
+
// process.stdin.isTTY before ever importing inquirer, so a
|
|
518
|
+
// non-interactive shell without --force now fails loudly (exit
|
|
519
|
+
// code 1) naming --force, instead of no-op-ing.
|
|
520
|
+
const confirmed = await confirmDestructive(program, options, `Apply these changes to workflow ${wf.id}?`);
|
|
521
|
+
if (!confirmed) {
|
|
522
|
+
printInfo('Cancelled.');
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
const putEp = resolveEndpoint('workflows.applyUpdate', { workflowId: wf.id });
|
|
527
|
+
const data = await client.put(putEp.path, { service: putEp.service, body: wf });
|
|
528
|
+
printEvent(glyphLine('quox', 'workflow updated', { result: `${wf.id} (quoxflow)`, status: 'ok' }));
|
|
529
|
+
formatDetail(data.data || data, { format: program.opts().output });
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// An id was given but nothing is stored under it yet — falls
|
|
534
|
+
// through to create below (POST also accepts a caller-supplied id).
|
|
535
|
+
printInfo(`No existing workflow found for id "${wf.id}" — creating.`);
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
const createEp = resolveEndpoint('workflows.applyCreate');
|
|
539
|
+
// #1000: quoxflow's WorkflowSchema requires an id, so "no id = create" 400'd. Mint one in the
|
|
540
|
+
// engine's own wf-<ms>-<6> shape; the created id is printed, so the file can be given it to update later.
|
|
541
|
+
const body = wf.id ? wf : { ...wf, id: `wf-${Date.now()}-${Math.random().toString(36).slice(2, 8).padEnd(6, '0')}` };
|
|
542
|
+
const data = await client.post(createEp.path, { service: createEp.service, body });
|
|
543
|
+
const created = data.data || data;
|
|
544
|
+
printEvent(events.created('quox', 'workflow', `${created.name || wf.name || created.id} (quoxflow) id:${created.id}`));
|
|
545
|
+
formatDetail(created, { format: program.opts().output });
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
workflow
|
|
549
|
+
.command('validate')
|
|
550
|
+
.description('Validate a workflow (quoxflow-direct). Give a stored workflow id to fetch and validate it (the same read path as `workflow get`), or -f <file> to validate the workflow stored under that file\'s "id" field. NOTE: this validates the STORED workflow, not local file content; run `workflow apply -f <file>` first to validate your latest edits.')
|
|
551
|
+
.argument('[workflowId]', 'stored workflow ID (fetched the same way `quox workflow get` does, then validated). Omit if using -f instead.')
|
|
552
|
+
.option('-f, --file <path>', 'workflow file — only its "id" field is used to look up the stored workflow')
|
|
553
|
+
.action(async (workflowId, options) => {
|
|
554
|
+
auth.requireAuth();
|
|
555
|
+
|
|
556
|
+
let targetId;
|
|
557
|
+
if (options.file) {
|
|
558
|
+
const wf = readDeclarativeFile(options.file);
|
|
559
|
+
if (!wf?.id) {
|
|
560
|
+
throw new Error(`${options.file} has no "id" field. \`workflow validate\` checks the STORED workflow by id, not local file content — apply the file first with \`quox workflow apply -f ${options.file}\`, or add an "id" matching an existing workflow.`);
|
|
561
|
+
}
|
|
562
|
+
targetId = wf.id;
|
|
563
|
+
} else if (workflowId) {
|
|
564
|
+
// fleet #468 item 1: `quox workflow validate <id>` previously required
|
|
565
|
+
// -f/--file even when the caller already had a stored workflow id.
|
|
566
|
+
// Fetch it through the same read path `quox workflow get` uses, so a
|
|
567
|
+
// missing id fails with the same "not found" signal that command gives.
|
|
568
|
+
const orgId = resolveOrgId(auth, program);
|
|
569
|
+
const getEp = resolveEndpoint('workflows.get', { orgId, workflowId });
|
|
570
|
+
try {
|
|
571
|
+
await client.get(getEp.path, { service: getEp.service });
|
|
572
|
+
} catch (err) {
|
|
573
|
+
if (err.status !== 404) throw err;
|
|
574
|
+
// `workflow get` also reads the engine (quoxflow) store; engine-only workflows
|
|
575
|
+
// (most of them) are not in the auth store, so fall back the same way.
|
|
576
|
+
try {
|
|
577
|
+
const engEp = resolveEndpoint('workflows.applyGet', { workflowId });
|
|
578
|
+
await client.get(engEp.path, { service: engEp.service });
|
|
579
|
+
} catch (err2) {
|
|
580
|
+
if (err2.status === 404) {
|
|
581
|
+
throw new Error(`Workflow "${workflowId}" was not found on the read path \`quox workflow get\` uses. Use -f <file> to validate a local file instead.`);
|
|
582
|
+
}
|
|
583
|
+
throw err2;
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
targetId = workflowId;
|
|
587
|
+
} else {
|
|
588
|
+
throw new Error('Provide either a workflow id argument or -f <file>. Example: `quox workflow validate <id>`, or `quox workflow validate -f workflow.yaml`.');
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
const ep = resolveEndpoint('workflows.validate', { workflowId: targetId });
|
|
592
|
+
const data = await client.post(ep.path, { service: ep.service });
|
|
593
|
+
const result = data.data || data;
|
|
594
|
+
formatDetail(result, { format: program.opts().output, title: `Validation: ${targetId}` });
|
|
595
|
+
});
|
|
596
|
+
|
|
597
|
+
workflow
|
|
598
|
+
.command('import-n8n')
|
|
599
|
+
.description('Import an n8n workflow export as a quoxflow workflow')
|
|
600
|
+
.requiredOption('-f, --file <path>', 'path to an n8n workflow export (.yaml/.yml/.json)')
|
|
601
|
+
.option('--generate-ids', 'generate new node/workflow ids instead of reusing the n8n ones')
|
|
602
|
+
.option('--include-positions', 'preserve n8n canvas node positions')
|
|
603
|
+
.action(async (options) => {
|
|
604
|
+
auth.requireAuth();
|
|
605
|
+
const parsed = readDeclarativeFile(options.file);
|
|
606
|
+
// Accept either a raw n8n export, or an already-wrapped { workflow, options } body.
|
|
607
|
+
const n8nWorkflow = parsed && typeof parsed === 'object' && 'workflow' in parsed ? parsed.workflow : parsed;
|
|
608
|
+
const importOptions = { ...(parsed?.options || {}) };
|
|
609
|
+
if (options.generateIds) importOptions.generateIds = true;
|
|
610
|
+
if (options.includePositions) importOptions.includePositions = true;
|
|
611
|
+
|
|
612
|
+
const ep = resolveEndpoint('workflows.importN8n');
|
|
613
|
+
const data = await client.post(ep.path, {
|
|
614
|
+
service: ep.service,
|
|
615
|
+
body: { workflow: n8nWorkflow, options: Object.keys(importOptions).length ? importOptions : undefined },
|
|
616
|
+
});
|
|
617
|
+
const result = data.data || data;
|
|
618
|
+
printEvent(glyphLine('quox', 'n8n workflow imported', { result: result.workflow?.id || result.id, status: 'ok' }));
|
|
619
|
+
if (result.warnings?.length) {
|
|
620
|
+
for (const w of result.warnings) printWarning(w);
|
|
621
|
+
}
|
|
622
|
+
formatDetail(result.workflow || result, { format: program.opts().output });
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
workflow
|
|
626
|
+
.command('import-objective')
|
|
627
|
+
.description('Import an objective template as a quoxflow workflow')
|
|
628
|
+
.requiredOption('-f, --file <path>', 'path to an objective template (.yaml/.yml/.json) — a bare template object, or { template, schedule, options }')
|
|
629
|
+
.option('--cron <expr>', 'cron schedule expression (adds/overrides schedule.cron)')
|
|
630
|
+
.option('--timezone <tz>', 'schedule timezone (adds/overrides schedule.timezone)')
|
|
631
|
+
.option('--activate', 'activate the imported workflow (registers triggers)')
|
|
632
|
+
.option('--include-approval-gate', 'insert an approval gate node')
|
|
633
|
+
.action(async (options) => {
|
|
634
|
+
auth.requireAuth();
|
|
635
|
+
const parsed = readDeclarativeFile(options.file);
|
|
636
|
+
if (!parsed || typeof parsed !== 'object') {
|
|
637
|
+
throw new Error(`${options.file} did not parse to an object.`);
|
|
638
|
+
}
|
|
639
|
+
const template = parsed.template && typeof parsed.template === 'object' ? parsed.template : parsed;
|
|
640
|
+
const schedule = { ...(parsed.schedule || {}) };
|
|
641
|
+
if (options.cron) schedule.cron = options.cron;
|
|
642
|
+
if (options.timezone) schedule.timezone = options.timezone;
|
|
643
|
+
const importOptions = { ...(parsed.options || {}) };
|
|
644
|
+
if (options.activate) importOptions.activate = true;
|
|
645
|
+
if (options.includeApprovalGate) importOptions.includeApprovalGate = true;
|
|
646
|
+
|
|
647
|
+
const body = { template };
|
|
648
|
+
if (schedule.cron) body.schedule = schedule;
|
|
649
|
+
if (Object.keys(importOptions).length) body.options = importOptions;
|
|
650
|
+
|
|
651
|
+
const ep = resolveEndpoint('workflows.importObjective');
|
|
652
|
+
const data = await client.post(ep.path, { service: ep.service, body });
|
|
653
|
+
const result = data.data || data;
|
|
654
|
+
printEvent(glyphLine('quox', 'objective template imported', { result: `${result.workflow?.id || result.id}${result.activated ? ' (activated)' : ''}`, status: 'ok' }));
|
|
655
|
+
if (result.warnings?.length) {
|
|
656
|
+
for (const w of result.warnings) printWarning(w);
|
|
657
|
+
}
|
|
658
|
+
formatDetail(result.workflow || result, { format: program.opts().output });
|
|
659
|
+
});
|
|
660
|
+
}
|