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.
Files changed (442) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +2 -0
  3. package/bin/quox.js +99 -126
  4. package/lib/api/endpoints/actions.js +15 -0
  5. package/lib/api/endpoints/admin.js +46 -0
  6. package/lib/api/endpoints/agent-schedules.js +26 -0
  7. package/lib/api/endpoints/agents.js +7 -0
  8. package/lib/api/endpoints/assistants.js +12 -0
  9. package/lib/api/endpoints/away.js +15 -0
  10. package/lib/api/endpoints/blog.js +16 -0
  11. package/lib/api/endpoints/chat.js +14 -0
  12. package/lib/api/endpoints/contactsq.js +6 -0
  13. package/lib/api/endpoints/conversations.js +14 -0
  14. package/lib/api/endpoints/devServerRegistry.js +25 -0
  15. package/lib/api/endpoints/engagement.js +26 -0
  16. package/lib/api/endpoints/evidence.js +20 -0
  17. package/lib/api/endpoints/executions.js +13 -0
  18. package/lib/api/endpoints/fetchAllowlist.js +27 -0
  19. package/lib/api/endpoints/files.js +14 -0
  20. package/lib/api/endpoints/hat.js +44 -0
  21. package/lib/api/endpoints/inbox.js +20 -0
  22. package/lib/api/endpoints/index.js +26 -0
  23. package/lib/api/endpoints/loops.js +26 -0
  24. package/lib/api/endpoints/memory.js +19 -0
  25. package/lib/api/endpoints/mind.js +13 -0
  26. package/lib/api/endpoints/mon.js +179 -0
  27. package/lib/api/endpoints/objectives.js +12 -0
  28. package/lib/api/endpoints/plugins.js +18 -0
  29. package/lib/api/endpoints/presence.js +33 -0
  30. package/lib/api/endpoints/quoxplan.js +7 -0
  31. package/lib/api/endpoints/quoxseo.js +12 -0
  32. package/lib/api/endpoints/router.js +17 -0
  33. package/lib/api/endpoints/safety.js +7 -0
  34. package/lib/api/endpoints/server.js +21 -0
  35. package/lib/api/endpoints/setup.js +6 -0
  36. package/lib/api/endpoints/skills.js +20 -0
  37. package/lib/api/endpoints/stepup.js +20 -0
  38. package/lib/api/endpoints/teams.js +2 -2
  39. package/lib/api/endpoints/triggers.js +26 -0
  40. package/lib/api/endpoints/v1.js +120 -0
  41. package/lib/api/endpoints/ward.js +7 -0
  42. package/lib/api/endpoints/workflows.js +7 -0
  43. package/lib/api/endpoints/x.js +8 -0
  44. package/lib/commands/actions.js +131 -0
  45. package/lib/commands/admin.js +16 -0
  46. package/lib/commands/agent.js +170 -27
  47. package/lib/commands/agentAttach.js +140 -13
  48. package/lib/commands/approvals.js +78 -1
  49. package/lib/commands/aside.js +24 -0
  50. package/lib/commands/ask.js +146 -0
  51. package/lib/commands/assistant.js +59 -4
  52. package/lib/commands/auth.js +8 -2
  53. package/lib/commands/backup.js +8 -4
  54. package/lib/commands/blog.js +110 -0
  55. package/lib/commands/brain.js +105 -14
  56. package/lib/commands/build.js +19 -5
  57. package/lib/commands/chat.js +180 -26
  58. package/lib/commands/chatCompanion.js +27 -0
  59. package/lib/commands/chatState.js +45 -0
  60. package/lib/commands/claude.js +35 -38
  61. package/lib/commands/contacts.js +134 -4
  62. package/lib/commands/contactsSendAt.js +21 -0
  63. package/lib/commands/conversation.js +35 -4
  64. package/lib/commands/dashboard.js +43 -0
  65. package/lib/commands/department.js +28 -7
  66. package/lib/commands/devResident.js +15 -3
  67. package/lib/commands/devSupervisor.js +13 -1
  68. package/lib/commands/discord.js +75 -39
  69. package/lib/commands/done.js +98 -28
  70. package/lib/commands/engagement.js +107 -6
  71. package/lib/commands/evidence.js +154 -0
  72. package/lib/commands/ext.js +23 -0
  73. package/lib/commands/fetchAllowlist.js +71 -0
  74. package/lib/commands/file.js +38 -1
  75. package/lib/commands/fleet.js +336 -4
  76. package/lib/commands/governance.js +12 -3
  77. package/lib/commands/gripe.js +44 -13
  78. package/lib/commands/hat.js +254 -9
  79. package/lib/commands/helpers.js +18 -1
  80. package/lib/commands/hyperion.js +58 -0
  81. package/lib/commands/image.js +68 -0
  82. package/lib/commands/inbox.js +74 -23
  83. package/lib/commands/kuma.js +1050 -0
  84. package/lib/commands/kumaSla.js +174 -0
  85. package/lib/commands/licence.js +52 -155
  86. package/lib/commands/loop.js +291 -0
  87. package/lib/commands/lucid.js +10 -4
  88. package/lib/commands/memory.js +85 -8
  89. package/lib/commands/mind.js +71 -0
  90. package/lib/commands/mon.js +69 -2
  91. package/lib/commands/monitoring.js +1 -1
  92. package/lib/commands/motion.js +2 -2
  93. package/lib/commands/nucleus.js +48 -17
  94. package/lib/commands/objective.js +59 -1
  95. package/lib/commands/offload.js +89 -0
  96. package/lib/commands/onboard.js +2 -1
  97. package/lib/commands/org.js +30 -3
  98. package/lib/commands/peek.js +28 -0
  99. package/lib/commands/plan.js +38 -0
  100. package/lib/commands/plugin.js +110 -0
  101. package/lib/commands/presence.js +89 -0
  102. package/lib/commands/provider.js +217 -0
  103. package/lib/commands/qlarity.js +19 -3
  104. package/lib/commands/quoxchat.js +13 -4
  105. package/lib/commands/quoxseo.js +69 -0
  106. package/lib/commands/reach.js +2 -1
  107. package/lib/commands/research.js +79 -0
  108. package/lib/commands/router.js +77 -0
  109. package/lib/commands/rows.js +367 -0
  110. package/lib/commands/run.js +82 -10
  111. package/lib/commands/scope.js +2 -2
  112. package/lib/commands/server.js +405 -0
  113. package/lib/commands/setup.js +33 -7
  114. package/lib/commands/shortcut.js +41 -0
  115. package/lib/commands/skills.js +68 -9
  116. package/lib/commands/slot.js +260 -7
  117. package/lib/commands/status.js +1 -1
  118. package/lib/commands/team.js +3 -1
  119. package/lib/commands/tips.js +23 -0
  120. package/lib/commands/tool.js +133 -45
  121. package/lib/commands/trace.js +12 -7
  122. package/lib/commands/trigger.js +95 -0
  123. package/lib/commands/update.js +46 -0
  124. package/lib/commands/vault.js +31 -8
  125. package/lib/commands/vm.js +237 -0
  126. package/lib/commands/voice.js +34 -3
  127. package/lib/commands/volt.js +45 -3
  128. package/lib/commands/walkthrough.js +32 -0
  129. package/lib/commands/watch.js +73 -21
  130. package/lib/commands/welcome.js +14 -0
  131. package/lib/commands/workflow.js +189 -32
  132. package/lib/commands/x.js +13 -4
  133. package/lib/commands/youtube.js +172 -0
  134. package/lib/core/actingAs.js +2 -2
  135. package/lib/core/actionItems.js +115 -0
  136. package/lib/core/activeSummary.js +186 -0
  137. package/lib/core/agentAlias.js +2 -0
  138. package/lib/core/agentFamilies.js +7 -0
  139. package/lib/core/aside.js +54 -0
  140. package/lib/core/askDecision.js +60 -0
  141. package/lib/core/auth.js +164 -13
  142. package/lib/core/callerSource.js +1 -1
  143. package/lib/core/cliBuild.js +62 -0
  144. package/lib/core/client.js +152 -35
  145. package/lib/core/commandRegistrars.js +130 -0
  146. package/lib/core/config.js +143 -25
  147. package/lib/core/customHats.js +168 -0
  148. package/lib/core/doneEngineFetch.js +8 -0
  149. package/lib/core/errors.js +329 -20
  150. package/lib/core/findingReconcile.js +90 -0
  151. package/lib/core/global-flags.js +105 -5
  152. package/lib/core/governedSession.js +1 -3
  153. package/lib/core/hyperion.js +102 -0
  154. package/lib/core/inboxResolution.js +65 -0
  155. package/lib/core/lazyCommands.js +4 -1
  156. package/lib/core/liveSessionsReport.js +1 -1
  157. package/lib/core/loopGlyph.js +97 -0
  158. package/lib/core/offload.js +533 -0
  159. package/lib/core/org-override.js +1 -1
  160. package/lib/core/orgResolve.js +70 -0
  161. package/lib/core/output.js +11 -11
  162. package/lib/core/peek.js +126 -0
  163. package/lib/core/profileUsers.js +51 -0
  164. package/lib/core/promptCancel.js +18 -0
  165. package/lib/core/repoPath.js +11 -0
  166. package/lib/core/residentSeed.js +3 -1
  167. package/lib/core/rootOptions.js +39 -0
  168. package/lib/core/router.js +168 -0
  169. package/lib/core/selfUpdate.js +188 -0
  170. package/lib/core/sessionHeartbeat.js +12 -8
  171. package/lib/core/sessionsOverview.js +64 -0
  172. package/lib/core/shortcut.js +83 -0
  173. package/lib/core/slotKeeper.js +248 -3
  174. package/lib/core/slotWake.js +120 -0
  175. package/lib/core/supervisorEngine.js +71 -2
  176. package/lib/core/version.js +1 -1
  177. package/lib/core/vm.js +366 -0
  178. package/lib/core/vmTui.js +86 -0
  179. package/lib/core/walkthrough.js +35 -0
  180. package/lib/dragon/companionView.js +34 -0
  181. package/lib/dragon/frames.js +118 -0
  182. package/lib/dragon/jobs.js +123 -0
  183. package/lib/dragon/renderWorker.js +15 -0
  184. package/lib/dragon/select.js +72 -0
  185. package/lib/evidence/bundleExport.js +47 -0
  186. package/lib/evidence/bundleVerify.js +77 -0
  187. package/lib/hyperion/deck.js +29 -0
  188. package/lib/lucid/LucidCard.jsx +68 -0
  189. package/lib/lucid/LucidIntro.jsx +45 -58
  190. package/lib/lucid/cards.js +86 -0
  191. package/lib/lucid/markdown.js +81 -0
  192. package/lib/lucid/motion.js +56 -0
  193. package/lib/quoxon/EXTENSIONS.md +23 -0
  194. package/lib/quoxon/activity.js +14 -2
  195. package/lib/quoxon/aocl.js +117 -0
  196. package/lib/quoxon/api.js +690 -25
  197. package/lib/quoxon/app.js +3586 -250
  198. package/lib/quoxon/approvalFacts.js +96 -0
  199. package/lib/quoxon/approvalSentence.js +44 -0
  200. package/lib/quoxon/approvalsView.js +384 -0
  201. package/lib/quoxon/asideView.js +40 -0
  202. package/lib/quoxon/ask.js +121 -0
  203. package/lib/quoxon/away.js +52 -0
  204. package/lib/quoxon/bang.js +224 -0
  205. package/lib/quoxon/bulkRisk.js +17 -0
  206. package/lib/quoxon/capabilities.js +13 -6
  207. package/lib/quoxon/cells.js +53 -0
  208. package/lib/quoxon/commandShort.js +124 -0
  209. package/lib/quoxon/commands.js +429 -39
  210. package/lib/quoxon/connect.js +210 -23
  211. package/lib/quoxon/connectCard.js +19 -2
  212. package/lib/quoxon/consent.js +86 -0
  213. package/lib/quoxon/copy.js +4 -3
  214. package/lib/quoxon/countdownLine.js +20 -0
  215. package/lib/quoxon/deck.js +30 -0
  216. package/lib/quoxon/demo.autoplay.js +29 -0
  217. package/lib/quoxon/demo.fixtures.js +1 -1
  218. package/lib/quoxon/demo.js +46 -3
  219. package/lib/quoxon/devObserver.js +20 -0
  220. package/lib/quoxon/dividerAocl.js +192 -0
  221. package/lib/quoxon/dividerRule.js +57 -0
  222. package/lib/quoxon/dragonCompanion.js +255 -0
  223. package/lib/quoxon/dragonModes.js +151 -0
  224. package/lib/quoxon/dragonMoments.js +82 -0
  225. package/lib/quoxon/dragonReads.js +68 -0
  226. package/lib/quoxon/dragonView.js +41 -0
  227. package/lib/quoxon/earlyInput.js +40 -0
  228. package/lib/quoxon/echo.js +15 -0
  229. package/lib/quoxon/egg.js +2 -2
  230. package/lib/quoxon/events.js +266 -26
  231. package/lib/quoxon/extensions.js +276 -0
  232. package/lib/quoxon/feedback.js +50 -0
  233. package/lib/quoxon/gallery.js +21 -7
  234. package/lib/quoxon/handoffOffer.js +78 -0
  235. package/lib/quoxon/hatSwitch.js +37 -0
  236. package/lib/quoxon/headless.js +161 -0
  237. package/lib/quoxon/hostUrls.js +41 -0
  238. package/lib/quoxon/inboxView.js +79 -0
  239. package/lib/quoxon/indicator.js +6 -2
  240. package/lib/quoxon/installCli.js +79 -0
  241. package/lib/quoxon/intent.js +12 -1
  242. package/lib/quoxon/keys.js +51 -8
  243. package/lib/quoxon/launcher.js +103 -0
  244. package/lib/quoxon/loopCard.js +456 -0
  245. package/lib/quoxon/loopCards.js +180 -0
  246. package/lib/quoxon/loopStream.js +87 -0
  247. package/lib/quoxon/main.js +101 -160
  248. package/lib/quoxon/markdown.js +45 -21
  249. package/lib/quoxon/markdownSettle.js +19 -0
  250. package/lib/quoxon/matrixDomains.json +46 -0
  251. package/lib/quoxon/mentions.js +90 -0
  252. package/lib/quoxon/mind.js +103 -0
  253. package/lib/quoxon/mode.js +46 -0
  254. package/lib/quoxon/model.js +132 -26
  255. package/lib/quoxon/modelPrefs.js +345 -0
  256. package/lib/quoxon/motion/clock.js +3 -2
  257. package/lib/quoxon/motion/level.js +91 -0
  258. package/lib/quoxon/mouse.js +222 -0
  259. package/lib/quoxon/noai.js +57 -0
  260. package/lib/quoxon/objectiveOffer.js +82 -0
  261. package/lib/quoxon/objects/away.js +72 -0
  262. package/lib/quoxon/objects/fetch.js +17 -5
  263. package/lib/quoxon/objects/fleet.js +149 -0
  264. package/lib/quoxon/objects/health.js +186 -0
  265. package/lib/quoxon/objects/index.js +46 -7
  266. package/lib/quoxon/objects/mirror.js +137 -0
  267. package/lib/quoxon/objects/model.js +33 -0
  268. package/lib/quoxon/objects/peek.js +17 -0
  269. package/lib/quoxon/objects/proof.js +12 -4
  270. package/lib/quoxon/objects/prove.js +130 -0
  271. package/lib/quoxon/objects/replay.js +125 -0
  272. package/lib/quoxon/objects/sitrep.js +58 -0
  273. package/lib/quoxon/objects/view.js +11 -0
  274. package/lib/quoxon/onboarding/copy.js +32 -4
  275. package/lib/quoxon/onboarding/credential.js +17 -4
  276. package/lib/quoxon/onboarding/dockerOffer.js +45 -0
  277. package/lib/quoxon/onboarding/failureCause.js +33 -0
  278. package/lib/quoxon/onboarding/finish.js +15 -0
  279. package/lib/quoxon/onboarding/index.js +34 -7
  280. package/lib/quoxon/onboarding/installRunner.js +64 -5
  281. package/lib/quoxon/onboarding/preflight.js +17 -3
  282. package/lib/quoxon/onboarding/probe.js +65 -0
  283. package/lib/quoxon/onboarding/shortcutOffer.js +28 -0
  284. package/lib/quoxon/onboarding/view.js +76 -19
  285. package/lib/quoxon/orgLabel.js +10 -0
  286. package/lib/quoxon/orgSwitch.js +59 -0
  287. package/lib/quoxon/ownBroadcast.js +22 -0
  288. package/lib/quoxon/palette.js +43 -0
  289. package/lib/quoxon/planSteps.js +51 -0
  290. package/lib/quoxon/print.js +12 -5
  291. package/lib/quoxon/profileSwitch.js +53 -0
  292. package/lib/quoxon/promptHistory.js +41 -0
  293. package/lib/quoxon/providers.js +48 -0
  294. package/lib/quoxon/qart.js +20 -51
  295. package/lib/quoxon/qdragon.js +410 -0
  296. package/lib/quoxon/qdragonFly.js +62 -0
  297. package/lib/quoxon/qdragonScenes.js +282 -0
  298. package/lib/quoxon/qeyes.js +213 -0
  299. package/lib/quoxon/qmotion.js +75 -37
  300. package/lib/quoxon/render.js +740 -124
  301. package/lib/quoxon/resend.js +72 -0
  302. package/lib/quoxon/roster.js +117 -17
  303. package/lib/quoxon/route.js +6 -5
  304. package/lib/quoxon/rows.js +954 -0
  305. package/lib/quoxon/rowsStore.js +80 -0
  306. package/lib/quoxon/screenContext.js +150 -0
  307. package/lib/quoxon/screenFrames.js +254 -0
  308. package/lib/quoxon/serverChip.js +42 -0
  309. package/lib/quoxon/session.js +182 -2
  310. package/lib/quoxon/sessionDead.js +17 -0
  311. package/lib/quoxon/signin.js +125 -0
  312. package/lib/quoxon/slash/agents.js +112 -0
  313. package/lib/quoxon/slash/argSources.js +65 -0
  314. package/lib/quoxon/slash/args.js +65 -0
  315. package/lib/quoxon/slash/automate.js +89 -0
  316. package/lib/quoxon/slash/broadcast.js +140 -0
  317. package/lib/quoxon/slash/broadcastRun.js +108 -0
  318. package/lib/quoxon/slash/card.js +30 -0
  319. package/lib/quoxon/slash/challenge.js +114 -0
  320. package/lib/quoxon/slash/cliCommand.js +94 -0
  321. package/lib/quoxon/slash/cliLeaves.json +1023 -0
  322. package/lib/quoxon/slash/compact.js +1 -1
  323. package/lib/quoxon/slash/confirm.js +30 -0
  324. package/lib/quoxon/slash/crystallize.js +69 -0
  325. package/lib/quoxon/slash/dashboard.js +136 -0
  326. package/lib/quoxon/slash/draft.js +54 -0
  327. package/lib/quoxon/slash/evidenceCmd.js +34 -0
  328. package/lib/quoxon/slash/hatWalk.js +206 -0
  329. package/lib/quoxon/slash/inverses.js +77 -0
  330. package/lib/quoxon/slash/killer.js +24 -0
  331. package/lib/quoxon/slash/killerApi.js +16 -0
  332. package/lib/quoxon/slash/ledger.js +13 -8
  333. package/lib/quoxon/slash/listView.js +3 -1
  334. package/lib/quoxon/slash/modelCmd.js +261 -0
  335. package/lib/quoxon/slash/palette.js +22 -8
  336. package/lib/quoxon/slash/paletteController.js +117 -7
  337. package/lib/quoxon/slash/panels.js +21 -6
  338. package/lib/quoxon/slash/picker.js +20 -2
  339. package/lib/quoxon/slash/qpeCmd.js +35 -0
  340. package/lib/quoxon/slash/restart.js +95 -13
  341. package/lib/quoxon/slash/resume.js +18 -1
  342. package/lib/quoxon/slash/rowCmd.js +157 -0
  343. package/lib/quoxon/slash/rowWalk.js +175 -0
  344. package/lib/quoxon/slash/settings.js +16 -6
  345. package/lib/quoxon/slash/spawn.js +177 -0
  346. package/lib/quoxon/slash/stepUp.js +274 -0
  347. package/lib/quoxon/slash/stepUpCard.js +60 -0
  348. package/lib/quoxon/slash/stepUpCmds.js +78 -0
  349. package/lib/quoxon/slash/todoCmd.js +122 -0
  350. package/lib/quoxon/slash/undoApi.js +38 -0
  351. package/lib/quoxon/slash/undoCmd.js +49 -0
  352. package/lib/quoxon/slash/undoWish.js +15 -0
  353. package/lib/quoxon/slash/watch.js +164 -0
  354. package/lib/quoxon/slash/wish.js +23 -0
  355. package/lib/quoxon/slash/wishCmd.js +32 -0
  356. package/lib/quoxon/slotChat.js +300 -0
  357. package/lib/quoxon/slotFormat.js +195 -0
  358. package/lib/quoxon/slotRun.js +131 -0
  359. package/lib/quoxon/sound/mapping.js +2 -1
  360. package/lib/quoxon/stray.js +22 -0
  361. package/lib/quoxon/suggestCommand.js +90 -0
  362. package/lib/quoxon/term.js +46 -11
  363. package/lib/quoxon/textBuffer.js +176 -2
  364. package/lib/quoxon/theme.js +58 -25
  365. package/lib/quoxon/tips.js +433 -0
  366. package/lib/quoxon/toolCards.js +30 -0
  367. package/lib/quoxon/toolError.js +41 -0
  368. package/lib/quoxon/transcript.js +42 -4
  369. package/lib/quoxon/web/qface.js +18 -10
  370. package/lib/quoxon/welcome.js +1 -1
  371. package/lib/quoxon/whatsnew.js +136 -0
  372. package/lib/quoxon/whatsnew.jsonl +134 -0
  373. package/lib/quoxon/windowOrg.js +123 -0
  374. package/lib/server/devServers.js +137 -0
  375. package/lib/server/diag.js +129 -0
  376. package/lib/server/doctor.js +71 -0
  377. package/lib/server/install.js +149 -0
  378. package/lib/server/pluginEnable.js +161 -0
  379. package/lib/server/skew.js +56 -0
  380. package/lib/server/snapshot.js +131 -0
  381. package/lib/server/status.js +128 -0
  382. package/lib/server/sys.js +65 -0
  383. package/lib/server/uninstall.js +63 -0
  384. package/lib/server/update.js +234 -0
  385. package/lib/toolOutputShape.js +315 -0
  386. package/lib/tui/views/HyperionDeck.jsx +79 -0
  387. package/lib/tui/views/LucidDeck.jsx +209 -217
  388. package/package.json +5 -1
  389. package/vendor/dragonlab/SOURCE +4 -0
  390. package/vendor/dragonlab/design/cipher/v1/cipher-master.svg +46 -0
  391. package/vendor/dragonlab/design/cipher/v2/character.mjs +49 -0
  392. package/vendor/dragonlab/design/cipher/v2/review.mjs +33 -0
  393. package/vendor/dragonlab/design/cipher/v2/watch.mjs +56 -0
  394. package/vendor/dragonlab/design/drago/v1/character.mjs +19 -0
  395. package/vendor/dragonlab/design/drago/v1/drago-master.svg +56 -0
  396. package/vendor/dragonlab/design/drago/v2/character.mjs +87 -0
  397. package/vendor/dragonlab/design/drago/v4/character.mjs +67 -0
  398. package/vendor/dragonlab/design/drago/v4/profile.mjs +35 -0
  399. package/vendor/dragonlab/design/drago/v4/review.mjs +33 -0
  400. package/vendor/dragonlab/design/drago/v4/watch.mjs +56 -0
  401. package/vendor/dragonlab/design/hyperion/v1/hyperion-master.svg +56 -0
  402. package/vendor/dragonlab/design/hyperion/v2/character.mjs +79 -0
  403. package/vendor/dragonlab/design/hyperion/v2/review.mjs +33 -0
  404. package/vendor/dragonlab/design/hyperion/v2/watch.mjs +56 -0
  405. package/vendor/dragonlab/design/lucid/recovery/arrival.mjs +22 -0
  406. package/vendor/dragonlab/design/lucid/recovery/character.mjs +106 -0
  407. package/vendor/dragonlab/design/lucid/recovery/layout.mjs +50 -0
  408. package/vendor/dragonlab/design/lucid/recovery/lucid-master.svg +78 -0
  409. package/vendor/dragonlab/design/lucid/recovery/motion.mjs +42 -0
  410. package/vendor/dragonlab/design/lucid/recovery/render.mjs +121 -0
  411. package/vendor/dragonlab/design/lucid/recovery/review.mjs +68 -0
  412. package/vendor/dragonlab/design/lucid/recovery/verify.mjs +84 -0
  413. package/vendor/dragonlab/design/lucid/recovery/watch.mjs +56 -0
  414. package/vendor/dragonlab/design/seer/v1/seer-master.svg +53 -0
  415. package/vendor/dragonlab/design/seer/v2/character.mjs +88 -0
  416. package/vendor/dragonlab/design/seer/v3/character.mjs +33 -0
  417. package/vendor/dragonlab/design/seer/v4/character.mjs +66 -0
  418. package/vendor/dragonlab/design/seer/v4/review.mjs +33 -0
  419. package/vendor/dragonlab/design/seer/v4/watch.mjs +56 -0
  420. package/vendor/dragonlab/design/stage3/companion-review.mjs +28 -0
  421. package/vendor/dragonlab/design/stage3/sentinel-art.js +39 -0
  422. package/vendor/dragonlab/design/stage3/sentinel-braille.js +216 -0
  423. package/vendor/dragonlab/design/stage3/sentinel-character.js +257 -0
  424. package/vendor/dragonlab/design/stage3/sentinel-icon.js +103 -0
  425. package/vendor/dragonlab/design/stage3/sentinel-warden-eyes.js +95 -0
  426. package/vendor/dragonlab/design/stage3/svg-coverage.js +148 -0
  427. package/vendor/dragonlab/factory/svg-rig.mjs +63 -0
  428. package/vendor/dragonlab/integration/companions-v1/characters.mjs +88 -0
  429. package/vendor/dragonlab/integration/companions-v1/cipher.mjs +2 -0
  430. package/vendor/dragonlab/integration/companions-v1/companion.mjs +63 -0
  431. package/vendor/dragonlab/integration/companions-v1/drago.mjs +2 -0
  432. package/vendor/dragonlab/integration/companions-v1/hyperion.mjs +2 -0
  433. package/vendor/dragonlab/integration/companions-v1/lucid.mjs +2 -0
  434. package/vendor/dragonlab/integration/companions-v1/seer.mjs +2 -0
  435. package/vendor/dragonlab/integration/companions-v1/sentinel.mjs +2 -0
  436. package/vendor/dragonlab/integration/companions-v1/view.mjs +28 -0
  437. package/vendor/dragonlab/integration/hyperion-v3/activity.mjs +76 -0
  438. package/vendor/dragonlab/lib/power-effects.js +294 -0
  439. package/vendor/dragonlab/lib/power-registry.js +74 -0
  440. package/vendor/dragonlab/vendor/quox-dragon/qart.js +348 -0
  441. package/vendor/dragonlab/vendor/quox-dragon/qeyes.js +213 -0
  442. package/lib/core/websiteSession.js +0 -148
