quoxcli 0.0.0-stage → 0.2.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 (348) hide show
  1. package/LICENSE +35 -0
  2. package/README.md +142 -2
  3. package/bin/quox.js +292 -0
  4. package/bin/quoxon.js +8 -0
  5. package/lib/api/endpoints/admin.js +125 -0
  6. package/lib/api/endpoints/aee.js +38 -0
  7. package/lib/api/endpoints/agent-attach.js +126 -0
  8. package/lib/api/endpoints/agents.js +56 -0
  9. package/lib/api/endpoints/assistants.js +50 -0
  10. package/lib/api/endpoints/audit.js +14 -0
  11. package/lib/api/endpoints/auth.js +50 -0
  12. package/lib/api/endpoints/awareness.js +34 -0
  13. package/lib/api/endpoints/backup-schedules.js +20 -0
  14. package/lib/api/endpoints/backups.js +86 -0
  15. package/lib/api/endpoints/brain.js +140 -0
  16. package/lib/api/endpoints/budgets.js +62 -0
  17. package/lib/api/endpoints/calendarq.js +71 -0
  18. package/lib/api/endpoints/catalog.js +14 -0
  19. package/lib/api/endpoints/chat.js +54 -0
  20. package/lib/api/endpoints/consent.js +26 -0
  21. package/lib/api/endpoints/contactsq.js +66 -0
  22. package/lib/api/endpoints/conversations.js +32 -0
  23. package/lib/api/endpoints/custom-roles.js +92 -0
  24. package/lib/api/endpoints/divisions.js +56 -0
  25. package/lib/api/endpoints/domains.js +38 -0
  26. package/lib/api/endpoints/engagement.js +231 -0
  27. package/lib/api/endpoints/engine.js +11 -0
  28. package/lib/api/endpoints/entity.js +50 -0
  29. package/lib/api/endpoints/executions.js +32 -0
  30. package/lib/api/endpoints/file.js +20 -0
  31. package/lib/api/endpoints/files.js +26 -0
  32. package/lib/api/endpoints/github.js +21 -0
  33. package/lib/api/endpoints/governance.js +43 -0
  34. package/lib/api/endpoints/hat.js +36 -0
  35. package/lib/api/endpoints/inbox.js +32 -0
  36. package/lib/api/endpoints/inboxq.js +120 -0
  37. package/lib/api/endpoints/index.js +228 -0
  38. package/lib/api/endpoints/invites.js +19 -0
  39. package/lib/api/endpoints/llm-policy.js +43 -0
  40. package/lib/api/endpoints/lucid.js +89 -0
  41. package/lib/api/endpoints/members.js +32 -0
  42. package/lib/api/endpoints/memory.js +75 -0
  43. package/lib/api/endpoints/mfa.js +32 -0
  44. package/lib/api/endpoints/mon.js +178 -0
  45. package/lib/api/endpoints/notesq.js +43 -0
  46. package/lib/api/endpoints/objectives.js +62 -0
  47. package/lib/api/endpoints/orgs.js +38 -0
  48. package/lib/api/endpoints/plugins.js +134 -0
  49. package/lib/api/endpoints/powers.js +75 -0
  50. package/lib/api/endpoints/prompts.js +69 -0
  51. package/lib/api/endpoints/qlarity.js +122 -0
  52. package/lib/api/endpoints/quoxbox.js +55 -0
  53. package/lib/api/endpoints/quoxchat.js +54 -0
  54. package/lib/api/endpoints/quoxflow.js +32 -0
  55. package/lib/api/endpoints/quoxplan.js +114 -0
  56. package/lib/api/endpoints/quoxseo.js +108 -0
  57. package/lib/api/endpoints/reach.js +47 -0
  58. package/lib/api/endpoints/runs.js +38 -0
  59. package/lib/api/endpoints/safety.js +46 -0
  60. package/lib/api/endpoints/scim.js +46 -0
  61. package/lib/api/endpoints/scopes.js +14 -0
  62. package/lib/api/endpoints/security.js +69 -0
  63. package/lib/api/endpoints/setup.js +90 -0
  64. package/lib/api/endpoints/skills.js +180 -0
  65. package/lib/api/endpoints/sso.js +67 -0
  66. package/lib/api/endpoints/tags.js +32 -0
  67. package/lib/api/endpoints/teams.js +56 -0
  68. package/lib/api/endpoints/telephony.js +46 -0
  69. package/lib/api/endpoints/tg.js +56 -0
  70. package/lib/api/endpoints/tools.js +50 -0
  71. package/lib/api/endpoints/traces.js +157 -0
  72. package/lib/api/endpoints/triggers.js +70 -0
  73. package/lib/api/endpoints/user-library.js +41 -0
  74. package/lib/api/endpoints/users.js +20 -0
  75. package/lib/api/endpoints/v1.js +499 -0
  76. package/lib/api/endpoints/vault.js +192 -0
  77. package/lib/api/endpoints/voice.js +22 -0
  78. package/lib/api/endpoints/volt.js +84 -0
  79. package/lib/api/endpoints/ward.js +40 -0
  80. package/lib/api/endpoints/webhooks.js +46 -0
  81. package/lib/api/endpoints/workflows.js +162 -0
  82. package/lib/api/endpoints/x.js +24 -0
  83. package/lib/api/endpoints.js +15 -0
  84. package/lib/commands/admin.js +359 -0
  85. package/lib/commands/aee.js +99 -0
  86. package/lib/commands/agent.js +201 -0
  87. package/lib/commands/agentAttach.js +1440 -0
  88. package/lib/commands/approvals.js +47 -0
  89. package/lib/commands/assistant.js +114 -0
  90. package/lib/commands/auth.js +458 -0
  91. package/lib/commands/awareness.js +88 -0
  92. package/lib/commands/backup.js +373 -0
  93. package/lib/commands/brain.js +994 -0
  94. package/lib/commands/budget.js +241 -0
  95. package/lib/commands/build.js +156 -0
  96. package/lib/commands/calendar.js +216 -0
  97. package/lib/commands/capabilities.js +34 -0
  98. package/lib/commands/chat.js +240 -0
  99. package/lib/commands/claude.js +354 -0
  100. package/lib/commands/config-cmd.js +106 -0
  101. package/lib/commands/consent.js +84 -0
  102. package/lib/commands/contacts.js +967 -0
  103. package/lib/commands/contactsSelftest.js +132 -0
  104. package/lib/commands/conversation.js +74 -0
  105. package/lib/commands/department.js +75 -0
  106. package/lib/commands/dev.js +620 -0
  107. package/lib/commands/devResident.js +314 -0
  108. package/lib/commands/devSupervisor.js +1050 -0
  109. package/lib/commands/discord.js +96 -0
  110. package/lib/commands/doctor.js +181 -0
  111. package/lib/commands/domain.js +114 -0
  112. package/lib/commands/done.js +277 -0
  113. package/lib/commands/engagement.js +586 -0
  114. package/lib/commands/entity.js +119 -0
  115. package/lib/commands/facebook.js +212 -0
  116. package/lib/commands/file.js +128 -0
  117. package/lib/commands/fleet.js +882 -0
  118. package/lib/commands/governance.js +638 -0
  119. package/lib/commands/gripe.js +324 -0
  120. package/lib/commands/hat.js +205 -0
  121. package/lib/commands/helpers.js +79 -0
  122. package/lib/commands/inbox.js +122 -0
  123. package/lib/commands/instagram.js +264 -0
  124. package/lib/commands/integration.js +100 -0
  125. package/lib/commands/inventory.js +115 -0
  126. package/lib/commands/invites.js +45 -0
  127. package/lib/commands/licence.js +280 -0
  128. package/lib/commands/linkedin.js +178 -0
  129. package/lib/commands/llm-policy.js +117 -0
  130. package/lib/commands/llm.js +45 -0
  131. package/lib/commands/lucid.js +591 -0
  132. package/lib/commands/mail.js +531 -0
  133. package/lib/commands/memory.js +284 -0
  134. package/lib/commands/mon.js +514 -0
  135. package/lib/commands/monitoring.js +86 -0
  136. package/lib/commands/motion.js +22 -0
  137. package/lib/commands/notes.js +1246 -0
  138. package/lib/commands/notesBatch.js +160 -0
  139. package/lib/commands/notesBridges.js +56 -0
  140. package/lib/commands/notesLinks.js +60 -0
  141. package/lib/commands/notification.js +57 -0
  142. package/lib/commands/nucleus.js +92 -0
  143. package/lib/commands/objective.js +229 -0
  144. package/lib/commands/onboard.js +328 -0
  145. package/lib/commands/open.js +56 -0
  146. package/lib/commands/org.js +266 -0
  147. package/lib/commands/plan.js +906 -0
  148. package/lib/commands/plugin-doctor.js +1091 -0
  149. package/lib/commands/plugin.js +539 -0
  150. package/lib/commands/policy.js +78 -0
  151. package/lib/commands/powers.js +296 -0
  152. package/lib/commands/prompt.js +350 -0
  153. package/lib/commands/qlarity.js +690 -0
  154. package/lib/commands/quoxbox.js +270 -0
  155. package/lib/commands/quoxchat.js +266 -0
  156. package/lib/commands/quoxseo.js +340 -0
  157. package/lib/commands/reach.js +158 -0
  158. package/lib/commands/role.js +286 -0
  159. package/lib/commands/run.js +132 -0
  160. package/lib/commands/safety.js +107 -0
  161. package/lib/commands/scim.js +88 -0
  162. package/lib/commands/scope.js +58 -0
  163. package/lib/commands/security.js +157 -0
  164. package/lib/commands/service.js +89 -0
  165. package/lib/commands/settings.js +99 -0
  166. package/lib/commands/setup.js +543 -0
  167. package/lib/commands/skillManifest.js +113 -0
  168. package/lib/commands/skillRightsFormat.js +38 -0
  169. package/lib/commands/skills.js +644 -0
  170. package/lib/commands/slot.js +1198 -0
  171. package/lib/commands/sso.js +229 -0
  172. package/lib/commands/status.js +137 -0
  173. package/lib/commands/tag.js +91 -0
  174. package/lib/commands/team.js +140 -0
  175. package/lib/commands/tg.js +240 -0
  176. package/lib/commands/tiktok.js +218 -0
  177. package/lib/commands/tool.js +216 -0
  178. package/lib/commands/trace.js +525 -0
  179. package/lib/commands/trigger.js +128 -0
  180. package/lib/commands/vault.js +1424 -0
  181. package/lib/commands/voice.js +182 -0
  182. package/lib/commands/volt.js +528 -0
  183. package/lib/commands/watch.js +115 -0
  184. package/lib/commands/webhook.js +137 -0
  185. package/lib/commands/workflow.js +503 -0
  186. package/lib/commands/x.js +439 -0
  187. package/lib/core/actingAs.js +136 -0
  188. package/lib/core/aee.js +53 -0
  189. package/lib/core/agentAlias.js +163 -0
  190. package/lib/core/approvalContext.js +61 -0
  191. package/lib/core/auth.js +350 -0
  192. package/lib/core/banner.js +143 -0
  193. package/lib/core/brainPrime.js +90 -0
  194. package/lib/core/callerSource.js +129 -0
  195. package/lib/core/claudeLs.js +94 -0
  196. package/lib/core/client.js +584 -0
  197. package/lib/core/completions.js +163 -0
  198. package/lib/core/config.js +551 -0
  199. package/lib/core/confirm.js +37 -0
  200. package/lib/core/deeplinks.js +76 -0
  201. package/lib/core/doneEngineFetch.js +219 -0
  202. package/lib/core/errors.js +132 -0
  203. package/lib/core/global-flags.js +90 -0
  204. package/lib/core/governedSession.js +402 -0
  205. package/lib/core/lazyCommands.js +119 -0
  206. package/lib/core/liveSessionsReport.js +112 -0
  207. package/lib/core/org-override.js +52 -0
  208. package/lib/core/output.js +239 -0
  209. package/lib/core/prodEnv.js +7 -0
  210. package/lib/core/quoxglyph.js +158 -0
  211. package/lib/core/residentAlarm.js +282 -0
  212. package/lib/core/residentAuth.js +243 -0
  213. package/lib/core/residentPump.js +229 -0
  214. package/lib/core/residentSeed.js +104 -0
  215. package/lib/core/sessionHeartbeat.js +192 -0
  216. package/lib/core/sessionsOverview.js +160 -0
  217. package/lib/core/slotKeeper.js +282 -0
  218. package/lib/core/supervisorEngine.js +864 -0
  219. package/lib/core/systemdInstaller.js +256 -0
  220. package/lib/core/version.js +87 -0
  221. package/lib/core/websiteSession.js +148 -0
  222. package/lib/lucid/LucidIntro.jsx +105 -0
  223. package/lib/lucid/blind.js +71 -0
  224. package/lib/lucid/deck.js +32 -0
  225. package/lib/lucid/fmt.js +139 -0
  226. package/lib/lucid/sitrep.js +173 -0
  227. package/lib/plugins/loader.js +93 -0
  228. package/lib/quoxon/activity.js +104 -0
  229. package/lib/quoxon/api.js +473 -0
  230. package/lib/quoxon/app.js +1262 -0
  231. package/lib/quoxon/capabilities.js +75 -0
  232. package/lib/quoxon/commands.js +154 -0
  233. package/lib/quoxon/connect.js +467 -0
  234. package/lib/quoxon/connectCard.js +28 -0
  235. package/lib/quoxon/context.js +68 -0
  236. package/lib/quoxon/copy.js +33 -0
  237. package/lib/quoxon/demo.fixtures.js +24 -0
  238. package/lib/quoxon/demo.js +35 -0
  239. package/lib/quoxon/egg.js +36 -0
  240. package/lib/quoxon/events.js +458 -0
  241. package/lib/quoxon/gallery.js +223 -0
  242. package/lib/quoxon/indicator.js +193 -0
  243. package/lib/quoxon/intent.js +41 -0
  244. package/lib/quoxon/keys.js +121 -0
  245. package/lib/quoxon/main.js +244 -0
  246. package/lib/quoxon/markdown.js +317 -0
  247. package/lib/quoxon/model.js +205 -0
  248. package/lib/quoxon/motion/clock.js +69 -0
  249. package/lib/quoxon/motion/color.js +35 -0
  250. package/lib/quoxon/motion/frame.js +14 -0
  251. package/lib/quoxon/motion/reveal.js +54 -0
  252. package/lib/quoxon/motion/ring.js +79 -0
  253. package/lib/quoxon/objects/agent.js +61 -0
  254. package/lib/quoxon/objects/crystal.js +100 -0
  255. package/lib/quoxon/objects/fetch.js +170 -0
  256. package/lib/quoxon/objects/index.js +85 -0
  257. package/lib/quoxon/objects/ladder.js +71 -0
  258. package/lib/quoxon/objects/memory.js +44 -0
  259. package/lib/quoxon/objects/model.js +118 -0
  260. package/lib/quoxon/objects/proof.js +110 -0
  261. package/lib/quoxon/objects/skill.js +105 -0
  262. package/lib/quoxon/objects/view.js +76 -0
  263. package/lib/quoxon/objects/workflow.js +119 -0
  264. package/lib/quoxon/observers.js +42 -0
  265. package/lib/quoxon/onboarding/copy.js +235 -0
  266. package/lib/quoxon/onboarding/credential.js +95 -0
  267. package/lib/quoxon/onboarding/detect.js +22 -0
  268. package/lib/quoxon/onboarding/finish.js +119 -0
  269. package/lib/quoxon/onboarding/index.js +67 -0
  270. package/lib/quoxon/onboarding/installRunner.js +225 -0
  271. package/lib/quoxon/onboarding/intent.js +53 -0
  272. package/lib/quoxon/onboarding/journey.js +73 -0
  273. package/lib/quoxon/onboarding/preflight.js +142 -0
  274. package/lib/quoxon/onboarding/runner.js +58 -0
  275. package/lib/quoxon/onboarding/view.js +625 -0
  276. package/lib/quoxon/print.js +94 -0
  277. package/lib/quoxon/qart.js +379 -0
  278. package/lib/quoxon/qglyph.js +143 -0
  279. package/lib/quoxon/qhello.js +58 -0
  280. package/lib/quoxon/qmotion.js +111 -0
  281. package/lib/quoxon/render.js +648 -0
  282. package/lib/quoxon/roster.js +221 -0
  283. package/lib/quoxon/route.js +38 -0
  284. package/lib/quoxon/rules.js +130 -0
  285. package/lib/quoxon/session.js +68 -0
  286. package/lib/quoxon/skills.js +30 -0
  287. package/lib/quoxon/slash/compact.js +90 -0
  288. package/lib/quoxon/slash/ledger.js +83 -0
  289. package/lib/quoxon/slash/listView.js +28 -0
  290. package/lib/quoxon/slash/palette.js +104 -0
  291. package/lib/quoxon/slash/paletteController.js +59 -0
  292. package/lib/quoxon/slash/panels.js +101 -0
  293. package/lib/quoxon/slash/picker.js +38 -0
  294. package/lib/quoxon/slash/restart.js +144 -0
  295. package/lib/quoxon/slash/resume.js +59 -0
  296. package/lib/quoxon/slash/settings.js +50 -0
  297. package/lib/quoxon/sound/index.js +186 -0
  298. package/lib/quoxon/sound/mapping.js +119 -0
  299. package/lib/quoxon/sound/output.js +52 -0
  300. package/lib/quoxon/sound/synth.js +122 -0
  301. package/lib/quoxon/term.js +106 -0
  302. package/lib/quoxon/textBuffer.js +242 -0
  303. package/lib/quoxon/theme.js +253 -0
  304. package/lib/quoxon/transcript.js +150 -0
  305. package/lib/quoxon/web/qface.js +81 -0
  306. package/lib/quoxon/welcome.js +52 -0
  307. package/lib/tui/app.jsx +289 -0
  308. package/lib/tui/components/Breadcrumb.jsx +42 -0
  309. package/lib/tui/components/ChatInput.jsx +68 -0
  310. package/lib/tui/components/Confirm.jsx +32 -0
  311. package/lib/tui/components/DataTable.jsx +270 -0
  312. package/lib/tui/components/DetailPanel.jsx +241 -0
  313. package/lib/tui/components/HelpOverlay.jsx +67 -0
  314. package/lib/tui/components/Layout.jsx +129 -0
  315. package/lib/tui/components/Palette.jsx +141 -0
  316. package/lib/tui/components/SearchInput.jsx +125 -0
  317. package/lib/tui/components/Sparkline.jsx +55 -0
  318. package/lib/tui/components/StatusBar.jsx +133 -0
  319. package/lib/tui/components/Toast.jsx +52 -0
  320. package/lib/tui/hooks/useApi.js +44 -0
  321. package/lib/tui/hooks/useHealth.js +90 -0
  322. package/lib/tui/hooks/usePolledApi.js +86 -0
  323. package/lib/tui/hooks/useTerminalSize.js +46 -0
  324. package/lib/tui/index.js +56 -0
  325. package/lib/tui/intro/Intro.jsx +332 -0
  326. package/lib/tui/jsx-loader.js +32 -0
  327. package/lib/tui/keys.js +87 -0
  328. package/lib/tui/registry.js +49 -0
  329. package/lib/tui/theme.js +103 -0
  330. package/lib/tui/views/AdminView.jsx +398 -0
  331. package/lib/tui/views/AgentView.jsx +204 -0
  332. package/lib/tui/views/BackupView.jsx +225 -0
  333. package/lib/tui/views/BrainView.jsx +223 -0
  334. package/lib/tui/views/ChatView.jsx +468 -0
  335. package/lib/tui/views/FilesView.jsx +218 -0
  336. package/lib/tui/views/FleetView.jsx +234 -0
  337. package/lib/tui/views/GovernanceView.jsx +453 -0
  338. package/lib/tui/views/HealthView.jsx +216 -0
  339. package/lib/tui/views/InboxView.jsx +228 -0
  340. package/lib/tui/views/LucidDeck.jsx +727 -0
  341. package/lib/tui/views/MainMenu.jsx +123 -0
  342. package/lib/tui/views/MemoryView.jsx +377 -0
  343. package/lib/tui/views/ObjectivesView.jsx +230 -0
  344. package/lib/tui/views/OrgView.jsx +229 -0
  345. package/lib/tui/views/ToolView.jsx +221 -0
  346. package/lib/tui/views/VaultView.jsx +422 -0
  347. package/lib/tui/views/WorkflowView.jsx +329 -0
  348. package/package.json +72 -4
