quoxcli 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +2 -0
- package/bin/quox.js +99 -126
- package/lib/api/endpoints/actions.js +15 -0
- package/lib/api/endpoints/admin.js +46 -0
- package/lib/api/endpoints/agent-schedules.js +26 -0
- package/lib/api/endpoints/agents.js +7 -0
- package/lib/api/endpoints/assistants.js +12 -0
- package/lib/api/endpoints/away.js +15 -0
- package/lib/api/endpoints/blog.js +16 -0
- package/lib/api/endpoints/chat.js +14 -0
- package/lib/api/endpoints/contactsq.js +6 -0
- package/lib/api/endpoints/conversations.js +14 -0
- package/lib/api/endpoints/devServerRegistry.js +25 -0
- package/lib/api/endpoints/engagement.js +26 -0
- package/lib/api/endpoints/evidence.js +20 -0
- package/lib/api/endpoints/executions.js +13 -0
- package/lib/api/endpoints/fetchAllowlist.js +27 -0
- package/lib/api/endpoints/files.js +14 -0
- package/lib/api/endpoints/hat.js +44 -0
- package/lib/api/endpoints/inbox.js +20 -0
- package/lib/api/endpoints/index.js +26 -0
- package/lib/api/endpoints/loops.js +26 -0
- package/lib/api/endpoints/memory.js +19 -0
- package/lib/api/endpoints/mind.js +13 -0
- package/lib/api/endpoints/mon.js +179 -0
- package/lib/api/endpoints/objectives.js +12 -0
- package/lib/api/endpoints/plugins.js +18 -0
- package/lib/api/endpoints/presence.js +33 -0
- package/lib/api/endpoints/quoxplan.js +7 -0
- package/lib/api/endpoints/quoxseo.js +12 -0
- package/lib/api/endpoints/router.js +17 -0
- package/lib/api/endpoints/safety.js +7 -0
- package/lib/api/endpoints/server.js +21 -0
- package/lib/api/endpoints/setup.js +6 -0
- package/lib/api/endpoints/skills.js +20 -0
- package/lib/api/endpoints/stepup.js +20 -0
- package/lib/api/endpoints/teams.js +2 -2
- package/lib/api/endpoints/triggers.js +26 -0
- package/lib/api/endpoints/v1.js +120 -0
- package/lib/api/endpoints/ward.js +7 -0
- package/lib/api/endpoints/workflows.js +7 -0
- package/lib/api/endpoints/x.js +8 -0
- package/lib/commands/actions.js +131 -0
- package/lib/commands/admin.js +16 -0
- package/lib/commands/agent.js +170 -27
- package/lib/commands/agentAttach.js +140 -13
- package/lib/commands/approvals.js +78 -1
- package/lib/commands/aside.js +24 -0
- package/lib/commands/ask.js +146 -0
- package/lib/commands/assistant.js +59 -4
- package/lib/commands/auth.js +8 -2
- package/lib/commands/backup.js +8 -4
- package/lib/commands/blog.js +110 -0
- package/lib/commands/brain.js +105 -14
- package/lib/commands/build.js +19 -5
- package/lib/commands/chat.js +180 -26
- package/lib/commands/chatCompanion.js +27 -0
- package/lib/commands/chatState.js +45 -0
- package/lib/commands/claude.js +35 -38
- package/lib/commands/contacts.js +134 -4
- package/lib/commands/contactsSendAt.js +21 -0
- package/lib/commands/conversation.js +35 -4
- package/lib/commands/dashboard.js +43 -0
- package/lib/commands/department.js +28 -7
- package/lib/commands/devResident.js +15 -3
- package/lib/commands/devSupervisor.js +13 -1
- package/lib/commands/discord.js +75 -39
- package/lib/commands/done.js +98 -28
- package/lib/commands/engagement.js +107 -6
- package/lib/commands/evidence.js +154 -0
- package/lib/commands/ext.js +23 -0
- package/lib/commands/fetchAllowlist.js +71 -0
- package/lib/commands/file.js +38 -1
- package/lib/commands/fleet.js +336 -4
- package/lib/commands/governance.js +12 -3
- package/lib/commands/gripe.js +44 -13
- package/lib/commands/hat.js +254 -9
- package/lib/commands/helpers.js +18 -1
- package/lib/commands/hyperion.js +58 -0
- package/lib/commands/image.js +68 -0
- package/lib/commands/inbox.js +74 -23
- package/lib/commands/kuma.js +1050 -0
- package/lib/commands/kumaSla.js +174 -0
- package/lib/commands/licence.js +52 -155
- package/lib/commands/loop.js +291 -0
- package/lib/commands/lucid.js +10 -4
- package/lib/commands/memory.js +85 -8
- package/lib/commands/mind.js +71 -0
- package/lib/commands/mon.js +69 -2
- package/lib/commands/monitoring.js +1 -1
- package/lib/commands/motion.js +2 -2
- package/lib/commands/nucleus.js +48 -17
- package/lib/commands/objective.js +59 -1
- package/lib/commands/offload.js +89 -0
- package/lib/commands/onboard.js +2 -1
- package/lib/commands/org.js +30 -3
- package/lib/commands/peek.js +28 -0
- package/lib/commands/plan.js +38 -0
- package/lib/commands/plugin.js +110 -0
- package/lib/commands/presence.js +89 -0
- package/lib/commands/provider.js +217 -0
- package/lib/commands/qlarity.js +19 -3
- package/lib/commands/quoxchat.js +13 -4
- package/lib/commands/quoxseo.js +69 -0
- package/lib/commands/reach.js +2 -1
- package/lib/commands/research.js +79 -0
- package/lib/commands/router.js +77 -0
- package/lib/commands/rows.js +367 -0
- package/lib/commands/run.js +82 -10
- package/lib/commands/scope.js +2 -2
- package/lib/commands/server.js +405 -0
- package/lib/commands/setup.js +33 -7
- package/lib/commands/shortcut.js +41 -0
- package/lib/commands/skills.js +68 -9
- package/lib/commands/slot.js +260 -7
- package/lib/commands/status.js +1 -1
- package/lib/commands/team.js +3 -1
- package/lib/commands/tips.js +23 -0
- package/lib/commands/tool.js +133 -45
- package/lib/commands/trace.js +12 -7
- package/lib/commands/trigger.js +95 -0
- package/lib/commands/update.js +46 -0
- package/lib/commands/vault.js +31 -8
- package/lib/commands/vm.js +237 -0
- package/lib/commands/voice.js +34 -3
- package/lib/commands/volt.js +45 -3
- package/lib/commands/walkthrough.js +32 -0
- package/lib/commands/watch.js +73 -21
- package/lib/commands/welcome.js +14 -0
- package/lib/commands/workflow.js +189 -32
- package/lib/commands/x.js +13 -4
- package/lib/commands/youtube.js +172 -0
- package/lib/core/actingAs.js +2 -2
- package/lib/core/actionItems.js +115 -0
- package/lib/core/activeSummary.js +186 -0
- package/lib/core/agentAlias.js +2 -0
- package/lib/core/agentFamilies.js +7 -0
- package/lib/core/aside.js +54 -0
- package/lib/core/askDecision.js +60 -0
- package/lib/core/auth.js +164 -13
- package/lib/core/callerSource.js +1 -1
- package/lib/core/cliBuild.js +62 -0
- package/lib/core/client.js +152 -35
- package/lib/core/commandRegistrars.js +130 -0
- package/lib/core/config.js +143 -25
- package/lib/core/customHats.js +168 -0
- package/lib/core/doneEngineFetch.js +8 -0
- package/lib/core/errors.js +329 -20
- package/lib/core/findingReconcile.js +90 -0
- package/lib/core/global-flags.js +105 -5
- package/lib/core/governedSession.js +1 -3
- package/lib/core/hyperion.js +102 -0
- package/lib/core/inboxResolution.js +65 -0
- package/lib/core/lazyCommands.js +4 -1
- package/lib/core/liveSessionsReport.js +1 -1
- package/lib/core/loopGlyph.js +97 -0
- package/lib/core/offload.js +533 -0
- package/lib/core/org-override.js +1 -1
- package/lib/core/orgResolve.js +70 -0
- package/lib/core/output.js +11 -11
- package/lib/core/peek.js +126 -0
- package/lib/core/profileUsers.js +51 -0
- package/lib/core/promptCancel.js +18 -0
- package/lib/core/repoPath.js +11 -0
- package/lib/core/residentSeed.js +3 -1
- package/lib/core/rootOptions.js +39 -0
- package/lib/core/router.js +168 -0
- package/lib/core/selfUpdate.js +188 -0
- package/lib/core/sessionHeartbeat.js +12 -8
- package/lib/core/sessionsOverview.js +64 -0
- package/lib/core/shortcut.js +83 -0
- package/lib/core/slotKeeper.js +248 -3
- package/lib/core/slotWake.js +120 -0
- package/lib/core/supervisorEngine.js +71 -2
- package/lib/core/version.js +1 -1
- package/lib/core/vm.js +366 -0
- package/lib/core/vmTui.js +86 -0
- package/lib/core/walkthrough.js +35 -0
- package/lib/dragon/companionView.js +34 -0
- package/lib/dragon/frames.js +118 -0
- package/lib/dragon/jobs.js +123 -0
- package/lib/dragon/renderWorker.js +15 -0
- package/lib/dragon/select.js +72 -0
- package/lib/evidence/bundleExport.js +47 -0
- package/lib/evidence/bundleVerify.js +77 -0
- package/lib/hyperion/deck.js +29 -0
- package/lib/lucid/LucidCard.jsx +68 -0
- package/lib/lucid/LucidIntro.jsx +45 -58
- package/lib/lucid/cards.js +86 -0
- package/lib/lucid/markdown.js +81 -0
- package/lib/lucid/motion.js +56 -0
- package/lib/quoxon/EXTENSIONS.md +23 -0
- package/lib/quoxon/activity.js +14 -2
- package/lib/quoxon/aocl.js +117 -0
- package/lib/quoxon/api.js +690 -25
- package/lib/quoxon/app.js +3586 -250
- package/lib/quoxon/approvalFacts.js +96 -0
- package/lib/quoxon/approvalSentence.js +44 -0
- package/lib/quoxon/approvalsView.js +384 -0
- package/lib/quoxon/asideView.js +40 -0
- package/lib/quoxon/ask.js +121 -0
- package/lib/quoxon/away.js +52 -0
- package/lib/quoxon/bang.js +224 -0
- package/lib/quoxon/bulkRisk.js +17 -0
- package/lib/quoxon/capabilities.js +13 -6
- package/lib/quoxon/cells.js +53 -0
- package/lib/quoxon/commandShort.js +124 -0
- package/lib/quoxon/commands.js +429 -39
- package/lib/quoxon/connect.js +210 -23
- package/lib/quoxon/connectCard.js +19 -2
- package/lib/quoxon/consent.js +86 -0
- package/lib/quoxon/copy.js +4 -3
- package/lib/quoxon/countdownLine.js +20 -0
- package/lib/quoxon/deck.js +30 -0
- package/lib/quoxon/demo.autoplay.js +29 -0
- package/lib/quoxon/demo.fixtures.js +1 -1
- package/lib/quoxon/demo.js +46 -3
- package/lib/quoxon/devObserver.js +20 -0
- package/lib/quoxon/dividerAocl.js +192 -0
- package/lib/quoxon/dividerRule.js +57 -0
- package/lib/quoxon/dragonCompanion.js +255 -0
- package/lib/quoxon/dragonModes.js +151 -0
- package/lib/quoxon/dragonMoments.js +82 -0
- package/lib/quoxon/dragonReads.js +68 -0
- package/lib/quoxon/dragonView.js +41 -0
- package/lib/quoxon/earlyInput.js +40 -0
- package/lib/quoxon/echo.js +15 -0
- package/lib/quoxon/egg.js +2 -2
- package/lib/quoxon/events.js +266 -26
- package/lib/quoxon/extensions.js +276 -0
- package/lib/quoxon/feedback.js +50 -0
- package/lib/quoxon/gallery.js +21 -7
- package/lib/quoxon/handoffOffer.js +78 -0
- package/lib/quoxon/hatSwitch.js +37 -0
- package/lib/quoxon/headless.js +161 -0
- package/lib/quoxon/hostUrls.js +41 -0
- package/lib/quoxon/inboxView.js +79 -0
- package/lib/quoxon/indicator.js +6 -2
- package/lib/quoxon/installCli.js +79 -0
- package/lib/quoxon/intent.js +12 -1
- package/lib/quoxon/keys.js +51 -8
- package/lib/quoxon/launcher.js +103 -0
- package/lib/quoxon/loopCard.js +456 -0
- package/lib/quoxon/loopCards.js +180 -0
- package/lib/quoxon/loopStream.js +87 -0
- package/lib/quoxon/main.js +101 -160
- package/lib/quoxon/markdown.js +45 -21
- package/lib/quoxon/markdownSettle.js +19 -0
- package/lib/quoxon/matrixDomains.json +46 -0
- package/lib/quoxon/mentions.js +90 -0
- package/lib/quoxon/mind.js +103 -0
- package/lib/quoxon/mode.js +46 -0
- package/lib/quoxon/model.js +132 -26
- package/lib/quoxon/modelPrefs.js +345 -0
- package/lib/quoxon/motion/clock.js +3 -2
- package/lib/quoxon/motion/level.js +91 -0
- package/lib/quoxon/mouse.js +222 -0
- package/lib/quoxon/noai.js +57 -0
- package/lib/quoxon/objectiveOffer.js +82 -0
- package/lib/quoxon/objects/away.js +72 -0
- package/lib/quoxon/objects/fetch.js +17 -5
- package/lib/quoxon/objects/fleet.js +149 -0
- package/lib/quoxon/objects/health.js +186 -0
- package/lib/quoxon/objects/index.js +46 -7
- package/lib/quoxon/objects/mirror.js +137 -0
- package/lib/quoxon/objects/model.js +33 -0
- package/lib/quoxon/objects/peek.js +17 -0
- package/lib/quoxon/objects/proof.js +12 -4
- package/lib/quoxon/objects/prove.js +130 -0
- package/lib/quoxon/objects/replay.js +125 -0
- package/lib/quoxon/objects/sitrep.js +58 -0
- package/lib/quoxon/objects/view.js +11 -0
- package/lib/quoxon/onboarding/copy.js +32 -4
- package/lib/quoxon/onboarding/credential.js +17 -4
- package/lib/quoxon/onboarding/dockerOffer.js +45 -0
- package/lib/quoxon/onboarding/failureCause.js +33 -0
- package/lib/quoxon/onboarding/finish.js +15 -0
- package/lib/quoxon/onboarding/index.js +34 -7
- package/lib/quoxon/onboarding/installRunner.js +64 -5
- package/lib/quoxon/onboarding/preflight.js +17 -3
- package/lib/quoxon/onboarding/probe.js +65 -0
- package/lib/quoxon/onboarding/shortcutOffer.js +28 -0
- package/lib/quoxon/onboarding/view.js +76 -19
- package/lib/quoxon/orgLabel.js +10 -0
- package/lib/quoxon/orgSwitch.js +59 -0
- package/lib/quoxon/ownBroadcast.js +22 -0
- package/lib/quoxon/palette.js +43 -0
- package/lib/quoxon/planSteps.js +51 -0
- package/lib/quoxon/print.js +12 -5
- package/lib/quoxon/profileSwitch.js +53 -0
- package/lib/quoxon/promptHistory.js +41 -0
- package/lib/quoxon/providers.js +48 -0
- package/lib/quoxon/qart.js +20 -51
- package/lib/quoxon/qdragon.js +410 -0
- package/lib/quoxon/qdragonFly.js +62 -0
- package/lib/quoxon/qdragonScenes.js +282 -0
- package/lib/quoxon/qeyes.js +213 -0
- package/lib/quoxon/qmotion.js +75 -37
- package/lib/quoxon/render.js +740 -124
- package/lib/quoxon/resend.js +72 -0
- package/lib/quoxon/roster.js +117 -17
- package/lib/quoxon/route.js +6 -5
- package/lib/quoxon/rows.js +954 -0
- package/lib/quoxon/rowsStore.js +80 -0
- package/lib/quoxon/screenContext.js +150 -0
- package/lib/quoxon/screenFrames.js +254 -0
- package/lib/quoxon/serverChip.js +42 -0
- package/lib/quoxon/session.js +182 -2
- package/lib/quoxon/sessionDead.js +17 -0
- package/lib/quoxon/signin.js +125 -0
- package/lib/quoxon/slash/agents.js +112 -0
- package/lib/quoxon/slash/argSources.js +65 -0
- package/lib/quoxon/slash/args.js +65 -0
- package/lib/quoxon/slash/automate.js +89 -0
- package/lib/quoxon/slash/broadcast.js +140 -0
- package/lib/quoxon/slash/broadcastRun.js +108 -0
- package/lib/quoxon/slash/card.js +30 -0
- package/lib/quoxon/slash/challenge.js +114 -0
- package/lib/quoxon/slash/cliCommand.js +94 -0
- package/lib/quoxon/slash/cliLeaves.json +1023 -0
- package/lib/quoxon/slash/compact.js +1 -1
- package/lib/quoxon/slash/confirm.js +30 -0
- package/lib/quoxon/slash/crystallize.js +69 -0
- package/lib/quoxon/slash/dashboard.js +136 -0
- package/lib/quoxon/slash/draft.js +54 -0
- package/lib/quoxon/slash/evidenceCmd.js +34 -0
- package/lib/quoxon/slash/hatWalk.js +206 -0
- package/lib/quoxon/slash/inverses.js +77 -0
- package/lib/quoxon/slash/killer.js +24 -0
- package/lib/quoxon/slash/killerApi.js +16 -0
- package/lib/quoxon/slash/ledger.js +13 -8
- package/lib/quoxon/slash/listView.js +3 -1
- package/lib/quoxon/slash/modelCmd.js +261 -0
- package/lib/quoxon/slash/palette.js +22 -8
- package/lib/quoxon/slash/paletteController.js +117 -7
- package/lib/quoxon/slash/panels.js +21 -6
- package/lib/quoxon/slash/picker.js +20 -2
- package/lib/quoxon/slash/qpeCmd.js +35 -0
- package/lib/quoxon/slash/restart.js +95 -13
- package/lib/quoxon/slash/resume.js +18 -1
- package/lib/quoxon/slash/rowCmd.js +157 -0
- package/lib/quoxon/slash/rowWalk.js +175 -0
- package/lib/quoxon/slash/settings.js +16 -6
- package/lib/quoxon/slash/spawn.js +177 -0
- package/lib/quoxon/slash/stepUp.js +274 -0
- package/lib/quoxon/slash/stepUpCard.js +60 -0
- package/lib/quoxon/slash/stepUpCmds.js +78 -0
- package/lib/quoxon/slash/todoCmd.js +122 -0
- package/lib/quoxon/slash/undoApi.js +38 -0
- package/lib/quoxon/slash/undoCmd.js +49 -0
- package/lib/quoxon/slash/undoWish.js +15 -0
- package/lib/quoxon/slash/watch.js +164 -0
- package/lib/quoxon/slash/wish.js +23 -0
- package/lib/quoxon/slash/wishCmd.js +32 -0
- package/lib/quoxon/slotChat.js +300 -0
- package/lib/quoxon/slotFormat.js +195 -0
- package/lib/quoxon/slotRun.js +131 -0
- package/lib/quoxon/sound/mapping.js +2 -1
- package/lib/quoxon/stray.js +22 -0
- package/lib/quoxon/suggestCommand.js +90 -0
- package/lib/quoxon/term.js +46 -11
- package/lib/quoxon/textBuffer.js +176 -2
- package/lib/quoxon/theme.js +58 -25
- package/lib/quoxon/tips.js +433 -0
- package/lib/quoxon/toolCards.js +30 -0
- package/lib/quoxon/toolError.js +41 -0
- package/lib/quoxon/transcript.js +42 -4
- package/lib/quoxon/web/qface.js +18 -10
- package/lib/quoxon/welcome.js +1 -1
- package/lib/quoxon/whatsnew.js +136 -0
- package/lib/quoxon/whatsnew.jsonl +134 -0
- package/lib/quoxon/windowOrg.js +123 -0
- package/lib/server/devServers.js +137 -0
- package/lib/server/diag.js +129 -0
- package/lib/server/doctor.js +71 -0
- package/lib/server/install.js +149 -0
- package/lib/server/pluginEnable.js +161 -0
- package/lib/server/skew.js +56 -0
- package/lib/server/snapshot.js +131 -0
- package/lib/server/status.js +128 -0
- package/lib/server/sys.js +65 -0
- package/lib/server/uninstall.js +63 -0
- package/lib/server/update.js +234 -0
- package/lib/toolOutputShape.js +315 -0
- package/lib/tui/views/HyperionDeck.jsx +79 -0
- package/lib/tui/views/LucidDeck.jsx +209 -217
- package/package.json +5 -1
- package/vendor/dragonlab/SOURCE +4 -0
- package/vendor/dragonlab/design/cipher/v1/cipher-master.svg +46 -0
- package/vendor/dragonlab/design/cipher/v2/character.mjs +49 -0
- package/vendor/dragonlab/design/cipher/v2/review.mjs +33 -0
- package/vendor/dragonlab/design/cipher/v2/watch.mjs +56 -0
- package/vendor/dragonlab/design/drago/v1/character.mjs +19 -0
- package/vendor/dragonlab/design/drago/v1/drago-master.svg +56 -0
- package/vendor/dragonlab/design/drago/v2/character.mjs +87 -0
- package/vendor/dragonlab/design/drago/v4/character.mjs +67 -0
- package/vendor/dragonlab/design/drago/v4/profile.mjs +35 -0
- package/vendor/dragonlab/design/drago/v4/review.mjs +33 -0
- package/vendor/dragonlab/design/drago/v4/watch.mjs +56 -0
- package/vendor/dragonlab/design/hyperion/v1/hyperion-master.svg +56 -0
- package/vendor/dragonlab/design/hyperion/v2/character.mjs +79 -0
- package/vendor/dragonlab/design/hyperion/v2/review.mjs +33 -0
- package/vendor/dragonlab/design/hyperion/v2/watch.mjs +56 -0
- package/vendor/dragonlab/design/lucid/recovery/arrival.mjs +22 -0
- package/vendor/dragonlab/design/lucid/recovery/character.mjs +106 -0
- package/vendor/dragonlab/design/lucid/recovery/layout.mjs +50 -0
- package/vendor/dragonlab/design/lucid/recovery/lucid-master.svg +78 -0
- package/vendor/dragonlab/design/lucid/recovery/motion.mjs +42 -0
- package/vendor/dragonlab/design/lucid/recovery/render.mjs +121 -0
- package/vendor/dragonlab/design/lucid/recovery/review.mjs +68 -0
- package/vendor/dragonlab/design/lucid/recovery/verify.mjs +84 -0
- package/vendor/dragonlab/design/lucid/recovery/watch.mjs +56 -0
- package/vendor/dragonlab/design/seer/v1/seer-master.svg +53 -0
- package/vendor/dragonlab/design/seer/v2/character.mjs +88 -0
- package/vendor/dragonlab/design/seer/v3/character.mjs +33 -0
- package/vendor/dragonlab/design/seer/v4/character.mjs +66 -0
- package/vendor/dragonlab/design/seer/v4/review.mjs +33 -0
- package/vendor/dragonlab/design/seer/v4/watch.mjs +56 -0
- package/vendor/dragonlab/design/stage3/companion-review.mjs +28 -0
- package/vendor/dragonlab/design/stage3/sentinel-art.js +39 -0
- package/vendor/dragonlab/design/stage3/sentinel-braille.js +216 -0
- package/vendor/dragonlab/design/stage3/sentinel-character.js +257 -0
- package/vendor/dragonlab/design/stage3/sentinel-icon.js +103 -0
- package/vendor/dragonlab/design/stage3/sentinel-warden-eyes.js +95 -0
- package/vendor/dragonlab/design/stage3/svg-coverage.js +148 -0
- package/vendor/dragonlab/factory/svg-rig.mjs +63 -0
- package/vendor/dragonlab/integration/companions-v1/characters.mjs +88 -0
- package/vendor/dragonlab/integration/companions-v1/cipher.mjs +2 -0
- package/vendor/dragonlab/integration/companions-v1/companion.mjs +63 -0
- package/vendor/dragonlab/integration/companions-v1/drago.mjs +2 -0
- package/vendor/dragonlab/integration/companions-v1/hyperion.mjs +2 -0
- package/vendor/dragonlab/integration/companions-v1/lucid.mjs +2 -0
- package/vendor/dragonlab/integration/companions-v1/seer.mjs +2 -0
- package/vendor/dragonlab/integration/companions-v1/sentinel.mjs +2 -0
- package/vendor/dragonlab/integration/companions-v1/view.mjs +28 -0
- package/vendor/dragonlab/integration/hyperion-v3/activity.mjs +76 -0
- package/vendor/dragonlab/lib/power-effects.js +294 -0
- package/vendor/dragonlab/lib/power-registry.js +74 -0
- package/vendor/dragonlab/vendor/quox-dragon/qart.js +348 -0
- package/vendor/dragonlab/vendor/quox-dragon/qeyes.js +213 -0
- package/lib/core/websiteSession.js +0 -148
package/lib/commands/workflow.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import { readFileSync } from 'node:fs';
|
|
6
6
|
import { extname } from 'node:path';
|
|
7
7
|
import yaml from 'js-yaml';
|
|
8
|
-
import { formatOutput, formatDetail, printWarning, printInfo } from '../core/output.js';
|
|
8
|
+
import { formatOutput, formatDetail, printWarning, printInfo, getOutputFormat } from '../core/output.js';
|
|
9
9
|
import { events, printEvent, line as glyphLine } from '../core/quoxglyph.js';
|
|
10
10
|
import { resolveOrgId, parseJSON } from './helpers.js';
|
|
11
11
|
import { resolveEndpoint } from '../api/endpoints.js';
|
|
@@ -77,6 +77,33 @@ function printApplyDiff(workflowId, changes) {
|
|
|
77
77
|
console.log('');
|
|
78
78
|
}
|
|
79
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
|
+
|
|
80
107
|
export function registerWorkflowCommands(program, ctx) {
|
|
81
108
|
const { client, auth } = ctx;
|
|
82
109
|
|
|
@@ -105,9 +132,17 @@ export function registerWorkflowCommands(program, ctx) {
|
|
|
105
132
|
|
|
106
133
|
try {
|
|
107
134
|
const ep = resolveEndpoint('workflows.applyList');
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|
+
}
|
|
111
146
|
} catch (err) {
|
|
112
147
|
printWarning(`Engine (quoxflow) workflow list failed: ${err.message}`);
|
|
113
148
|
}
|
|
@@ -131,16 +166,14 @@ export function registerWorkflowCommands(program, ctx) {
|
|
|
131
166
|
workflow
|
|
132
167
|
.command('get')
|
|
133
168
|
.description('Get workflow details')
|
|
134
|
-
.argument('<workflowId>', 'workflow ID')
|
|
169
|
+
.argument('<workflowId>', 'workflow ID or exact name')
|
|
135
170
|
.action(async (workflowId) => {
|
|
136
171
|
auth.requireAuth();
|
|
137
172
|
const orgId = resolveOrgId(auth, program);
|
|
138
173
|
|
|
139
174
|
let data, source;
|
|
140
175
|
try {
|
|
141
|
-
|
|
142
|
-
const res = await client.get(ep.path, { service: ep.service });
|
|
143
|
-
data = res.data || res;
|
|
176
|
+
data = await getEngineWorkflow(client, workflowId);
|
|
144
177
|
source = 'engine';
|
|
145
178
|
} catch (err) {
|
|
146
179
|
if (err.status !== 404) throw err;
|
|
@@ -160,20 +193,85 @@ export function registerWorkflowCommands(program, ctx) {
|
|
|
160
193
|
formatDetail({ ...data, source }, { format: program.opts().output, title: data.name });
|
|
161
194
|
});
|
|
162
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
|
+
|
|
163
243
|
workflow
|
|
164
244
|
.command('create')
|
|
165
|
-
.description('Create a new workflow')
|
|
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')
|
|
166
246
|
.requiredOption('--name <name>', 'workflow name')
|
|
167
247
|
.option('--description <desc>', 'description')
|
|
168
|
-
.option('--steps <json>', '
|
|
248
|
+
.option('--steps <json>', 'nodes as a JSON array (chained sequentially), or {"nodes":[...],"edges":[...]}')
|
|
169
249
|
.action(async (options) => {
|
|
170
250
|
auth.requireAuth();
|
|
171
|
-
|
|
172
|
-
const
|
|
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 };
|
|
173
268
|
if (options.description) body.description = options.description;
|
|
174
|
-
|
|
175
|
-
await client.post(
|
|
176
|
-
|
|
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' });
|
|
177
275
|
});
|
|
178
276
|
|
|
179
277
|
workflow
|
|
@@ -190,8 +288,9 @@ export function registerWorkflowCommands(program, ctx) {
|
|
|
190
288
|
if (options.name) body.name = options.name;
|
|
191
289
|
if (options.description) body.description = options.description;
|
|
192
290
|
if (options.status) body.status = options.status;
|
|
193
|
-
await client.patch(resolveEndpoint('workflows.update', { orgId, workflowId }).path, { service: resolveEndpoint('workflows.update', { orgId, workflowId }).service, body });
|
|
291
|
+
const data = await client.patch(resolveEndpoint('workflows.update', { orgId, workflowId }).path, { service: resolveEndpoint('workflows.update', { orgId, workflowId }).service, body });
|
|
194
292
|
printEvent(events.updated('quox', 'workflow', workflowId));
|
|
293
|
+
if (getOutputFormat() === 'json') formatDetail(data?.workflow || data, { format: 'json' });
|
|
195
294
|
});
|
|
196
295
|
|
|
197
296
|
workflow
|
|
@@ -201,8 +300,17 @@ export function registerWorkflowCommands(program, ctx) {
|
|
|
201
300
|
.action(async (workflowId) => {
|
|
202
301
|
auth.requireAuth();
|
|
203
302
|
const orgId = resolveOrgId(auth, program);
|
|
204
|
-
|
|
205
|
-
|
|
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' }));
|
|
206
314
|
});
|
|
207
315
|
|
|
208
316
|
workflow
|
|
@@ -232,7 +340,8 @@ export function registerWorkflowCommands(program, ctx) {
|
|
|
232
340
|
}
|
|
233
341
|
if (engineExists) {
|
|
234
342
|
const exEp = resolveEndpoint('workflows.applyExecute', { workflowId });
|
|
235
|
-
|
|
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 } });
|
|
236
345
|
const runId = run.executionId || run.execution_id || run.runId || run.run_id || run.id || run.execution?.id || 'ok';
|
|
237
346
|
const st = run.status || run.execution?.status;
|
|
238
347
|
printEvent(glyphLine('quox', 'workflow run', { result: `${workflowId} (quoxflow) -> run ${runId}${st ? ` [${st}]` : ''}`, status: 'ok' }));
|
|
@@ -299,15 +408,27 @@ export function registerWorkflowCommands(program, ctx) {
|
|
|
299
408
|
.action(async (workflowId) => {
|
|
300
409
|
auth.requireAuth();
|
|
301
410
|
const orgId = resolveOrgId(auth, program);
|
|
302
|
-
// No dedicated GET .../steps route exists
|
|
303
|
-
// workflow object itself.
|
|
304
|
-
|
|
305
|
-
|
|
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
|
+
}
|
|
306
427
|
formatOutput({
|
|
307
428
|
// fleet #392: workflow_steps columns are idx/label/step_type/ref_id.
|
|
308
429
|
columns: ['idx', 'label', 'step_type', 'ref_id'],
|
|
309
430
|
headers: ['#', 'Name', 'Type', 'Tool'],
|
|
310
|
-
rows
|
|
431
|
+
rows,
|
|
311
432
|
}, { format: program.opts().output });
|
|
312
433
|
});
|
|
313
434
|
|
|
@@ -319,10 +440,35 @@ export function registerWorkflowCommands(program, ctx) {
|
|
|
319
440
|
.action(async (workflowId, options) => {
|
|
320
441
|
auth.requireAuth();
|
|
321
442
|
const orgId = resolveOrgId(auth, program);
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
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
|
+
}
|
|
326
472
|
printEvent(events.updated('quox', 'workflow steps', workflowId));
|
|
327
473
|
});
|
|
328
474
|
|
|
@@ -390,7 +536,10 @@ export function registerWorkflowCommands(program, ctx) {
|
|
|
390
536
|
}
|
|
391
537
|
|
|
392
538
|
const createEp = resolveEndpoint('workflows.applyCreate');
|
|
393
|
-
|
|
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 });
|
|
394
543
|
const created = data.data || data;
|
|
395
544
|
printEvent(events.created('quox', 'workflow', `${created.name || wf.name || created.id} (quoxflow) id:${created.id}`));
|
|
396
545
|
formatDetail(created, { format: program.opts().output });
|
|
@@ -421,10 +570,18 @@ export function registerWorkflowCommands(program, ctx) {
|
|
|
421
570
|
try {
|
|
422
571
|
await client.get(getEp.path, { service: getEp.service });
|
|
423
572
|
} catch (err) {
|
|
424
|
-
if (err.status
|
|
425
|
-
|
|
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;
|
|
426
584
|
}
|
|
427
|
-
throw err;
|
|
428
585
|
}
|
|
429
586
|
targetId = workflowId;
|
|
430
587
|
} else {
|
package/lib/commands/x.js
CHANGED
|
@@ -340,10 +340,19 @@ export function registerXCommands(program, ctx) {
|
|
|
340
340
|
const orgId = resolveOrgId(auth, program);
|
|
341
341
|
const ep = resolveEndpoint('v1.tools.execute');
|
|
342
342
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
343
|
+
// Budget over the org-scoped REST route, not the x_budget_status tool: that tool left the
|
|
344
|
+
// Default hat (cap room), so the tool path answered "not granted" for every caller.
|
|
345
|
+
let budgetData;
|
|
346
|
+
try {
|
|
347
|
+
const actEp = resolveEndpoint('x.activity');
|
|
348
|
+
const act = await client.get(actEp.path, { service: actEp.service, query: { org_id: orgId } });
|
|
349
|
+
const b = act && act.budget;
|
|
350
|
+
budgetData = b && typeof b === 'object'
|
|
351
|
+
? { success: true, daily_used: b.dailyUsed, daily_limit: b.dailyLimit, monthly_used: b.monthlyUsed, monthly_limit: b.monthlyLimit, org_id: act.org_id || orgId }
|
|
352
|
+
: { success: false, error: (act && act.error) || 'budget missing from /x/activity response' };
|
|
353
|
+
} catch (err) {
|
|
354
|
+
budgetData = { success: false, error: err.message };
|
|
355
|
+
}
|
|
347
356
|
|
|
348
357
|
let credResult = null;
|
|
349
358
|
let credError = null;
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `quox youtube` — YouTube plugin verbs, YT-O-6 (tiktok/linkedin CLI parity).
|
|
3
|
+
*
|
|
4
|
+
* quox youtube upload "<title>" --file <path-in-media-root> [--description t] [--privacy private|unlisted|public] [--thumbnail <image-path>]
|
|
5
|
+
* quox youtube thumbnail <videoId> --image <path-in-media-root>
|
|
6
|
+
* quox youtube videos [--limit <n>]
|
|
7
|
+
* quox youtube channel
|
|
8
|
+
* quox youtube analytics [--metrics a,b,c] [--video <id>]
|
|
9
|
+
* quox youtube comment <commentId> "<text>"
|
|
10
|
+
*
|
|
11
|
+
* The write verbs (upload, thumbnail, comment) execute the youtube connector
|
|
12
|
+
* tool through the governed collector endpoint (POST /api/v1/tools/execute ->
|
|
13
|
+
* connector bridge). The bridge gates them on requiresApproval: it files an
|
|
14
|
+
* owner approval card and returns { pending:true, status:'awaiting_approval',
|
|
15
|
+
* approvalId, inbox_item_id }. The YouTube API write only happens once an
|
|
16
|
+
* approver says yes (dashboard inbox or `quox inbox respond <id> --action approve`).
|
|
17
|
+
*
|
|
18
|
+
* Honest limit on --thumbnail: YouTube assigns the video id only when the
|
|
19
|
+
* upload actually executes, i.e. AFTER approval. While the upload card is
|
|
20
|
+
* pending there is no id to attach a thumbnail to, so the chained
|
|
21
|
+
* set_thumbnail step runs only if the upload came back executed with a
|
|
22
|
+
* video id; otherwise the CLI says so and names the follow-up verb.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { formatDetail, printSuccess, printInfo, printWarning } from '../core/output.js';
|
|
26
|
+
import { resolveEndpoint } from '../api/endpoints.js';
|
|
27
|
+
import { resolveOrgId } from './helpers.js';
|
|
28
|
+
|
|
29
|
+
const PRIVACY = ['private', 'unlisted', 'public'];
|
|
30
|
+
|
|
31
|
+
async function executeYouTubeTool({ client, auth, program }, toolName, input) {
|
|
32
|
+
auth.requireAuth();
|
|
33
|
+
const orgId = resolveOrgId(auth, program);
|
|
34
|
+
const ep = resolveEndpoint('v1.tools.execute');
|
|
35
|
+
const body = { tool_name: toolName, input, org_id: orgId };
|
|
36
|
+
const data = await client.post(ep.path, { service: ep.service, body });
|
|
37
|
+
if (data && data.success === false && !data.pending) {
|
|
38
|
+
throw new Error(`${toolName} via POST ${ep.path} returned success:false: ${data.error || 'unknown error'}`);
|
|
39
|
+
}
|
|
40
|
+
return data;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function printAwaitingApproval(action, data) {
|
|
44
|
+
const itemRef = data.inbox_item_id || data.approvalId || 'unknown';
|
|
45
|
+
printSuccess(`${action} filed for owner approval - inbox item ${itemRef} (awaiting approval). Nothing is sent to YouTube until an approver says yes.`);
|
|
46
|
+
if (data.inbox_item_id) {
|
|
47
|
+
printInfo(`Review and approve: quox inbox get ${data.inbox_item_id} (or: quox inbox respond ${data.inbox_item_id} --action approve)`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function registerYouTubeCommands(program, ctx) {
|
|
52
|
+
const { client, auth } = ctx;
|
|
53
|
+
const run = (tool, input) => executeYouTubeTool({ client, auth, program }, tool, input);
|
|
54
|
+
const asJson = () => program.opts().output === 'json';
|
|
55
|
+
|
|
56
|
+
const youtube = program
|
|
57
|
+
.command('youtube')
|
|
58
|
+
.description('YouTube uploads, thumbnails, comments and reads: writes are governed and HITL-approved via HITL Inbox, on your own vaulted Google credential');
|
|
59
|
+
|
|
60
|
+
youtube
|
|
61
|
+
.command('upload')
|
|
62
|
+
.description('Upload a video from a file under the org media root. Files an owner approval card and returns immediately; never uploads directly.')
|
|
63
|
+
.argument('<title>', 'video title (max 100 characters)')
|
|
64
|
+
.requiredOption('--file <path>', 'video file path, relative to the org media root')
|
|
65
|
+
.option('--description <text>', 'video description')
|
|
66
|
+
.option('--privacy <status>', `privacy status: ${PRIVACY.join('|')} (default private)`, 'private')
|
|
67
|
+
.option('--thumbnail <path>', 'thumbnail image path (media root); chained set_thumbnail, only possible once the upload has a video id')
|
|
68
|
+
.action(async (title, options) => {
|
|
69
|
+
const privacy = options.privacy || 'private';
|
|
70
|
+
if (!PRIVACY.includes(privacy)) {
|
|
71
|
+
throw new Error(`--privacy must be one of ${PRIVACY.join(', ')} (got ${privacy})`);
|
|
72
|
+
}
|
|
73
|
+
const input = { title, file_path: options.file, privacy_status: privacy };
|
|
74
|
+
if (options.description) input.description = options.description;
|
|
75
|
+
|
|
76
|
+
const data = await run('youtube_upload_video', input);
|
|
77
|
+
const videoId = data?.result?.video_id || data?.video_id || null;
|
|
78
|
+
let thumb = null;
|
|
79
|
+
if (options.thumbnail && videoId && !data.pending) {
|
|
80
|
+
thumb = await run('youtube_set_thumbnail', { video_id: videoId, image_path: options.thumbnail });
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (asJson()) {
|
|
84
|
+
formatDetail(thumb ? { upload: data, thumbnail: thumb } : data, { format: 'json' });
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
if (data.pending) {
|
|
88
|
+
printAwaitingApproval('Upload', data);
|
|
89
|
+
if (options.thumbnail) {
|
|
90
|
+
printWarning(`Thumbnail not filed: the video id does not exist until the upload is approved and runs. After approval run: quox youtube thumbnail <videoId> --image ${options.thumbnail} (that files a second, separate approval).`);
|
|
91
|
+
}
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
printSuccess(`Upload executed${videoId ? ` - video ${videoId}` : ''}.`);
|
|
95
|
+
if (thumb) {
|
|
96
|
+
if (thumb.pending) printAwaitingApproval('Thumbnail', thumb);
|
|
97
|
+
else printSuccess('Thumbnail set.');
|
|
98
|
+
} else if (options.thumbnail) {
|
|
99
|
+
printWarning(`Upload returned no video id, so the thumbnail was not set. Run: quox youtube thumbnail <videoId> --image ${options.thumbnail}`);
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
youtube
|
|
104
|
+
.command('thumbnail')
|
|
105
|
+
.description('Set a custom thumbnail on an existing video from an image under the org media root. Files an owner approval card; never sets it directly.')
|
|
106
|
+
.argument('<videoId>', 'YouTube video id')
|
|
107
|
+
.requiredOption('--image <path>', 'image path (.jpg/.jpeg/.png, max 2MB), relative to the org media root')
|
|
108
|
+
.action(async (videoId, options) => {
|
|
109
|
+
const data = await run('youtube_set_thumbnail', { video_id: videoId, image_path: options.image });
|
|
110
|
+
if (asJson()) { formatDetail(data, { format: 'json' }); return; }
|
|
111
|
+
printAwaitingApproval('Thumbnail', data);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
youtube
|
|
115
|
+
.command('videos')
|
|
116
|
+
.description('List the connected channel\'s recent videos (read-only, no approval needed).')
|
|
117
|
+
.option('--limit <n>', 'max videos to return (max 50)', '20')
|
|
118
|
+
.action(async (options) => {
|
|
119
|
+
const count = Math.min(parseInt(options.limit, 10) || 20, 50);
|
|
120
|
+
const data = await run('youtube_list_videos', { count });
|
|
121
|
+
if (asJson()) { formatDetail(data, { format: 'json' }); return; }
|
|
122
|
+
const videos = data.result?.videos || [];
|
|
123
|
+
if (videos.length === 0) { printInfo('No videos returned.'); return; }
|
|
124
|
+
for (const v of videos) {
|
|
125
|
+
console.log(`${v.video_id} ${v.title || '(no title)'} [${v.privacy_status || 'unknown'}]`);
|
|
126
|
+
console.log(` views: ${v.view_count ?? 0} likes: ${v.like_count ?? 0} comments: ${v.comment_count ?? 0}`);
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
youtube
|
|
131
|
+
.command('channel')
|
|
132
|
+
.description('Show the connected YouTube channel (also the cheapest live credential check).')
|
|
133
|
+
.action(async () => {
|
|
134
|
+
const data = await run('youtube_get_channel', {});
|
|
135
|
+
if (asJson()) { formatDetail(data, { format: 'json' }); return; }
|
|
136
|
+
const ch = data.result || {};
|
|
137
|
+
if (!ch.channel_id && !ch.title) { printInfo('No channel returned.'); return; }
|
|
138
|
+
console.log(`${ch.title || 'unknown'} (${ch.channel_id || 'unknown'})`);
|
|
139
|
+
console.log(` subscribers: ${ch.subscriber_count ?? 'unknown'} videos: ${ch.video_count ?? 'unknown'} views: ${ch.view_count ?? 'unknown'}`);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
youtube
|
|
143
|
+
.command('analytics')
|
|
144
|
+
.description('Channel (or single video) analytics totals for the last 28 days.')
|
|
145
|
+
.option('--metrics <list>', 'comma-separated YouTube Analytics metric names (default views,estimatedMinutesWatched,averageViewDuration,likes,subscribersGained)')
|
|
146
|
+
.option('--video <id>', 'restrict to one video id')
|
|
147
|
+
.action(async (options) => {
|
|
148
|
+
const input = {};
|
|
149
|
+
if (options.metrics) input.metrics = options.metrics;
|
|
150
|
+
if (options.video) input.video_id = options.video;
|
|
151
|
+
const data = await run('youtube_get_analytics', input);
|
|
152
|
+
if (asJson()) { formatDetail(data, { format: 'json' }); return; }
|
|
153
|
+
const r = data.result || {};
|
|
154
|
+
const metrics = r.metrics || {};
|
|
155
|
+
const keys = Object.keys(metrics);
|
|
156
|
+
if (keys.length === 0) { printInfo('No analytics returned.'); return; }
|
|
157
|
+
console.log(`${r.start_date || '?'} to ${r.end_date || '?'}${r.video_id ? ` video ${r.video_id}` : ''}`);
|
|
158
|
+
for (const k of keys) console.log(` ${k}: ${metrics[k]}`);
|
|
159
|
+
if (r.missing_metrics?.length) printWarning(`Not returned by YouTube: ${r.missing_metrics.join(', ')}`);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
youtube
|
|
163
|
+
.command('comment')
|
|
164
|
+
.description('Reply to a comment thread. Files an owner approval card and returns immediately; never posts directly.')
|
|
165
|
+
.argument('<commentId>', 'comment thread id to reply to (the id of the top-level comment)')
|
|
166
|
+
.argument('<text>', 'reply text')
|
|
167
|
+
.action(async (parentId, text) => {
|
|
168
|
+
const data = await run('youtube_reply_comment', { parent_id: parentId, text });
|
|
169
|
+
if (asJson()) { formatDetail(data, { format: 'json' }); return; }
|
|
170
|
+
printAwaitingApproval('Comment reply', data);
|
|
171
|
+
});
|
|
172
|
+
}
|
package/lib/core/actingAs.js
CHANGED
|
@@ -83,14 +83,14 @@ export function hasExplicitProfile(argv, env = process.env) {
|
|
|
83
83
|
* session's sole attach (implicit default), else the profile identity.
|
|
84
84
|
* @returns {{name: string|null, source: 'flag'|'env'|'self'|'profile'|'default'|null}}
|
|
85
85
|
*/
|
|
86
|
-
export async function resolveActing({ argv = process.argv.slice(2), env = process.env, resolveAttach } = {}) {
|
|
86
|
+
export async function resolveActing({ argv = process.argv.slice(2), env = process.env, resolveAttach, explicitProfile = false } = {}) {
|
|
87
87
|
const explicit = actingAsName(env);
|
|
88
88
|
if (explicit) {
|
|
89
89
|
const source = env.QUOX_AS_FLAG === '1' ? 'flag' : 'env';
|
|
90
90
|
if (explicit.toLowerCase() === 'self') return { name: null, source: 'self', via: source };
|
|
91
91
|
return { name: explicit, source };
|
|
92
92
|
}
|
|
93
|
-
if (hasExplicitProfile(argv, env)) return { name: null, source: 'profile' };
|
|
93
|
+
if (explicitProfile || hasExplicitProfile(argv, env)) return { name: null, source: 'profile' };
|
|
94
94
|
if (isDefaultExempt(argv)) return { name: null, source: null };
|
|
95
95
|
const mine = resolveAttach ? resolveAttach() : (await import('../commands/agentAttach.js')).resolveMyAttach();
|
|
96
96
|
if (mine?.agentId) return { name: mine.agentId, source: 'default' };
|