@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,715 @@
1
+ import { useEffect, useRef } from "react";
2
+
3
+ /*
4
+ * Pixel-art octopus rendered via Canvas 2D.
5
+ * Shape based on classic pixel ghost/invader: outlined dome, square eyes,
6
+ * jagged 3-tooth tentacle bottom.
7
+ *
8
+ * Sprite is 16 × 14 pixels. Canvas height is padded by BOUNCE_PAD rows
9
+ * so bounce/float animations have vertical room without clipping.
10
+ */
11
+
12
+ const DEFAULT_SCALE = 14; // CSS pixels per sprite pixel
13
+ const BOUNCE_PAD = 2; // extra canvas rows reserved for vertical animations
14
+ // Extra rows above the sprite for overlays (ZZZ, accessories).
15
+ const ZZZ_PAD = 7; // sleepy ZZZ needs 7 rows
16
+ const ACCESSORY_PAD = 4; // tallest hair (mohawk/curly) reaches row -3
17
+ const ZZZ_COLOR = "#7ec8e3"; // soft sky-blue for the floating z glyphs
18
+ const HAIR_COLOR = "#4a2c0a"; // dark brown
19
+
20
+ const B = "B"; // body (accent fill)
21
+ const O = "O"; // outline (dark)
22
+ const E = "E"; // eye (dark)
23
+ const _ = ""; // transparent
24
+
25
+ // ─── HEAD construction ───────────────────────────────────────────────────────
26
+ // Rows 0-2 and 6-9 are identical across all expressions.
27
+ // Rows 3-5 carry the expression detail; buildHead() assembles the full array.
28
+
29
+ // prettier-ignore
30
+ const HEAD_TOP: string[][] = [
31
+ [_, _, _, _, O, O, O, O, O, O, O, O, _, _, _, _], // 0
32
+ [_, _, _, O, B, B, B, B, B, B, B, B, O, _, _, _], // 1
33
+ [_, _, O, B, B, B, B, B, B, B, B, B, B, O, _, _], // 2
34
+ ];
35
+
36
+ // Angry variant — outer brow pixel at col 4 (left) and col 11 (right) in row 2.
37
+ // Combined with FACE_ANGRY row 3 inner pixels (col 5 / col 10), this forms a
38
+ // diagonal V-slash brow: outer-high → inner-low on each side.
39
+ // prettier-ignore
40
+ const HEAD_TOP_ANGRY: string[][] = [
41
+ [_, _, _, _, O, O, O, O, O, O, O, O, _, _, _, _], // 0
42
+ [_, _, _, O, B, B, B, B, B, B, B, B, O, _, _, _], // 1
43
+ [_, _, O, B, O, B, B, B, B, B, B, O, B, O, _, _], // 2 cols 4 and 11 → outer brow start
44
+ ];
45
+
46
+ // prettier-ignore
47
+ const HEAD_BODY: string[][] = [
48
+ [_, O, B, B, B, B, B, B, B, B, B, B, B, B, O, _], // 6
49
+ [_, O, B, B, B, B, B, B, B, B, B, B, B, B, O, _], // 7
50
+ [_, O, B, B, B, B, B, B, B, B, B, B, B, B, O, _], // 8
51
+ [_, O, B, B, B, B, B, B, B, B, B, B, B, B, O, _], // 9
52
+ ];
53
+
54
+ // Happy — open mouth: solid black rectangle in rows 7-8, cols 5-10.
55
+ // prettier-ignore
56
+ const HEAD_BODY_HAPPY: string[][] = [
57
+ [_, O, B, B, B, B, B, B, B, B, B, B, B, B, O, _], // 6
58
+ [_, O, B, B, B, O, O, O, O, O, O, O, B, B, O, _], // 7 top of open mouth (cols 5-10)
59
+ [_, O, B, B, B, O, O, O, O, O, O, O, B, B, O, _], // 8 bottom of open mouth (cols 5-10)
60
+ [_, O, B, B, B, B, B, B, B, B, B, B, B, B, O, _], // 9
61
+ ];
62
+
63
+ // Angry — open mouth, narrower than happy to read as a shout/snarl.
64
+ // prettier-ignore
65
+ const HEAD_BODY_ANGRY: string[][] = [
66
+ [_, O, B, B, B, B, B, B, B, B, B, B, B, B, O, _], // 6
67
+ [_, O, B, B, B, B, O, O, O, O, O, B, B, B, O, _], // 7 top of open mouth (cols 6-10)
68
+ [_, O, B, B, B, B, O, O, O, O, O, B, B, B, O, _], // 8 bottom of open mouth (cols 6-10)
69
+ [_, O, B, B, B, B, B, B, B, B, B, B, B, B, O, _], // 9
70
+ ];
71
+
72
+ // Normal — 2×2 square eyes (rows 4-5).
73
+ // prettier-ignore
74
+ const FACE_NORMAL: string[][] = [
75
+ [_, O, B, B, B, B, B, B, B, B, B, B, B, B, O, _], // 3
76
+ [_, O, B, B, E, E, B, B, B, B, E, E, B, B, O, _], // 4 eyes
77
+ [_, O, B, B, E, E, B, B, B, B, E, E, B, B, O, _], // 5 eyes
78
+ ];
79
+
80
+ // Happy — upward-curved eyes (^_^ style): bottom row lit, top row clear.
81
+ // The open space above the pupil makes the eye read as curving upward = smile.
82
+ // prettier-ignore
83
+ const FACE_HAPPY: string[][] = [
84
+ [_, O, B, B, B, B, B, B, B, B, B, B, B, B, O, _], // 3
85
+ [_, O, B, B, B, B, B, B, B, B, B, B, B, B, O, _], // 4 clear = top of eye open
86
+ [_, O, B, B, E, E, B, B, B, B, E, E, B, B, O, _], // 5 bottom row lit = eyes curve up
87
+ ];
88
+
89
+ // Sleepy — heavy eyelid (solid outline stripe) with tiny pupils peeking below.
90
+ // prettier-ignore
91
+ const FACE_SLEEPY: string[][] = [
92
+ [_, O, B, B, B, B, B, B, B, B, B, B, B, B, O, _], // 3
93
+ [_, O, B, B, O, O, B, B, B, B, O, O, B, B, O, _], // 4 closed eyelid (outline color)
94
+ [_, O, B, B, E, B, B, B, B, B, B, E, B, B, O, _], // 5 tiny pupils peeking
95
+ ];
96
+
97
+ // Angry — brow diagonal continues: outer pixel lands at col 4/11 here too,
98
+ // making a 2-pixel-wide brow that reads clearly as a hard scowl.
99
+ // prettier-ignore
100
+ const FACE_ANGRY: string[][] = [
101
+ [_, O, B, O, O, B, B, B, B, B, B, O, O, B, O, _], // 3 both cols 3-4 left brow and 11-12 right brow
102
+ [_, O, B, B, E, E, B, B, B, B, E, E, B, B, O, _], // 4 eyes
103
+ [_, O, B, B, E, E, B, B, B, B, E, E, B, B, O, _], // 5 eyes
104
+ ];
105
+
106
+ // Surprised — eyes extend up into row 3, making them taller (3-row tall eyes).
107
+ // prettier-ignore
108
+ const FACE_SURPRISED: string[][] = [
109
+ [_, O, B, B, E, E, B, B, B, B, E, E, B, B, O, _], // 3 eyes start early
110
+ [_, O, B, B, E, E, B, B, B, B, E, E, B, B, O, _], // 4 eyes
111
+ [_, O, B, B, E, E, B, B, B, B, E, E, B, B, O, _], // 5 eyes
112
+ ];
113
+
114
+ function buildHead(
115
+ face: string[][],
116
+ topRows: string[][] = HEAD_TOP,
117
+ bodyRows: string[][] = HEAD_BODY,
118
+ ): string[][] {
119
+ return [...topRows, ...face, ...bodyRows];
120
+ }
121
+
122
+ // ─── Tentacle / tail variants ────────────────────────────────────────────────
123
+
124
+ // Static tentacle split — always drawn.
125
+ // prettier-ignore
126
+ const TENTACLE_TOP: string[][] = [
127
+ [_, O, B, B, O, _, O, B, B, O, _, O, B, B, O, _], // 10 three equal splits
128
+ ];
129
+
130
+ // 3-tooth rectangular bottom — neutral (square ghost-style bumps).
131
+ // prettier-ignore
132
+ const TAIL_NEUTRAL: string[][] = [
133
+ [_, O, B, B, O, _, O, B, B, O, _, O, B, B, O, _], // 11
134
+ [_, O, B, B, O, _, O, B, B, O, _, O, B, B, O, _], // 12
135
+ [_, _, O, O, _, _, _, O, O, _, _, _, O, O, _, _], // 13 bottom caps
136
+ ];
137
+
138
+ // Legs bend right — top row stays anchored, lower rows shift 1px right.
139
+ // prettier-ignore
140
+ const TAIL_RIGHT: string[][] = [
141
+ [_, O, B, B, O, _, O, B, B, O, _, O, B, B, O, _], // 11 straight (pivot)
142
+ [_, _, O, B, B, O, _, O, B, B, O, _, O, B, B, O], // 12 bent 1px right
143
+ [_, _, _, O, O, _, _, _, O, O, _, _, _, O, O, _], // 13 caps follow bend
144
+ ];
145
+
146
+ // Legs bend left — top row stays anchored, lower rows shift 1px left.
147
+ // prettier-ignore
148
+ const TAIL_LEFT: string[][] = [
149
+ [_, O, B, B, O, _, O, B, B, O, _, O, B, B, O, _], // 11 straight (pivot)
150
+ [O, B, B, O, _, O, B, B, O, _, O, B, B, O, _, _], // 12 bent 1px left
151
+ [_, O, O, _, _, _, O, O, _, _, _, O, O, _, _, _], // 13 caps follow bend
152
+ ];
153
+
154
+ // Sway: center → right → center → left → repeat
155
+ const SWAY_FRAMES_TAILS = [TAIL_NEUTRAL, TAIL_RIGHT, TAIL_NEUTRAL, TAIL_LEFT];
156
+
157
+ // Walk-up: all three legs extend and retract in unison.
158
+ // short (1 row + cap) → medium (2 rows + cap) → extended (3 rows + cap) → medium → repeat
159
+ // prettier-ignore
160
+ const WALKUP_0: string[][] = [
161
+ [_, O, B, B, O, _, O, B, B, O, _, O, B, B, O, _], // 10 all start
162
+ [_, _, O, O, _, _, _, O, O, _, _, _, O, O, _, _], // 11 all cap
163
+ [_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _], // 12 empty
164
+ [_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _], // 13 empty
165
+ ];
166
+ // prettier-ignore
167
+ const WALKUP_1: string[][] = [
168
+ [_, O, B, B, O, _, O, B, B, O, _, O, B, B, O, _], // 10 all start
169
+ [_, O, B, B, O, _, O, B, B, O, _, O, B, B, O, _], // 11 all continue
170
+ [_, _, O, O, _, _, _, O, O, _, _, _, O, O, _, _], // 12 all cap
171
+ [_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _], // 13 empty
172
+ ];
173
+ // prettier-ignore
174
+ const WALKUP_2: string[][] = [
175
+ [_, O, B, B, O, _, O, B, B, O, _, O, B, B, O, _], // 10 all start
176
+ [_, O, B, B, O, _, O, B, B, O, _, O, B, B, O, _], // 11 all continue
177
+ [_, O, B, B, O, _, O, B, B, O, _, O, B, B, O, _], // 12 all continue
178
+ [_, _, O, O, _, _, _, O, O, _, _, _, O, O, _, _], // 13 all cap
179
+ ];
180
+ // prettier-ignore
181
+ const WALKUP_3: string[][] = [
182
+ [_, O, B, B, O, _, O, B, B, O, _, O, B, B, O, _], // 10 all start
183
+ [_, O, B, B, O, _, O, B, B, O, _, O, B, B, O, _], // 11 all continue
184
+ [_, _, O, O, _, _, _, O, O, _, _, _, O, O, _, _], // 12 all cap
185
+ [_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _], // 13 empty
186
+ ];
187
+ const WALKUP_FRAMES = [WALKUP_0, WALKUP_1, WALKUP_2, WALKUP_3];
188
+
189
+ // ─── Bounce / float types (needed by walk frames below) ─────────────────────
190
+
191
+ type SpriteFrame = {
192
+ bottom: string[][];
193
+ /** Shift the sprite down by this many pixels (0..BOUNCE_PAD). */
194
+ yOffset?: number;
195
+ };
196
+
197
+ // Walk: lateral stepping — legs bend at a "knee" with feet kicking sideways.
198
+ // Outer legs (L/R) oppose the middle leg direction. Body bobs via yOffset.
199
+
200
+ // Neutral stance — all legs straight down, caps centered.
201
+ // prettier-ignore
202
+ const WALK_S0: string[][] = [
203
+ [_, O, B, B, O, _, O, B, B, O, _, O, B, B, O, _], // 10 anchor
204
+ [_, O, B, B, O, _, O, B, B, O, _, O, B, B, O, _], // 11 legs straight
205
+ [_, _, O, O, _, _, _, O, O, _, _, _, O, O, _, _], // 12 caps centered
206
+ [_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _], // 13 empty
207
+ ];
208
+
209
+ // Outer legs kick right, middle kicks left — knee narrows (OBO), cap follows.
210
+ // prettier-ignore
211
+ const WALK_S1: string[][] = [
212
+ [_, O, B, B, O, _, O, B, B, O, _, O, B, B, O, _], // 10 anchor
213
+ [_, _, O, B, O, _, O, B, O, _, _, _, O, B, O, _], // 11 bent: L→R, M→L, R→R
214
+ [_, _, _, O, O, _, O, O, _, _, _, _, _, O, O, _], // 12 caps follow bend
215
+ [_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _], // 13 empty
216
+ ];
217
+
218
+ // Outer legs kick left, middle kicks right — mirror of S1.
219
+ // prettier-ignore
220
+ const WALK_S3: string[][] = [
221
+ [_, O, B, B, O, _, O, B, B, O, _, O, B, B, O, _], // 10 anchor
222
+ [_, O, B, O, _, _, _, O, B, O, _, O, B, O, _, _], // 11 bent: L→L, M→R, R→L
223
+ [_, O, O, _, _, _, _, _, O, O, _, O, O, _, _, _], // 12 caps follow bend
224
+ [_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _], // 13 empty
225
+ ];
226
+
227
+ const JOG_FRAMES: SpriteFrame[] = [
228
+ { bottom: WALK_S0, yOffset: 1 }, // neutral (dip)
229
+ { bottom: WALK_S1, yOffset: 0 }, // step right
230
+ { bottom: WALK_S0, yOffset: 1 }, // neutral (dip)
231
+ { bottom: WALK_S3, yOffset: 0 }, // step left
232
+ ];
233
+
234
+ // Walk: wave stride — motion ripples across tentacles left → right.
235
+ // Three leg states: neutral (straight), mid (knee bends, foot stays), bent (full step).
236
+ // The stepping leg cycles L → M → R. The leg before the stepper shows the mid state,
237
+ // creating a smooth wave: neutral → mid → bent → neutral.
238
+
239
+ // Frame 0: L=bent, M=neutral, R=mid (trailing from previous cycle)
240
+ // prettier-ignore
241
+ const WALK_WAVE_0: string[][] = [
242
+ [_, O, B, B, O, _, O, B, B, O, _, O, B, B, O, _], // 10 upper legs
243
+ [_, _, _, B, B, _, _, B, B, _, _, _, _, B, B, _], // 11 L bent knee, M center, R mid knee
244
+ [_, _, _, O, B, O, O, B, B, O, _, O, B, B, O, _], // 12 L bent(narrow), M neutral, R mid(full)
245
+ [_, _, _, _, O, O, _, O, O, _, _, _, O, O, _, _], // 13 caps follow
246
+ ];
247
+
248
+ // Frame 1: M=bent, L=mid (trailing), R=neutral
249
+ // prettier-ignore
250
+ const WALK_WAVE_1: string[][] = [
251
+ [_, O, B, B, O, _, O, B, B, O, _, O, B, B, O, _], // 10 upper legs
252
+ [_, _, _, B, B, _, _, _, B, B, _, _, B, B, _, _], // 11 L mid knee, M bent knee, R center
253
+ [_, O, B, B, O, _, _, _, O, B, O, O, B, B, O, _], // 12 L mid(full), M bent(narrow), R neutral
254
+ [_, _, O, O, _, _, _, _, _, O, O, _, O, O, _, _], // 13 caps follow
255
+ ];
256
+
257
+ // Frame 2: R=bent, M=mid (trailing), L=neutral
258
+ // prettier-ignore
259
+ const WALK_WAVE_2: string[][] = [
260
+ [_, O, B, B, O, _, O, B, B, O, _, O, B, B, O, _], // 10 upper legs
261
+ [_, _, B, B, _, _, _, _, B, B, _, _, _, B, B, _], // 11 L center, M mid knee, R bent knee
262
+ [_, O, B, B, O, _, O, B, B, O, _, _, _, O, B, O], // 12 L neutral, M mid(full), R bent(narrow)
263
+ [_, _, O, O, _, _, _, O, O, _, _, _, _, _, O, O], // 13 caps follow
264
+ ];
265
+
266
+ const WALK_FRAMES: SpriteFrame[] = [
267
+ { bottom: WALK_WAVE_0 },
268
+ { bottom: WALK_WAVE_1 },
269
+ { bottom: WALK_WAVE_2 },
270
+ ];
271
+
272
+ // ─── Bounce / float animations ───────────────────────────────────────────────
273
+ // Canvas height includes BOUNCE_PAD extra rows; yOffset shifts the sprite down
274
+ // so it can move upward without clipping. Sequence: squat → rise → apex → fall.
275
+
276
+ const BOUNCE_STRAIGHT = [...TENTACLE_TOP, ...TAIL_NEUTRAL];
277
+
278
+ // Crouch — outer legs splay outward (L bends left, R bends right), coiling to jump.
279
+ // prettier-ignore
280
+ const BOUNCE_CROUCH: string[][] = [
281
+ [_, O, B, B, O, _, O, B, B, O, _, O, B, B, O, _], // 10 anchor
282
+ [_, O, B, O, _, _, O, B, B, O, _, _, O, B, O, _], // 11 L→left, M straight, R→right
283
+ [_, O, O, _, _, _, _, O, O, _, _, _, _, O, O, _], // 12 caps follow splay
284
+ [_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _], // 13 empty
285
+ ];
286
+
287
+ // Apex — legs tuck short (retracted), airborne.
288
+ // prettier-ignore
289
+ const BOUNCE_TUCKED: string[][] = [
290
+ [_, O, B, B, O, _, O, B, B, O, _, O, B, B, O, _], // 10 anchor
291
+ [_, _, O, O, _, _, _, O, O, _, _, _, O, O, _, _], // 11 caps only (short legs)
292
+ [_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _], // 12 empty
293
+ [_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _], // 13 empty
294
+ ];
295
+
296
+ const BOUNCE_FRAMES: SpriteFrame[] = [
297
+ { bottom: BOUNCE_CROUCH, yOffset: 2 }, // crouch (splay + low)
298
+ { bottom: BOUNCE_STRAIGHT, yOffset: 1 }, // launch (straighten + rise)
299
+ { bottom: BOUNCE_TUCKED, yOffset: 0 }, // apex (tucked + highest)
300
+ { bottom: BOUNCE_STRAIGHT, yOffset: 1 }, // fall (straight + descend)
301
+ ];
302
+
303
+ // Float: slow buoyancy — dwell longer at top and bottom.
304
+ const FLOAT_FRAMES: SpriteFrame[] = [
305
+ { bottom: BOUNCE_STRAIGHT, yOffset: 0 },
306
+ { bottom: BOUNCE_STRAIGHT, yOffset: 0 },
307
+ { bottom: BOUNCE_STRAIGHT, yOffset: 1 },
308
+ { bottom: BOUNCE_STRAIGHT, yOffset: 2 },
309
+ { bottom: BOUNCE_STRAIGHT, yOffset: 2 },
310
+ { bottom: BOUNCE_STRAIGHT, yOffset: 1 },
311
+ ];
312
+
313
+ // ─── Frame timing ────────────────────────────────────────────────────────────
314
+
315
+ const JOG_FRAME_MS = 220;
316
+ const WALK_FRAME_MS = 320;
317
+ const SWAY_FRAME_MS = 350;
318
+ const FLOAT_FRAME_MS = 420;
319
+
320
+ // ─── Types ───────────────────────────────────────────────────────────────────
321
+
322
+ const SPRITE_W = 16;
323
+ // HEAD_TOP(3) + face(3) + HEAD_BODY(4) + TENTACLE_TOP(1) + TAIL_NEUTRAL(3) = 14
324
+ const SPRITE_H =
325
+ HEAD_TOP.length +
326
+ FACE_NORMAL.length +
327
+ HEAD_BODY.length +
328
+ TENTACLE_TOP.length +
329
+ TAIL_NEUTRAL.length;
330
+
331
+ export type OctopusAnimation = "idle" | "sway" | "walk" | "jog" | "swim-up" | "bounce" | "float";
332
+ // "sleepy" is reserved for idle/inactive tentacles — never assign it randomly on creation.
333
+ export type OctopusExpression = "normal" | "happy" | "sleepy" | "angry" | "surprised";
334
+ export type OctopusAccessory = "none" | "long" | "mohawk" | "side-sweep" | "curly";
335
+
336
+ // ─── Accessories ──────────────────────────────────────────────────────────────
337
+ // Drawn as smooth vector shapes on the canvas (not pixel art) so they look
338
+ // good at any scale. All drawing is relative to the dome center/top.
339
+
340
+ const HEADS: Record<OctopusExpression, string[][]> = {
341
+ normal: buildHead(FACE_NORMAL),
342
+ happy: buildHead(FACE_HAPPY, HEAD_TOP, HEAD_BODY_HAPPY),
343
+ sleepy: buildHead(FACE_SLEEPY),
344
+ angry: buildHead(FACE_ANGRY, HEAD_TOP_ANGRY, HEAD_BODY_ANGRY),
345
+ surprised: buildHead(FACE_SURPRISED),
346
+ };
347
+
348
+ // ─── Drawing ─────────────────────────────────────────────────────────────────
349
+
350
+ function drawSprite(
351
+ ctx: CanvasRenderingContext2D,
352
+ accentColor: string,
353
+ frame: SpriteFrame,
354
+ head: string[][],
355
+ scale: number,
356
+ topPad: number,
357
+ ) {
358
+ ctx.clearRect(0, 0, SPRITE_W * scale, (topPad + SPRITE_H + BOUNCE_PAD) * scale);
359
+
360
+ const yOff = (frame.yOffset ?? 0) + topPad;
361
+ const layers = [...head, ...frame.bottom];
362
+ for (let y = 0; y < layers.length; y++) {
363
+ const row = layers[y];
364
+ if (!row) continue;
365
+ for (let x = 0; x < row.length; x++) {
366
+ const cell = row[x];
367
+ if (!cell) continue;
368
+ ctx.fillStyle = cell === E || cell === O ? "#000000" : accentColor;
369
+ ctx.fillRect(x * scale, (y + yOff) * scale, scale, scale);
370
+ }
371
+ }
372
+ }
373
+
374
+ // Three 3×5 Z glyphs staggered rising right→left above the sprite.
375
+ // Each Z: top-bar / 3-step diagonal (top-right→bottom-left) / bottom-bar.
376
+ // Phase 0: Z1 only · Phase 1: all three · Phase 2-3: hidden.
377
+ function drawZZZ(ctx: CanvasRenderingContext2D, scale: number, zzzPhase: number) {
378
+ if (zzzPhase >= 3) return; // phases 3-4 are the blank pause
379
+
380
+ ctx.fillStyle = ZZZ_COLOR;
381
+
382
+ // Z1 — lowest, right side. Rows 2-6, cols 13-15.
383
+ // prettier-ignore
384
+ const Z1: Array<[number, number]> = [
385
+ [13, 2],
386
+ [14, 2],
387
+ [15, 2],
388
+ [15, 3],
389
+ [14, 4],
390
+ [13, 5],
391
+ [13, 6],
392
+ [14, 6],
393
+ [15, 6],
394
+ ];
395
+
396
+ // Z2 — middle height. Rows 1-5, cols 9-11.
397
+ // prettier-ignore
398
+ const Z2: Array<[number, number]> = [
399
+ [9, 1],
400
+ [10, 1],
401
+ [11, 1],
402
+ [11, 2],
403
+ [10, 3],
404
+ [9, 4],
405
+ [9, 5],
406
+ [10, 5],
407
+ [11, 5],
408
+ ];
409
+
410
+ // Z3 — highest, left side. Rows 0-4, cols 5-7.
411
+ // prettier-ignore
412
+ const Z3: Array<[number, number]> = [
413
+ [5, 0],
414
+ [6, 0],
415
+ [7, 0],
416
+ [7, 1],
417
+ [6, 2],
418
+ [5, 3],
419
+ [5, 4],
420
+ [6, 4],
421
+ [7, 4],
422
+ ];
423
+
424
+ const pixels = zzzPhase === 0 ? Z1 : zzzPhase === 1 ? [...Z1, ...Z2] : [...Z1, ...Z2, ...Z3];
425
+ for (const [x, y] of pixels) {
426
+ ctx.fillRect(x * scale, y * scale, scale, scale);
427
+ }
428
+ }
429
+
430
+ // Draw accessory as smooth vector shapes on top of the pixel sprite.
431
+ // Dome top-center is at sprite pixel (8, 0) — used as anchor for all accessories.
432
+ function drawAccessory(
433
+ ctx: CanvasRenderingContext2D,
434
+ accessory: OctopusAccessory,
435
+ scale: number,
436
+ yOff: number,
437
+ hColor: string,
438
+ ) {
439
+ if (accessory === "none") return;
440
+
441
+ // Dome geometry in canvas pixels
442
+ const domeL = 4 * scale; // dome outline left edge (col 4)
443
+ const domeR = 12 * scale; // dome outline right edge (col 12)
444
+ const domeCX = 8 * scale; // dome center x
445
+ const domeTop = yOff * scale; // dome top y (row 0 of sprite)
446
+ const domeW = domeR - domeL;
447
+
448
+ ctx.save();
449
+ ctx.fillStyle = hColor;
450
+
451
+ switch (accessory) {
452
+ case "long": {
453
+ // Long hair — dome cap, two wide straight strands, zigzag bangs with center part.
454
+ // Shaped like the pixel-art wig: covers top, frames face, strands reach tentacle area.
455
+ const hairTop = domeTop - scale * 1.5;
456
+ const strandEnd = domeTop + scale * 10.5;
457
+ const bangY = domeTop + scale * 2.5; // bang line, just above face/eyes
458
+ // Strand edges — inner edges align with dome outline
459
+ const lOut = scale * 0.5;
460
+ const lIn = domeL; // col 4
461
+ const rIn = domeR; // col 12
462
+ const rOut = scale * 15.5;
463
+
464
+ // Single path for the entire hair shape
465
+ ctx.beginPath();
466
+ // Top center
467
+ ctx.moveTo(domeCX, hairTop);
468
+ // Arc over to left
469
+ ctx.quadraticCurveTo(lOut, hairTop, lOut, domeTop + scale * 2);
470
+ // Left strand straight down
471
+ ctx.lineTo(lOut, strandEnd);
472
+ // Left strand bottom
473
+ ctx.lineTo(lIn, strandEnd);
474
+ // Left inner edge up to bangs
475
+ ctx.lineTo(lIn, bangY);
476
+ // Bangs — zigzag W with center part
477
+ ctx.lineTo(lIn + scale * 1.5, bangY + scale * 1.8);
478
+ ctx.lineTo(domeCX - scale * 0.5, bangY + scale * 0.5);
479
+ ctx.lineTo(domeCX, bangY + scale * 1.2); // center part dip
480
+ ctx.lineTo(domeCX + scale * 0.5, bangY + scale * 0.5);
481
+ ctx.lineTo(rIn - scale * 1.5, bangY + scale * 1.8);
482
+ // Right inner edge from bangs down
483
+ ctx.lineTo(rIn, bangY);
484
+ ctx.lineTo(rIn, strandEnd);
485
+ // Right strand bottom
486
+ ctx.lineTo(rOut, strandEnd);
487
+ // Right strand straight up
488
+ ctx.lineTo(rOut, domeTop + scale * 2);
489
+ // Arc back to top center
490
+ ctx.quadraticCurveTo(rOut, hairTop, domeCX, hairTop);
491
+ ctx.closePath();
492
+
493
+ // Outline first (drawn behind fill via stroke order)
494
+ ctx.strokeStyle = "rgba(0,0,0,0.6)";
495
+ ctx.lineWidth = scale * 0.6;
496
+ ctx.stroke();
497
+ // Fill on top
498
+ ctx.fill();
499
+ break;
500
+ }
501
+ case "mohawk": {
502
+ // Spiky ridge along the dome center — three pointed triangles.
503
+
504
+ const baseY = domeTop + scale * 0.3;
505
+ const spikes: Array<[number, number, number]> = [
506
+ [domeCX - domeW * 0.15, domeTop - scale * 2, domeW * 0.2], // left spike
507
+ [domeCX, domeTop - scale * 3.2, domeW * 0.22], // center spike (tallest)
508
+ [domeCX + domeW * 0.18, domeTop - scale * 2.2, domeW * 0.2], // right spike
509
+ ];
510
+ for (const [cx, tipY, halfW] of spikes) {
511
+ ctx.beginPath();
512
+ ctx.moveTo(cx - halfW, baseY);
513
+ ctx.lineTo(cx, tipY);
514
+ ctx.lineTo(cx + halfW, baseY);
515
+ ctx.closePath();
516
+ ctx.fill();
517
+ }
518
+ // Base strip connecting the spikes
519
+ ctx.beginPath();
520
+ ctx.ellipse(domeCX, baseY, domeW * 0.35, scale * 0.8, 0, 0, Math.PI * 2);
521
+ ctx.fill();
522
+ break;
523
+ }
524
+ case "side-sweep": {
525
+ // Asymmetric bangs flowing to the left.
526
+
527
+ ctx.beginPath();
528
+ // Start from right side of dome
529
+ ctx.moveTo(domeCX + domeW * 0.2, domeTop + scale * 0.5);
530
+ // Sweep up and over to the left
531
+ ctx.quadraticCurveTo(domeCX, domeTop - scale * 2, domeL - scale * 1.5, domeTop - scale * 0.5);
532
+ // Bang tip curves down
533
+ ctx.quadraticCurveTo(
534
+ domeL - scale * 2,
535
+ domeTop + scale * 1.5,
536
+ domeL - scale * 1,
537
+ domeTop + scale * 3,
538
+ );
539
+ // Curve back along the dome edge
540
+ ctx.quadraticCurveTo(domeL - scale * 0.2, domeTop + scale * 2, domeL, domeTop + scale * 0.5);
541
+ // Follow dome top back to start
542
+ ctx.quadraticCurveTo(
543
+ domeCX,
544
+ domeTop + scale * 0.2,
545
+ domeCX + domeW * 0.2,
546
+ domeTop + scale * 0.5,
547
+ );
548
+ ctx.closePath();
549
+ ctx.fill();
550
+ break;
551
+ }
552
+ case "curly": {
553
+ // Curly poof — many small bumpy circles forming a textured cloud.
554
+ // Smaller radius so individual curls are visible, packed densely.
555
+ const r = domeW * 0.18;
556
+ const centers: Array<[number, number]> = [
557
+ // Bottom row — wide, covers head sides
558
+ [domeCX - domeW * 0.5, domeTop + scale * 1.2],
559
+ [domeCX - domeW * 0.25, domeTop + scale * 1.2],
560
+ [domeCX, domeTop + scale * 1.2],
561
+ [domeCX + domeW * 0.25, domeTop + scale * 1.2],
562
+ [domeCX + domeW * 0.5, domeTop + scale * 1.2],
563
+ // Row 2
564
+ [domeCX - domeW * 0.5, domeTop + scale * 0.3],
565
+ [domeCX - domeW * 0.2, domeTop + scale * 0.3],
566
+ [domeCX + domeW * 0.2, domeTop + scale * 0.3],
567
+ [domeCX + domeW * 0.5, domeTop + scale * 0.3],
568
+ // Row 3
569
+ [domeCX - domeW * 0.4, domeTop - scale * 0.3],
570
+ [domeCX - domeW * 0.12, domeTop - scale * 0.3],
571
+ [domeCX + domeW * 0.12, domeTop - scale * 0.3],
572
+ [domeCX + domeW * 0.4, domeTop - scale * 0.3],
573
+ // Row 3
574
+ [domeCX - domeW * 0.3, domeTop - scale * 1.1],
575
+ [domeCX, domeTop - scale * 1.1],
576
+ [domeCX + domeW * 0.3, domeTop - scale * 1.1],
577
+ // Row 4
578
+ [domeCX - domeW * 0.18, domeTop - scale * 1.8],
579
+ [domeCX + domeW * 0.18, domeTop - scale * 1.8],
580
+ // Top
581
+ [domeCX, domeTop - scale * 2.4],
582
+ ];
583
+ for (const [cx, cy] of centers) {
584
+ ctx.beginPath();
585
+ ctx.arc(cx, cy, r, 0, Math.PI * 2);
586
+ ctx.fill();
587
+ }
588
+ break;
589
+ }
590
+ }
591
+
592
+ ctx.restore();
593
+ }
594
+
595
+ // ─── Animation builder ───────────────────────────────────────────────────────
596
+
597
+ function buildFrameSequence(animation: OctopusAnimation): SpriteFrame[] {
598
+ switch (animation) {
599
+ case "swim-up":
600
+ return WALKUP_FRAMES.map((bottom) => ({ bottom }));
601
+ case "walk":
602
+ return WALK_FRAMES;
603
+ case "jog":
604
+ return JOG_FRAMES;
605
+ case "bounce":
606
+ return BOUNCE_FRAMES;
607
+ case "float":
608
+ return FLOAT_FRAMES;
609
+ default:
610
+ return SWAY_FRAMES_TAILS.map((tail) => ({ bottom: [...TENTACLE_TOP, ...tail] }));
611
+ }
612
+ }
613
+
614
+ function animationFrameMs(animation: OctopusAnimation): number {
615
+ if (animation === "jog" || animation === "swim-up") return JOG_FRAME_MS;
616
+ if (animation === "walk") return WALK_FRAME_MS;
617
+ if (animation === "float") return FLOAT_FRAME_MS;
618
+ return SWAY_FRAME_MS;
619
+ }
620
+
621
+ const IDLE_FRAME: SpriteFrame = { bottom: [...TENTACLE_TOP, ...TAIL_NEUTRAL] };
622
+
623
+ // ─── Component ───────────────────────────────────────────────────────────────
624
+
625
+ type OctopusGlyphProps = {
626
+ animation?: OctopusAnimation;
627
+ expression?: OctopusExpression;
628
+ accessory?: OctopusAccessory;
629
+ /** Hair color override. Default: dark brown. */
630
+ hairColor?: string;
631
+ /** Override the pixel scale (CSS px per sprite pixel). Default: 14. */
632
+ scale?: number;
633
+ className?: string;
634
+ color?: string;
635
+ testId?: string;
636
+ };
637
+
638
+ export const OctopusGlyph = ({
639
+ animation = "sway",
640
+ expression = "normal",
641
+ accessory = "none",
642
+ hairColor = HAIR_COLOR,
643
+ scale = DEFAULT_SCALE,
644
+ className,
645
+ color,
646
+ testId,
647
+ }: OctopusGlyphProps) => {
648
+ const canvasRef = useRef<HTMLCanvasElement>(null);
649
+ const frameRef = useRef(0);
650
+ const zzzPhaseRef = useRef(0);
651
+
652
+ // Extra canvas rows above the sprite for overlays (ZZZ, accessories).
653
+ const topPad = Math.max(
654
+ expression === "sleepy" ? ZZZ_PAD : 0,
655
+ accessory !== "none" ? ACCESSORY_PAD : 0,
656
+ );
657
+
658
+ useEffect(() => {
659
+ const canvas = canvasRef.current;
660
+ if (!canvas) return;
661
+ const ctx = canvas.getContext("2d");
662
+ if (!ctx) return;
663
+
664
+ ctx.imageSmoothingEnabled = false;
665
+
666
+ const accentColor =
667
+ color ??
668
+ (getComputedStyle(document.documentElement).getPropertyValue("--accent-primary").trim() ||
669
+ "#d4a017");
670
+
671
+ const head = HEADS[expression];
672
+
673
+ const drawFrame = (frame: SpriteFrame, zzzPhase: number) => {
674
+ drawSprite(ctx, accentColor, frame, head, scale, topPad);
675
+ if (expression === "sleepy") drawZZZ(ctx, scale, zzzPhase);
676
+ const yOff = (frame.yOffset ?? 0) + topPad;
677
+ drawAccessory(ctx, accessory, scale, yOff, hairColor);
678
+ };
679
+
680
+ // Idle with no ZZZ: static, no interval.
681
+ if (animation === "idle" && expression !== "sleepy") {
682
+ frameRef.current = 0;
683
+ drawFrame(IDLE_FRAME, 0);
684
+ return;
685
+ }
686
+
687
+ // Idle sleepy: sprite is static but ZZZ blinks — use sway timing for ZZZ cycle.
688
+ const frames = animation === "idle" ? null : buildFrameSequence(animation);
689
+ const ms = animation === "idle" ? SWAY_FRAME_MS : animationFrameMs(animation);
690
+
691
+ frameRef.current = 0;
692
+ zzzPhaseRef.current = 0;
693
+ drawFrame(frames?.[0] ?? IDLE_FRAME, 0);
694
+
695
+ const id = setInterval(() => {
696
+ if (frames) {
697
+ frameRef.current = (frameRef.current + 1) % frames.length;
698
+ }
699
+ zzzPhaseRef.current = (zzzPhaseRef.current + 1) % 5;
700
+ drawFrame(frames?.[frameRef.current] ?? IDLE_FRAME, zzzPhaseRef.current);
701
+ }, ms);
702
+
703
+ return () => clearInterval(id);
704
+ }, [animation, expression, accessory, hairColor, color, scale, topPad]);
705
+
706
+ return (
707
+ <canvas
708
+ ref={canvasRef}
709
+ className={className}
710
+ width={SPRITE_W * scale}
711
+ height={(topPad + SPRITE_H + BOUNCE_PAD) * scale}
712
+ data-testid={testId}
713
+ />
714
+ );
715
+ };