@@ -0,0 +1,81 @@
1
+ /**
2
+ * The Q face for the web (cli.quox.ai): the SAME pure frames the CLI draws (qart.js, qglyph.js, qhello.js),
3
+ * rendered into <pre> elements as coloured spans. Plays Orbit & Lock, then the Q says hello (the line
4
+ * decodes in from katakana), then idles: the mark breathes, the status Q breathes beside "Ready", and now
5
+ * and then the Dragon's Eye opens and blinks. prefers-reduced-motion: the settled frame only.
6
+ *
7
+ * Browser ESM, no dependencies, no build: import it next to ../qart.js, ../qglyph.js, ../qhello.js.
8
+ * import { mountQFace } from './qface.js';
9
+ * mountQFace(document.getElementById('face'), { hello: "Hi, I'm Quox!" });
10
+ */
11
+ import { introPlan, restCellsAt, eyeCellsAt, INTRO_MS, EYE_MS } from '../qart.js';
12
+ import { qGlyph } from '../qglyph.js';
13
+ import { helloCells, helloGradient, helloMs } from '../qhello.js';
14
+
15
+ const esc = (s) => s.replace(/[&<>]/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;' }[c]));
16
+
17
+ /** One row of cells -> HTML spans, merging runs of the same colour. */
18
+ export function rowToHTML(row) {
19
+ let html = '', run = '', key = null;
20
+ const flush = () => { if (!run) return; html += key ? `<span style="${key}">${esc(run)}</span>` : esc(run); run = ''; };
21
+ for (const c of row) {
22
+ const k = c.rgb ? `color:rgb(${c.rgb.join(',')})${c.bold ? ';font-weight:700' : ''}` : (c.bold ? 'font-weight:700' : null);
23
+ if (k !== key) { flush(); key = k; }
24
+ run += c.ch;
25
+ }
26
+ flush();
27
+ return html;
28
+ }
29
+ // Blank cells in Braille art are drawn as U+2800 (the empty Braille pattern), not a space: a browser can
30
+ // give a space and a Braille glyph different advances (font fallback), which shears the Q row by row.
31
+ const BLANK = '\u2800';
32
+ export const cellsToHTML = (rows) => rows.map((r) => rowToHTML(r.map((c) => (c.ch === ' ' ? { ...c, ch: BLANK } : c)))).join('\n');
33
+
34
+ /** Which Q size fits a container of `px` CSS pixels at a monospace advance of `cw` px. */
35
+ export function sizeForWidth(px, cw = 8.4) {
36
+ const cols = Math.floor(px / cw);
37
+ return cols >= 34 ? 'large' : cols >= 22 ? 'mid' : 'small';
38
+ }
39
+
40
+ /**
41
+ * Timeline state at t ms (pure): which picture, the hello progress, and whether the eye is open.
42
+ * eyeAt: list of start times (ms) for idle eye blinks.
43
+ */
44
+ export function faceFrame(t, { size, hello, eyeAt = [], color = true }) {
45
+ const plan = introPlan(size, { color, quant: false });
46
+ const hT = t - INTRO_MS, settledAt = INTRO_MS + helloMs(hello);
47
+ let q;
48
+ const eye = eyeAt.find((s) => t >= s && t < s + EYE_MS + 400);
49
+ if (t < INTRO_MS) q = plan.at(t);
50
+ else if (eye != null) q = eyeCellsAt(size, t - eye, { color, quant: false });
51
+ else q = restCellsAt(size, Math.max(0, t - settledAt), { color, quant: false });
52
+ const line = helloCells(hello, hT, { fg: helloGradient(hello), mono: !color });
53
+ const glyph = qGlyph('resting', Math.max(0, t - settledAt), { mono: !color });
54
+ return { q, line, glyph, showStatus: t >= settledAt };
55
+ }
56
+
57
+ export function mountQFace(root, { hello = "Hi, I'm Quox!", status = 'Ready', seed = 7, reduced = null, eyeEveryMs = [9000, 16000] } = {}) {
58
+ const doc = root.ownerDocument, win = doc.defaultView;
59
+ root.classList.add('qface');
60
+ root.innerHTML = '<pre class="qface-q" aria-hidden="true"></pre><div class="qface-side"><pre class="qface-hello"></pre><pre class="qface-status"></pre></div>';
61
+ root.setAttribute('role', 'img');
62
+ root.setAttribute('aria-label', `The Quox Q says: ${hello}`);
63
+ const [qEl, hEl, sEl] = ['.qface-q', '.qface-hello', '.qface-status'].map((s) => root.querySelector(s));
64
+ const still = reduced ?? Boolean(win.matchMedia?.('(prefers-reduced-motion: reduce)').matches);
65
+ let r = seed; const rand = () => { r = (r * 16807) % 2147483647; return r / 2147483647; };
66
+ const eyeAt = [];
67
+ let next = INTRO_MS + helloMs(hello) + 2500; // the first blink comes soon after hello, then rarely
68
+ for (let i = 0; i < 40; i++) { eyeAt.push(next); next += EYE_MS + eyeEveryMs[0] + rand() * (eyeEveryMs[1] - eyeEveryMs[0]); }
69
+ const size = () => sizeForWidth(Math.min(root.clientWidth || 600, win.innerWidth || 600) * (root.clientWidth > 520 ? 0.55 : 1));
70
+ const draw = (t) => {
71
+ const f = faceFrame(t, { size: size(), hello, eyeAt });
72
+ qEl.innerHTML = cellsToHTML(f.q);
73
+ hEl.innerHTML = rowToHTML(f.line);
74
+ sEl.innerHTML = f.showStatus ? `${rowToHTML(f.glyph.cells)} <span class="qface-word">${esc(status)}</span>` : '&nbsp;';
75
+ };
76
+ if (still) { draw(INTRO_MS + helloMs(hello) + 10); return { stop() {} }; }
77
+ let raf = 0, t0 = null, stopped = false;
78
+ const tick = (now) => { if (stopped) return; t0 ??= now; draw(now - t0); raf = win.requestAnimationFrame(tick); };
79
+ raf = win.requestAnimationFrame(tick);
80
+ return { stop() { stopped = true; win.cancelAnimationFrame(raf); }, seek: draw };
81
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * First-run notice for bare `quox`: who we are, legal, docs. Pure model + a tiny "seen" marker kept in the
3
+ * CLI's existing config dir (~/.config/quox, same place as config.json). Links live ONLY here.
4
+ */
5
+ import { readFileSync, writeFileSync, mkdirSync } from 'node:fs';
6
+ import { homedir } from 'node:os';
7
+ import { join } from 'node:path';
8
+
9
+ export const DOCS_URL = 'https://github.com/quoxai/quoxcore'; // will become https://cli.quox.ai
10
+ export const TERMS_URL = 'https://quox.ai/terms';
11
+ export const PRIVACY_URL = 'https://quox.ai/privacy';
12
+ export const LICENCE = 'Quox Community Source Licence 1.0 (source-available)';
13
+ export const WELCOME_VERSION = 1;
14
+
15
+ export function packageVersion() {
16
+ try { return JSON.parse(readFileSync(new URL('../../package.json', import.meta.url), 'utf8')).version || '0.1.0'; } catch { return '0.1.0'; }
17
+ }
18
+
19
+ /** [{ kind: 'title'|'sub'|'rule'|'field'|'para'|'gap'|'hint', ... }] */
20
+ export function welcomeModel(version = packageVersion()) {
21
+ return [
22
+ { kind: 'title', text: 'Welcome to Quox' },
23
+ { kind: 'sub', text: `Quox CLI ${version}` },
24
+ { kind: 'para', text: 'The main front door to Quox, your self-hosted control plane for AI agents.' },
25
+ { kind: 'rule' },
26
+ { kind: 'field', label: 'Docs', value: DOCS_URL, link: true },
27
+ { kind: 'field', label: 'Terms', value: TERMS_URL, link: true },
28
+ { kind: 'field', label: 'Privacy', value: PRIVACY_URL, link: true },
29
+ { kind: 'field', label: 'Licence', value: LICENCE },
30
+ { kind: 'rule' },
31
+ { kind: 'para', text: 'Your prompts go to your own QuoxCORE server and to the AI provider you connect to it.' },
32
+ { kind: 'para', text: 'Risky actions ask you first, and the actions Quox takes leave receipts you can verify.' },
33
+ { kind: 'para', text: 'This CLI sends no telemetry to Quox.' },
34
+ { kind: 'para', text: 'By using Quox CLI you agree to the Terms.' },
35
+ { kind: 'hint', text: 'Enter to continue · /about to see this again' }
36
+ ];
37
+ }
38
+
39
+ export const welcomeStatePath = () => join(homedir(), '.config', 'quox', 'welcome.json');
40
+
41
+ export function welcomeSeen(path = welcomeStatePath()) {
42
+ try { return (JSON.parse(readFileSync(path, 'utf8')).version || 0) >= WELCOME_VERSION; } catch { return false; }
43
+ }
44
+ export function markWelcomeSeen(path = welcomeStatePath()) {
45
+ try { mkdirSync(join(path, '..'), { recursive: true }); writeFileSync(path, JSON.stringify({ version: WELCOME_VERSION, seenAt: new Date().toISOString() }) + '\n'); return true; } catch { return false; }
46
+ }
47
+
48
+ /** First interactive launch only: a TTY, not suppressed, not seen at this notice version. */
49
+ export function shouldShowWelcome({ env = process.env, isTTY = process.stdout.isTTY && process.stdin.isTTY, print = false, path } = {}) {
50
+ if (print || !isTTY || env.QUOX_NO_WELCOME === '1') return false;
51
+ return !welcomeSeen(path);
52
+ }
@@ -0,0 +1,289 @@
1
+ /**
2
+ * QuoxCLI TUI — Root Application Component
3
+ *
4
+ * Manages the view state machine:
5
+ * intro -> menu -> specific view (fleet, agents, tools, etc.)
6
+ *
7
+ * Provides TuiContext to all descendants with navigation helpers,
8
+ * the CLI context (config, auth, client), and the app exit function.
9
+ *
10
+ * ── Input-mode registry ────────────────────────────────────────────────
11
+ * `inputMode` on the context tells the global keybinding handler whether
12
+ * it's safe to steal single-character keys like `?` (help) and `q` (quit):
13
+ * 'nav' - default; global shortcuts are active
14
+ * 'input' - a text field (chat box, search box) has focus; global
15
+ * single-char shortcuts are suppressed so typing works
16
+ * 'overlay' - the help overlay or command palette is open; it owns its
17
+ * own close handler and (for the palette) all keystrokes
18
+ * Escape/back always works regardless of mode (text inputs intentionally
19
+ * ignore Escape so navigation can still happen).
20
+ *
21
+ * ── Command palette + global jump keys ─────────────────────────────────
22
+ * Ctrl+K or ':' (nav mode only) opens the palette (components/Palette.jsx)
23
+ * over the current screen, content-swapped like the help overlay. It's a
24
+ * fuzzy search over lib/tui/registry.js's SCREEN_REGISTRY. Separately,
25
+ * whenever inputMode is 'nav' and a sub-view is active (screen !== 'menu'),
26
+ * pressing a screen's hotkey (1-0 / o i v g h — same as MainMenu's number
27
+ * keys) jumps straight there without returning to the menu first
28
+ * (k9s-style). Both are gated on inputMode === 'nav' so they never steal
29
+ * keystrokes from a focused chat/search box.
30
+ *
31
+ * ── Keep-alive views ───────────────────────────────────────────────────
32
+ * A screen is mounted the first time it's visited and then stays mounted
33
+ * (hidden via `display: none`) so its state — chat history, filters,
34
+ * selections — survives navigating away and back. Views only fetch data
35
+ * on their first activation (see each view's data hook), so this does
36
+ * NOT boot-fetch all screens at startup.
37
+ */
38
+
39
+ import React, { useState, useCallback, createContext, useContext } from 'react';
40
+ import { Box, Text, useApp, useInput } from 'ink';
41
+ import { matchKey } from './keys.js';
42
+ import { colors } from './theme.js';
43
+ import { MainMenu } from './views/MainMenu.jsx';
44
+ import { FleetView } from './views/FleetView.jsx';
45
+ import { AgentView } from './views/AgentView.jsx';
46
+ import { ToolView } from './views/ToolView.jsx';
47
+ import { OrgView } from './views/OrgView.jsx';
48
+ import { MemoryView } from './views/MemoryView.jsx';
49
+ import { BrainView } from './views/BrainView.jsx';
50
+ import { WorkflowView } from './views/WorkflowView.jsx';
51
+ import { ChatView } from './views/ChatView.jsx';
52
+ import { AdminView } from './views/AdminView.jsx';
53
+ import { FilesView } from './views/FilesView.jsx';
54
+ import { BackupView } from './views/BackupView.jsx';
55
+ import { ObjectivesView } from './views/ObjectivesView.jsx';
56
+ import { InboxView } from './views/InboxView.jsx';
57
+ import { VaultView } from './views/VaultView.jsx';
58
+ import { GovernanceView } from './views/GovernanceView.jsx';
59
+ import { HealthView } from './views/HealthView.jsx';
60
+ import { StatusBar } from './components/StatusBar.jsx';
61
+ import { HelpOverlay } from './components/HelpOverlay.jsx';
62
+ import { Palette } from './components/Palette.jsx';
63
+ import { Toast } from './components/Toast.jsx';
64
+ import { SCREEN_BY_HOTKEY } from './registry.js';
65
+
66
+ // ── TUI Context ───────────────────────────────────────────────────────
67
+ export const TuiContext = createContext(null);
68
+
69
+ /**
70
+ * Hook to access TUI context from any descendant component.
71
+ * @returns {{ config, auth, client, navigate, goBack, screen, history, exit, inputMode, setInputMode, orgVersion, onOrgChange }}
72
+ */
73
+ export const useTui = () => useContext(TuiContext);
74
+
75
+ // ── Screen registry ───────────────────────────────────────────────────
76
+ const SCREENS = [
77
+ 'menu',
78
+ 'fleet', 'agents', 'tools', 'orgs',
79
+ 'memory', 'brain', 'workflows', 'chat',
80
+ 'admin', 'files', 'backups',
81
+ 'objectives', 'inbox', 'vault', 'governance', 'health',
82
+ ];
83
+
84
+ // Sub-views rendered (keep-alive) alongside the menu. Keyed by screen id.
85
+ const VIEW_COMPONENTS = {
86
+ fleet: FleetView,
87
+ agents: AgentView,
88
+ tools: ToolView,
89
+ orgs: OrgView,
90
+ memory: MemoryView,
91
+ brain: BrainView,
92
+ workflows: WorkflowView,
93
+ chat: ChatView,
94
+ admin: AdminView,
95
+ files: FilesView,
96
+ backups: BackupView,
97
+ objectives: ObjectivesView,
98
+ inbox: InboxView,
99
+ vault: VaultView,
100
+ governance: GovernanceView,
101
+ health: HealthView,
102
+ };
103
+
104
+ // ── Root component ────────────────────────────────────────────────────
105
+ export function App({ ctx, initialScreen = null }) {
106
+ const { exit } = useApp();
107
+ // Deep-link: `quox tui <screen>` (or the web QuoxCLIView ?screen= param) can
108
+ // open directly on a view instead of the menu. Guard against unknown names.
109
+ const startScreen =
110
+ initialScreen && initialScreen !== 'menu' && SCREENS.includes(initialScreen)
111
+ ? initialScreen
112
+ : 'menu';
113
+ const [screen, setScreen] = useState(startScreen);
114
+ const [history, setHistory] = useState([]);
115
+ const [showHelp, setShowHelp] = useState(false);
116
+ const [showPalette, setShowPalette] = useState(false);
117
+ const [toast, setToast] = useState(null);
118
+ const [inputMode, setInputMode] = useState('nav');
119
+ const [orgVersion, setOrgVersion] = useState(0);
120
+
121
+ // Screens that have been visited at least once — kept mounted (hidden
122
+ // via display:none when not current) so their state survives navigation.
123
+ const [visited, setVisited] = useState(() =>
124
+ startScreen === 'menu' ? new Set(['menu']) : new Set(['menu', startScreen])
125
+ );
126
+
127
+ // Navigation helpers
128
+ const navigate = (target) => {
129
+ if (!SCREENS.includes(target)) return;
130
+ setHistory((prev) => [...prev, screen]);
131
+ setVisited((prev) => (prev.has(target) ? prev : new Set(prev).add(target)));
132
+ setScreen(target);
133
+ };
134
+
135
+ const goBack = () => {
136
+ if (history.length > 0) {
137
+ const prev = history[history.length - 1];
138
+ setHistory((h) => h.slice(0, -1));
139
+ setScreen(prev);
140
+ }
141
+ };
142
+
143
+ // Toast helper
144
+ const showToast = useCallback((message, type = 'info') => {
145
+ setToast({ message, type });
146
+ }, []);
147
+
148
+ const dismissToast = useCallback(() => {
149
+ setToast(null);
150
+ }, []);
151
+
152
+ // Called by OrgView after switching the active org — bumps a version
153
+ // counter that org-scoped views include in their fetch deps so they
154
+ // refetch automatically.
155
+ const onOrgChange = useCallback(() => {
156
+ setOrgVersion((v) => v + 1);
157
+ }, []);
158
+
159
+ const closeHelp = useCallback(() => {
160
+ setShowHelp(false);
161
+ setInputMode('nav');
162
+ }, []);
163
+
164
+ const closePalette = useCallback(() => {
165
+ setShowPalette(false);
166
+ setInputMode('nav');
167
+ }, []);
168
+
169
+ // Build context value
170
+ const tuiCtx = {
171
+ ...(ctx || {}),
172
+ navigate,
173
+ goBack,
174
+ screen,
175
+ history,
176
+ exit,
177
+ showToast,
178
+ inputMode,
179
+ setInputMode,
180
+ orgVersion,
181
+ onOrgChange,
182
+ };
183
+
184
+ // ── Global keybindings ──────────────────────────────────────────────
185
+ useInput((input, key) => {
186
+ // While the help overlay is open, it owns ? and Esc via its own listener.
187
+ if (showHelp) return;
188
+
189
+ // While the command palette is open, it owns all keystrokes (typing,
190
+ // arrows, Enter, Esc) via its own useInput listener.
191
+ if (showPalette) return;
192
+
193
+ // Single-character global shortcuts (help, quit, palette) and the
194
+ // global jump keys only fire in 'nav' mode — otherwise typing into a
195
+ // chat/search box would be hijacked.
196
+ if (inputMode === 'nav') {
197
+ if (matchKey('help', input, key)) {
198
+ setShowHelp(true);
199
+ setInputMode('overlay');
200
+ return;
201
+ }
202
+
203
+ // Command palette: Ctrl+K (can't be expressed via matchKey's plain
204
+ // string comparison) or ':' (a normal KEYS binding).
205
+ if ((key.ctrl && input === 'k') || matchKey('palette', input, key)) {
206
+ setShowPalette(true);
207
+ setInputMode('overlay');
208
+ return;
209
+ }
210
+
211
+ if (screen === 'menu' && matchKey('quit', input, key)) {
212
+ exit();
213
+ return;
214
+ }
215
+
216
+ // Global jump keys (k9s-style): from any non-menu screen, a screen's
217
+ // hotkey (1-0 / o i v g h — see SCREEN_BY_HOTKEY) jumps straight
218
+ // there, mirroring MainMenu's own number-key shortcuts.
219
+ if (screen !== 'menu') {
220
+ const target = SCREEN_BY_HOTKEY[input];
221
+ if (target) {
222
+ navigate(target);
223
+ return;
224
+ }
225
+ }
226
+ }
227
+
228
+ // Escape goes back from any sub-view — works regardless of input mode
229
+ // (text inputs deliberately ignore Escape so this can still fire).
230
+ if (screen !== 'menu' && matchKey('back', input, key)) {
231
+ goBack();
232
+ }
233
+ });
234
+
235
+ // ── Render ──────────────────────────────────────────────────────────
236
+ return (
237
+ <TuiContext.Provider value={tuiCtx}>
238
+ <Box flexDirection="column" width="100%" height="100%">
239
+ {/* Toast notification */}
240
+ {toast && (
241
+ <Toast
242
+ message={toast.message}
243
+ type={toast.type}
244
+ duration={3000}
245
+ onDismiss={dismissToast}
246
+ />
247
+ )}
248
+
249
+ {/* Main content area — the palette and help overlay swap in place
250
+ of the screens (no layout shift) rather than pushing them down
251
+ as a sibling. Palette takes priority if somehow both are open. */}
252
+ <Box flexGrow={1}>
253
+ {showPalette ? (
254
+ <Palette
255
+ onSelect={(id) => {
256
+ navigate(id);
257
+ closePalette();
258
+ }}
259
+ onClose={closePalette}
260
+ />
261
+ ) : showHelp ? (
262
+ <HelpOverlay visible={showHelp} onClose={closeHelp} />
263
+ ) : (
264
+ <>
265
+ <Box display={screen === 'menu' ? 'flex' : 'none'} width="100%" height="100%">
266
+ <MainMenu />
267
+ </Box>
268
+ {Object.entries(VIEW_COMPONENTS).map(([name, ViewComponent]) => (
269
+ visited.has(name) && (
270
+ <Box
271
+ key={name}
272
+ display={screen === name ? 'flex' : 'none'}
273
+ width="100%"
274
+ height="100%"
275
+ >
276
+ <ViewComponent />
277
+ </Box>
278
+ )
279
+ ))}
280
+ </>
281
+ )}
282
+ </Box>
283
+
284
+ {/* Status bar */}
285
+ <StatusBar />
286
+ </Box>
287
+ </TuiContext.Provider>
288
+ );
289
+ }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Breadcrumb — Navigation trail showing current position
3
+ *
4
+ * Renders a breadcrumb path like: Main > Fleet > Agent Details
5
+ * where the last segment is highlighted in the primary color
6
+ * and preceding segments are dimmed.
7
+ *
8
+ * Props:
9
+ * path - array of strings, e.g. ['Main', 'Fleet', 'Agent Details']
10
+ */
11
+
12
+ import React from 'react';
13
+ import { Box, Text } from 'ink';
14
+ import { colors } from '../theme.js';
15
+
16
+ export function Breadcrumb({ path = [] }) {
17
+ if (path.length === 0) return null;
18
+
19
+ return (
20
+ <Box>
21
+ {path.map((segment, i) => {
22
+ const isLast = i === path.length - 1;
23
+ return (
24
+ <React.Fragment key={i}>
25
+ {/* Separator */}
26
+ {i > 0 && (
27
+ <Text color={colors.dim}> {'>'} </Text>
28
+ )}
29
+
30
+ {/* Segment */}
31
+ <Text
32
+ color={isLast ? colors.primary : colors.dim}
33
+ bold={isLast}
34
+ >
35
+ {segment}
36
+ </Text>
37
+ </React.Fragment>
38
+ );
39
+ })}
40
+ </Box>
41
+ );
42
+ }
@@ -0,0 +1,68 @@
1
+ /**
2
+ * ChatInput — Presentational input line + agent picker overlay for ChatView
3
+ *
4
+ * Pure rendering component: ChatView owns all input state (draft text,
5
+ * cursor position, history, agent selection) and passes it down as props.
6
+ * Kept separate so the caret-rendering and agent-picker markup don't
7
+ * clutter ChatView's streaming/keyboard logic.
8
+ *
9
+ * Props:
10
+ * input - current draft text
11
+ * cursor - caret index into `input` (0..input.length)
12
+ * disabled - true while a response is streaming (dims the border)
13
+ * agents - array of agent objects/strings for the picker list
14
+ * pickerOpen - whether the agent picker overlay is visible
15
+ * pickerIndex - highlighted row in the picker
16
+ */
17
+
18
+ import React from 'react';
19
+ import { Box, Text } from 'ink';
20
+ import { colors } from '../theme.js';
21
+
22
+ function agentLabel(a) {
23
+ if (typeof a === 'string') return a;
24
+ return a?.name || a?.display_name || a?.id || String(a);
25
+ }
26
+
27
+ export function ChatInput({
28
+ input = '',
29
+ cursor = 0,
30
+ disabled = false,
31
+ agents = [],
32
+ pickerOpen = false,
33
+ pickerIndex = 0,
34
+ }) {
35
+ const safeCursor = Math.max(0, Math.min(cursor, input.length));
36
+ const pre = input.slice(0, safeCursor);
37
+ const rest = input.slice(safeCursor);
38
+
39
+ return (
40
+ <Box flexDirection="column">
41
+ <Box borderStyle="single" borderColor={disabled ? colors.subtle : colors.primary} paddingX={1}>
42
+ <Text color={colors.cyberBlue}>{'> '}</Text>
43
+ <Text>{pre}</Text>
44
+ <Text color={colors.dim}>{'█'}</Text>
45
+ <Text>{rest}</Text>
46
+ </Box>
47
+
48
+ {pickerOpen && (
49
+ <Box flexDirection="column" borderStyle="round" borderColor={colors.accent} paddingX={1}>
50
+ <Text color={colors.accent} bold>Select agent — Tab/↑↓ move, Enter confirm</Text>
51
+ {agents.length === 0 ? (
52
+ <Text color={colors.dim}>No agents found</Text>
53
+ ) : (
54
+ agents.map((a, i) => (
55
+ <Text
56
+ key={`${agentLabel(a)}-${i}`}
57
+ color={i === pickerIndex ? colors.cyberBlue : colors.textMuted}
58
+ bold={i === pickerIndex}
59
+ >
60
+ {i === pickerIndex ? '➤ ' : ' '}{agentLabel(a)}
61
+ </Text>
62
+ ))
63
+ )}
64
+ </Box>
65
+ )}
66
+ </Box>
67
+ );
68
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Confirm — Confirmation dialog component
3
+ *
4
+ * Shows an inline confirmation prompt with y/n keybindings.
5
+ *
6
+ * Props:
7
+ * message - question text (e.g. "Delete backup XYZ?")
8
+ * onConfirm - called when user presses y
9
+ * onCancel - called when user presses n or Esc
10
+ * active - whether dialog is shown/active (default true)
11
+ */
12
+
13
+ import React from 'react';
14
+ import { Box, Text, useInput } from 'ink';
15
+ import { colors } from '../theme.js';
16
+
17
+ export function Confirm({ message, onConfirm, onCancel, active = true }) {
18
+ useInput((input, key) => {
19
+ if (!active) return;
20
+ if (input === 'y' || input === 'Y') { onConfirm?.(); return; }
21
+ if (input === 'n' || input === 'N' || key.escape) { onCancel?.(); return; }
22
+ }, { isActive: active });
23
+
24
+ if (!active) return null;
25
+
26
+ return (
27
+ <Box borderStyle="single" borderColor={colors.warning} paddingX={2} paddingY={1}>
28
+ <Text color={colors.warning}>{message} </Text>
29
+ <Text color={colors.dim}>(y/n)</Text>
30
+ </Box>
31
+ );
32
+ }