@gokulvenkatareddy/cortex 0.1.7

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 (299) hide show
  1. package/README.md +1295 -0
  2. package/apps/octogent/.github/workflows/ci.yml +40 -0
  3. package/apps/octogent/.shims/claude +4 -0
  4. package/apps/octogent/AGENTS.md +71 -0
  5. package/apps/octogent/CONTRIBUTING.md +72 -0
  6. package/apps/octogent/LICENSE +21 -0
  7. package/apps/octogent/README.md +184 -0
  8. package/apps/octogent/apps/api/AGENTS.md +32 -0
  9. package/apps/octogent/apps/api/package.json +19 -0
  10. package/apps/octogent/apps/api/src/agentStateDetection.ts +181 -0
  11. package/apps/octogent/apps/api/src/claudeSessionScanner.ts +235 -0
  12. package/apps/octogent/apps/api/src/claudeSkills.ts +182 -0
  13. package/apps/octogent/apps/api/src/claudeUsage.ts +922 -0
  14. package/apps/octogent/apps/api/src/cli.ts +595 -0
  15. package/apps/octogent/apps/api/src/codeIntelStore.ts +46 -0
  16. package/apps/octogent/apps/api/src/codexUsage.ts +278 -0
  17. package/apps/octogent/apps/api/src/createApiServer/codeIntelRoutes.ts +60 -0
  18. package/apps/octogent/apps/api/src/createApiServer/conversationRoutes.ts +128 -0
  19. package/apps/octogent/apps/api/src/createApiServer/deckRoutes.ts +873 -0
  20. package/apps/octogent/apps/api/src/createApiServer/gitParsers.ts +140 -0
  21. package/apps/octogent/apps/api/src/createApiServer/gitRoutes.ts +214 -0
  22. package/apps/octogent/apps/api/src/createApiServer/miscRoutes.ts +316 -0
  23. package/apps/octogent/apps/api/src/createApiServer/monitorParsers.ts +137 -0
  24. package/apps/octogent/apps/api/src/createApiServer/monitorRoutes.ts +95 -0
  25. package/apps/octogent/apps/api/src/createApiServer/requestHandler.ts +311 -0
  26. package/apps/octogent/apps/api/src/createApiServer/requestParsers.ts +25 -0
  27. package/apps/octogent/apps/api/src/createApiServer/routeHelpers.ts +97 -0
  28. package/apps/octogent/apps/api/src/createApiServer/security.ts +70 -0
  29. package/apps/octogent/apps/api/src/createApiServer/terminalParsers.ts +167 -0
  30. package/apps/octogent/apps/api/src/createApiServer/terminalRoutes.ts +315 -0
  31. package/apps/octogent/apps/api/src/createApiServer/types.ts +24 -0
  32. package/apps/octogent/apps/api/src/createApiServer/uiStateParsers.ts +255 -0
  33. package/apps/octogent/apps/api/src/createApiServer/upgradeHandler.ts +38 -0
  34. package/apps/octogent/apps/api/src/createApiServer/usageRoutes.ts +84 -0
  35. package/apps/octogent/apps/api/src/createApiServer.ts +176 -0
  36. package/apps/octogent/apps/api/src/deck/readDeckTentacles.ts +595 -0
  37. package/apps/octogent/apps/api/src/githubRepoSummary.ts +397 -0
  38. package/apps/octogent/apps/api/src/logging.ts +9 -0
  39. package/apps/octogent/apps/api/src/monitor/defaults.ts +3 -0
  40. package/apps/octogent/apps/api/src/monitor/index.ts +8 -0
  41. package/apps/octogent/apps/api/src/monitor/repository.ts +303 -0
  42. package/apps/octogent/apps/api/src/monitor/service.ts +349 -0
  43. package/apps/octogent/apps/api/src/monitor/types.ts +120 -0
  44. package/apps/octogent/apps/api/src/monitor/xProvider.ts +587 -0
  45. package/apps/octogent/apps/api/src/projectPersistence.ts +377 -0
  46. package/apps/octogent/apps/api/src/prompts/index.ts +10 -0
  47. package/apps/octogent/apps/api/src/prompts/promptResolver.ts +145 -0
  48. package/apps/octogent/apps/api/src/runtimeMetadata.ts +69 -0
  49. package/apps/octogent/apps/api/src/server.ts +80 -0
  50. package/apps/octogent/apps/api/src/setupState.ts +80 -0
  51. package/apps/octogent/apps/api/src/setupStatus.ts +174 -0
  52. package/apps/octogent/apps/api/src/startupPrerequisites.ts +146 -0
  53. package/apps/octogent/apps/api/src/terminalRuntime/channelMessaging.ts +87 -0
  54. package/apps/octogent/apps/api/src/terminalRuntime/claudeTranscript.ts +279 -0
  55. package/apps/octogent/apps/api/src/terminalRuntime/constants.ts +15 -0
  56. package/apps/octogent/apps/api/src/terminalRuntime/conversations.ts +492 -0
  57. package/apps/octogent/apps/api/src/terminalRuntime/gitOperations.ts +341 -0
  58. package/apps/octogent/apps/api/src/terminalRuntime/hookProcessor.ts +405 -0
  59. package/apps/octogent/apps/api/src/terminalRuntime/protocol.ts +46 -0
  60. package/apps/octogent/apps/api/src/terminalRuntime/ptyEnvironment.ts +50 -0
  61. package/apps/octogent/apps/api/src/terminalRuntime/registry.ts +423 -0
  62. package/apps/octogent/apps/api/src/terminalRuntime/sessionRuntime.ts +671 -0
  63. package/apps/octogent/apps/api/src/terminalRuntime/systemClients.ts +432 -0
  64. package/apps/octogent/apps/api/src/terminalRuntime/types.ts +157 -0
  65. package/apps/octogent/apps/api/src/terminalRuntime/worktreeManager.ts +135 -0
  66. package/apps/octogent/apps/api/src/terminalRuntime.ts +567 -0
  67. package/apps/octogent/apps/api/src/usageUtils.ts +16 -0
  68. package/apps/octogent/apps/api/src/ws-shim.d.ts +28 -0
  69. package/apps/octogent/apps/api/tests/agentStateDetection.test.ts +67 -0
  70. package/apps/octogent/apps/api/tests/claudeUsage.test.ts +583 -0
  71. package/apps/octogent/apps/api/tests/codexUsage.test.ts +107 -0
  72. package/apps/octogent/apps/api/tests/createApiServer.test.ts +3207 -0
  73. package/apps/octogent/apps/api/tests/githubRepoSummary.test.ts +100 -0
  74. package/apps/octogent/apps/api/tests/logging.test.ts +33 -0
  75. package/apps/octogent/apps/api/tests/monitorApi.test.ts +467 -0
  76. package/apps/octogent/apps/api/tests/monitorCore.test.ts +104 -0
  77. package/apps/octogent/apps/api/tests/promptResolver.test.ts +109 -0
  78. package/apps/octogent/apps/api/tests/protocol.test.ts +14 -0
  79. package/apps/octogent/apps/api/tests/sessionRuntime.test.ts +608 -0
  80. package/apps/octogent/apps/api/tests/startupPrerequisites.test.ts +70 -0
  81. package/apps/octogent/apps/api/tests/upgradeHandler.test.ts +40 -0
  82. package/apps/octogent/apps/api/tests/xMonitorProvider.test.ts +109 -0
  83. package/apps/octogent/apps/api/tsconfig.json +7 -0
  84. package/apps/octogent/apps/api/vitest.config.ts +7 -0
  85. package/apps/octogent/apps/web/AGENTS.md +38 -0
  86. package/apps/octogent/apps/web/index.html +13 -0
  87. package/apps/octogent/apps/web/package.json +32 -0
  88. package/apps/octogent/apps/web/public/octopus-favicon.svg +26 -0
  89. package/apps/octogent/apps/web/src/App.tsx +646 -0
  90. package/apps/octogent/apps/web/src/app/canvas/types.ts +34 -0
  91. package/apps/octogent/apps/web/src/app/codeIntelAggregation.ts +278 -0
  92. package/apps/octogent/apps/web/src/app/constants.ts +28 -0
  93. package/apps/octogent/apps/web/src/app/conversationNormalizers.ts +135 -0
  94. package/apps/octogent/apps/web/src/app/formatTimestamp.ts +18 -0
  95. package/apps/octogent/apps/web/src/app/githubMetrics.ts +76 -0
  96. package/apps/octogent/apps/web/src/app/githubNormalizers.ts +91 -0
  97. package/apps/octogent/apps/web/src/app/hooks/useAgentRuntimeStates.ts +18 -0
  98. package/apps/octogent/apps/web/src/app/hooks/useBackendLivenessPolling.ts +53 -0
  99. package/apps/octogent/apps/web/src/app/hooks/useCanvasGraphData.ts +449 -0
  100. package/apps/octogent/apps/web/src/app/hooks/useCanvasTransform.ts +260 -0
  101. package/apps/octogent/apps/web/src/app/hooks/useClaudeUsagePolling.ts +40 -0
  102. package/apps/octogent/apps/web/src/app/hooks/useClickOutside.ts +30 -0
  103. package/apps/octogent/apps/web/src/app/hooks/useCodeIntelRuntime.ts +83 -0
  104. package/apps/octogent/apps/web/src/app/hooks/useCodexUsagePolling.ts +35 -0
  105. package/apps/octogent/apps/web/src/app/hooks/useConsoleKeyboardShortcuts.ts +31 -0
  106. package/apps/octogent/apps/web/src/app/hooks/useConversationsRuntime.ts +377 -0
  107. package/apps/octogent/apps/web/src/app/hooks/useForceSimulation.ts +319 -0
  108. package/apps/octogent/apps/web/src/app/hooks/useGitHubPrimaryViewModel.ts +143 -0
  109. package/apps/octogent/apps/web/src/app/hooks/useGithubSummaryPolling.ts +28 -0
  110. package/apps/octogent/apps/web/src/app/hooks/useInitialColumnsHydration.ts +64 -0
  111. package/apps/octogent/apps/web/src/app/hooks/useMonitorRuntime.ts +220 -0
  112. package/apps/octogent/apps/web/src/app/hooks/usePersistedUiState.ts +536 -0
  113. package/apps/octogent/apps/web/src/app/hooks/usePollingData.ts +79 -0
  114. package/apps/octogent/apps/web/src/app/hooks/usePromptLibrary.ts +185 -0
  115. package/apps/octogent/apps/web/src/app/hooks/useTentacleGitLifecycle.ts +530 -0
  116. package/apps/octogent/apps/web/src/app/hooks/useTerminalCompletionNotification.ts +94 -0
  117. package/apps/octogent/apps/web/src/app/hooks/useTerminalMutations.ts +266 -0
  118. package/apps/octogent/apps/web/src/app/hooks/useTerminalStateReconciliation.ts +23 -0
  119. package/apps/octogent/apps/web/src/app/hooks/useUsageHeatmapPolling.ts +43 -0
  120. package/apps/octogent/apps/web/src/app/hooks/useWorkspaceSetup.ts +80 -0
  121. package/apps/octogent/apps/web/src/app/hotkeys.ts +31 -0
  122. package/apps/octogent/apps/web/src/app/monitorNormalizers.ts +145 -0
  123. package/apps/octogent/apps/web/src/app/notificationSounds.ts +164 -0
  124. package/apps/octogent/apps/web/src/app/terminalRuntimeStateStore.ts +261 -0
  125. package/apps/octogent/apps/web/src/app/terminalState.ts +21 -0
  126. package/apps/octogent/apps/web/src/app/types.ts +42 -0
  127. package/apps/octogent/apps/web/src/app/uiStateNormalizers.ts +113 -0
  128. package/apps/octogent/apps/web/src/app/usageNormalizers.ts +58 -0
  129. package/apps/octogent/apps/web/src/components/ActiveAgentsSidebar.tsx +60 -0
  130. package/apps/octogent/apps/web/src/components/ActivityPrimaryView.tsx +21 -0
  131. package/apps/octogent/apps/web/src/components/AgentStateBadge.tsx +47 -0
  132. package/apps/octogent/apps/web/src/components/CanvasPrimaryView.tsx +1532 -0
  133. package/apps/octogent/apps/web/src/components/ClearAllConversationsDialog.tsx +33 -0
  134. package/apps/octogent/apps/web/src/components/CodeIntelArcDiagram.tsx +245 -0
  135. package/apps/octogent/apps/web/src/components/CodeIntelPrimaryView.tsx +104 -0
  136. package/apps/octogent/apps/web/src/components/CodeIntelTreemap.tsx +138 -0
  137. package/apps/octogent/apps/web/src/components/ConsolePrimaryNav.tsx +31 -0
  138. package/apps/octogent/apps/web/src/components/ConversationsPrimaryView.tsx +243 -0
  139. package/apps/octogent/apps/web/src/components/DeckPrimaryView.tsx +613 -0
  140. package/apps/octogent/apps/web/src/components/DeleteTentacleDialog.tsx +91 -0
  141. package/apps/octogent/apps/web/src/components/EmptyOctopus.tsx +715 -0
  142. package/apps/octogent/apps/web/src/components/GitHubPrimaryView.tsx +494 -0
  143. package/apps/octogent/apps/web/src/components/MonitorPrimaryView.tsx +475 -0
  144. package/apps/octogent/apps/web/src/components/PrimaryViewRouter.tsx +99 -0
  145. package/apps/octogent/apps/web/src/components/PromptsPrimaryView.tsx +243 -0
  146. package/apps/octogent/apps/web/src/components/RuntimeStatusStrip.tsx +273 -0
  147. package/apps/octogent/apps/web/src/components/SettingsPrimaryView.tsx +92 -0
  148. package/apps/octogent/apps/web/src/components/SidebarActionPanel.tsx +124 -0
  149. package/apps/octogent/apps/web/src/components/SidebarConversationsList.tsx +279 -0
  150. package/apps/octogent/apps/web/src/components/SidebarPromptsList.tsx +116 -0
  151. package/apps/octogent/apps/web/src/components/TelemetryTape.tsx +106 -0
  152. package/apps/octogent/apps/web/src/components/TentacleGitActionsDialog.tsx +341 -0
  153. package/apps/octogent/apps/web/src/components/Terminal.tsx +524 -0
  154. package/apps/octogent/apps/web/src/components/TerminalPromptPicker.tsx +140 -0
  155. package/apps/octogent/apps/web/src/components/UsageHeatmap.tsx +702 -0
  156. package/apps/octogent/apps/web/src/components/canvas/CanvasTentaclePanel.tsx +485 -0
  157. package/apps/octogent/apps/web/src/components/canvas/CanvasTerminalColumn.tsx +89 -0
  158. package/apps/octogent/apps/web/src/components/canvas/DeleteAllTerminalsDialog.tsx +221 -0
  159. package/apps/octogent/apps/web/src/components/canvas/OctopusNode.tsx +307 -0
  160. package/apps/octogent/apps/web/src/components/canvas/SessionNode.tsx +185 -0
  161. package/apps/octogent/apps/web/src/components/deck/ActionCards.tsx +118 -0
  162. package/apps/octogent/apps/web/src/components/deck/AddTentacleForm.tsx +269 -0
  163. package/apps/octogent/apps/web/src/components/deck/DeckBottomActions.tsx +56 -0
  164. package/apps/octogent/apps/web/src/components/deck/TentaclePod.tsx +334 -0
  165. package/apps/octogent/apps/web/src/components/deck/WorkspaceSetupCard.tsx +105 -0
  166. package/apps/octogent/apps/web/src/components/deck/octopusVisuals.ts +72 -0
  167. package/apps/octogent/apps/web/src/components/terminalReplay.ts +62 -0
  168. package/apps/octogent/apps/web/src/components/terminalWheel.ts +54 -0
  169. package/apps/octogent/apps/web/src/components/ui/ActionButton.tsx +34 -0
  170. package/apps/octogent/apps/web/src/components/ui/ConfirmationDialog.tsx +86 -0
  171. package/apps/octogent/apps/web/src/components/ui/MarkdownContent.tsx +43 -0
  172. package/apps/octogent/apps/web/src/components/ui/SettingsToggle.tsx +34 -0
  173. package/apps/octogent/apps/web/src/components/ui/StatusBadge.tsx +24 -0
  174. package/apps/octogent/apps/web/src/main.tsx +17 -0
  175. package/apps/octogent/apps/web/src/runtime/HttpTerminalSnapshotReader.ts +87 -0
  176. package/apps/octogent/apps/web/src/runtime/runtimeEndpoints.ts +412 -0
  177. package/apps/octogent/apps/web/src/styles/chrome-and-buttons.css +272 -0
  178. package/apps/octogent/apps/web/src/styles/console-canvas-activity.css +358 -0
  179. package/apps/octogent/apps/web/src/styles/console-canvas-canvas.css +1843 -0
  180. package/apps/octogent/apps/web/src/styles/console-canvas-code-intel.css +227 -0
  181. package/apps/octogent/apps/web/src/styles/console-canvas-conversations.css +705 -0
  182. package/apps/octogent/apps/web/src/styles/console-canvas-deck.css +1524 -0
  183. package/apps/octogent/apps/web/src/styles/console-canvas-github.css +541 -0
  184. package/apps/octogent/apps/web/src/styles/console-canvas-monitor.css +595 -0
  185. package/apps/octogent/apps/web/src/styles/console-canvas-pixpack.css +81 -0
  186. package/apps/octogent/apps/web/src/styles/console-canvas-prompts.css +474 -0
  187. package/apps/octogent/apps/web/src/styles/console-canvas-settings.css +207 -0
  188. package/apps/octogent/apps/web/src/styles/console-chrome-status-nav.css +441 -0
  189. package/apps/octogent/apps/web/src/styles/console-overrides-telemetry.css +320 -0
  190. package/apps/octogent/apps/web/src/styles/console-theme-tokens.css +25 -0
  191. package/apps/octogent/apps/web/src/styles/cortex-theme.css +412 -0
  192. package/apps/octogent/apps/web/src/styles/foundation.css +100 -0
  193. package/apps/octogent/apps/web/src/styles/sidebar-and-scrollbars.css +447 -0
  194. package/apps/octogent/apps/web/src/styles/terminal-and-status.css +356 -0
  195. package/apps/octogent/apps/web/src/styles.css +25 -0
  196. package/apps/octogent/apps/web/src/types/ws.d.ts +23 -0
  197. package/apps/octogent/apps/web/tests/CanvasPrimaryView.test.tsx +347 -0
  198. package/apps/octogent/apps/web/tests/HttpTerminalSnapshotReader.test.tsx +54 -0
  199. package/apps/octogent/apps/web/tests/RuntimeStatusStrip.test.tsx +70 -0
  200. package/apps/octogent/apps/web/tests/Terminal.test.tsx +87 -0
  201. package/apps/octogent/apps/web/tests/add-tentacle-form.test.tsx +48 -0
  202. package/apps/octogent/apps/web/tests/app-github-runtime.test.tsx +162 -0
  203. package/apps/octogent/apps/web/tests/app-monitor-runtime.test.tsx +657 -0
  204. package/apps/octogent/apps/web/tests/app-shell-navigation.test.tsx +109 -0
  205. package/apps/octogent/apps/web/tests/app-swarm-refresh.test.tsx +268 -0
  206. package/apps/octogent/apps/web/tests/app-ui-state-persistence.test.tsx +116 -0
  207. package/apps/octogent/apps/web/tests/app-workspace-setup.test.tsx +217 -0
  208. package/apps/octogent/apps/web/tests/canvas-tentacle-panel.test.tsx +195 -0
  209. package/apps/octogent/apps/web/tests/delete-all-terminals-dialog.test.tsx +76 -0
  210. package/apps/octogent/apps/web/tests/githubMetrics.test.tsx +52 -0
  211. package/apps/octogent/apps/web/tests/hotkeys.test.tsx +44 -0
  212. package/apps/octogent/apps/web/tests/runtimeEndpoints.test.tsx +240 -0
  213. package/apps/octogent/apps/web/tests/setup.ts +39 -0
  214. package/apps/octogent/apps/web/tests/tentacle-pod.test.tsx +62 -0
  215. package/apps/octogent/apps/web/tests/terminalReplay.test.ts +71 -0
  216. package/apps/octogent/apps/web/tests/terminalState.test.tsx +49 -0
  217. package/apps/octogent/apps/web/tests/terminalWheel.test.tsx +51 -0
  218. package/apps/octogent/apps/web/tests/test-utils/appTestHarness.ts +48 -0
  219. package/apps/octogent/apps/web/tests/uiPrimitives.test.tsx +31 -0
  220. package/apps/octogent/apps/web/tests/useAgentRuntimeStates.test.tsx +47 -0
  221. package/apps/octogent/apps/web/tsconfig.json +8 -0
  222. package/apps/octogent/apps/web/vite.api.bundle.config.mts +32 -0
  223. package/apps/octogent/apps/web/vite.config.ts +22 -0
  224. package/apps/octogent/bin/octogent +3 -0
  225. package/apps/octogent/biome.json +21 -0
  226. package/apps/octogent/docs/concepts/mental-model.md +79 -0
  227. package/apps/octogent/docs/concepts/runtime-and-api.md +60 -0
  228. package/apps/octogent/docs/concepts/tentacles.md +85 -0
  229. package/apps/octogent/docs/getting-started/installation.md +54 -0
  230. package/apps/octogent/docs/getting-started/quickstart.md +79 -0
  231. package/apps/octogent/docs/guides/inter-agent-messaging.md +43 -0
  232. package/apps/octogent/docs/guides/orchestrating-child-agents.md +49 -0
  233. package/apps/octogent/docs/guides/working-with-todos.md +56 -0
  234. package/apps/octogent/docs/index.md +40 -0
  235. package/apps/octogent/docs/reference/api.md +103 -0
  236. package/apps/octogent/docs/reference/cli.md +71 -0
  237. package/apps/octogent/docs/reference/experimental-features.md +28 -0
  238. package/apps/octogent/docs/reference/filesystem-layout.md +62 -0
  239. package/apps/octogent/docs/reference/troubleshooting.md +49 -0
  240. package/apps/octogent/package.json +35 -0
  241. package/apps/octogent/packages/core/AGENTS.md +31 -0
  242. package/apps/octogent/packages/core/package.json +12 -0
  243. package/apps/octogent/packages/core/src/adapters/InMemoryTerminalSnapshotReader.ts +10 -0
  244. package/apps/octogent/packages/core/src/application/buildTerminalList.ts +13 -0
  245. package/apps/octogent/packages/core/src/domain/agentRuntime.ts +18 -0
  246. package/apps/octogent/packages/core/src/domain/channel.ts +8 -0
  247. package/apps/octogent/packages/core/src/domain/completionSound.ts +14 -0
  248. package/apps/octogent/packages/core/src/domain/conversation.ts +48 -0
  249. package/apps/octogent/packages/core/src/domain/deck.ts +33 -0
  250. package/apps/octogent/packages/core/src/domain/git.ts +32 -0
  251. package/apps/octogent/packages/core/src/domain/monitor.ts +62 -0
  252. package/apps/octogent/packages/core/src/domain/setup.ts +27 -0
  253. package/apps/octogent/packages/core/src/domain/terminal.ts +17 -0
  254. package/apps/octogent/packages/core/src/domain/uiState.ts +22 -0
  255. package/apps/octogent/packages/core/src/domain/usage.ts +60 -0
  256. package/apps/octogent/packages/core/src/index.ts +15 -0
  257. package/apps/octogent/packages/core/src/ports/TerminalSnapshotReader.ts +5 -0
  258. package/apps/octogent/packages/core/src/util/typeCoercion.ts +20 -0
  259. package/apps/octogent/packages/core/tests/buildTerminalList.test.ts +75 -0
  260. package/apps/octogent/packages/core/tsconfig.json +7 -0
  261. package/apps/octogent/packages/core/tsconfig.tsbuildinfo +1 -0
  262. package/apps/octogent/packages/core/vitest.config.ts +7 -0
  263. package/apps/octogent/pnpm-lock.yaml +3212 -0
  264. package/apps/octogent/pnpm-workspace.yaml +3 -0
  265. package/apps/octogent/prompts/meta-prompt-generator.md +223 -0
  266. package/apps/octogent/prompts/octoboss-clean-contexts.md +30 -0
  267. package/apps/octogent/prompts/octoboss-reorganize-tentacles.md +29 -0
  268. package/apps/octogent/prompts/octoboss-reorganize-todos.md +27 -0
  269. package/apps/octogent/prompts/sandbox-init.md +3 -0
  270. package/apps/octogent/prompts/swarm-parent.md +83 -0
  271. package/apps/octogent/prompts/swarm-worker.md +50 -0
  272. package/apps/octogent/prompts/tentacle-context-init.md +1 -0
  273. package/apps/octogent/prompts/tentacle-planner.md +110 -0
  274. package/apps/octogent/prompts/tentacle-reorganize-todos.md +20 -0
  275. package/apps/octogent/prompts/tentacle-update-tentacle.md +18 -0
  276. package/apps/octogent/scripts/build-package.mjs +23 -0
  277. package/apps/octogent/scripts/dev.mjs +158 -0
  278. package/apps/octogent/scripts/smoke-public-install.mjs +271 -0
  279. package/apps/octogent/static/images/octogent-header.png +0 -0
  280. package/apps/octogent/static/images/preview_1.jpg +0 -0
  281. package/apps/octogent/static/images/preview_2.jpg +0 -0
  282. package/apps/octogent/static/images/preview_3.jpg +0 -0
  283. package/apps/octogent/static/images/preview_4.jpg +0 -0
  284. package/apps/octogent/static/images/preview_5.jpg +0 -0
  285. package/apps/octogent/static/images/preview_6.jpg +0 -0
  286. package/apps/octogent/tsconfig.base.json +16 -0
  287. package/bin/AGI +3 -0
  288. package/bin/AGI-install-app +71 -0
  289. package/bin/AGI-ui +16 -0
  290. package/bin/AGI-voice +15 -0
  291. package/bin/AGI-web +16 -0
  292. package/bin/cortex +109 -0
  293. package/bin/cortex-octogent +99 -0
  294. package/bin/import-specifier.mjs +13 -0
  295. package/bin/import-specifier.test.mjs +13 -0
  296. package/bin/octo +150 -0
  297. package/dist/cli.mjs +555650 -0
  298. package/package.json +157 -0
  299. package/scripts/setup-wizard.ts +390 -0
