@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,94 @@
1
+ import { useCallback, useEffect, useRef } from "react";
2
+
3
+ import {
4
+ type TerminalCompletionSoundId,
5
+ buildTerminalCompletionSoundDataUrl,
6
+ } from "../notificationSounds";
7
+ import type { TerminalRuntimeStateStore } from "../terminalRuntimeStateStore";
8
+
9
+ const createCompletionAudio = (soundId: TerminalCompletionSoundId): HTMLAudioElement | null => {
10
+ if (soundId === "silent" || typeof Audio === "undefined") {
11
+ return null;
12
+ }
13
+
14
+ if (import.meta.env.MODE === "test" && !("mock" in Audio)) {
15
+ return null;
16
+ }
17
+
18
+ const source = buildTerminalCompletionSoundDataUrl(soundId);
19
+ if (!source) {
20
+ return null;
21
+ }
22
+
23
+ const audio = new Audio(source);
24
+ audio.preload = "auto";
25
+ return audio;
26
+ };
27
+
28
+ export const useTerminalCompletionNotification = (
29
+ runtimeStateStore: TerminalRuntimeStateStore,
30
+ selectedSound: TerminalCompletionSoundId,
31
+ ) => {
32
+ const previousTerminalStatesRef = useRef(runtimeStateStore.getSnapshot());
33
+ const audioCacheRef = useRef<Partial<Record<TerminalCompletionSoundId, HTMLAudioElement | null>>>(
34
+ {},
35
+ );
36
+
37
+ const playCompletionSound = useCallback((soundId: TerminalCompletionSoundId) => {
38
+ if (soundId === "silent") {
39
+ return;
40
+ }
41
+
42
+ if (audioCacheRef.current[soundId] === undefined) {
43
+ audioCacheRef.current[soundId] = createCompletionAudio(soundId);
44
+ }
45
+
46
+ const audio = audioCacheRef.current[soundId];
47
+ if (!audio) {
48
+ return;
49
+ }
50
+
51
+ audio.currentTime = 0;
52
+ try {
53
+ const playResult = audio.play();
54
+ if (typeof playResult?.catch === "function") {
55
+ void playResult.catch(() => {
56
+ // Browsers can block untrusted audio playback; ignore and keep UI responsive.
57
+ });
58
+ }
59
+ } catch {
60
+ // Some environments throw synchronously for media playback; ignore.
61
+ }
62
+ }, []);
63
+
64
+ useEffect(() => {
65
+ previousTerminalStatesRef.current = runtimeStateStore.getSnapshot();
66
+
67
+ return runtimeStateStore.subscribe(() => {
68
+ const previousTerminalStates = previousTerminalStatesRef.current;
69
+ const nextTerminalStates = runtimeStateStore.getSnapshot();
70
+ const shouldPlayCompletionSound = Object.entries(nextTerminalStates).some(
71
+ ([terminalId, state]) =>
72
+ previousTerminalStates[terminalId]?.state === "processing" && state.state === "idle",
73
+ );
74
+
75
+ previousTerminalStatesRef.current = nextTerminalStates;
76
+ if (!shouldPlayCompletionSound) {
77
+ return;
78
+ }
79
+
80
+ playCompletionSound(selectedSound);
81
+ });
82
+ }, [playCompletionSound, runtimeStateStore, selectedSound]);
83
+
84
+ const playCompletionSoundPreview = useCallback(
85
+ (soundId?: TerminalCompletionSoundId) => {
86
+ playCompletionSound(soundId ?? selectedSound);
87
+ },
88
+ [playCompletionSound, selectedSound],
89
+ );
90
+
91
+ return {
92
+ playCompletionSoundPreview,
93
+ };
94
+ };
@@ -0,0 +1,266 @@
1
+ import { useCallback, useRef, useState } from "react";
2
+ import type { Dispatch, SetStateAction } from "react";
3
+
4
+ import type { TerminalAgentProvider, TerminalView, TerminalWorkspaceMode } from "../types";
5
+
6
+ export type PendingDeleteTerminal = {
7
+ terminalId: string;
8
+ tentacleName: string;
9
+ workspaceMode: TerminalWorkspaceMode;
10
+ intent: "delete-terminal" | "cleanup-worktree";
11
+ };
12
+
13
+ type UseTerminalMutationsOptions = {
14
+ readColumns: () => Promise<TerminalView>;
15
+ setColumns: Dispatch<SetStateAction<TerminalView>>;
16
+ setLoadError: Dispatch<SetStateAction<string | null>>;
17
+ setMinimizedTerminalIds: Dispatch<SetStateAction<string[]>>;
18
+ };
19
+
20
+ type UseTerminalMutationsResult = {
21
+ editingTerminalId: string | null;
22
+ terminalNameDraft: string;
23
+ isCreatingTerminal: boolean;
24
+ isDeletingTerminalId: string | null;
25
+ pendingDeleteTerminal: PendingDeleteTerminal | null;
26
+ setTerminalNameDraft: Dispatch<SetStateAction<string>>;
27
+ setEditingTerminalId: Dispatch<SetStateAction<string | null>>;
28
+ beginTerminalNameEdit: (terminalId: string, currentTerminalName: string) => void;
29
+ submitTerminalRename: (terminalId: string, currentTerminalName: string) => Promise<void>;
30
+ createTerminal: (
31
+ workspaceMode: TerminalWorkspaceMode,
32
+ agentProvider?: TerminalAgentProvider,
33
+ tentacleId?: string,
34
+ ) => Promise<string | undefined>;
35
+ requestDeleteTerminal: (
36
+ terminalId: string,
37
+ terminalName: string,
38
+ options?: {
39
+ workspaceMode?: TerminalWorkspaceMode;
40
+ intent?: "delete-terminal" | "cleanup-worktree";
41
+ },
42
+ ) => void;
43
+ confirmDeleteTerminal: () => Promise<void>;
44
+ clearPendingDeleteTerminal: () => void;
45
+ cancelTerminalRename: () => void;
46
+ };
47
+
48
+ export const useTerminalMutations = ({
49
+ readColumns,
50
+ setColumns,
51
+ setLoadError,
52
+ setMinimizedTerminalIds,
53
+ }: UseTerminalMutationsOptions): UseTerminalMutationsResult => {
54
+ const [editingTerminalId, setEditingTerminalId] = useState<string | null>(null);
55
+ const [terminalNameDraft, setTerminalNameDraft] = useState("");
56
+ const [isCreatingTerminal, setIsCreatingTerminal] = useState(false);
57
+ const [isDeletingTerminalId, setIsDeletingTerminalId] = useState<string | null>(null);
58
+ const [pendingDeleteTerminal, setPendingDeleteTerminal] = useState<PendingDeleteTerminal | null>(
59
+ null,
60
+ );
61
+ const cancelTerminalNameSubmitRef = useRef(false);
62
+
63
+ const beginTerminalNameEdit = useCallback(
64
+ (terminalId: string, currentTerminalName: string) => {
65
+ setLoadError(null);
66
+ setEditingTerminalId(terminalId);
67
+ setTerminalNameDraft(currentTerminalName);
68
+ },
69
+ [setLoadError],
70
+ );
71
+
72
+ const submitTerminalRename = useCallback(
73
+ async (terminalId: string, currentTerminalName: string) => {
74
+ if (cancelTerminalNameSubmitRef.current) {
75
+ cancelTerminalNameSubmitRef.current = false;
76
+ return;
77
+ }
78
+
79
+ const trimmedName = terminalNameDraft.trim();
80
+ if (trimmedName.length === 0) {
81
+ setLoadError("Terminal name cannot be empty.");
82
+ return;
83
+ }
84
+
85
+ if (trimmedName === currentTerminalName) {
86
+ setEditingTerminalId(null);
87
+ return;
88
+ }
89
+
90
+ try {
91
+ setLoadError(null);
92
+ const encodedTerminalId = encodeURIComponent(terminalId);
93
+ const response = await fetch(`/api/terminals/${encodedTerminalId}`, {
94
+ method: "PATCH",
95
+ headers: {
96
+ Accept: "application/json",
97
+ "Content-Type": "application/json",
98
+ },
99
+ body: JSON.stringify({ name: trimmedName }),
100
+ });
101
+
102
+ if (!response.ok) {
103
+ throw new Error(`Unable to rename terminal (${response.status})`);
104
+ }
105
+
106
+ const nextColumns = await readColumns();
107
+ setColumns(nextColumns);
108
+ setEditingTerminalId(null);
109
+ } catch {
110
+ setLoadError("Unable to rename terminal.");
111
+ }
112
+ },
113
+ [readColumns, setColumns, setLoadError, terminalNameDraft],
114
+ );
115
+
116
+ const createTerminal = useCallback(
117
+ async (
118
+ workspaceMode: TerminalWorkspaceMode,
119
+ agentProvider?: TerminalAgentProvider,
120
+ tentacleId?: string,
121
+ ) => {
122
+ try {
123
+ setIsCreatingTerminal(true);
124
+ setLoadError(null);
125
+ const response = await fetch("/api/terminals", {
126
+ method: "POST",
127
+ headers: {
128
+ Accept: "application/json",
129
+ "Content-Type": "application/json",
130
+ },
131
+ body: JSON.stringify({
132
+ workspaceMode,
133
+ agentProvider: agentProvider ?? "claude-code",
134
+ ...(tentacleId ? { tentacleId } : {}),
135
+ }),
136
+ });
137
+
138
+ if (!response.ok) {
139
+ throw new Error(`Unable to create terminal (${response.status})`);
140
+ }
141
+
142
+ const createdSnapshot = (await response.json()) as {
143
+ terminalId?: unknown;
144
+ tentacleName?: unknown;
145
+ };
146
+ const nextColumns = await readColumns();
147
+ setColumns(nextColumns);
148
+
149
+ const createdTerminalId =
150
+ typeof createdSnapshot.terminalId === "string" ? createdSnapshot.terminalId : null;
151
+ if (!createdTerminalId) {
152
+ return undefined;
153
+ }
154
+
155
+ const createdEntry = nextColumns.find((entry) => entry.terminalId === createdTerminalId);
156
+ const createdTerminalName =
157
+ createdEntry?.tentacleName ??
158
+ (typeof createdSnapshot.tentacleName === "string"
159
+ ? createdSnapshot.tentacleName
160
+ : createdTerminalId);
161
+ setMinimizedTerminalIds((current) => current.filter((id) => id !== createdTerminalId));
162
+ beginTerminalNameEdit(createdTerminalId, createdTerminalName);
163
+ return createdTerminalId;
164
+ } catch {
165
+ setLoadError("Unable to create a new terminal.");
166
+ return undefined;
167
+ } finally {
168
+ setIsCreatingTerminal(false);
169
+ }
170
+ },
171
+ [beginTerminalNameEdit, readColumns, setColumns, setLoadError, setMinimizedTerminalIds],
172
+ );
173
+
174
+ const requestDeleteTerminal = useCallback(
175
+ (
176
+ terminalId: string,
177
+ terminalName: string,
178
+ options?: {
179
+ workspaceMode?: TerminalWorkspaceMode;
180
+ intent?: "delete-terminal" | "cleanup-worktree";
181
+ },
182
+ ) => {
183
+ setLoadError(null);
184
+ setPendingDeleteTerminal({
185
+ terminalId,
186
+ tentacleName: terminalName,
187
+ workspaceMode: options?.workspaceMode ?? "shared",
188
+ intent: options?.intent ?? "delete-terminal",
189
+ });
190
+ },
191
+ [setLoadError],
192
+ );
193
+
194
+ const confirmDeleteTerminal = useCallback(async () => {
195
+ if (!pendingDeleteTerminal) {
196
+ return;
197
+ }
198
+
199
+ const { terminalId } = pendingDeleteTerminal;
200
+ try {
201
+ setLoadError(null);
202
+ setIsDeletingTerminalId(terminalId);
203
+ const encodedTerminalId = encodeURIComponent(terminalId);
204
+ const response = await fetch(`/api/terminals/${encodedTerminalId}`, {
205
+ method: "DELETE",
206
+ headers: {
207
+ Accept: "application/json",
208
+ },
209
+ });
210
+
211
+ if (!response.ok) {
212
+ throw new Error(`Unable to delete terminal (${response.status})`);
213
+ }
214
+
215
+ if (editingTerminalId === terminalId) {
216
+ setEditingTerminalId(null);
217
+ setTerminalNameDraft("");
218
+ }
219
+ setMinimizedTerminalIds((current) =>
220
+ current.filter((currentTerminalId) => currentTerminalId !== terminalId),
221
+ );
222
+
223
+ const nextColumns = await readColumns();
224
+ setColumns(nextColumns);
225
+ setPendingDeleteTerminal(null);
226
+ } catch {
227
+ setLoadError("Unable to delete terminal.");
228
+ } finally {
229
+ setIsDeletingTerminalId(null);
230
+ }
231
+ }, [
232
+ editingTerminalId,
233
+ pendingDeleteTerminal,
234
+ readColumns,
235
+ setColumns,
236
+ setLoadError,
237
+ setMinimizedTerminalIds,
238
+ ]);
239
+
240
+ const clearPendingDeleteTerminal = useCallback(() => {
241
+ setPendingDeleteTerminal(null);
242
+ }, []);
243
+
244
+ const cancelTerminalRename = useCallback(() => {
245
+ cancelTerminalNameSubmitRef.current = true;
246
+ setEditingTerminalId(null);
247
+ setTerminalNameDraft("");
248
+ }, []);
249
+
250
+ return {
251
+ editingTerminalId,
252
+ terminalNameDraft,
253
+ isCreatingTerminal,
254
+ isDeletingTerminalId,
255
+ pendingDeleteTerminal,
256
+ setTerminalNameDraft,
257
+ setEditingTerminalId,
258
+ beginTerminalNameEdit,
259
+ submitTerminalRename,
260
+ createTerminal,
261
+ requestDeleteTerminal,
262
+ confirmDeleteTerminal,
263
+ clearPendingDeleteTerminal,
264
+ cancelTerminalRename,
265
+ };
266
+ };
@@ -0,0 +1,23 @@
1
+ import { useEffect } from "react";
2
+ import type { Dispatch, SetStateAction } from "react";
3
+
4
+ import { retainActiveTerminalIds } from "../terminalState";
5
+ import type { TerminalView } from "../types";
6
+
7
+ type UseTerminalStateReconciliationOptions = {
8
+ columns: TerminalView;
9
+ setMinimizedTerminalIds: Dispatch<SetStateAction<string[]>>;
10
+ onActiveTerminalIdsChange?: (activeTerminalIds: ReadonlySet<string>) => void;
11
+ };
12
+
13
+ export const useTerminalStateReconciliation = ({
14
+ columns,
15
+ setMinimizedTerminalIds,
16
+ onActiveTerminalIdsChange,
17
+ }: UseTerminalStateReconciliationOptions) => {
18
+ useEffect(() => {
19
+ const activeTerminalIds = new Set(columns.map((entry) => entry.terminalId));
20
+ setMinimizedTerminalIds((current) => retainActiveTerminalIds(current, activeTerminalIds));
21
+ onActiveTerminalIdsChange?.(activeTerminalIds);
22
+ }, [columns, onActiveTerminalIdsChange, setMinimizedTerminalIds]);
23
+ };
@@ -0,0 +1,43 @@
1
+ import { buildUsageHeatmapUrl } from "../../runtime/runtimeEndpoints";
2
+ import { usePollingData } from "./usePollingData";
3
+
4
+ export type UsageSlice = {
5
+ key: string;
6
+ tokens: number;
7
+ };
8
+
9
+ export type UsageDayEntry = {
10
+ date: string;
11
+ totalTokens: number;
12
+ projects: UsageSlice[];
13
+ models: UsageSlice[];
14
+ sessions: number;
15
+ };
16
+
17
+ export type UsageChartData = {
18
+ days: UsageDayEntry[];
19
+ projects: string[];
20
+ models: string[];
21
+ };
22
+
23
+ const POLL_INTERVAL_MS = 120_000;
24
+
25
+ const normalize = (raw: unknown): UsageChartData | null => raw as UsageChartData | null;
26
+
27
+ const fallback = (): UsageChartData => ({ days: [], projects: [], models: [] });
28
+
29
+ export const useUsageHeatmapPolling = (options: { enabled: boolean }) => {
30
+ const { data, isLoading, refresh } = usePollingData<UsageChartData>({
31
+ fetchUrl: buildUsageHeatmapUrl("all"),
32
+ intervalMs: POLL_INTERVAL_MS,
33
+ normalize,
34
+ fallback,
35
+ enabled: options.enabled,
36
+ });
37
+
38
+ return {
39
+ heatmapData: data,
40
+ isLoadingHeatmap: isLoading,
41
+ refreshHeatmap: refresh,
42
+ };
43
+ };
@@ -0,0 +1,80 @@
1
+ import type { WorkspaceSetupSnapshot, WorkspaceSetupStepId } from "@octogent/core";
2
+ import { useCallback, useEffect, useState } from "react";
3
+
4
+ import { buildWorkspaceSetupStepUrl, buildWorkspaceSetupUrl } from "../../runtime/runtimeEndpoints";
5
+
6
+ type UseWorkspaceSetupResult = {
7
+ workspaceSetup: WorkspaceSetupSnapshot | null;
8
+ isWorkspaceSetupLoading: boolean;
9
+ workspaceSetupError: string | null;
10
+ refreshWorkspaceSetup: () => Promise<WorkspaceSetupSnapshot | null>;
11
+ runWorkspaceSetupStep: (stepId: WorkspaceSetupStepId) => Promise<WorkspaceSetupSnapshot | null>;
12
+ };
13
+
14
+ const readErrorMessage = async (response: Response, fallback: string) => {
15
+ try {
16
+ const payload = (await response.json()) as { error?: unknown };
17
+ return typeof payload.error === "string" ? payload.error : fallback;
18
+ } catch {
19
+ return fallback;
20
+ }
21
+ };
22
+
23
+ export const useWorkspaceSetup = (): UseWorkspaceSetupResult => {
24
+ const [workspaceSetup, setWorkspaceSetup] = useState<WorkspaceSetupSnapshot | null>(null);
25
+ const [isWorkspaceSetupLoading, setIsWorkspaceSetupLoading] = useState(true);
26
+ const [workspaceSetupError, setWorkspaceSetupError] = useState<string | null>(null);
27
+
28
+ const refreshWorkspaceSetup = useCallback(async () => {
29
+ try {
30
+ setWorkspaceSetupError(null);
31
+ const response = await fetch(buildWorkspaceSetupUrl(), {
32
+ headers: { Accept: "application/json" },
33
+ });
34
+ if (!response.ok) {
35
+ throw new Error(await readErrorMessage(response, "Unable to load workspace setup."));
36
+ }
37
+ const payload = (await response.json()) as WorkspaceSetupSnapshot;
38
+ setWorkspaceSetup(payload);
39
+ return payload;
40
+ } catch (error) {
41
+ const message = error instanceof Error ? error.message : "Unable to load workspace setup.";
42
+ setWorkspaceSetupError(message);
43
+ return null;
44
+ } finally {
45
+ setIsWorkspaceSetupLoading(false);
46
+ }
47
+ }, []);
48
+
49
+ const runWorkspaceSetupStep = useCallback(async (stepId: WorkspaceSetupStepId) => {
50
+ try {
51
+ setWorkspaceSetupError(null);
52
+ const response = await fetch(buildWorkspaceSetupStepUrl(stepId), {
53
+ method: "POST",
54
+ headers: { Accept: "application/json" },
55
+ });
56
+ if (!response.ok) {
57
+ throw new Error(await readErrorMessage(response, `Unable to run ${stepId}.`));
58
+ }
59
+ const payload = (await response.json()) as WorkspaceSetupSnapshot;
60
+ setWorkspaceSetup(payload);
61
+ return payload;
62
+ } catch (error) {
63
+ const message = error instanceof Error ? error.message : `Unable to run ${stepId}.`;
64
+ setWorkspaceSetupError(message);
65
+ return null;
66
+ }
67
+ }, []);
68
+
69
+ useEffect(() => {
70
+ void refreshWorkspaceSetup();
71
+ }, [refreshWorkspaceSetup]);
72
+
73
+ return {
74
+ workspaceSetup,
75
+ isWorkspaceSetupLoading,
76
+ workspaceSetupError,
77
+ refreshWorkspaceSetup,
78
+ runWorkspaceSetupStep,
79
+ };
80
+ };
@@ -0,0 +1,31 @@
1
+ import { PRIMARY_NAV_MAX, type PrimaryNavIndex } from "./constants";
2
+ const MAX_TICKER_QUERY_LENGTH = 16;
3
+ const TICKER_QUERY_ALLOWED_PATTERN = /[^A-Z0-9._/-]/g;
4
+
5
+ export const isEditableEventTarget = (target: EventTarget | null): boolean => {
6
+ if (
7
+ target instanceof HTMLInputElement ||
8
+ target instanceof HTMLTextAreaElement ||
9
+ target instanceof HTMLSelectElement
10
+ ) {
11
+ return true;
12
+ }
13
+
14
+ if (!(target instanceof HTMLElement)) {
15
+ return false;
16
+ }
17
+
18
+ return target.isContentEditable === true || target.contentEditable === "true";
19
+ };
20
+
21
+ export const parsePrimaryNavKey = (key: string): PrimaryNavIndex | null => {
22
+ const n = Number.parseInt(key, 10);
23
+ if (Number.isNaN(n) || n < 1 || n > PRIMARY_NAV_MAX) {
24
+ return null;
25
+ }
26
+
27
+ return n as PrimaryNavIndex;
28
+ };
29
+
30
+ export const normalizeTickerQueryInput = (value: string): string =>
31
+ value.toUpperCase().replace(TICKER_QUERY_ALLOWED_PATTERN, "").slice(0, MAX_TICKER_QUERY_LENGTH);
@@ -0,0 +1,145 @@
1
+ import { asNumber, asRecord, asString } from "@octogent/core";
2
+
3
+ import type {
4
+ MonitorConfigSnapshot,
5
+ MonitorFeedSnapshot,
6
+ MonitorPost,
7
+ MonitorUsageSnapshot,
8
+ } from "./types";
9
+
10
+ const normalizeMonitorUsageSnapshot = (value: unknown): MonitorUsageSnapshot | null => {
11
+ const record = asRecord(value);
12
+ if (!record) {
13
+ return null;
14
+ }
15
+
16
+ const status = record.status;
17
+ if (status !== "ok" && status !== "unavailable" && status !== "error") {
18
+ return null;
19
+ }
20
+
21
+ return {
22
+ status,
23
+ source: record.source === "x-api" ? "x-api" : "none",
24
+ fetchedAt: asString(record.fetchedAt) ?? new Date().toISOString(),
25
+ message: asString(record.message),
26
+ cap: asNumber(record.cap),
27
+ used: asNumber(record.used),
28
+ remaining: asNumber(record.remaining),
29
+ resetAt: asString(record.resetAt),
30
+ };
31
+ };
32
+
33
+ const normalizeMonitorPost = (value: unknown): MonitorPost | null => {
34
+ const record = asRecord(value);
35
+ if (!record) {
36
+ return null;
37
+ }
38
+
39
+ const id = asString(record.id);
40
+ const text = asString(record.text);
41
+ const author = asString(record.author);
42
+ const createdAt = asString(record.createdAt);
43
+ const permalink = asString(record.permalink);
44
+ const likeCount = asNumber(record.likeCount);
45
+ const matchedQueryTerm = asString(record.matchedQueryTerm);
46
+
47
+ if (!id || !text || !author || !createdAt || !permalink || likeCount === null) {
48
+ return null;
49
+ }
50
+
51
+ return {
52
+ source: "x",
53
+ id,
54
+ text,
55
+ author,
56
+ createdAt,
57
+ permalink,
58
+ likeCount: Math.max(0, Math.floor(likeCount)),
59
+ matchedQueryTerm,
60
+ };
61
+ };
62
+
63
+ export const normalizeMonitorConfigSnapshot = (value: unknown): MonitorConfigSnapshot | null => {
64
+ const record = asRecord(value);
65
+ if (!record || record.providerId !== "x") {
66
+ return null;
67
+ }
68
+
69
+ const queryTerms = Array.isArray(record.queryTerms)
70
+ ? record.queryTerms.filter((term): term is string => typeof term === "string")
71
+ : [];
72
+ const refreshPolicy = asRecord(record.refreshPolicy);
73
+ const searchWindowDaysRaw = asNumber(refreshPolicy?.searchWindowDays);
74
+ const searchWindowDays =
75
+ searchWindowDaysRaw === 1 || searchWindowDaysRaw === 3 || searchWindowDaysRaw === 7
76
+ ? searchWindowDaysRaw
77
+ : 7;
78
+ const providers = asRecord(record.providers);
79
+ const xProvider = providers ? asRecord(providers.x) : null;
80
+ const credentials = xProvider ? asRecord(xProvider.credentials) : null;
81
+ if (!credentials) {
82
+ return null;
83
+ }
84
+
85
+ return {
86
+ providerId: "x",
87
+ queryTerms,
88
+ refreshPolicy: {
89
+ maxCacheAgeMs: asNumber(refreshPolicy?.maxCacheAgeMs) ?? 24 * 60 * 60 * 1000,
90
+ maxPosts: asNumber(refreshPolicy?.maxPosts) ?? 30,
91
+ searchWindowDays,
92
+ },
93
+ providers: {
94
+ x: {
95
+ credentials: {
96
+ isConfigured: credentials.isConfigured === true,
97
+ bearerTokenHint: asString(credentials.bearerTokenHint),
98
+ apiKeyHint: asString(credentials.apiKeyHint),
99
+ hasApiSecret: credentials.hasApiSecret === true,
100
+ hasAccessToken: credentials.hasAccessToken === true,
101
+ hasAccessTokenSecret: credentials.hasAccessTokenSecret === true,
102
+ updatedAt: asString(credentials.updatedAt),
103
+ },
104
+ },
105
+ },
106
+ };
107
+ };
108
+
109
+ export const normalizeMonitorFeedSnapshot = (value: unknown): MonitorFeedSnapshot | null => {
110
+ const record = asRecord(value);
111
+ if (!record || record.providerId !== "x") {
112
+ return null;
113
+ }
114
+
115
+ const queryTerms = Array.isArray(record.queryTerms)
116
+ ? record.queryTerms.filter((term): term is string => typeof term === "string")
117
+ : [];
118
+ const refreshPolicy = asRecord(record.refreshPolicy);
119
+ const searchWindowDaysRaw = asNumber(refreshPolicy?.searchWindowDays);
120
+ const searchWindowDays =
121
+ searchWindowDaysRaw === 1 || searchWindowDaysRaw === 3 || searchWindowDaysRaw === 7
122
+ ? searchWindowDaysRaw
123
+ : 7;
124
+ const posts = Array.isArray(record.posts)
125
+ ? record.posts
126
+ .map((post) => normalizeMonitorPost(post))
127
+ .filter((post): post is MonitorPost => post !== null)
128
+ : [];
129
+
130
+ return {
131
+ providerId: "x",
132
+ queryTerms,
133
+ refreshPolicy: {
134
+ maxCacheAgeMs: asNumber(refreshPolicy?.maxCacheAgeMs) ?? 24 * 60 * 60 * 1000,
135
+ maxPosts: asNumber(refreshPolicy?.maxPosts) ?? 30,
136
+ searchWindowDays,
137
+ },
138
+ lastFetchedAt: asString(record.lastFetchedAt),
139
+ staleAfter: asString(record.staleAfter),
140
+ isStale: record.isStale === true,
141
+ lastError: asString(record.lastError),
142
+ posts,
143
+ usage: normalizeMonitorUsageSnapshot(record.usage),
144
+ };
145
+ };