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,87 @@
1
+ /**
2
+ * QuoxCLI TUI Keybindings
3
+ *
4
+ * Defines keyboard shortcuts for the TUI and help overlay entries.
5
+ * Vim-style navigation (h/j/k/l) plus standard arrow keys.
6
+ *
7
+ * Exports:
8
+ * KEYS - action name → key arrays (for useInput matching)
9
+ * KEY_HELP - ordered list of { keys, desc } for the help overlay
10
+ */
11
+
12
+ // ── Key mappings ──────────────────────────────────────────────────────
13
+ export const KEYS = {
14
+ up: ['up', 'k'],
15
+ down: ['down', 'j'],
16
+ left: ['left', 'h'],
17
+ right: ['right', 'l'],
18
+ select: ['return'],
19
+ back: ['escape'],
20
+ tab: ['tab'],
21
+ search: ['/'],
22
+ help: ['?'],
23
+ quit: ['q'],
24
+ refresh: ['r'],
25
+ delete: ['d'],
26
+ edit: ['e'],
27
+ create: ['c'],
28
+ nextPage: ['n'],
29
+ prevPage: ['p'],
30
+ // Command palette. Ctrl+K can't be expressed as a plain input string
31
+ // (matchKey's default case only compares `input === binding`), so the
32
+ // Ctrl+K half is detected directly in app.jsx via `key.ctrl && input
33
+ // === 'k'`. ':' works here since it's a normal single character.
34
+ palette: [':'],
35
+ };
36
+
37
+ // ── Help overlay entries ──────────────────────────────────────────────
38
+ export const KEY_HELP = [
39
+ { keys: '\u2191/k \u2193/j', desc: 'Navigate' },
40
+ { keys: 'Enter', desc: 'Select' },
41
+ { keys: 'Esc', desc: 'Back' },
42
+ { keys: 'Tab', desc: 'Switch panel' },
43
+ { keys: '/', desc: 'Search' },
44
+ { keys: '?', desc: 'Help' },
45
+ { keys: 'q', desc: 'Quit' },
46
+ { keys: 'r', desc: 'Refresh' },
47
+ { keys: 'n / p', desc: 'Next / prev page' },
48
+ { keys: 'c', desc: 'Create' },
49
+ { keys: 'd', desc: 'Delete' },
50
+ { keys: 'e', desc: 'Edit' },
51
+ { keys: 'Ctrl+K / :', desc: 'Command palette' },
52
+ { keys: '1-0 o i v g h', desc: 'Jump to screen (anywhere)' },
53
+ ];
54
+
55
+ /**
56
+ * Check whether a key + input flags match a named action.
57
+ *
58
+ * Usage with ink's useInput:
59
+ * useInput((input, key) => {
60
+ * if (matchKey('up', input, key)) { ... }
61
+ * });
62
+ *
63
+ * @param {string} action - key from KEYS (e.g. 'up', 'quit')
64
+ * @param {string} input - the raw character from useInput
65
+ * @param {Object} key - the key flags from useInput ({ return, escape, tab, upArrow, downArrow, leftArrow, rightArrow })
66
+ * @returns {boolean}
67
+ */
68
+ export function matchKey(action, input, key) {
69
+ const bindings = KEYS[action];
70
+ if (!bindings) return false;
71
+
72
+ for (const binding of bindings) {
73
+ switch (binding) {
74
+ case 'up': if (key.upArrow) return true; break;
75
+ case 'down': if (key.downArrow) return true; break;
76
+ case 'left': if (key.leftArrow) return true; break;
77
+ case 'right': if (key.rightArrow) return true; break;
78
+ case 'return': if (key.return) return true; break;
79
+ case 'escape': if (key.escape) return true; break;
80
+ case 'tab': if (key.tab) return true; break;
81
+ default:
82
+ if (input === binding) return true;
83
+ }
84
+ }
85
+
86
+ return false;
87
+ }
@@ -0,0 +1,49 @@
1
+ /**
2
+ * SCREEN_REGISTRY — single source of truth for navigable TUI screens
3
+ *
4
+ * Consumed by:
5
+ * - components/Palette.jsx — command-palette search + result list
6
+ * - app.jsx — global jump-key (hotkey -> screen id) map
7
+ * - components/Layout.jsx — breadcrumb title lookup
8
+ *
9
+ * Mirrors MainMenu.jsx's MENU_ITEMS (same ids/hotkeys) but lives outside
10
+ * views/ so non-view files (app.jsx, Palette, Layout) can import it
11
+ * without reaching into MainMenu.jsx.
12
+ *
13
+ * Each entry: { id, title, keywords, hotkey }
14
+ * id - matches app.jsx SCREENS / VIEW_COMPONENTS key
15
+ * title - human-readable label (palette list + breadcrumb segment)
16
+ * keywords - extra terms the palette substring-matches against
17
+ * hotkey - single-char global jump key (1-0, then o/i/v/g/h)
18
+ */
19
+
20
+ export const SCREEN_REGISTRY = [
21
+ { id: 'fleet', title: 'Fleet', keywords: ['status', 'agents', 'health', 'hosts'], hotkey: '1' },
22
+ { id: 'agents', title: 'Agents', keywords: ['activate', 'deactivate', 'tools'], hotkey: '2' },
23
+ { id: 'tools', title: 'Tools', keywords: ['catalog', 'install', 'manage'], hotkey: '3' },
24
+ { id: 'orgs', title: 'Organizations', keywords: ['org', 'members', 'divisions', 'switch'], hotkey: '4' },
25
+ { id: 'memory', title: 'Memory', keywords: ['entities', 'conversations', 'search'], hotkey: '5' },
26
+ { id: 'brain', title: 'Brain', keywords: ['quoxbrain', 'brain2', 'notes', 'knowledge', 'vaults', 'second brain', 'search'], hotkey: 'b' },
27
+ { id: 'workflows', title: 'Workflows', keywords: ['crud', 'run', 'steps', 'versions'], hotkey: '6' },
28
+ { id: 'chat', title: 'Chat', keywords: ['commanderq', 'interactive'], hotkey: '7' },
29
+ { id: 'admin', title: 'Admin', keywords: ['config', 'api keys', 'services', 'logs', 'retention'], hotkey: '8' },
30
+ { id: 'files', title: 'Files', keywords: ['upload', 'download', 'list', 'delete'], hotkey: '9' },
31
+ { id: 'backups', title: 'Backups', keywords: ['restore', 'verify', 'schedules'], hotkey: '0' },
32
+ { id: 'objectives', title: 'Objectives', keywords: ['goals', 'plan', 'approve', 'start', 'track'], hotkey: 'o' },
33
+ { id: 'inbox', title: 'Inbox', keywords: ['hitl', 'approvals', 'review', 'approve', 'deny'], hotkey: 'i' },
34
+ { id: 'vault', title: 'Vault', keywords: ['credentials', 'test', 'expiry', 'activity'], hotkey: 'v' },
35
+ { id: 'governance', title: 'Governance', keywords: ['ward', 'volt', 'evidence', 'policies', 'approvals'], hotkey: 'g' },
36
+ { id: 'health', title: 'Health', keywords: ['service health', 'incidents', 'sparklines'], hotkey: 'h' },
37
+ ];
38
+
39
+ /** Screen id -> title, with 'menu' -> 'Main' added for the breadcrumb root. */
40
+ export const SCREEN_TITLES = {
41
+ menu: 'Main',
42
+ ...Object.fromEntries(SCREEN_REGISTRY.map((entry) => [entry.id, entry.title])),
43
+ };
44
+
45
+ /** Screen id -> registry entry. */
46
+ export const SCREEN_BY_ID = Object.fromEntries(SCREEN_REGISTRY.map((entry) => [entry.id, entry]));
47
+
48
+ /** hotkey -> screen id, for the global jump-key handler in app.jsx. */
49
+ export const SCREEN_BY_HOTKEY = Object.fromEntries(SCREEN_REGISTRY.map((entry) => [entry.hotkey, entry.id]));
@@ -0,0 +1,103 @@
1
+ /**
2
+ * QuoxCLI TUI Theme
3
+ *
4
+ * Color palette matching QuoxCORE brand (blue/cyan/violet gradient).
5
+ * Inspired by QuoxBastion's BubbleTea TUI theme.
6
+ *
7
+ * Exports:
8
+ * colors - hex color values
9
+ * styles - chalk formatting functions
10
+ * BORDERS - box-drawing characters for panels
11
+ */
12
+
13
+ import chalk from 'chalk';
14
+
15
+ // ── Brand color palette ───────────────────────────────────────────────
16
+ export const colors = {
17
+ // Brand gradient
18
+ primary: '#3B82F6', // electric blue
19
+ secondary: '#6366F1', // indigo
20
+ accent: '#C084FC', // lilac
21
+
22
+ // Status
23
+ success: '#22C55E',
24
+ error: '#EF4444',
25
+ warning: '#FBBF24',
26
+
27
+ // Highlights
28
+ cyberBlue: '#38BDF8',
29
+ info: '#38BDF8',
30
+
31
+ // Text hierarchy
32
+ text: '#E4E4E7',
33
+ textMuted: '#A1A1AA',
34
+ dim: '#71717A',
35
+ subtle: '#3F3F46',
36
+
37
+ // Backgrounds
38
+ bgPanel: '#111827',
39
+ bgHighlight:'#1E293B',
40
+ };
41
+
42
+ // ── Chalk-based style functions ───────────────────────────────────────
43
+ export const styles = {
44
+ // Headings
45
+ title: (t) => chalk.hex(colors.primary).bold(t),
46
+ subtitle: (t) => chalk.hex(colors.secondary)(t),
47
+ heading: (t) => chalk.hex(colors.accent).bold(t),
48
+
49
+ // Text
50
+ text: (t) => chalk.hex(colors.text)(t),
51
+ muted: (t) => chalk.hex(colors.textMuted)(t),
52
+ dim: (t) => chalk.hex(colors.dim)(t),
53
+ subtle: (t) => chalk.hex(colors.subtle)(t),
54
+
55
+ // Status
56
+ success: (t) => chalk.hex(colors.success)(t),
57
+ error: (t) => chalk.hex(colors.error).bold(t),
58
+ warning: (t) => chalk.hex(colors.warning)(t),
59
+ info: (t) => chalk.hex(colors.cyberBlue)(t),
60
+
61
+ // Brand
62
+ primary: (t) => chalk.hex(colors.primary)(t),
63
+ secondary: (t) => chalk.hex(colors.secondary)(t),
64
+ accent: (t) => chalk.hex(colors.accent)(t),
65
+ cyber: (t) => chalk.hex(colors.cyberBlue).bold(t),
66
+
67
+ // Compound
68
+ label: (t) => chalk.hex(colors.textMuted).bold(t),
69
+ key: (t) => chalk.hex(colors.cyberBlue).bold(t),
70
+ value: (t) => chalk.hex(colors.text)(t),
71
+ badge: (t) => chalk.hex(colors.bgPanel).bgHex(colors.primary).bold(` ${t} `),
72
+ badgeAccent:(t) => chalk.hex(colors.bgPanel).bgHex(colors.accent).bold(` ${t} `),
73
+
74
+ // Inline status indicators
75
+ statusDot: (status) => {
76
+ const map = {
77
+ online: chalk.hex(colors.success)('\u25CF'),
78
+ healthy: chalk.hex(colors.success)('\u25CF'),
79
+ active: chalk.hex(colors.success)('\u25CF'),
80
+ warning: chalk.hex(colors.warning)('\u25CF'),
81
+ degraded:chalk.hex(colors.warning)('\u25CF'),
82
+ error: chalk.hex(colors.error)('\u25CF'),
83
+ offline: chalk.hex(colors.error)('\u25CF'),
84
+ unknown: chalk.hex(colors.dim)('\u25CB'),
85
+ };
86
+ return map[status] || map.unknown;
87
+ },
88
+ };
89
+
90
+ // ── Box-drawing border characters ─────────────────────────────────────
91
+ export const BORDERS = {
92
+ topLeft: '\u256D', // ╭
93
+ topRight: '\u256E', // ╮
94
+ bottomLeft: '\u2570', // ╰
95
+ bottomRight: '\u256F', // ╯
96
+ horizontal: '\u2500', // ─
97
+ vertical: '\u2502', // │
98
+ teeRight: '\u251C', // ├
99
+ teeLeft: '\u2524', // ┤
100
+ teeDown: '\u252C', // ┬
101
+ teeUp: '\u2534', // ┴
102
+ cross: '\u253C', // ┼
103
+ };
@@ -0,0 +1,398 @@
1
+ /**
2
+ * AdminView — Admin panel with tabbed navigation
3
+ *
4
+ * Tabs: Config | Keys | Services | Logs | Retention
5
+ *
6
+ * Each tab has its own DataTable sourced from admin API endpoints.
7
+ * Tab key cycles through tabs.
8
+ *
9
+ * API (all service: auth):
10
+ * GET /api/admin/config -> key-value pairs
11
+ * GET /api/admin/keys -> table (id, name, prefix, role, last_used)
12
+ * GET /api/admin/services -> table (id, name, status, url)
13
+ * GET /api/admin/logs -> table (timestamp, user_email, action, detail)
14
+ * GET /api/admin/retention/config -> key-value pairs
15
+ */
16
+
17
+ import React, { useState, useEffect, useCallback } from 'react';
18
+ import { Box, Text, useInput } from 'ink';
19
+ import { Layout } from '../components/Layout.jsx';
20
+ import { DataTable } from '../components/DataTable.jsx';
21
+ import { DetailPanel } from '../components/DetailPanel.jsx';
22
+ import { useApi } from '../hooks/useApi.js';
23
+ import { useTui } from '../app.jsx';
24
+ import { matchKey } from '../keys.js';
25
+ import { colors, styles } from '../theme.js';
26
+ import { resolveEndpoint } from '../../api/endpoints.js';
27
+
28
+ // ── Tab definitions ───────────────────────────────────────────────────
29
+ const TABS = ['Config', 'Keys', 'Services', 'Logs', 'Retention'];
30
+
31
+ // ── Column definitions per tab ────────────────────────────────────────
32
+ const CONFIG_COLUMNS = [
33
+ { key: 'key', label: 'Key', width: 28 },
34
+ { key: 'value', label: 'Value', width: 36 },
35
+ ];
36
+
37
+ const KEYS_COLUMNS = [
38
+ { key: 'id', label: 'ID', width: 10 },
39
+ { key: 'name', label: 'Name', width: 18 },
40
+ { key: 'prefix', label: 'Prefix', width: 12 },
41
+ { key: 'role', label: 'Role', width: 12 },
42
+ { key: 'last_used', label: 'Last Used', width: 14 },
43
+ ];
44
+
45
+ const SERVICES_COLUMNS = [
46
+ { key: 'id', label: 'ID', width: 10 },
47
+ { key: 'name', label: 'Name', width: 18 },
48
+ { key: 'status', label: 'Status', width: 12 },
49
+ { key: 'url', label: 'URL', width: 28 },
50
+ ];
51
+
52
+ const LOGS_COLUMNS = [
53
+ { key: 'timestamp', label: 'Time', width: 20 },
54
+ { key: 'user_email', label: 'User', width: 20 },
55
+ { key: 'action', label: 'Action', width: 14 },
56
+ { key: 'detail', label: 'Detail', width: 20 },
57
+ ];
58
+
59
+ const RETENTION_COLUMNS = [
60
+ { key: 'key', label: 'Key', width: 28 },
61
+ { key: 'value', label: 'Value', width: 36 },
62
+ ];
63
+
64
+ // ── Row formatters ────────────────────────────────────────────────────
65
+
66
+ function formatConfigRows(data) {
67
+ if (!data || typeof data !== 'object') return [];
68
+ if (Array.isArray(data)) {
69
+ return data.map((item) => ({
70
+ key: item.key || item.name || '',
71
+ value: String(item.value != null ? item.value : ''),
72
+ _raw: item,
73
+ }));
74
+ }
75
+ // Object key-value pairs
76
+ return Object.entries(data).map(([k, v]) => ({
77
+ key: k,
78
+ value: String(v != null ? v : ''),
79
+ _raw: { key: k, value: v },
80
+ }));
81
+ }
82
+
83
+ function formatKeysRows(keys) {
84
+ if (!Array.isArray(keys)) return [];
85
+ return keys.map((k) => ({
86
+ id: k.id || '--',
87
+ name: k.name || k.display_name || '--',
88
+ prefix: k.prefix || '--',
89
+ role: k.role || '--',
90
+ last_used: k.last_used ? formatDate(k.last_used) : '--',
91
+ _raw: k,
92
+ }));
93
+ }
94
+
95
+ function formatServicesRows(services) {
96
+ if (!Array.isArray(services)) return [];
97
+ return services.map((s) => ({
98
+ id: s.id || '--',
99
+ name: s.name || s.display_name || '--',
100
+ status: `${styles.statusDot(s.status || 'unknown')} ${s.status || 'unknown'}`,
101
+ url: s.url || s.endpoint || '--',
102
+ _raw: s,
103
+ }));
104
+ }
105
+
106
+ function formatLogsRows(logs) {
107
+ if (!Array.isArray(logs)) return [];
108
+ return logs.map((l) => ({
109
+ timestamp: l.timestamp ? formatDate(l.timestamp) : '--',
110
+ user_email: l.user_email || l.email || l.user || '--',
111
+ action: l.action || l.type || '--',
112
+ detail: truncate(l.detail || l.message || l.description || '', 18),
113
+ _raw: l,
114
+ }));
115
+ }
116
+
117
+ // ── Detail builders ───────────────────────────────────────────────────
118
+
119
+ function buildConfigDetail(raw) {
120
+ if (!raw) return null;
121
+ return {
122
+ 'Key': raw.key || raw.name,
123
+ 'Value': raw.value,
124
+ };
125
+ }
126
+
127
+ function buildKeyDetail(raw) {
128
+ if (!raw) return null;
129
+ return {
130
+ 'ID': raw.id,
131
+ 'Name': raw.name || raw.display_name,
132
+ 'Prefix': raw.prefix,
133
+ 'Role': raw.role,
134
+ 'Last Used': raw.last_used,
135
+ 'Created': raw.created_at,
136
+ 'Expires': raw.expires_at || '--',
137
+ };
138
+ }
139
+
140
+ function buildServiceDetail(raw) {
141
+ if (!raw) return null;
142
+ return {
143
+ 'ID': raw.id,
144
+ 'Name': raw.name || raw.display_name,
145
+ 'Status': raw.status,
146
+ 'URL': raw.url || raw.endpoint,
147
+ 'Version': raw.version || '--',
148
+ 'Uptime': raw.uptime || '--',
149
+ 'Last Check': raw.last_check || raw.checked_at || '--',
150
+ };
151
+ }
152
+
153
+ function buildLogDetail(raw) {
154
+ if (!raw) return null;
155
+ return {
156
+ 'Timestamp': raw.timestamp,
157
+ 'User': raw.user_email || raw.email || raw.user,
158
+ 'Action': raw.action || raw.type,
159
+ 'Detail': raw.detail || raw.message || raw.description,
160
+ 'IP': raw.ip || raw.ip_address || '--',
161
+ 'ID': raw.id,
162
+ };
163
+ }
164
+
165
+ function buildRetentionDetail(raw) {
166
+ if (!raw) return null;
167
+ return {
168
+ 'Key': raw.key || raw.name,
169
+ 'Value': raw.value,
170
+ };
171
+ }
172
+
173
+ // ── Utility ───────────────────────────────────────────────────────────
174
+
175
+ function truncate(str, max) {
176
+ if (!str) return '';
177
+ const s = String(str).replace(/\n/g, ' ');
178
+ return s.length > max ? s.slice(0, max - 1) + '\u2026' : s;
179
+ }
180
+
181
+ function formatDate(iso) {
182
+ if (!iso) return '--';
183
+ try {
184
+ const d = new Date(iso);
185
+ return d.toISOString().slice(0, 16).replace('T', ' ');
186
+ } catch {
187
+ return String(iso).slice(0, 16);
188
+ }
189
+ }
190
+
191
+ // ── Component ─────────────────────────────────────────────────────────
192
+ export function AdminView() {
193
+ const ctx = useTui();
194
+ const { client, screen, orgVersion } = ctx;
195
+ const isActive = screen === 'admin';
196
+
197
+ const [activeTab, setActiveTab] = useState(0);
198
+ const [selected, setSelected] = useState(0);
199
+
200
+ // ── Fetch config (refetches when the active org changes) ────────────
201
+ const { data: configData, loading: configLoading, error: configError, refresh: refreshConfig } = useApi(
202
+ async () => {
203
+ const res = await client.get(resolveEndpoint('admin.config.list').path, { service: resolveEndpoint('admin.config.list').service });
204
+ return res?.config || res || {};
205
+ },
206
+ [client, orgVersion]
207
+ );
208
+
209
+ // ── Fetch keys ────────────────────────────────────────────────────
210
+ const { data: keysData, loading: keysLoading, error: keysError, refresh: refreshKeys } = useApi(
211
+ async () => {
212
+ const res = await client.get(resolveEndpoint('admin.keys.list').path, { service: resolveEndpoint('admin.keys.list').service });
213
+ return res?.keys || [];
214
+ },
215
+ [client, orgVersion]
216
+ );
217
+
218
+ // ── Fetch services ────────────────────────────────────────────────
219
+ const { data: servicesData, loading: servicesLoading, error: servicesError, refresh: refreshServices } = useApi(
220
+ async () => {
221
+ const res = await client.get(resolveEndpoint('admin.services.list').path, { service: resolveEndpoint('admin.services.list').service });
222
+ return res?.services || [];
223
+ },
224
+ [client, orgVersion]
225
+ );
226
+
227
+ // ── Fetch logs ────────────────────────────────────────────────────
228
+ const { data: logsData, loading: logsLoading, error: logsError, refresh: refreshLogs } = useApi(
229
+ async () => {
230
+ const res = await client.get(resolveEndpoint('admin.logs.list').path, { service: resolveEndpoint('admin.logs.list').service });
231
+ return res?.logs || [];
232
+ },
233
+ [client, orgVersion]
234
+ );
235
+
236
+ // ── Fetch retention ───────────────────────────────────────────────
237
+ const { data: retentionData, loading: retentionLoading, error: retentionError, refresh: refreshRetention } = useApi(
238
+ async () => {
239
+ const res = await client.get(resolveEndpoint('admin.retention.config.list').path, { service: resolveEndpoint('admin.retention.config.list').service });
240
+ return res?.config || res || {};
241
+ },
242
+ [client, orgVersion]
243
+ );
244
+
245
+ // ── Active tab data ─────────────────────────────────────────────────
246
+ let columns, rows, detail, loading, error;
247
+
248
+ switch (activeTab) {
249
+ case 0: // Config
250
+ columns = CONFIG_COLUMNS;
251
+ rows = formatConfigRows(configData);
252
+ detail = rows[selected] ? buildConfigDetail(rows[selected]._raw) : null;
253
+ loading = configLoading;
254
+ error = configError;
255
+ break;
256
+ case 1: // Keys
257
+ columns = KEYS_COLUMNS;
258
+ rows = formatKeysRows(keysData);
259
+ detail = rows[selected] ? buildKeyDetail(rows[selected]._raw) : null;
260
+ loading = keysLoading;
261
+ error = keysError;
262
+ break;
263
+ case 2: // Services
264
+ columns = SERVICES_COLUMNS;
265
+ rows = formatServicesRows(servicesData);
266
+ detail = rows[selected] ? buildServiceDetail(rows[selected]._raw) : null;
267
+ loading = servicesLoading;
268
+ error = servicesError;
269
+ break;
270
+ case 3: // Logs
271
+ columns = LOGS_COLUMNS;
272
+ rows = formatLogsRows(logsData);
273
+ detail = rows[selected] ? buildLogDetail(rows[selected]._raw) : null;
274
+ loading = logsLoading;
275
+ error = logsError;
276
+ break;
277
+ case 4: // Retention
278
+ columns = RETENTION_COLUMNS;
279
+ rows = formatConfigRows(retentionData);
280
+ detail = rows[selected] ? buildRetentionDetail(rows[selected]._raw) : null;
281
+ loading = retentionLoading;
282
+ error = retentionError;
283
+ break;
284
+ }
285
+
286
+ // ── Reset selection on tab change ───────────────────────────────────
287
+ useEffect(() => {
288
+ setSelected(0);
289
+ }, [activeTab]);
290
+
291
+ // ── Refresh current tab ─────────────────────────────────────────────
292
+ const refreshCurrent = useCallback(() => {
293
+ const refreshers = [refreshConfig, refreshKeys, refreshServices, refreshLogs, refreshRetention];
294
+ refreshers[activeTab]?.();
295
+ }, [activeTab, refreshConfig, refreshKeys, refreshServices, refreshLogs, refreshRetention]);
296
+
297
+ // ── Keyboard handling ──────────────────────────────────────────────
298
+ useInput((input, key) => {
299
+ if (!isActive) return;
300
+
301
+ // Tab to switch sub-tabs
302
+ if (matchKey('tab', input, key)) {
303
+ setActiveTab((t) => (t + 1) % TABS.length);
304
+ return;
305
+ }
306
+
307
+ // Manual refresh
308
+ if (matchKey('refresh', input, key)) {
309
+ refreshCurrent();
310
+ return;
311
+ }
312
+ }, { isActive });
313
+
314
+ // ── Tab bar rendering ──────────────────────────────────────────────
315
+ const tabBar = (
316
+ <Box marginBottom={1}>
317
+ {TABS.map((tab, i) => {
318
+ const isSel = i === activeTab;
319
+ return (
320
+ <React.Fragment key={tab}>
321
+ {i > 0 && <Text color={colors.subtle}> | </Text>}
322
+ <Text
323
+ color={isSel ? colors.cyberBlue : colors.dim}
324
+ bold={isSel}
325
+ underline={isSel}
326
+ >
327
+ {tab}
328
+ </Text>
329
+ </React.Fragment>
330
+ );
331
+ })}
332
+ <Text color={colors.dim}> (Tab to switch)</Text>
333
+ </Box>
334
+ );
335
+
336
+ // ── Detail title ───────────────────────────────────────────────────
337
+ let detailTitle;
338
+ if (rows[selected]) {
339
+ switch (activeTab) {
340
+ case 0: detailTitle = rows[selected]._raw.key || rows[selected]._raw.name || 'Config'; break;
341
+ case 1: detailTitle = rows[selected]._raw.name || rows[selected]._raw.display_name || 'Key'; break;
342
+ case 2: detailTitle = rows[selected]._raw.name || rows[selected]._raw.display_name || 'Service'; break;
343
+ case 3: detailTitle = rows[selected]._raw.action || rows[selected]._raw.type || 'Log Entry'; break;
344
+ case 4: detailTitle = rows[selected]._raw.key || rows[selected]._raw.name || 'Retention'; break;
345
+ }
346
+ }
347
+
348
+ // ── Footer hint ────────────────────────────────────────────────────
349
+ const footer = [
350
+ '\u2191/k \u2193/j Navigate',
351
+ 'Tab Switch tab',
352
+ 'r Refresh',
353
+ 'Esc Back',
354
+ ].join(' ');
355
+
356
+ // ── Render ─────────────────────────────────────────────────────────
357
+ return (
358
+ <Layout
359
+ title="Admin"
360
+ subtitle={TABS[activeTab]}
361
+ footer={footer}
362
+ left={
363
+ <Box flexDirection="column">
364
+ {/* Tab bar */}
365
+ {tabBar}
366
+
367
+ {error && (
368
+ <Box marginBottom={1}>
369
+ <Text color={colors.error}>Error: {error.message}</Text>
370
+ </Box>
371
+ )}
372
+
373
+ <DataTable
374
+ columns={columns}
375
+ rows={rows}
376
+ selected={selected}
377
+ onSelectionChange={setSelected}
378
+ loading={loading}
379
+ emptyMessage={[
380
+ 'No config values set',
381
+ 'No API keys — create one via `quox admin key create`',
382
+ 'No services registered',
383
+ 'No audit events yet',
384
+ 'No retention settings configured',
385
+ ][activeTab]}
386
+ active={isActive}
387
+ />
388
+ </Box>
389
+ }
390
+ right={
391
+ <DetailPanel
392
+ title={detailTitle}
393
+ data={detail}
394
+ />
395
+ }
396
+ />
397
+ );
398
+ }