@@ -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
- const data = await client.get(ep.path, { service: ep.service });
109
- const items = data.data || data.items || data.workflows || (Array.isArray(data) ? data : []);
110
- for (const item of items) rows.push({ ...item, source: 'engine' });
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
- const ep = resolveEndpoint('workflows.applyGet', { workflowId });
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>', 'workflow steps as 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
- const orgId = resolveOrgId(auth, program);
172
- const body = { name: options.name };
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
- if (options.steps) body.steps = parseJSON(options.steps, '--steps');
175
- await client.post(resolveEndpoint('workflows.create', { orgId }).path, { service: resolveEndpoint('workflows.create', { orgId }).service, body });
176
- printEvent(events.created('quox', 'workflow', options.name));
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
- await client.delete(resolveEndpoint('workflows.delete', { orgId, workflowId }).path, { service: resolveEndpoint('workflows.delete', { orgId, workflowId }).service });
205
- printEvent(glyphLine('quox', 'workflow deleted', { result: workflowId, status: 'ok' }));
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
- const run = await client.post(exEp.path, { service: exEp.service, body: body.context || {} });
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 — steps are a field on the
303
- // workflow object itself.
304
- const data = await client.get(resolveEndpoint('workflows.get', { orgId, workflowId }).path, { service: resolveEndpoint('workflows.get', { orgId, workflowId }).service });
305
- const workflow = data.workflow || data;
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: workflow.steps || [],
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
- await client.put(resolveEndpoint('workflows.steps', { orgId, workflowId }).path, {
323
- service: resolveEndpoint('workflows.steps', { orgId, workflowId }).service,
324
- body: { steps: parseJSON(options.steps, '--steps') },
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
- const data = await client.post(createEp.path, { service: createEp.service, body: wf });
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 === 404) {
425
- 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.`);
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
- const budgetData = await client.post(ep.path, {
344
- service: ep.service,
345
- body: { tool_name: 'x_budget_status', input: {}, org_id: orgId },
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
+ }
@@ -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' };