quoxcli 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +2 -0
- package/bin/quox.js +99 -126
- package/lib/api/endpoints/actions.js +15 -0
- package/lib/api/endpoints/admin.js +46 -0
- package/lib/api/endpoints/agent-schedules.js +26 -0
- package/lib/api/endpoints/agents.js +7 -0
- package/lib/api/endpoints/assistants.js +12 -0
- package/lib/api/endpoints/away.js +15 -0
- package/lib/api/endpoints/blog.js +16 -0
- package/lib/api/endpoints/chat.js +14 -0
- package/lib/api/endpoints/contactsq.js +6 -0
- package/lib/api/endpoints/conversations.js +14 -0
- package/lib/api/endpoints/devServerRegistry.js +25 -0
- package/lib/api/endpoints/engagement.js +26 -0
- package/lib/api/endpoints/evidence.js +20 -0
- package/lib/api/endpoints/executions.js +13 -0
- package/lib/api/endpoints/fetchAllowlist.js +27 -0
- package/lib/api/endpoints/files.js +14 -0
- package/lib/api/endpoints/hat.js +44 -0
- package/lib/api/endpoints/inbox.js +20 -0
- package/lib/api/endpoints/index.js +26 -0
- package/lib/api/endpoints/loops.js +26 -0
- package/lib/api/endpoints/memory.js +19 -0
- package/lib/api/endpoints/mind.js +13 -0
- package/lib/api/endpoints/mon.js +179 -0
- package/lib/api/endpoints/objectives.js +12 -0
- package/lib/api/endpoints/plugins.js +18 -0
- package/lib/api/endpoints/presence.js +33 -0
- package/lib/api/endpoints/quoxplan.js +7 -0
- package/lib/api/endpoints/quoxseo.js +12 -0
- package/lib/api/endpoints/router.js +17 -0
- package/lib/api/endpoints/safety.js +7 -0
- package/lib/api/endpoints/server.js +21 -0
- package/lib/api/endpoints/setup.js +6 -0
- package/lib/api/endpoints/skills.js +20 -0
- package/lib/api/endpoints/stepup.js +20 -0
- package/lib/api/endpoints/teams.js +2 -2
- package/lib/api/endpoints/triggers.js +26 -0
- package/lib/api/endpoints/v1.js +120 -0
- package/lib/api/endpoints/ward.js +7 -0
- package/lib/api/endpoints/workflows.js +7 -0
- package/lib/api/endpoints/x.js +8 -0
- package/lib/commands/actions.js +131 -0
- package/lib/commands/admin.js +16 -0
- package/lib/commands/agent.js +170 -27
- package/lib/commands/agentAttach.js +140 -13
- package/lib/commands/approvals.js +78 -1
- package/lib/commands/aside.js +24 -0
- package/lib/commands/ask.js +146 -0
- package/lib/commands/assistant.js +59 -4
- package/lib/commands/auth.js +8 -2
- package/lib/commands/backup.js +8 -4
- package/lib/commands/blog.js +110 -0
- package/lib/commands/brain.js +105 -14
- package/lib/commands/build.js +19 -5
- package/lib/commands/chat.js +180 -26
- package/lib/commands/chatCompanion.js +27 -0
- package/lib/commands/chatState.js +45 -0
- package/lib/commands/claude.js +35 -38
- package/lib/commands/contacts.js +134 -4
- package/lib/commands/contactsSendAt.js +21 -0
- package/lib/commands/conversation.js +35 -4
- package/lib/commands/dashboard.js +43 -0
- package/lib/commands/department.js +28 -7
- package/lib/commands/devResident.js +15 -3
- package/lib/commands/devSupervisor.js +13 -1
- package/lib/commands/discord.js +75 -39
- package/lib/commands/done.js +98 -28
- package/lib/commands/engagement.js +107 -6
- package/lib/commands/evidence.js +154 -0
- package/lib/commands/ext.js +23 -0
- package/lib/commands/fetchAllowlist.js +71 -0
- package/lib/commands/file.js +38 -1
- package/lib/commands/fleet.js +336 -4
- package/lib/commands/governance.js +12 -3
- package/lib/commands/gripe.js +44 -13
- package/lib/commands/hat.js +254 -9
- package/lib/commands/helpers.js +18 -1
- package/lib/commands/hyperion.js +58 -0
- package/lib/commands/image.js +68 -0
- package/lib/commands/inbox.js +74 -23
- package/lib/commands/kuma.js +1050 -0
- package/lib/commands/kumaSla.js +174 -0
- package/lib/commands/licence.js +52 -155
- package/lib/commands/loop.js +291 -0
- package/lib/commands/lucid.js +10 -4
- package/lib/commands/memory.js +85 -8
- package/lib/commands/mind.js +71 -0
- package/lib/commands/mon.js +69 -2
- package/lib/commands/monitoring.js +1 -1
- package/lib/commands/motion.js +2 -2
- package/lib/commands/nucleus.js +48 -17
- package/lib/commands/objective.js +59 -1
- package/lib/commands/offload.js +89 -0
- package/lib/commands/onboard.js +2 -1
- package/lib/commands/org.js +30 -3
- package/lib/commands/peek.js +28 -0
- package/lib/commands/plan.js +38 -0
- package/lib/commands/plugin.js +110 -0
- package/lib/commands/presence.js +89 -0
- package/lib/commands/provider.js +217 -0
- package/lib/commands/qlarity.js +19 -3
- package/lib/commands/quoxchat.js +13 -4
- package/lib/commands/quoxseo.js +69 -0
- package/lib/commands/reach.js +2 -1
- package/lib/commands/research.js +79 -0
- package/lib/commands/router.js +77 -0
- package/lib/commands/rows.js +367 -0
- package/lib/commands/run.js +82 -10
- package/lib/commands/scope.js +2 -2
- package/lib/commands/server.js +405 -0
- package/lib/commands/setup.js +33 -7
- package/lib/commands/shortcut.js +41 -0
- package/lib/commands/skills.js +68 -9
- package/lib/commands/slot.js +260 -7
- package/lib/commands/status.js +1 -1
- package/lib/commands/team.js +3 -1
- package/lib/commands/tips.js +23 -0
- package/lib/commands/tool.js +133 -45
- package/lib/commands/trace.js +12 -7
- package/lib/commands/trigger.js +95 -0
- package/lib/commands/update.js +46 -0
- package/lib/commands/vault.js +31 -8
- package/lib/commands/vm.js +237 -0
- package/lib/commands/voice.js +34 -3
- package/lib/commands/volt.js +45 -3
- package/lib/commands/walkthrough.js +32 -0
- package/lib/commands/watch.js +73 -21
- package/lib/commands/welcome.js +14 -0
- package/lib/commands/workflow.js +189 -32
- package/lib/commands/x.js +13 -4
- package/lib/commands/youtube.js +172 -0
- package/lib/core/actingAs.js +2 -2
- package/lib/core/actionItems.js +115 -0
- package/lib/core/activeSummary.js +186 -0
- package/lib/core/agentAlias.js +2 -0
- package/lib/core/agentFamilies.js +7 -0
- package/lib/core/aside.js +54 -0
- package/lib/core/askDecision.js +60 -0
- package/lib/core/auth.js +164 -13
- package/lib/core/callerSource.js +1 -1
- package/lib/core/cliBuild.js +62 -0
- package/lib/core/client.js +152 -35
- package/lib/core/commandRegistrars.js +130 -0
- package/lib/core/config.js +143 -25
- package/lib/core/customHats.js +168 -0
- package/lib/core/doneEngineFetch.js +8 -0
- package/lib/core/errors.js +329 -20
- package/lib/core/findingReconcile.js +90 -0
- package/lib/core/global-flags.js +105 -5
- package/lib/core/governedSession.js +1 -3
- package/lib/core/hyperion.js +102 -0
- package/lib/core/inboxResolution.js +65 -0
- package/lib/core/lazyCommands.js +4 -1
- package/lib/core/liveSessionsReport.js +1 -1
- package/lib/core/loopGlyph.js +97 -0
- package/lib/core/offload.js +533 -0
- package/lib/core/org-override.js +1 -1
- package/lib/core/orgResolve.js +70 -0
- package/lib/core/output.js +11 -11
- package/lib/core/peek.js +126 -0
- package/lib/core/profileUsers.js +51 -0
- package/lib/core/promptCancel.js +18 -0
- package/lib/core/repoPath.js +11 -0
- package/lib/core/residentSeed.js +3 -1
- package/lib/core/rootOptions.js +39 -0
- package/lib/core/router.js +168 -0
- package/lib/core/selfUpdate.js +188 -0
- package/lib/core/sessionHeartbeat.js +12 -8
- package/lib/core/sessionsOverview.js +64 -0
- package/lib/core/shortcut.js +83 -0
- package/lib/core/slotKeeper.js +248 -3
- package/lib/core/slotWake.js +120 -0
- package/lib/core/supervisorEngine.js +71 -2
- package/lib/core/version.js +1 -1
- package/lib/core/vm.js +366 -0
- package/lib/core/vmTui.js +86 -0
- package/lib/core/walkthrough.js +35 -0
- package/lib/dragon/companionView.js +34 -0
- package/lib/dragon/frames.js +118 -0
- package/lib/dragon/jobs.js +123 -0
- package/lib/dragon/renderWorker.js +15 -0
- package/lib/dragon/select.js +72 -0
- package/lib/evidence/bundleExport.js +47 -0
- package/lib/evidence/bundleVerify.js +77 -0
- package/lib/hyperion/deck.js +29 -0
- package/lib/lucid/LucidCard.jsx +68 -0
- package/lib/lucid/LucidIntro.jsx +45 -58
- package/lib/lucid/cards.js +86 -0
- package/lib/lucid/markdown.js +81 -0
- package/lib/lucid/motion.js +56 -0
- package/lib/quoxon/EXTENSIONS.md +23 -0
- package/lib/quoxon/activity.js +14 -2
- package/lib/quoxon/aocl.js +117 -0
- package/lib/quoxon/api.js +690 -25
- package/lib/quoxon/app.js +3586 -250
- package/lib/quoxon/approvalFacts.js +96 -0
- package/lib/quoxon/approvalSentence.js +44 -0
- package/lib/quoxon/approvalsView.js +384 -0
- package/lib/quoxon/asideView.js +40 -0
- package/lib/quoxon/ask.js +121 -0
- package/lib/quoxon/away.js +52 -0
- package/lib/quoxon/bang.js +224 -0
- package/lib/quoxon/bulkRisk.js +17 -0
- package/lib/quoxon/capabilities.js +13 -6
- package/lib/quoxon/cells.js +53 -0
- package/lib/quoxon/commandShort.js +124 -0
- package/lib/quoxon/commands.js +429 -39
- package/lib/quoxon/connect.js +210 -23
- package/lib/quoxon/connectCard.js +19 -2
- package/lib/quoxon/consent.js +86 -0
- package/lib/quoxon/copy.js +4 -3
- package/lib/quoxon/countdownLine.js +20 -0
- package/lib/quoxon/deck.js +30 -0
- package/lib/quoxon/demo.autoplay.js +29 -0
- package/lib/quoxon/demo.fixtures.js +1 -1
- package/lib/quoxon/demo.js +46 -3
- package/lib/quoxon/devObserver.js +20 -0
- package/lib/quoxon/dividerAocl.js +192 -0
- package/lib/quoxon/dividerRule.js +57 -0
- package/lib/quoxon/dragonCompanion.js +255 -0
- package/lib/quoxon/dragonModes.js +151 -0
- package/lib/quoxon/dragonMoments.js +82 -0
- package/lib/quoxon/dragonReads.js +68 -0
- package/lib/quoxon/dragonView.js +41 -0
- package/lib/quoxon/earlyInput.js +40 -0
- package/lib/quoxon/echo.js +15 -0
- package/lib/quoxon/egg.js +2 -2
- package/lib/quoxon/events.js +266 -26
- package/lib/quoxon/extensions.js +276 -0
- package/lib/quoxon/feedback.js +50 -0
- package/lib/quoxon/gallery.js +21 -7
- package/lib/quoxon/handoffOffer.js +78 -0
- package/lib/quoxon/hatSwitch.js +37 -0
- package/lib/quoxon/headless.js +161 -0
- package/lib/quoxon/hostUrls.js +41 -0
- package/lib/quoxon/inboxView.js +79 -0
- package/lib/quoxon/indicator.js +6 -2
- package/lib/quoxon/installCli.js +79 -0
- package/lib/quoxon/intent.js +12 -1
- package/lib/quoxon/keys.js +51 -8
- package/lib/quoxon/launcher.js +103 -0
- package/lib/quoxon/loopCard.js +456 -0
- package/lib/quoxon/loopCards.js +180 -0
- package/lib/quoxon/loopStream.js +87 -0
- package/lib/quoxon/main.js +101 -160
- package/lib/quoxon/markdown.js +45 -21
- package/lib/quoxon/markdownSettle.js +19 -0
- package/lib/quoxon/matrixDomains.json +46 -0
- package/lib/quoxon/mentions.js +90 -0
- package/lib/quoxon/mind.js +103 -0
- package/lib/quoxon/mode.js +46 -0
- package/lib/quoxon/model.js +132 -26
- package/lib/quoxon/modelPrefs.js +345 -0
- package/lib/quoxon/motion/clock.js +3 -2
- package/lib/quoxon/motion/level.js +91 -0
- package/lib/quoxon/mouse.js +222 -0
- package/lib/quoxon/noai.js +57 -0
- package/lib/quoxon/objectiveOffer.js +82 -0
- package/lib/quoxon/objects/away.js +72 -0
- package/lib/quoxon/objects/fetch.js +17 -5
- package/lib/quoxon/objects/fleet.js +149 -0
- package/lib/quoxon/objects/health.js +186 -0
- package/lib/quoxon/objects/index.js +46 -7
- package/lib/quoxon/objects/mirror.js +137 -0
- package/lib/quoxon/objects/model.js +33 -0
- package/lib/quoxon/objects/peek.js +17 -0
- package/lib/quoxon/objects/proof.js +12 -4
- package/lib/quoxon/objects/prove.js +130 -0
- package/lib/quoxon/objects/replay.js +125 -0
- package/lib/quoxon/objects/sitrep.js +58 -0
- package/lib/quoxon/objects/view.js +11 -0
- package/lib/quoxon/onboarding/copy.js +32 -4
- package/lib/quoxon/onboarding/credential.js +17 -4
- package/lib/quoxon/onboarding/dockerOffer.js +45 -0
- package/lib/quoxon/onboarding/failureCause.js +33 -0
- package/lib/quoxon/onboarding/finish.js +15 -0
- package/lib/quoxon/onboarding/index.js +34 -7
- package/lib/quoxon/onboarding/installRunner.js +64 -5
- package/lib/quoxon/onboarding/preflight.js +17 -3
- package/lib/quoxon/onboarding/probe.js +65 -0
- package/lib/quoxon/onboarding/shortcutOffer.js +28 -0
- package/lib/quoxon/onboarding/view.js +76 -19
- package/lib/quoxon/orgLabel.js +10 -0
- package/lib/quoxon/orgSwitch.js +59 -0
- package/lib/quoxon/ownBroadcast.js +22 -0
- package/lib/quoxon/palette.js +43 -0
- package/lib/quoxon/planSteps.js +51 -0
- package/lib/quoxon/print.js +12 -5
- package/lib/quoxon/profileSwitch.js +53 -0
- package/lib/quoxon/promptHistory.js +41 -0
- package/lib/quoxon/providers.js +48 -0
- package/lib/quoxon/qart.js +20 -51
- package/lib/quoxon/qdragon.js +410 -0
- package/lib/quoxon/qdragonFly.js +62 -0
- package/lib/quoxon/qdragonScenes.js +282 -0
- package/lib/quoxon/qeyes.js +213 -0
- package/lib/quoxon/qmotion.js +75 -37
- package/lib/quoxon/render.js +740 -124
- package/lib/quoxon/resend.js +72 -0
- package/lib/quoxon/roster.js +117 -17
- package/lib/quoxon/route.js +6 -5
- package/lib/quoxon/rows.js +954 -0
- package/lib/quoxon/rowsStore.js +80 -0
- package/lib/quoxon/screenContext.js +150 -0
- package/lib/quoxon/screenFrames.js +254 -0
- package/lib/quoxon/serverChip.js +42 -0
- package/lib/quoxon/session.js +182 -2
- package/lib/quoxon/sessionDead.js +17 -0
- package/lib/quoxon/signin.js +125 -0
- package/lib/quoxon/slash/agents.js +112 -0
- package/lib/quoxon/slash/argSources.js +65 -0
- package/lib/quoxon/slash/args.js +65 -0
- package/lib/quoxon/slash/automate.js +89 -0
- package/lib/quoxon/slash/broadcast.js +140 -0
- package/lib/quoxon/slash/broadcastRun.js +108 -0
- package/lib/quoxon/slash/card.js +30 -0
- package/lib/quoxon/slash/challenge.js +114 -0
- package/lib/quoxon/slash/cliCommand.js +94 -0
- package/lib/quoxon/slash/cliLeaves.json +1023 -0
- package/lib/quoxon/slash/compact.js +1 -1
- package/lib/quoxon/slash/confirm.js +30 -0
- package/lib/quoxon/slash/crystallize.js +69 -0
- package/lib/quoxon/slash/dashboard.js +136 -0
- package/lib/quoxon/slash/draft.js +54 -0
- package/lib/quoxon/slash/evidenceCmd.js +34 -0
- package/lib/quoxon/slash/hatWalk.js +206 -0
- package/lib/quoxon/slash/inverses.js +77 -0
- package/lib/quoxon/slash/killer.js +24 -0
- package/lib/quoxon/slash/killerApi.js +16 -0
- package/lib/quoxon/slash/ledger.js +13 -8
- package/lib/quoxon/slash/listView.js +3 -1
- package/lib/quoxon/slash/modelCmd.js +261 -0
- package/lib/quoxon/slash/palette.js +22 -8
- package/lib/quoxon/slash/paletteController.js +117 -7
- package/lib/quoxon/slash/panels.js +21 -6
- package/lib/quoxon/slash/picker.js +20 -2
- package/lib/quoxon/slash/qpeCmd.js +35 -0
- package/lib/quoxon/slash/restart.js +95 -13
- package/lib/quoxon/slash/resume.js +18 -1
- package/lib/quoxon/slash/rowCmd.js +157 -0
- package/lib/quoxon/slash/rowWalk.js +175 -0
- package/lib/quoxon/slash/settings.js +16 -6
- package/lib/quoxon/slash/spawn.js +177 -0
- package/lib/quoxon/slash/stepUp.js +274 -0
- package/lib/quoxon/slash/stepUpCard.js +60 -0
- package/lib/quoxon/slash/stepUpCmds.js +78 -0
- package/lib/quoxon/slash/todoCmd.js +122 -0
- package/lib/quoxon/slash/undoApi.js +38 -0
- package/lib/quoxon/slash/undoCmd.js +49 -0
- package/lib/quoxon/slash/undoWish.js +15 -0
- package/lib/quoxon/slash/watch.js +164 -0
- package/lib/quoxon/slash/wish.js +23 -0
- package/lib/quoxon/slash/wishCmd.js +32 -0
- package/lib/quoxon/slotChat.js +300 -0
- package/lib/quoxon/slotFormat.js +195 -0
- package/lib/quoxon/slotRun.js +131 -0
- package/lib/quoxon/sound/mapping.js +2 -1
- package/lib/quoxon/stray.js +22 -0
- package/lib/quoxon/suggestCommand.js +90 -0
- package/lib/quoxon/term.js +46 -11
- package/lib/quoxon/textBuffer.js +176 -2
- package/lib/quoxon/theme.js +58 -25
- package/lib/quoxon/tips.js +433 -0
- package/lib/quoxon/toolCards.js +30 -0
- package/lib/quoxon/toolError.js +41 -0
- package/lib/quoxon/transcript.js +42 -4
- package/lib/quoxon/web/qface.js +18 -10
- package/lib/quoxon/welcome.js +1 -1
- package/lib/quoxon/whatsnew.js +136 -0
- package/lib/quoxon/whatsnew.jsonl +134 -0
- package/lib/quoxon/windowOrg.js +123 -0
- package/lib/server/devServers.js +137 -0
- package/lib/server/diag.js +129 -0
- package/lib/server/doctor.js +71 -0
- package/lib/server/install.js +149 -0
- package/lib/server/pluginEnable.js +161 -0
- package/lib/server/skew.js +56 -0
- package/lib/server/snapshot.js +131 -0
- package/lib/server/status.js +128 -0
- package/lib/server/sys.js +65 -0
- package/lib/server/uninstall.js +63 -0
- package/lib/server/update.js +234 -0
- package/lib/toolOutputShape.js +315 -0
- package/lib/tui/views/HyperionDeck.jsx +79 -0
- package/lib/tui/views/LucidDeck.jsx +209 -217
- package/package.json +5 -1
- package/vendor/dragonlab/SOURCE +4 -0
- package/vendor/dragonlab/design/cipher/v1/cipher-master.svg +46 -0
- package/vendor/dragonlab/design/cipher/v2/character.mjs +49 -0
- package/vendor/dragonlab/design/cipher/v2/review.mjs +33 -0
- package/vendor/dragonlab/design/cipher/v2/watch.mjs +56 -0
- package/vendor/dragonlab/design/drago/v1/character.mjs +19 -0
- package/vendor/dragonlab/design/drago/v1/drago-master.svg +56 -0
- package/vendor/dragonlab/design/drago/v2/character.mjs +87 -0
- package/vendor/dragonlab/design/drago/v4/character.mjs +67 -0
- package/vendor/dragonlab/design/drago/v4/profile.mjs +35 -0
- package/vendor/dragonlab/design/drago/v4/review.mjs +33 -0
- package/vendor/dragonlab/design/drago/v4/watch.mjs +56 -0
- package/vendor/dragonlab/design/hyperion/v1/hyperion-master.svg +56 -0
- package/vendor/dragonlab/design/hyperion/v2/character.mjs +79 -0
- package/vendor/dragonlab/design/hyperion/v2/review.mjs +33 -0
- package/vendor/dragonlab/design/hyperion/v2/watch.mjs +56 -0
- package/vendor/dragonlab/design/lucid/recovery/arrival.mjs +22 -0
- package/vendor/dragonlab/design/lucid/recovery/character.mjs +106 -0
- package/vendor/dragonlab/design/lucid/recovery/layout.mjs +50 -0
- package/vendor/dragonlab/design/lucid/recovery/lucid-master.svg +78 -0
- package/vendor/dragonlab/design/lucid/recovery/motion.mjs +42 -0
- package/vendor/dragonlab/design/lucid/recovery/render.mjs +121 -0
- package/vendor/dragonlab/design/lucid/recovery/review.mjs +68 -0
- package/vendor/dragonlab/design/lucid/recovery/verify.mjs +84 -0
- package/vendor/dragonlab/design/lucid/recovery/watch.mjs +56 -0
- package/vendor/dragonlab/design/seer/v1/seer-master.svg +53 -0
- package/vendor/dragonlab/design/seer/v2/character.mjs +88 -0
- package/vendor/dragonlab/design/seer/v3/character.mjs +33 -0
- package/vendor/dragonlab/design/seer/v4/character.mjs +66 -0
- package/vendor/dragonlab/design/seer/v4/review.mjs +33 -0
- package/vendor/dragonlab/design/seer/v4/watch.mjs +56 -0
- package/vendor/dragonlab/design/stage3/companion-review.mjs +28 -0
- package/vendor/dragonlab/design/stage3/sentinel-art.js +39 -0
- package/vendor/dragonlab/design/stage3/sentinel-braille.js +216 -0
- package/vendor/dragonlab/design/stage3/sentinel-character.js +257 -0
- package/vendor/dragonlab/design/stage3/sentinel-icon.js +103 -0
- package/vendor/dragonlab/design/stage3/sentinel-warden-eyes.js +95 -0
- package/vendor/dragonlab/design/stage3/svg-coverage.js +148 -0
- package/vendor/dragonlab/factory/svg-rig.mjs +63 -0
- package/vendor/dragonlab/integration/companions-v1/characters.mjs +88 -0
- package/vendor/dragonlab/integration/companions-v1/cipher.mjs +2 -0
- package/vendor/dragonlab/integration/companions-v1/companion.mjs +63 -0
- package/vendor/dragonlab/integration/companions-v1/drago.mjs +2 -0
- package/vendor/dragonlab/integration/companions-v1/hyperion.mjs +2 -0
- package/vendor/dragonlab/integration/companions-v1/lucid.mjs +2 -0
- package/vendor/dragonlab/integration/companions-v1/seer.mjs +2 -0
- package/vendor/dragonlab/integration/companions-v1/sentinel.mjs +2 -0
- package/vendor/dragonlab/integration/companions-v1/view.mjs +28 -0
- package/vendor/dragonlab/integration/hyperion-v3/activity.mjs +76 -0
- package/vendor/dragonlab/lib/power-effects.js +294 -0
- package/vendor/dragonlab/lib/power-registry.js +74 -0
- package/vendor/dragonlab/vendor/quox-dragon/qart.js +348 -0
- package/vendor/dragonlab/vendor/quox-dragon/qeyes.js +213 -0
- package/lib/core/websiteSession.js +0 -148
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `quox ask`: a session puts a fork in front of a human through the Approvals inbox (one inbox item, kind decision).
|
|
3
|
+
* Who is asking, what, why, the options, the recommendation and the expiry travel with it, so ONE HITL session
|
|
4
|
+
* can answer for twenty terminals. Not an approval of an action: no step-up PIN unless --high-risk.
|
|
5
|
+
*/
|
|
6
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
7
|
+
import { execFileSync } from 'node:child_process';
|
|
8
|
+
import { homedir } from 'node:os';
|
|
9
|
+
import { join } from 'node:path';
|
|
10
|
+
import { resolveEndpoint } from '../api/endpoints.js';
|
|
11
|
+
import { resolveOrgId } from './helpers.js';
|
|
12
|
+
import { readPresence, approvalPresenceFields } from './presence.js';
|
|
13
|
+
import { toRepoRelative, LOOSENING_HINT } from '../core/repoPath.js';
|
|
14
|
+
import { parseOptions, parseDuration, YN_OPTIONS, waitForDecision, EXIT_NO_ANSWER } from '../core/askDecision.js';
|
|
15
|
+
|
|
16
|
+
function heldSlotFor(session) {
|
|
17
|
+
try {
|
|
18
|
+
const all = JSON.parse(readFileSync(join(homedir(), '.quox', 'held-slots.json'), 'utf8'));
|
|
19
|
+
const want = String(session || '').toLowerCase();
|
|
20
|
+
return Object.keys(all).find((k) => String(all[k]?.session || '').toLowerCase() === want) || null;
|
|
21
|
+
} catch { return null; }
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function registerAskCommands(program, { client, auth }) {
|
|
25
|
+
const err = (s) => process.stderr.write(`${s}\n`);
|
|
26
|
+
|
|
27
|
+
async function waitAndPrint(orgId, id, { timeoutSec = null } = {}) {
|
|
28
|
+
const r = await waitForDecision({
|
|
29
|
+
poll: (w) => { const ep = resolveEndpoint('inbox.decision.get', { orgId, id }); return client.get(ep.path, { service: ep.service, query: { wait: w }, timeout: (w + 15) * 1000, retries: 0 }); },
|
|
30
|
+
log: err,
|
|
31
|
+
maxWaitMs: timeoutSec != null ? timeoutSec * 1000 : null,
|
|
32
|
+
});
|
|
33
|
+
if (r.stdout) console.log(r.stdout);
|
|
34
|
+
process.exitCode = r.code;
|
|
35
|
+
return r;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const ask = program
|
|
39
|
+
.command('ask')
|
|
40
|
+
.description('Ask a human a question through the Approvals inbox: who asks, what, why, the options. Use this instead of asking in your own terminal')
|
|
41
|
+
.option('--session <name>', 'which session is asking (default: $QUOX_SESSION)')
|
|
42
|
+
.option('--title <text>', 'what you are asking, one line')
|
|
43
|
+
.option('--why <text>', 'a simple why: what the human needs to know to decide')
|
|
44
|
+
.option('--justify <text>', 'for a loosening (a check made easier to pass): why it is safe, in plain words; the card shows it on its JUSTIFY line and defaults to No without one')
|
|
45
|
+
.option('--options <list>', 'the choices: "a: Ship now|b: Wait for review|c: Drop it"')
|
|
46
|
+
.option('--yn', 'shorthand for a yes/no question (options y and n)')
|
|
47
|
+
.option('--default <key>', 'the option you would take if nobody answers (shown, never applied for you)')
|
|
48
|
+
.option('--recommend <key>', 'the option you recommend')
|
|
49
|
+
.option('--expires <dur>', 'how long it stays open: 30m, 2h, 1d (default 24h, max 30d)')
|
|
50
|
+
.option('--slot <name>', 'your slot, so the answer is also posted there (default: found from held slots)')
|
|
51
|
+
.option('--agent <name>', 'the agent asking, when it is an agent rather than a session')
|
|
52
|
+
.option('--high-risk', 'the answer needs the human step-up PIN')
|
|
53
|
+
.option('--wait', 'block until answered; prints the chosen option key (and any note) on stdout, exits 2 on expiry or deny')
|
|
54
|
+
.option('--timeout <dur>', 'with --wait: stop waiting after this long (the question stays open)')
|
|
55
|
+
.action((options) => fileAsk(options));
|
|
56
|
+
|
|
57
|
+
async function fileAsk(options) {
|
|
58
|
+
auth.requireAuth();
|
|
59
|
+
const orgId = resolveOrgId(auth, program);
|
|
60
|
+
const session = options.session || process.env.QUOX_SESSION;
|
|
61
|
+
if (!session) throw new Error('say which session is asking: --session <NAME>');
|
|
62
|
+
if (!options.title) throw new Error('say what you are asking: --title "<what>"');
|
|
63
|
+
if (options.yn && options.options) throw new Error('use --yn or --options, not both');
|
|
64
|
+
if (!options.yn && !options.options) throw new Error('give the choices: --options "a: Ship now|b: Wait" (or --yn)');
|
|
65
|
+
// QUOXON 2026-10-17: a high-risk ask (a loosening, anything the owner must PIN) carries a plain justification
|
|
66
|
+
// 2026-10-19: three PIN approvals were wasted on loosening asks filed without --high-risk or with unusable paths
|
|
67
|
+
if (!options.highRisk && /^\s*approve\s+(?:loosening|test)/i.test(options.title)) { err(`A test-edit approval must go through the loosening verb, which sets --high-risk and repo-relative paths for you:\n ${LOOSENING_HINT}`); process.exitCode = 2; return; }
|
|
68
|
+
if (options.highRisk && !String(options.justify || '').trim()) { err('High-risk asks need --justify "<2-3 plain lines the owner can judge>"'); process.exitCode = 2; return; }
|
|
69
|
+
const opts = options.yn ? YN_OPTIONS : parseOptions(options.options);
|
|
70
|
+
const body = {
|
|
71
|
+
session, title: options.title, why: options.why, options: opts,
|
|
72
|
+
...(options.justify ? { justification: options.justify } : {}),
|
|
73
|
+
default: options.default, recommend: options.recommend,
|
|
74
|
+
...(options.expires ? { expires_in_seconds: parseDuration(options.expires) } : {}),
|
|
75
|
+
high_risk: Boolean(options.highRisk),
|
|
76
|
+
slot: options.slot || heldSlotFor(session) || undefined,
|
|
77
|
+
profile: program.opts().profile || process.env.QUOX_PROFILE || undefined,
|
|
78
|
+
agent: options.agent,
|
|
79
|
+
};
|
|
80
|
+
const ep = resolveEndpoint('inbox.decision.create', { orgId });
|
|
81
|
+
const data = await client.post(ep.path, { service: ep.service, body });
|
|
82
|
+
// PRESENCE (owner 2026-10-16): say now whether the human is away, so this session can work on
|
|
83
|
+
// something else instead of waiting. The question is filed and queued exactly as before.
|
|
84
|
+
const presence = approvalPresenceFields(await readPresence(client, orgId));
|
|
85
|
+
const d = { ...data.decision, ...presence };
|
|
86
|
+
if (program.opts().output === 'json' && !options.wait) { console.log(JSON.stringify(d, null, 2)); return; }
|
|
87
|
+
err(`asked: ${d.id} (${session}: ${d.title}); it shows in /approvals${d.expires_at ? `, open until ${d.expires_at}` : ''}`);
|
|
88
|
+
if (presence.owner_away) err(`${presence.owner_away_notice}. Expect a wait: work on something else meanwhile.`);
|
|
89
|
+
if (!options.wait) { console.log(d.id); return; }
|
|
90
|
+
await waitAndPrint(orgId, d.id, { timeoutSec: options.timeout ? parseDuration(options.timeout) : null });
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
ask
|
|
94
|
+
.command('loosening')
|
|
95
|
+
.description('Ask a human to approve edits to test files (a check made easier to pass). Files a yes/no, high-risk (PIN) ask with repo-relative paths')
|
|
96
|
+
.option('--paths <files...>', 'every test file the commit edits (absolute, worktree or repo-relative: all are normalised)')
|
|
97
|
+
.option('--justify <text>', 'why it is safe, in plain words (required)')
|
|
98
|
+
.option('--why <text>', 'detail: the findings, before and after lines')
|
|
99
|
+
.option('--recommend <y|n>', 'your advice for the card: y or n (shown as the recommendation)')
|
|
100
|
+
.option('--session <name>', 'which session is asking (default: $QUOX_SESSION)')
|
|
101
|
+
.option('--slot <name>', 'your slot')
|
|
102
|
+
.option('--expires <dur>', 'how long it stays open')
|
|
103
|
+
.option('--wait', 'block until answered')
|
|
104
|
+
.option('--timeout <dur>', 'with --wait: stop waiting after this long')
|
|
105
|
+
.action(async (subOptions) => {
|
|
106
|
+
// commander lets the parent `ask` swallow flags it shares with this verb (--justify, --why, --session...), so merge both
|
|
107
|
+
const options = { ...ask.opts(), ...Object.fromEntries(Object.entries(subOptions).filter(([, v]) => v !== undefined)) };
|
|
108
|
+
if (!String(options.justify || '').trim()) { err('say why it is safe: --justify "<plain words>"'); process.exitCode = 2; return; }
|
|
109
|
+
if (!options.recommend) err('tip: add --recommend y|n so the card shows your advice');
|
|
110
|
+
const raw = [].concat(options.paths || []).flatMap((x) => String(x).split(',')).map((x) => x.trim()).filter(Boolean);
|
|
111
|
+
let root;
|
|
112
|
+
try { root = execFileSync('git', ['rev-parse', '--show-toplevel'], { encoding: 'utf8' }).trim(); } catch { root = process.cwd(); }
|
|
113
|
+
const paths = [...new Set(raw.map(toRepoRelative))];
|
|
114
|
+
const missing = paths.filter((p) => !p || !existsSync(join(root, p)));
|
|
115
|
+
if (!paths.length || missing.length) { err(`not found in this repo (${root}): ${missing.join(', ') || '(no paths)'}. Run from the worktree and name files that exist.`); process.exitCode = 2; return; }
|
|
116
|
+
// 2026-10-19: the server truncates long titles, and the attestation names only paths found in title+body,
|
|
117
|
+
// so a 7-file ask covered 3. Every path also goes in the body.
|
|
118
|
+
const files = `Files (${paths.length}):\n${paths.map((p) => `- ${p}`).join('\n')}`;
|
|
119
|
+
await fileAsk({ ...options, title: `Approve test edits: ${paths.join(' ')}`, why: options.why ? `${files}\n\n${options.why}` : files, yn: true, highRisk: true, justify: options.justify, recommend: options.recommend });
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
ask
|
|
123
|
+
.command('status')
|
|
124
|
+
.description('Show where a question stands: pending, answered, denied or expired, with the answer')
|
|
125
|
+
.argument('<id>', 'the id `quox ask` printed')
|
|
126
|
+
.action(async (id) => {
|
|
127
|
+
auth.requireAuth();
|
|
128
|
+
const orgId = resolveOrgId(auth, program);
|
|
129
|
+
const ep = resolveEndpoint('inbox.decision.get', { orgId, id });
|
|
130
|
+
const d = (await client.get(ep.path, { service: ep.service })).decision;
|
|
131
|
+
if (program.opts().output === 'json') { console.log(JSON.stringify(d, null, 2)); return; }
|
|
132
|
+
console.log(`${d.state}${d.answer?.option ? ` ${d.answer.option}` : ''}${d.answer?.note ? ` (${d.answer.note})` : ''}`);
|
|
133
|
+
if (d.state === 'expired' || d.state === 'denied') process.exitCode = EXIT_NO_ANSWER;
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
ask
|
|
137
|
+
.command('wait')
|
|
138
|
+
.description('Block until a question is answered; prints the chosen option key (and note), exits 2 on expiry or deny')
|
|
139
|
+
.argument('<id>', 'the id `quox ask` printed')
|
|
140
|
+
.option('--timeout <dur>', 'stop waiting after this long (the question stays open)')
|
|
141
|
+
.action(async (id, options) => {
|
|
142
|
+
auth.requireAuth();
|
|
143
|
+
const orgId = resolveOrgId(auth, program);
|
|
144
|
+
await waitAndPrint(orgId, id, { timeoutSec: options.timeout ? parseDuration(options.timeout) : null });
|
|
145
|
+
});
|
|
146
|
+
}
|
|
@@ -5,6 +5,10 @@
|
|
|
5
5
|
import { formatOutput, formatDetail, printSuccess } from '../core/output.js';
|
|
6
6
|
import { resolveOrgId } from './helpers.js';
|
|
7
7
|
import { resolveEndpoint } from '../api/endpoints.js';
|
|
8
|
+
import { QuoxError } from '../core/errors.js';
|
|
9
|
+
|
|
10
|
+
// #1049: auth routes/assistants.js DEPLOYMENT_MODES (the server is the authority).
|
|
11
|
+
const DEPLOY_MODES = ['private', 'shared', 'public'];
|
|
8
12
|
|
|
9
13
|
export function registerAssistantCommands(program, ctx) {
|
|
10
14
|
const { client, auth } = ctx;
|
|
@@ -84,20 +88,71 @@ export function registerAssistantCommands(program, ctx) {
|
|
|
84
88
|
printSuccess('Assistant deleted.');
|
|
85
89
|
});
|
|
86
90
|
|
|
91
|
+
assistant
|
|
92
|
+
.command('publish')
|
|
93
|
+
.description('Publish a draft assistant (draft to active), so it can be deployed')
|
|
94
|
+
.argument('<assistantId>', 'assistant ID')
|
|
95
|
+
.action(async (assistantId) => {
|
|
96
|
+
auth.requireAuth();
|
|
97
|
+
const orgId = resolveOrgId(auth, program);
|
|
98
|
+
// #1028: create makes a draft and deploy refuses anything not active; this is the step between.
|
|
99
|
+
const ep = resolveEndpoint('assistants.publish', { orgId, assistantId });
|
|
100
|
+
await client.post(ep.path, { service: ep.service });
|
|
101
|
+
printSuccess('Assistant published.');
|
|
102
|
+
});
|
|
103
|
+
|
|
87
104
|
assistant
|
|
88
105
|
.command('deploy')
|
|
89
|
-
.description('Deploy an assistant')
|
|
106
|
+
.description('Deploy an active assistant to a scope (scope ids: quox org divisions list, quox department list, quox team list)')
|
|
90
107
|
.argument('<assistantId>', 'assistant ID')
|
|
91
|
-
.option('--
|
|
108
|
+
.option('--scope <scopeId>', 'scope ID to deploy to (required)')
|
|
109
|
+
.option('--target <scopeId>', 'alias for --scope')
|
|
110
|
+
.option('--mode <mode>', `${DEPLOY_MODES.join(', ')} (default shared)`)
|
|
92
111
|
.action(async (assistantId, options) => {
|
|
93
112
|
auth.requireAuth();
|
|
94
113
|
const orgId = resolveOrgId(auth, program);
|
|
95
|
-
|
|
96
|
-
|
|
114
|
+
// #998: the auth route requires scope_id; the CLI used to send { target }.
|
|
115
|
+
const scopeId = options.scope || options.target;
|
|
116
|
+
if (!scopeId) throw new QuoxError('assistant deploy needs --scope <scopeId>.', 1);
|
|
117
|
+
// #1049: the server's modes (auth routes/assistants.js DEPLOYMENT_MODES); refuse anything else here.
|
|
118
|
+
if (options.mode && !DEPLOY_MODES.includes(options.mode)) {
|
|
119
|
+
throw new QuoxError(`--mode must be one of: ${DEPLOY_MODES.join(', ')} (got "${options.mode}").`, 1);
|
|
120
|
+
}
|
|
121
|
+
const body = { scope_id: scopeId };
|
|
122
|
+
if (options.mode) body.mode = options.mode;
|
|
97
123
|
await client.post(resolveEndpoint('assistants.deploy', { orgId, assistantId }).path, { service: resolveEndpoint('assistants.deploy', { orgId, assistantId }).service, body });
|
|
98
124
|
printSuccess('Assistant deployed.');
|
|
99
125
|
});
|
|
100
126
|
|
|
127
|
+
assistant
|
|
128
|
+
.command('undeploy')
|
|
129
|
+
.description('Remove a deployment of an assistant (by --deployment id, or the active one at --scope)')
|
|
130
|
+
.argument('<assistantId>', 'assistant ID')
|
|
131
|
+
.option('--deployment <deploymentId>', 'deployment ID to remove (see quox assistant get)')
|
|
132
|
+
.option('--scope <scopeId>', 'remove the active deployment at this scope')
|
|
133
|
+
.action(async (assistantId, options) => {
|
|
134
|
+
auth.requireAuth();
|
|
135
|
+
const orgId = resolveOrgId(auth, program);
|
|
136
|
+
// #1049: a deployed assistant could not be removed from the CLI.
|
|
137
|
+
let deploymentId = options.deployment;
|
|
138
|
+
if (!deploymentId) {
|
|
139
|
+
if (!options.scope) throw new QuoxError('assistant undeploy needs --deployment <id> or --scope <scopeId>.', 1);
|
|
140
|
+
const g = resolveEndpoint('assistants.get', { orgId, assistantId });
|
|
141
|
+
const data = await client.get(g.path, { service: g.service });
|
|
142
|
+
const deps = (data && data.assistant && data.assistant.deployments) || (data && data.deployments) || [];
|
|
143
|
+
const live = deps.filter((d) => d.status === 'active');
|
|
144
|
+
const hit = live.find((d) => d.scope_id === options.scope);
|
|
145
|
+
if (!hit) {
|
|
146
|
+
const where = live.length ? live.map((d) => `${d.scope_id} (${d.id})`).join(', ') : 'nowhere';
|
|
147
|
+
throw new QuoxError(`No active deployment of ${assistantId} at scope ${options.scope}. It is deployed at: ${where}.`, 1);
|
|
148
|
+
}
|
|
149
|
+
deploymentId = hit.id;
|
|
150
|
+
}
|
|
151
|
+
const ep = resolveEndpoint('assistants.undeploy', { orgId, assistantId, deploymentId });
|
|
152
|
+
await client.delete(ep.path, { service: ep.service });
|
|
153
|
+
printSuccess(`Deployment ${deploymentId} removed.`);
|
|
154
|
+
});
|
|
155
|
+
|
|
101
156
|
assistant
|
|
102
157
|
.command('fork')
|
|
103
158
|
.description('Fork an assistant')
|
package/lib/commands/auth.js
CHANGED
|
@@ -199,7 +199,9 @@ export function registerAuthCommands(program, ctx) {
|
|
|
199
199
|
.action(async () => {
|
|
200
200
|
auth.requireAuth();
|
|
201
201
|
const data = await client.get(resolveEndpoint('auth.me.list').path, { service: resolveEndpoint('auth.me.list').service });
|
|
202
|
-
|
|
202
|
+
// A QUOX_TOKEN_FILE principal (the W4 builder uid) has no stored profile:
|
|
203
|
+
// render from the server's /me answer rather than crash on null.
|
|
204
|
+
const authData = config.getAuth() || {};
|
|
203
205
|
|
|
204
206
|
// quox#463: resident logins carry org_id but no org_name (the
|
|
205
207
|
// bootstrap has only the id), and this rendered a real membership as
|
|
@@ -245,8 +247,12 @@ export function registerAuthCommands(program, ctx) {
|
|
|
245
247
|
org: orgDisplay,
|
|
246
248
|
role: authData.org_role || 'n/a',
|
|
247
249
|
mfa: data.user?.mfa_enabled ? 'enabled' : 'disabled',
|
|
250
|
+
// Z2H-3c: expires_at is the ACCESS token. With a refresh token the sign-in outlives it, so do not
|
|
251
|
+
// present it as the session ending (a fresh owner read "expires in 60m" right after logging in).
|
|
248
252
|
session: authData.expires_at
|
|
249
|
-
?
|
|
253
|
+
? (authData.refresh_token
|
|
254
|
+
? `active, auto-renews (token refresh in ${Math.max(0, Math.round((authData.expires_at - Date.now()) / 60000))}m)`
|
|
255
|
+
: `expires in ${Math.max(0, Math.round((authData.expires_at - Date.now()) / 60000))}m`)
|
|
250
256
|
: 'unknown',
|
|
251
257
|
profile: config.getActiveProfileName(),
|
|
252
258
|
};
|
package/lib/commands/backup.js
CHANGED
|
@@ -59,14 +59,18 @@ export function registerBackupCommands(program, ctx) {
|
|
|
59
59
|
backup
|
|
60
60
|
.command('create')
|
|
61
61
|
.description('Create a backup')
|
|
62
|
-
.option('--label <label>', 'backup label')
|
|
63
|
-
.option('--type <type>', 'backup type: full,
|
|
62
|
+
.option('--label <label>', 'backup label (stored as the backup name)')
|
|
63
|
+
.option('--type <type>', 'backup type: full, quick, selective', 'full')
|
|
64
64
|
.option('--components <list>', 'comma-separated components to include')
|
|
65
65
|
.action(async (options) => {
|
|
66
66
|
auth.requireAuth();
|
|
67
67
|
const orgId = resolveOrgId(auth, program);
|
|
68
|
-
|
|
69
|
-
|
|
68
|
+
// #1017: the server reads backup_type and name (services/auth/routes/backups.js); type/label were dropped,
|
|
69
|
+
// and a type outside the backups.backup_type CHECK would only fail later inside the server.
|
|
70
|
+
const TYPES = ['full', 'quick', 'selective'];
|
|
71
|
+
if (!TYPES.includes(options.type)) throw new Error(`--type must be one of: ${TYPES.join(', ')}`);
|
|
72
|
+
const body = { backup_type: options.type };
|
|
73
|
+
if (options.label) body.name = options.label;
|
|
70
74
|
if (options.components) body.components = options.components.split(',').map(c => c.trim());
|
|
71
75
|
|
|
72
76
|
const ora = (await import('ora')).default;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `quox blog draft submit|list|show|mark-published` (BLOG-DRAFT).
|
|
3
|
+
*
|
|
4
|
+
* Submit saves the draft as a NotesQ note in the blog-drafts collection and files a hand-off to
|
|
5
|
+
* the blog publisher (a line in the publisher's slot plus an inbox item). It NEVER publishes:
|
|
6
|
+
* publishing is the publisher's governed pipeline after the owner's yes. `mark-published` is the
|
|
7
|
+
* publisher-side verb that records the URL and tells the originating conversation/objective.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { readFileSync } from 'fs';
|
|
11
|
+
import { formatOutput, formatDetail, printSuccess, printInfo } from '../core/output.js';
|
|
12
|
+
import { line as glyphLine, printEvent } from '../core/quoxglyph.js';
|
|
13
|
+
import { resolveEndpoint } from '../api/endpoints.js';
|
|
14
|
+
|
|
15
|
+
const isJson = (program) => program.opts().output === 'json';
|
|
16
|
+
const printJson = (d) => console.log(JSON.stringify(d, null, 2));
|
|
17
|
+
|
|
18
|
+
async function readBody(options, ctx) {
|
|
19
|
+
if (options.file) return readFileSync(options.file, 'utf-8');
|
|
20
|
+
if (options.stdin) {
|
|
21
|
+
const input = ctx.stdin || process.stdin;
|
|
22
|
+
let buf = '';
|
|
23
|
+
for await (const chunk of input) buf += chunk;
|
|
24
|
+
return buf;
|
|
25
|
+
}
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function registerBlogCommands(program, ctx) {
|
|
30
|
+
const { client, auth } = ctx;
|
|
31
|
+
const blog = program.command('blog').description('Blog: hand drafts to the blog publisher (never publishes)');
|
|
32
|
+
const draft = blog.command('draft').description('Blog drafts: saved as revisioned NotesQ notes in the blog-drafts collection');
|
|
33
|
+
|
|
34
|
+
draft
|
|
35
|
+
.command('submit')
|
|
36
|
+
.description('Save a draft and hand it to the blog publisher (slot + inbox). Does NOT publish.')
|
|
37
|
+
.requiredOption('--title <title>', 'post title')
|
|
38
|
+
.option('--file <path>', 'read the post markdown from a file')
|
|
39
|
+
.option('--stdin', 'read the post markdown from stdin')
|
|
40
|
+
.option('--hero <image>', 'hero image path or URL')
|
|
41
|
+
.option('--tag <tag...>', 'tag (repeatable)')
|
|
42
|
+
.option('--conversation <id>', 'originating conversation id (so its publication is reported back)')
|
|
43
|
+
.option('--objective <id>', 'originating objective id')
|
|
44
|
+
.action(async (options) => {
|
|
45
|
+
auth.requireAuth();
|
|
46
|
+
const markdown = await readBody(options, ctx);
|
|
47
|
+
if (markdown === null) throw new Error('give the post body with --file <path> or --stdin');
|
|
48
|
+
if (!markdown.trim()) throw new Error('the draft body is empty; nothing to submit');
|
|
49
|
+
const body = { title: options.title, markdown };
|
|
50
|
+
if (options.hero) body.hero = options.hero;
|
|
51
|
+
if (options.tag && options.tag.length) body.tags = options.tag;
|
|
52
|
+
if (options.conversation) body.conversation_id = options.conversation;
|
|
53
|
+
if (options.objective) body.objective_id = options.objective;
|
|
54
|
+
const ep = resolveEndpoint('blog.drafts.submit');
|
|
55
|
+
const data = await client.post(ep.path, { service: ep.service, body });
|
|
56
|
+
if (isJson(program)) return printJson(data);
|
|
57
|
+
printEvent(glyphLine('quox', `blog draft submit "${options.title}"`, `note ${data.note_id}`));
|
|
58
|
+
printSuccess(`Draft saved as note ${data.note_id} and handed to the publisher slot ${data.slot} (hand-off ${data.handoff_id}).`);
|
|
59
|
+
if (data.link) console.log(`link: ${data.link}`);
|
|
60
|
+
printInfo('Not published: nothing goes live until the publisher publishes after the owner approves.');
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
draft
|
|
64
|
+
.command('list')
|
|
65
|
+
.description('List blog drafts')
|
|
66
|
+
.option('--status <status>', 'draft | published')
|
|
67
|
+
.action(async (options) => {
|
|
68
|
+
auth.requireAuth();
|
|
69
|
+
const ep = resolveEndpoint('blog.drafts.list');
|
|
70
|
+
const data = await client.get(ep.path, { service: ep.service, query: options.status ? { status: options.status } : {} });
|
|
71
|
+
if (isJson(program)) return printJson(data);
|
|
72
|
+
const rows = (data.drafts || []).map((d) => ({ id: d.id, title: d.title, status: d.status, url: d.url || '', updated: d.updated || '' }));
|
|
73
|
+
if (!rows.length) return printInfo('No blog drafts.');
|
|
74
|
+
formatOutput({ columns: ['id', 'title', 'status', 'url', 'updated'], rows });
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
draft
|
|
78
|
+
.command('show')
|
|
79
|
+
.description('Show one blog draft (body and hand-off facts)')
|
|
80
|
+
.argument('<id>', 'draft (note) id')
|
|
81
|
+
.action(async (id) => {
|
|
82
|
+
auth.requireAuth();
|
|
83
|
+
const ep = resolveEndpoint('blog.drafts.get', { id });
|
|
84
|
+
const data = await client.get(ep.path, { service: ep.service });
|
|
85
|
+
if (isJson(program)) return printJson(data);
|
|
86
|
+
const d = data.draft || {};
|
|
87
|
+
const { markdown, ...facts } = d;
|
|
88
|
+
formatDetail(facts);
|
|
89
|
+
console.log('');
|
|
90
|
+
console.log(markdown || '');
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
draft
|
|
94
|
+
.command('mark-published')
|
|
95
|
+
.description('Publisher side: record the published URL and tell the originating conversation/objective')
|
|
96
|
+
.argument('<id>', 'draft (note) id')
|
|
97
|
+
.requiredOption('--url <url>', 'the published https URL')
|
|
98
|
+
.action(async (id, options) => {
|
|
99
|
+
auth.requireAuth();
|
|
100
|
+
if (!/^https:\/\//i.test(options.url)) throw new Error('--url must be an https URL');
|
|
101
|
+
const ep = resolveEndpoint('blog.drafts.published', { id });
|
|
102
|
+
const data = await client.post(ep.path, { service: ep.service, body: { url: options.url } });
|
|
103
|
+
if (isJson(program)) return printJson(data);
|
|
104
|
+
printSuccess(data.already ? `Draft ${id} was already marked published at ${data.url}.` : `Draft ${id} marked published at ${data.url}.`);
|
|
105
|
+
if (data.notified) {
|
|
106
|
+
const n = data.notified;
|
|
107
|
+
printInfo(`Told: ${[n.conversation_id && `conversation ${n.conversation_id}`, n.objective_id && `objective ${n.objective_id}`, n.inbox_item_id && `inbox ${n.inbox_item_id}`].filter(Boolean).join(', ') || 'inbox only'}.`);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
}
|