@@ -0,0 +1,702 @@
1
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
2
+
3
+ import type { UsageChartData, UsageDayEntry } from "../app/hooks/useUsageHeatmapPolling";
4
+ import { ActionButton } from "./ui/ActionButton";
5
+
6
+ type UsageChartSectionProps = {
7
+ data: UsageChartData | null;
8
+ isLoading: boolean;
9
+ onRefresh: () => void;
10
+ };
11
+
12
+ type BarSegmentMode = "project" | "model";
13
+
14
+ const SEGMENT_COLORS = [
15
+ "#ff5722",
16
+ "#ffa726",
17
+ "#ffffff",
18
+ "#ffcc02",
19
+ "#e64a19",
20
+ "#ffb74d",
21
+ "#f5f5f5",
22
+ "#ff8a65",
23
+ "#ffd54f",
24
+ "#ff7043",
25
+ "#ffe082",
26
+ "#ffab91",
27
+ ];
28
+
29
+ const formatTokenCount = (tokens: number): string => {
30
+ if (tokens >= 1_000_000) return `${(tokens / 1_000_000).toFixed(1)}M`;
31
+ if (tokens >= 1_000) return `${(tokens / 1_000).toFixed(1)}K`;
32
+ return tokens.toString();
33
+ };
34
+
35
+ const formatDateLabel = (date: string): string => {
36
+ const d = new Date(`${date}T00:00:00`);
37
+ return d.toLocaleDateString("en-US", { month: "short", day: "numeric" });
38
+ };
39
+
40
+ /* ── Shared types ───────────────────────────────────── */
41
+
42
+ type Segment = { label: string; tokens: number; color: string };
43
+
44
+ type BarData = {
45
+ date: string;
46
+ totalTokens: number;
47
+ sessions: number;
48
+ segments: Segment[];
49
+ };
50
+
51
+ const buildColorMap = (keys: string[]) =>
52
+ new Map(
53
+ keys.map((k, i) => [
54
+ k,
55
+ SEGMENT_COLORS[i % SEGMENT_COLORS.length] ?? SEGMENT_COLORS[0] ?? "#ffffff",
56
+ ]),
57
+ );
58
+
59
+ const buildBars = (days: UsageDayEntry[], keys: string[], mode: BarSegmentMode): BarData[] => {
60
+ const colorMap = buildColorMap(keys);
61
+ return days.map((day) => {
62
+ const slices = mode === "model" ? day.models : day.projects;
63
+ return {
64
+ date: day.date,
65
+ totalTokens: day.totalTokens,
66
+ sessions: day.sessions,
67
+ segments: slices.map((s) => ({
68
+ label: s.key,
69
+ tokens: s.tokens,
70
+ color: colorMap.get(s.key) ?? "#555",
71
+ })),
72
+ };
73
+ });
74
+ };
75
+
76
+ const buildYTicks = (maxTokens: number): { value: number; label: string }[] => {
77
+ if (maxTokens === 0) return [];
78
+ const ticks: { value: number; label: string }[] = [];
79
+ const step = maxTokens / 4;
80
+ for (let i = 0; i <= 4; i++) {
81
+ const value = step * i;
82
+ ticks.push({ value, label: formatTokenCount(Math.round(value)) });
83
+ }
84
+ return ticks;
85
+ };
86
+
87
+ /* ── Tooltip (shared) ───────────────────────────────── */
88
+
89
+ const ChartTooltip = ({
90
+ bar,
91
+ x,
92
+ y,
93
+ containerWidth,
94
+ }: { bar: BarData; x: number; y: number; containerWidth: number }) => {
95
+ const isRightHalf = x > containerWidth / 2;
96
+ return (
97
+ <div
98
+ className="usage-heatmap-tooltip"
99
+ aria-live="polite"
100
+ style={
101
+ isRightHalf
102
+ ? { right: `${containerWidth - x + 12}px`, top: `${y + 12}px` }
103
+ : { left: `${x + 12}px`, top: `${y + 12}px` }
104
+ }
105
+ >
106
+ <p className="usage-heatmap-tooltip-date">{formatDateLabel(bar.date)}</p>
107
+ <dl className="usage-heatmap-tooltip-stats">
108
+ <div>
109
+ <dt>Total</dt>
110
+ <dd>{formatTokenCount(bar.totalTokens)}</dd>
111
+ </div>
112
+ {bar.segments.map((seg) => (
113
+ <div key={seg.label}>
114
+ <dt>
115
+ <span className="usage-chart-legend-dot" style={{ backgroundColor: seg.color }} />
116
+ {seg.label}
117
+ </dt>
118
+ <dd>{formatTokenCount(seg.tokens)}</dd>
119
+ </div>
120
+ ))}
121
+ <div>
122
+ <dt>Sessions</dt>
123
+ <dd>{bar.sessions}</dd>
124
+ </div>
125
+ </dl>
126
+ </div>
127
+ );
128
+ };
129
+
130
+ /* ── Trend curve ───────────────────────────────────── */
131
+
132
+ const buildTrendPath = (
133
+ bars: BarData[],
134
+ maxTokens: number,
135
+ chartHeight: number,
136
+ yAxisWidth: number,
137
+ topPad: number,
138
+ barSlotWidth: number,
139
+ ): string => {
140
+ if (bars.length < 2 || maxTokens === 0) return "";
141
+
142
+ const LIFT = 8;
143
+ const points = bars.map((bar, i) => ({
144
+ x: yAxisWidth + i * barSlotWidth + barSlotWidth / 2,
145
+ y: Math.max(topPad, topPad + chartHeight - (bar.totalTokens / maxTokens) * chartHeight - LIFT),
146
+ }));
147
+
148
+ if (points.length === 2) {
149
+ const [firstPoint, secondPoint] = points;
150
+ if (!firstPoint || !secondPoint) return "";
151
+ return `M${firstPoint.x},${firstPoint.y}L${secondPoint.x},${secondPoint.y}`;
152
+ }
153
+
154
+ const [firstPoint] = points;
155
+ if (!firstPoint) return "";
156
+
157
+ let d = `M${firstPoint.x},${firstPoint.y}`;
158
+ for (let i = 0; i < points.length - 1; i++) {
159
+ const p0 = points[Math.max(0, i - 1)];
160
+ const p1 = points[i];
161
+ const p2 = points[i + 1];
162
+ const p3 = points[Math.min(points.length - 1, i + 2)];
163
+ if (!p0 || !p1 || !p2 || !p3) continue;
164
+
165
+ const cp1x = p1.x + (p2.x - p0.x) / 10;
166
+ const cp1y = Math.max(topPad, p1.y + (p2.y - p0.y) / 10);
167
+ const cp2x = p2.x - (p3.x - p1.x) / 10;
168
+ const cp2y = Math.max(topPad, p2.y - (p3.y - p1.y) / 10);
169
+
170
+ d += ` C${cp1x},${cp1y} ${cp2x},${cp2y} ${p2.x},${p2.y}`;
171
+ }
172
+
173
+ return d;
174
+ };
175
+
176
+ /* ── Bar chart view ─────────────────────────────────── */
177
+
178
+ const Y_AXIS_WIDTH = 52;
179
+ const X_LABEL_HEIGHT = 18;
180
+ const TOP_PAD = 6;
181
+ const BAR_GAP_RATIO = 0.3;
182
+
183
+ const BarChartView = ({
184
+ bars,
185
+ maxTokens,
186
+ containerWidth,
187
+ containerHeight,
188
+ hoveredBar,
189
+ setHoveredBar,
190
+ }: {
191
+ bars: BarData[];
192
+ maxTokens: number;
193
+ containerWidth: number;
194
+ containerHeight: number;
195
+ hoveredBar: BarData | null;
196
+ setHoveredBar: (bar: BarData | null) => void;
197
+ }) => {
198
+ const chartAreaWidth = containerWidth - Y_AXIS_WIDTH;
199
+ const barCount = bars.length || 1;
200
+ const barSlotWidth = chartAreaWidth / barCount;
201
+ const barWidth = barSlotWidth * (1 - BAR_GAP_RATIO);
202
+ const barGap = barSlotWidth * BAR_GAP_RATIO;
203
+ const chartHeight = Math.max(60, containerHeight - X_LABEL_HEIGHT - TOP_PAD);
204
+ const svgHeight = TOP_PAD + chartHeight + X_LABEL_HEIGHT;
205
+
206
+ const yTicks = useMemo(() => buildYTicks(maxTokens), [maxTokens]);
207
+ const xLabelStep = Math.max(1, Math.ceil(barCount / Math.floor(chartAreaWidth / 60)));
208
+
209
+ const trendPath = useMemo(
210
+ () => buildTrendPath(bars, maxTokens, chartHeight, Y_AXIS_WIDTH, TOP_PAD, barSlotWidth),
211
+ [bars, maxTokens, chartHeight, barSlotWidth],
212
+ );
213
+
214
+ return (
215
+ <svg
216
+ className="usage-chart-svg"
217
+ viewBox={`0 0 ${containerWidth} ${svgHeight}`}
218
+ role="img"
219
+ aria-label="Token usage bar chart"
220
+ >
221
+ {yTicks.map((tick) => {
222
+ const y =
223
+ TOP_PAD + chartHeight - (maxTokens > 0 ? (tick.value / maxTokens) * chartHeight : 0);
224
+ return (
225
+ <g key={tick.value}>
226
+ <line
227
+ x1={Y_AXIS_WIDTH}
228
+ y1={y}
229
+ x2={containerWidth}
230
+ y2={y}
231
+ className="usage-chart-grid-line"
232
+ />
233
+ <text x={Y_AXIS_WIDTH - 6} y={y + 3.5} className="usage-chart-y-label">
234
+ {tick.label}
235
+ </text>
236
+ </g>
237
+ );
238
+ })}
239
+
240
+ {bars.map((bar, i) => {
241
+ const x = Y_AXIS_WIDTH + i * barSlotWidth + barGap / 2;
242
+ let yOffset = TOP_PAD + chartHeight;
243
+
244
+ return (
245
+ <g
246
+ key={bar.date}
247
+ onMouseEnter={() => setHoveredBar(bar)}
248
+ onMouseLeave={() => setHoveredBar(null)}
249
+ className="usage-chart-bar-group"
250
+ >
251
+ <rect
252
+ x={x}
253
+ y={TOP_PAD}
254
+ width={barWidth}
255
+ height={chartHeight}
256
+ fill="transparent"
257
+ className="usage-chart-bar-hit"
258
+ />
259
+ {bar.segments.map((seg) => {
260
+ const segHeight = maxTokens > 0 ? (seg.tokens / maxTokens) * chartHeight : 0;
261
+ yOffset -= segHeight;
262
+ return (
263
+ <rect
264
+ key={seg.label}
265
+ x={x}
266
+ y={yOffset}
267
+ width={barWidth}
268
+ height={Math.max(0.5, segHeight)}
269
+ fill={seg.color}
270
+ rx={1}
271
+ />
272
+ );
273
+ })}
274
+ </g>
275
+ );
276
+ })}
277
+
278
+ {bars.map((bar, i) => {
279
+ if (i % xLabelStep !== 0) return null;
280
+ const x = Y_AXIS_WIDTH + i * barSlotWidth + barSlotWidth / 2;
281
+ return (
282
+ <text
283
+ key={`label-${bar.date}`}
284
+ x={x}
285
+ y={TOP_PAD + chartHeight + X_LABEL_HEIGHT - 2}
286
+ className="usage-chart-x-label"
287
+ >
288
+ {formatDateLabel(bar.date)}
289
+ </text>
290
+ );
291
+ })}
292
+
293
+ {trendPath && (
294
+ <path
295
+ d={trendPath}
296
+ className="usage-chart-trend-line"
297
+ fill="none"
298
+ stroke="rgba(215, 166, 34, 0.55)"
299
+ strokeWidth={2}
300
+ strokeLinecap="round"
301
+ strokeLinejoin="round"
302
+ />
303
+ )}
304
+ </svg>
305
+ );
306
+ };
307
+
308
+ /* ── Heatmap view ───────────────────────────────────── */
309
+
310
+ const CELL_GAP = 3;
311
+ const CELL_RADIUS = 2;
312
+ const WEEKS_TO_SHOW = 26;
313
+ const DAY_LABELS = ["", "Mon", "", "Wed", "", "Fri", ""];
314
+ const MONTH_LABELS = [
315
+ "Jan",
316
+ "Feb",
317
+ "Mar",
318
+ "Apr",
319
+ "May",
320
+ "Jun",
321
+ "Jul",
322
+ "Aug",
323
+ "Sep",
324
+ "Oct",
325
+ "Nov",
326
+ "Dec",
327
+ ];
328
+
329
+ const INTENSITY_COLORS = ["transparent", "#3d2008", "#6b3a0e", "#b5611a", "#d7a622"];
330
+
331
+ type HeatmapCell = {
332
+ date: string;
333
+ week: number;
334
+ dayOfWeek: number;
335
+ totalTokens: number;
336
+ sessions: number;
337
+ intensity: number;
338
+ bar: BarData | null;
339
+ };
340
+
341
+ const buildHeatmapGrid = (bars: BarData[]): HeatmapCell[] => {
342
+ const barMap = new Map(bars.map((b) => [b.date, b]));
343
+ const tokenValues = bars.map((b) => b.totalTokens).filter((v) => v > 0);
344
+ tokenValues.sort((a, b) => a - b);
345
+
346
+ const getIntensity = (tokens: number): number => {
347
+ if (tokens === 0 || tokenValues.length === 0) return 0;
348
+ const idx = tokenValues.findIndex((v) => v >= tokens);
349
+ const pos = idx === -1 ? tokenValues.length : idx;
350
+ const ratio = pos / tokenValues.length;
351
+ if (ratio <= 0.25) return 1;
352
+ if (ratio <= 0.5) return 2;
353
+ if (ratio <= 0.75) return 3;
354
+ return 4;
355
+ };
356
+
357
+ const today = new Date();
358
+ const todayDow = today.getUTCDay();
359
+ const endDate = new Date(today);
360
+ endDate.setUTCDate(today.getUTCDate() + (6 - todayDow));
361
+ const startDate = new Date(endDate);
362
+ startDate.setUTCDate(endDate.getUTCDate() - WEEKS_TO_SHOW * 7 + 1);
363
+
364
+ const cells: HeatmapCell[] = [];
365
+ const cursor = new Date(startDate);
366
+ let week = 0;
367
+
368
+ while (cursor <= endDate) {
369
+ const dateStr = cursor.toISOString().slice(0, 10);
370
+ const dayOfWeek = cursor.getUTCDay();
371
+ const bar = barMap.get(dateStr) ?? null;
372
+ cells.push({
373
+ date: dateStr,
374
+ week,
375
+ dayOfWeek,
376
+ totalTokens: bar?.totalTokens ?? 0,
377
+ sessions: bar?.sessions ?? 0,
378
+ intensity: getIntensity(bar?.totalTokens ?? 0),
379
+ bar,
380
+ });
381
+ cursor.setUTCDate(cursor.getUTCDate() + 1);
382
+ if (cursor.getUTCDay() === 0) week++;
383
+ }
384
+
385
+ return cells;
386
+ };
387
+
388
+ const buildMonthLabels = (cells: HeatmapCell[]): { label: string; week: number }[] => {
389
+ const labels: { label: string; week: number }[] = [];
390
+ let lastMonth = -1;
391
+ for (const cell of cells) {
392
+ if (cell.dayOfWeek !== 0) continue;
393
+ const month = new Date(cell.date).getUTCMonth();
394
+ if (month !== lastMonth) {
395
+ const label = MONTH_LABELS[month];
396
+ if (!label) continue;
397
+ labels.push({ label, week: cell.week });
398
+ lastMonth = month;
399
+ }
400
+ }
401
+ return labels;
402
+ };
403
+
404
+ const HeatmapView = ({
405
+ bars,
406
+ containerWidth,
407
+ containerHeight,
408
+ hoveredBar,
409
+ setHoveredBar,
410
+ }: {
411
+ bars: BarData[];
412
+ containerWidth: number;
413
+ containerHeight: number;
414
+ hoveredBar: BarData | null;
415
+ setHoveredBar: (bar: BarData | null) => void;
416
+ }) => {
417
+ const cells = useMemo(() => buildHeatmapGrid(bars), [bars]);
418
+ const monthLabels = useMemo(() => buildMonthLabels(cells), [cells]);
419
+
420
+ const dayLabelWidth = 32;
421
+ const monthLabelHeight = 16;
422
+ const availableHeight = containerHeight - monthLabelHeight - 8;
423
+ const availableWidth = containerWidth - dayLabelWidth - 8;
424
+ const cellSize = Math.max(
425
+ 8,
426
+ Math.min(
427
+ Math.floor((availableHeight - 6 * CELL_GAP) / 7),
428
+ Math.floor((availableWidth - (WEEKS_TO_SHOW - 1) * CELL_GAP) / WEEKS_TO_SHOW),
429
+ ),
430
+ );
431
+ const gridWidth = WEEKS_TO_SHOW * (cellSize + CELL_GAP);
432
+ const gridHeight = 7 * (cellSize + CELL_GAP);
433
+ const svgWidth = dayLabelWidth + gridWidth + 8;
434
+ const svgHeight = monthLabelHeight + gridHeight + 8;
435
+
436
+ return (
437
+ <svg
438
+ className="usage-chart-svg usage-chart-svg--heatmap"
439
+ viewBox={`0 0 ${svgWidth} ${svgHeight}`}
440
+ width={svgWidth}
441
+ height={svgHeight}
442
+ role="img"
443
+ aria-label="Token usage heatmap"
444
+ >
445
+ {monthLabels.map(({ label, week }) => (
446
+ <text
447
+ key={`month-${week}`}
448
+ x={dayLabelWidth + week * (cellSize + CELL_GAP)}
449
+ y={monthLabelHeight - 4}
450
+ className="usage-heatmap-month-label"
451
+ >
452
+ {label}
453
+ </text>
454
+ ))}
455
+
456
+ {DAY_LABELS.map((label, dayIndex) =>
457
+ label ? (
458
+ <text
459
+ key={label}
460
+ x={dayLabelWidth - 6}
461
+ y={monthLabelHeight + dayIndex * (cellSize + CELL_GAP) + cellSize - 2}
462
+ className="usage-heatmap-day-label"
463
+ >
464
+ {label}
465
+ </text>
466
+ ) : null,
467
+ )}
468
+
469
+ {cells.map((cell) => (
470
+ <rect
471
+ key={cell.date}
472
+ x={dayLabelWidth + cell.week * (cellSize + CELL_GAP)}
473
+ y={monthLabelHeight + cell.dayOfWeek * (cellSize + CELL_GAP)}
474
+ width={cellSize}
475
+ height={cellSize}
476
+ rx={CELL_RADIUS}
477
+ fill={INTENSITY_COLORS[cell.intensity] ?? INTENSITY_COLORS[0] ?? "transparent"}
478
+ className="usage-heatmap-cell"
479
+ onMouseEnter={() => {
480
+ if (cell.bar) setHoveredBar(cell.bar);
481
+ }}
482
+ onMouseLeave={() => setHoveredBar(null)}
483
+ />
484
+ ))}
485
+ </svg>
486
+ );
487
+ };
488
+
489
+ /* ── Measured panel wrapper ──────────────────────────── */
490
+
491
+ const usePanelSize = () => {
492
+ const [width, setWidth] = useState(400);
493
+ const [height, setHeight] = useState(200);
494
+ const ref = useRef<HTMLDivElement>(null);
495
+
496
+ const measure = useCallback(() => {
497
+ if (ref.current) {
498
+ setWidth(ref.current.clientWidth);
499
+ setHeight(ref.current.clientHeight);
500
+ }
501
+ }, []);
502
+
503
+ useEffect(() => {
504
+ measure();
505
+ const observer = new ResizeObserver(measure);
506
+ if (ref.current) observer.observe(ref.current);
507
+ return () => observer.disconnect();
508
+ }, [measure]);
509
+
510
+ return { ref, width, height };
511
+ };
512
+
513
+ /* ── Main component ─────────────────────────────────── */
514
+
515
+ export const UsageBarChart = ({ data, isLoading, onRefresh }: UsageChartSectionProps) => {
516
+ const [segmentMode, setSegmentMode] = useState<BarSegmentMode>("project");
517
+ const [hoveredBar, setHoveredBar] = useState<BarData | null>(null);
518
+ const [mousePos, setMousePos] = useState({ x: 0, y: 0 });
519
+ const splitRef = useRef<HTMLDivElement>(null);
520
+ const barPanel = usePanelSize();
521
+ const heatmapPanel = usePanelSize();
522
+
523
+ const days = data?.days ?? [];
524
+ const projects = data?.projects ?? [];
525
+ const models = data?.models ?? [];
526
+
527
+ const segmentKeys = segmentMode === "model" ? models : projects;
528
+
529
+ const maxTokens = useMemo(() => {
530
+ let max = 0;
531
+ for (const d of days) {
532
+ if (d.totalTokens > max) max = d.totalTokens;
533
+ }
534
+ return max;
535
+ }, [days]);
536
+
537
+ const totalTokens = useMemo(() => days.reduce((s, d) => s + d.totalTokens, 0), [days]);
538
+ const totalSessions = useMemo(() => days.reduce((s, d) => s + d.sessions, 0), [days]);
539
+ const activeDays = useMemo(() => days.filter((d) => d.totalTokens > 0).length, [days]);
540
+
541
+ const bars = useMemo(
542
+ () => buildBars(days, segmentKeys, segmentMode),
543
+ [days, segmentKeys, segmentMode],
544
+ );
545
+ const heatmapBars = useMemo(() => buildBars(days, projects, "project"), [days, projects]);
546
+ const colorMap = useMemo(() => buildColorMap(segmentKeys), [segmentKeys]);
547
+
548
+ const stats = useMemo(() => {
549
+ const [firstDay] = days;
550
+ if (!firstDay) return null;
551
+ const peakDay = days.reduce(
552
+ (best, d) => (d.totalTokens > best.totalTokens ? d : best),
553
+ firstDay,
554
+ );
555
+ const avgPerSession = totalSessions > 0 ? Math.round(totalTokens / totalSessions) : 0;
556
+ const topModel = models[0] ?? "—";
557
+ const topProject = projects[0] ?? "—";
558
+
559
+ let streak = 0;
560
+ let maxStreak = 0;
561
+ for (let i = days.length - 1; i >= 0; i--) {
562
+ const day = days[i];
563
+ if (!day) continue;
564
+ if (day.totalTokens > 0) {
565
+ streak++;
566
+ if (streak > maxStreak) maxStreak = streak;
567
+ } else {
568
+ streak = 0;
569
+ }
570
+ }
571
+
572
+ return { peakDay, avgPerSession, topModel, topProject, maxStreak };
573
+ }, [days, totalTokens, totalSessions, models, projects]);
574
+
575
+ return (
576
+ <section className="usage-heatmap" aria-label="Claude token usage chart">
577
+ <header className="usage-heatmap-header">
578
+ <div className="usage-heatmap-header-left">
579
+ <h3>Claude Token Usage</h3>
580
+ <span className="usage-heatmap-summary">
581
+ {formatTokenCount(totalTokens)} tokens across {activeDays} days, {totalSessions}{" "}
582
+ sessions
583
+ </span>
584
+ </div>
585
+ <div className="usage-heatmap-header-actions">
586
+ <ActionButton
587
+ aria-label="Refresh usage chart data"
588
+ className="usage-heatmap-refresh"
589
+ disabled={isLoading}
590
+ onClick={onRefresh}
591
+ size="dense"
592
+ variant="accent"
593
+ >
594
+ {isLoading ? "Scanning..." : "Refresh"}
595
+ </ActionButton>
596
+ </div>
597
+ </header>
598
+
599
+ <div
600
+ className="usage-chart-split"
601
+ ref={splitRef}
602
+ onMouseMove={(e) => {
603
+ const rect = splitRef.current?.getBoundingClientRect();
604
+ if (rect) {
605
+ setMousePos({ x: e.clientX - rect.left, y: e.clientY - rect.top });
606
+ }
607
+ }}
608
+ >
609
+ <div className="usage-chart-bar-segment-toggle">
610
+ <button
611
+ type="button"
612
+ className={`usage-chart-bar-segment-btn${segmentMode === "project" ? " is-active" : ""}`}
613
+ onClick={() => setSegmentMode("project")}
614
+ >
615
+ Project
616
+ </button>
617
+ <button
618
+ type="button"
619
+ className={`usage-chart-bar-segment-btn${segmentMode === "model" ? " is-active" : ""}`}
620
+ onClick={() => setSegmentMode("model")}
621
+ >
622
+ Model
623
+ </button>
624
+ </div>
625
+ <div className="usage-chart-left-stack">
626
+ <div className="usage-chart-panel" ref={barPanel.ref}>
627
+ <BarChartView
628
+ bars={bars}
629
+ maxTokens={maxTokens}
630
+ containerWidth={barPanel.width}
631
+ containerHeight={barPanel.height}
632
+ hoveredBar={hoveredBar}
633
+ setHoveredBar={setHoveredBar}
634
+ />
635
+ </div>
636
+ {segmentKeys.length > 1 && (
637
+ <div className="usage-chart-legend">
638
+ {segmentKeys.map((key) => (
639
+ <span key={key} className="usage-chart-legend-item">
640
+ <span
641
+ className="usage-chart-legend-dot"
642
+ style={{ backgroundColor: colorMap.get(key) }}
643
+ />
644
+ {key}
645
+ </span>
646
+ ))}
647
+ </div>
648
+ )}
649
+ </div>
650
+ <div className="usage-chart-right-stack">
651
+ <div className="usage-chart-panel" ref={heatmapPanel.ref}>
652
+ <HeatmapView
653
+ bars={heatmapBars}
654
+ containerWidth={heatmapPanel.width}
655
+ containerHeight={heatmapPanel.height}
656
+ hoveredBar={hoveredBar}
657
+ setHoveredBar={setHoveredBar}
658
+ />
659
+ </div>
660
+ {stats && (
661
+ <dl className="usage-chart-stats">
662
+ <div className="usage-chart-stat">
663
+ <dt>Peak Day</dt>
664
+ <dd>
665
+ {formatDateLabel(stats.peakDay.date)}
666
+ <span className="usage-chart-stat-sub">
667
+ {formatTokenCount(stats.peakDay.totalTokens)}
668
+ </span>
669
+ </dd>
670
+ </div>
671
+ <div className="usage-chart-stat">
672
+ <dt>Avg / Session</dt>
673
+ <dd>{formatTokenCount(stats.avgPerSession)}</dd>
674
+ </div>
675
+ <div className="usage-chart-stat">
676
+ <dt>Top Model</dt>
677
+ <dd>{stats.topModel}</dd>
678
+ </div>
679
+ <div className="usage-chart-stat">
680
+ <dt>Top Project</dt>
681
+ <dd>{stats.topProject}</dd>
682
+ </div>
683
+ <div className="usage-chart-stat">
684
+ <dt>Best Streak</dt>
685
+ <dd>{stats.maxStreak}d</dd>
686
+ </div>
687
+ </dl>
688
+ )}
689
+ </div>
690
+
691
+ {hoveredBar && hoveredBar.totalTokens > 0 && (
692
+ <ChartTooltip
693
+ bar={hoveredBar}
694
+ x={mousePos.x}
695
+ y={mousePos.y}
696
+ containerWidth={splitRef.current?.clientWidth ?? 800}
697
+ />
698
+ )}
699
+ </div>
700
+ </section>
701
+ );
702
+ };