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,123 @@
1
+ /**
2
+ * Main Menu — top-level navigation for the TUI
3
+ *
4
+ * 10 sections covering all CLI capabilities.
5
+ * Supports vim-style navigation (j/k), number keys, and Enter to select.
6
+ */
7
+
8
+ import React, { useState } from 'react';
9
+ import { Box, Text, useInput } from 'ink';
10
+ import { colors } from '../theme.js';
11
+ import { matchKey } from '../keys.js';
12
+ import { useTui } from '../app.jsx';
13
+ import { IntroHeader } from '../intro/Intro.jsx';
14
+
15
+ const MENU_ITEMS = [
16
+ { key: '1', id: 'fleet', label: 'Fleet', desc: 'Fleet status, agent health, tool execution', icon: '\u25C8' },
17
+ { key: '2', id: 'agents', label: 'Agents', desc: 'Agent management, activate/deactivate, tools', icon: '\u2726' },
18
+ { key: '3', id: 'tools', label: 'Tools', desc: 'Tool catalog, install, manage', icon: '\u2692' },
19
+ { key: '4', id: 'orgs', label: 'Organizations', desc: 'Org management, members, divisions, switch', icon: '\u2616' },
20
+ { key: '5', id: 'memory', label: 'Memory', desc: 'Memory, entities, conversations, search', icon: '\u29BE' },
21
+ { key: 'b', id: 'brain', label: 'Brain', desc: 'Quox Qortex: search notes, vaults, growth', icon: '\u2B22' },
22
+ { key: '6', id: 'workflows', label: 'Workflows', desc: 'Workflow CRUD, run, steps, versions', icon: '\u2B82' },
23
+ { key: '7', id: 'chat', label: 'Chat', desc: 'Interactive CommanderQ chat', icon: '\u276F' },
24
+ { key: '8', id: 'admin', label: 'Admin', desc: 'Config, API keys, services, logs, retention', icon: '\u2699' },
25
+ { key: '9', id: 'files', label: 'Files', desc: 'File upload, download, list, delete', icon: '\u2630' },
26
+ { key: '0', id: 'backups', label: 'Backups', desc: 'Backup/restore, verify, schedules', icon: '\u29C9' },
27
+ { key: 'o', id: 'objectives', label: 'Objectives', desc: 'Autonomous goals: plan, approve, start, track', icon: '\u25CE' },
28
+ { key: 'i', id: 'inbox', label: 'Inbox', desc: 'HITL approvals: review, approve, deny', icon: '\u2709' },
29
+ { key: 'v', id: 'vault', label: 'Vault', desc: 'Credentials: list, test, expiry, activity', icon: '\u26BF' },
30
+ { key: 'g', id: 'governance', label: 'Governance', desc: 'WARD chain, VOLT evidence, policies, approvals', icon: '\u2696' },
31
+ { key: 'h', id: 'health', label: 'Health', desc: 'Live service health, incidents, sparklines', icon: '\u271a' },
32
+ ];
33
+
34
+ export function MainMenu() {
35
+ const { navigate, screen } = useTui();
36
+ const [selected, setSelected] = useState(0);
37
+
38
+ // MainMenu is kept alive (mounted permanently) once the app starts, so
39
+ // it must gate its own input on isActive — otherwise its number-key /
40
+ // vim-nav / Enter handling would also fire while a sub-view is active
41
+ // and hidden (e.g. pressing Enter to send a chat message would also
42
+ // "select" whatever menu item was last highlighted).
43
+ const isActive = (screen ?? 'menu') === 'menu';
44
+
45
+ useInput((input, key) => {
46
+ // Number keys for quick navigation
47
+ const byKey = MENU_ITEMS.find((item) => item.key === input);
48
+ if (byKey) {
49
+ navigate(byKey.id);
50
+ return;
51
+ }
52
+
53
+ // Vim / arrow navigation
54
+ if (matchKey('up', input, key)) {
55
+ setSelected((s) => (s > 0 ? s - 1 : MENU_ITEMS.length - 1));
56
+ return;
57
+ }
58
+ if (matchKey('down', input, key)) {
59
+ setSelected((s) => (s < MENU_ITEMS.length - 1 ? s + 1 : 0));
60
+ return;
61
+ }
62
+
63
+ // Enter to select
64
+ if (matchKey('select', input, key)) {
65
+ navigate(MENU_ITEMS[selected].id);
66
+ return;
67
+ }
68
+ }, { isActive });
69
+
70
+ return (
71
+ <Box flexDirection="column" paddingX={2} paddingY={0}>
72
+ {/* Animated intro header: orb + block title + subtitle */}
73
+ <IntroHeader />
74
+
75
+ {/* Menu items */}
76
+ <Box flexDirection="column" marginTop={1}>
77
+ {MENU_ITEMS.map((item, i) => {
78
+ const isSelected = i === selected;
79
+ return (
80
+ <Box key={item.id} height={1}>
81
+ {/* Selection indicator */}
82
+ <Text color={isSelected ? colors.primary : colors.subtle}>
83
+ {isSelected ? ' \u25B6 ' : ' '}
84
+ </Text>
85
+
86
+ {/* Number key */}
87
+ <Text color={colors.cyberBlue} bold>
88
+ [{item.key}]
89
+ </Text>
90
+
91
+ {/* Icon */}
92
+ <Text color={isSelected ? colors.accent : colors.dim}>
93
+ {' '}{item.icon}{' '}
94
+ </Text>
95
+
96
+ {/* Label */}
97
+ <Text color={isSelected ? colors.text : colors.textMuted} bold={isSelected}>
98
+ {item.label.padEnd(16)}
99
+ </Text>
100
+
101
+ {/* Description */}
102
+ <Text color={colors.dim}>
103
+ {item.desc}
104
+ </Text>
105
+ </Box>
106
+ );
107
+ })}
108
+ </Box>
109
+
110
+ {/* Footer hints */}
111
+ <Box marginTop={1}>
112
+ <Text color={colors.dim}>
113
+ {' \u2191/k \u2193/j'} Navigate {' '}
114
+ {'Enter'} Select {' '}
115
+ {'1-0 / o i v g'} Quick access {' '}
116
+ {'q'} Quit
117
+ </Text>
118
+ </Box>
119
+ </Box>
120
+ );
121
+ }
122
+
123
+ export { MENU_ITEMS };
@@ -0,0 +1,377 @@
1
+ /**
2
+ * MemoryView — Memory browser with 3 sub-tabs
3
+ *
4
+ * Tabs: Memories | Entities | Conversations
5
+ *
6
+ * Left panel: DataTable of the active tab's data
7
+ * Right panel: DetailPanel for the selected item
8
+ *
9
+ * Features:
10
+ * - Tab key switches between Memories / Entities / Conversations
11
+ * - `/` key focuses the search input
12
+ * - Search queries the memory search API
13
+ * - Memory detail shows type, content, tags
14
+ *
15
+ * API:
16
+ * GET /api/memory (service: memory) -> data.memories
17
+ * GET /api/memory/search (service: memory, query: { query }) -> results
18
+ * GET /api/entity (service: memory) -> data.entities
19
+ * GET /api/conversations (service: memory) -> data.conversations
20
+ */
21
+
22
+ import React, { useState, useCallback, useEffect } from 'react';
23
+ import { Box, Text, useInput } from 'ink';
24
+ import { Layout } from '../components/Layout.jsx';
25
+ import { DataTable } from '../components/DataTable.jsx';
26
+ import { DetailPanel } from '../components/DetailPanel.jsx';
27
+ import { SearchInput } from '../components/SearchInput.jsx';
28
+ import { useApi } from '../hooks/useApi.js';
29
+ import { useTui } from '../app.jsx';
30
+ import { matchKey } from '../keys.js';
31
+ import { colors } from '../theme.js';
32
+ import { resolveEndpoint } from '../../api/endpoints.js';
33
+
34
+ // ── Tab definitions ───────────────────────────────────────────────────
35
+ const TABS = ['Memories', 'Entities', 'Conversations'];
36
+
37
+ // ── Column definitions per tab ────────────────────────────────────────
38
+ const MEMORY_COLUMNS = [
39
+ { key: 'type', label: 'Type', width: 14 },
40
+ { key: 'content', label: 'Content', width: 36 },
41
+ { key: 'tags', label: 'Tags', width: 16 },
42
+ ];
43
+
44
+ const ENTITY_COLUMNS = [
45
+ { key: 'name', label: 'Name', width: 20 },
46
+ { key: 'type', label: 'Type', width: 14 },
47
+ { key: 'count', label: 'Refs', width: 8 },
48
+ ];
49
+
50
+ const CONVERSATION_COLUMNS = [
51
+ { key: 'title', label: 'Title', width: 28 },
52
+ { key: 'date', label: 'Date', width: 14 },
53
+ { key: 'turns', label: 'Turns', width: 8 },
54
+ ];
55
+
56
+ // ── Row formatters ────────────────────────────────────────────────────
57
+
58
+ function formatMemoryRows(memories) {
59
+ if (!Array.isArray(memories)) return [];
60
+ return memories.map((m) => ({
61
+ type: m.type || '--',
62
+ content: truncate(m.content || m.text || m.value || '', 34),
63
+ tags: Array.isArray(m.tags) ? m.tags.join(', ') : '--',
64
+ _raw: m,
65
+ }));
66
+ }
67
+
68
+ function formatEntityRows(entities) {
69
+ if (!Array.isArray(entities)) return [];
70
+ return entities.map((e) => ({
71
+ name: e.name || e.display_name || e.id,
72
+ type: e.type || e.entity_type || '--',
73
+ count: e.reference_count != null ? String(e.reference_count) : (e.count != null ? String(e.count) : '--'),
74
+ _raw: e,
75
+ }));
76
+ }
77
+
78
+ function formatConversationRows(conversations) {
79
+ if (!Array.isArray(conversations)) return [];
80
+ return conversations.map((c) => ({
81
+ title: truncate(c.title || c.summary || c.id || 'Untitled', 26),
82
+ date: c.created_at ? formatDate(c.created_at) : '--',
83
+ turns: c.turn_count != null ? String(c.turn_count) : (c.message_count != null ? String(c.message_count) : '--'),
84
+ _raw: c,
85
+ }));
86
+ }
87
+
88
+ // ── Detail builders ───────────────────────────────────────────────────
89
+
90
+ function buildMemoryDetail(raw) {
91
+ if (!raw) return null;
92
+ return {
93
+ 'Type': raw.type,
94
+ 'Content': raw.content || raw.text || raw.value,
95
+ 'Tags': Array.isArray(raw.tags) ? raw.tags : undefined,
96
+ 'Source': raw.source,
97
+ 'Confidence': raw.confidence,
98
+ 'Created': raw.created_at,
99
+ 'Updated': raw.updated_at,
100
+ 'ID': raw.id,
101
+ };
102
+ }
103
+
104
+ function buildEntityDetail(raw) {
105
+ if (!raw) return null;
106
+ return {
107
+ 'Name': raw.name || raw.display_name,
108
+ 'Type': raw.type || raw.entity_type,
109
+ 'ID': raw.id,
110
+ 'References': raw.reference_count || raw.count,
111
+ 'Properties': raw.properties || raw.metadata,
112
+ 'First Seen': raw.first_seen || raw.created_at,
113
+ 'Last Seen': raw.last_seen || raw.updated_at,
114
+ 'Related Entities':raw.related || raw.relationships,
115
+ };
116
+ }
117
+
118
+ function buildConversationDetail(raw) {
119
+ if (!raw) return null;
120
+ return {
121
+ 'Title': raw.title || raw.summary,
122
+ 'ID': raw.id,
123
+ 'Turns': raw.turn_count || raw.message_count,
124
+ 'Started': raw.created_at || raw.started_at,
125
+ 'Last Active':raw.updated_at || raw.last_message_at,
126
+ 'Participants':raw.participants,
127
+ 'Tags': raw.tags,
128
+ 'Summary': raw.summary || raw.description,
129
+ };
130
+ }
131
+
132
+ // ── Utility ───────────────────────────────────────────────────────────
133
+
134
+ function truncate(str, max) {
135
+ if (!str) return '';
136
+ const s = String(str).replace(/\n/g, ' ');
137
+ return s.length > max ? s.slice(0, max - 1) + '\u2026' : s;
138
+ }
139
+
140
+ function formatDate(iso) {
141
+ if (!iso) return '--';
142
+ try {
143
+ const d = new Date(iso);
144
+ return d.toISOString().slice(0, 10);
145
+ } catch {
146
+ return String(iso).slice(0, 10);
147
+ }
148
+ }
149
+
150
+ // ── Component ─────────────────────────────────────────────────────────
151
+ export function MemoryView() {
152
+ const ctx = useTui();
153
+ const { client, screen, setInputMode, orgVersion } = ctx;
154
+ const isActive = screen === 'memory';
155
+
156
+ const [activeTab, setActiveTab] = useState(0);
157
+ const [selected, setSelected] = useState(0);
158
+ const [search, setSearch] = useState('');
159
+ const [searchActive, setSearchActive] = useState(false);
160
+
161
+ // ── Input-mode registry: claim 'input' while the search box is
162
+ // focused so global shortcuts (e.g. '?') don't steal keystrokes.
163
+ useEffect(() => {
164
+ if (searchActive) setInputMode?.('input');
165
+ else if (isActive) setInputMode?.('nav');
166
+ }, [searchActive, isActive, setInputMode]);
167
+
168
+ // ── Fetch memories (refetches when the active org changes) ──────────
169
+ const { data: memoriesData, loading: memoriesLoading, error: memoriesError, refresh: refreshMemories } = useApi(
170
+ async () => {
171
+ if (search) {
172
+ const res = await client.get(resolveEndpoint('memory.search.list').path, {
173
+ service: resolveEndpoint('memory.search.list').service,
174
+ query: { query: search },
175
+ });
176
+ return res?.results || res?.memories || [];
177
+ }
178
+ const res = await client.get(resolveEndpoint('memory.list').path, { service: resolveEndpoint('memory.list').service });
179
+ return res?.memories || [];
180
+ },
181
+ [client, search, orgVersion]
182
+ );
183
+
184
+ // ── Fetch entities (refetches when the active org changes) ──────────
185
+ const { data: entitiesData, loading: entitiesLoading, error: entitiesError, refresh: refreshEntities } = useApi(
186
+ async () => {
187
+ const res = await client.get(resolveEndpoint('entity.list').path, { service: resolveEndpoint('entity.list').service });
188
+ return res?.entities || [];
189
+ },
190
+ [client, orgVersion]
191
+ );
192
+
193
+ // ── Fetch conversations (refetches when the active org changes) ─────
194
+ const { data: convoData, loading: convoLoading, error: convoError, refresh: refreshConvos } = useApi(
195
+ async () => {
196
+ const res = await client.get(resolveEndpoint('conversations.list').path, { service: resolveEndpoint('conversations.list').service });
197
+ return res?.conversations || [];
198
+ },
199
+ [client, orgVersion]
200
+ );
201
+
202
+ // ── Active tab data ─────────────────────────────────────────────────
203
+ let columns, rows, detail, loading, error;
204
+
205
+ switch (activeTab) {
206
+ case 0: // Memories
207
+ columns = MEMORY_COLUMNS;
208
+ rows = formatMemoryRows(memoriesData);
209
+ detail = rows[selected] ? buildMemoryDetail(rows[selected]._raw) : null;
210
+ loading = memoriesLoading;
211
+ error = memoriesError;
212
+ break;
213
+ case 1: // Entities
214
+ columns = ENTITY_COLUMNS;
215
+ rows = formatEntityRows(entitiesData);
216
+ detail = rows[selected] ? buildEntityDetail(rows[selected]._raw) : null;
217
+ loading = entitiesLoading;
218
+ error = entitiesError;
219
+ break;
220
+ case 2: // Conversations
221
+ columns = CONVERSATION_COLUMNS;
222
+ rows = formatConversationRows(convoData);
223
+ detail = rows[selected] ? buildConversationDetail(rows[selected]._raw) : null;
224
+ loading = convoLoading;
225
+ error = convoError;
226
+ break;
227
+ }
228
+
229
+ // ── Reset selection on tab change ───────────────────────────────────
230
+ useEffect(() => {
231
+ setSelected(0);
232
+ }, [activeTab]);
233
+
234
+ // ── Refresh all ─────────────────────────────────────────────────────
235
+ const refreshAll = useCallback(() => {
236
+ refreshMemories();
237
+ refreshEntities();
238
+ refreshConvos();
239
+ }, [refreshMemories, refreshEntities, refreshConvos]);
240
+
241
+ // ── Search submit ───────────────────────────────────────────────────
242
+ const handleSearchSubmit = useCallback(() => {
243
+ setSearchActive(false);
244
+ setActiveTab(0); // Search always applies to memories tab
245
+ setSelected(0);
246
+ refreshMemories();
247
+ }, [refreshMemories]);
248
+
249
+ // ── Keyboard handling ──────────────────────────────────────────────
250
+ useInput((input, key) => {
251
+ if (!isActive) return;
252
+
253
+ // Focus search
254
+ if (matchKey('search', input, key) && !searchActive) {
255
+ setSearchActive(true);
256
+ return;
257
+ }
258
+
259
+ // Escape from search
260
+ if (searchActive && matchKey('back', input, key)) {
261
+ setSearchActive(false);
262
+ return;
263
+ }
264
+
265
+ // Let SearchInput handle keys when focused
266
+ if (searchActive) return;
267
+
268
+ // Tab to switch sub-tabs
269
+ if (matchKey('tab', input, key)) {
270
+ setActiveTab((t) => (t + 1) % TABS.length);
271
+ return;
272
+ }
273
+
274
+ // Manual refresh
275
+ if (matchKey('refresh', input, key)) {
276
+ refreshAll();
277
+ return;
278
+ }
279
+ }, { isActive });
280
+
281
+ // ── Tab bar rendering ──────────────────────────────────────────────
282
+ const tabBar = (
283
+ <Box marginBottom={1}>
284
+ {TABS.map((tab, i) => {
285
+ const isSel = i === activeTab;
286
+ return (
287
+ <React.Fragment key={tab}>
288
+ {i > 0 && <Text color={colors.subtle}> | </Text>}
289
+ <Text
290
+ color={isSel ? colors.cyberBlue : colors.dim}
291
+ bold={isSel}
292
+ underline={isSel}
293
+ >
294
+ {tab}
295
+ </Text>
296
+ </React.Fragment>
297
+ );
298
+ })}
299
+ <Text color={colors.dim}> (Tab to switch)</Text>
300
+ </Box>
301
+ );
302
+
303
+ // ── Footer hint ────────────────────────────────────────────────────
304
+ const footer = searchActive
305
+ ? 'Type to search Enter Submit Esc Cancel'
306
+ : [
307
+ '\u2191/k \u2193/j Navigate',
308
+ 'Tab Switch tab',
309
+ '/ Search',
310
+ 'r Refresh',
311
+ 'Esc Back',
312
+ ].join(' ');
313
+
314
+ // ── Detail title ───────────────────────────────────────────────────
315
+ let detailTitle;
316
+ if (rows[selected]) {
317
+ switch (activeTab) {
318
+ case 0: detailTitle = `Memory: ${rows[selected]._raw.type || 'item'}`; break;
319
+ case 1: detailTitle = rows[selected]._raw.name || 'Entity'; break;
320
+ case 2: detailTitle = rows[selected]._raw.title || 'Conversation'; break;
321
+ }
322
+ }
323
+
324
+ // ── Render ─────────────────────────────────────────────────────────
325
+ return (
326
+ <Layout
327
+ title="Memory"
328
+ subtitle={TABS[activeTab]}
329
+ footer={footer}
330
+ left={
331
+ <Box flexDirection="column">
332
+ {/* Tab bar */}
333
+ {tabBar}
334
+
335
+ {/* Search (visible on Memories tab) */}
336
+ {activeTab === 0 && (
337
+ <Box marginBottom={1}>
338
+ <SearchInput
339
+ placeholder="Search memories..."
340
+ value={search}
341
+ onChange={setSearch}
342
+ onSubmit={handleSearchSubmit}
343
+ active={searchActive}
344
+ />
345
+ </Box>
346
+ )}
347
+
348
+ {error && (
349
+ <Box marginBottom={1}>
350
+ <Text color={colors.error}>Error: {error.message}</Text>
351
+ </Box>
352
+ )}
353
+
354
+ <DataTable
355
+ columns={columns}
356
+ rows={rows}
357
+ selected={selected}
358
+ onSelectionChange={setSelected}
359
+ loading={loading}
360
+ emptyMessage={
361
+ activeTab === 0 && search
362
+ ? 'No memories match your search'
363
+ : ['No memories yet', 'No entities yet', 'No conversations yet'][activeTab]
364
+ }
365
+ active={isActive && !searchActive}
366
+ />
367
+ </Box>
368
+ }
369
+ right={
370
+ <DetailPanel
371
+ title={detailTitle}
372
+ data={detail}
373
+ />
374
+ }
375
+ />
376
+ );
377
+ }