better-codex 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (405) hide show
  1. package/README.md +26 -0
  2. package/apps/backend/README.md +46 -0
  3. package/apps/backend/bun.lock +64 -0
  4. package/apps/backend/package.json +18 -0
  5. package/apps/backend/scripts/generate-protocol.ts +32 -0
  6. package/apps/backend/src/analytics/service.ts +219 -0
  7. package/apps/backend/src/analytics/store.ts +284 -0
  8. package/apps/backend/src/config.ts +98 -0
  9. package/apps/backend/src/core/app-server.ts +131 -0
  10. package/apps/backend/src/core/jsonrpc.ts +166 -0
  11. package/apps/backend/src/protocol/AbsolutePathBuf.ts +14 -0
  12. package/apps/backend/src/protocol/AddConversationListenerParams.ts +6 -0
  13. package/apps/backend/src/protocol/AddConversationSubscriptionResponse.ts +5 -0
  14. package/apps/backend/src/protocol/AgentMessageContent.ts +5 -0
  15. package/apps/backend/src/protocol/AgentMessageContentDeltaEvent.ts +5 -0
  16. package/apps/backend/src/protocol/AgentMessageDeltaEvent.ts +5 -0
  17. package/apps/backend/src/protocol/AgentMessageEvent.ts +5 -0
  18. package/apps/backend/src/protocol/AgentMessageItem.ts +6 -0
  19. package/apps/backend/src/protocol/AgentReasoningDeltaEvent.ts +5 -0
  20. package/apps/backend/src/protocol/AgentReasoningEvent.ts +5 -0
  21. package/apps/backend/src/protocol/AgentReasoningRawContentDeltaEvent.ts +5 -0
  22. package/apps/backend/src/protocol/AgentReasoningRawContentEvent.ts +5 -0
  23. package/apps/backend/src/protocol/AgentReasoningSectionBreakEvent.ts +5 -0
  24. package/apps/backend/src/protocol/Annotations.ts +9 -0
  25. package/apps/backend/src/protocol/ApplyPatchApprovalParams.ts +21 -0
  26. package/apps/backend/src/protocol/ApplyPatchApprovalRequestEvent.ts +23 -0
  27. package/apps/backend/src/protocol/ApplyPatchApprovalResponse.ts +6 -0
  28. package/apps/backend/src/protocol/ArchiveConversationParams.ts +6 -0
  29. package/apps/backend/src/protocol/ArchiveConversationResponse.ts +5 -0
  30. package/apps/backend/src/protocol/AskForApproval.ts +9 -0
  31. package/apps/backend/src/protocol/AudioContent.ts +9 -0
  32. package/apps/backend/src/protocol/AuthMode.ts +5 -0
  33. package/apps/backend/src/protocol/AuthStatusChangeNotification.ts +9 -0
  34. package/apps/backend/src/protocol/BackgroundEventEvent.ts +5 -0
  35. package/apps/backend/src/protocol/BlobResourceContents.ts +5 -0
  36. package/apps/backend/src/protocol/CallToolResult.ts +10 -0
  37. package/apps/backend/src/protocol/CancelLoginChatGptParams.ts +5 -0
  38. package/apps/backend/src/protocol/CancelLoginChatGptResponse.ts +5 -0
  39. package/apps/backend/src/protocol/ClientInfo.ts +5 -0
  40. package/apps/backend/src/protocol/ClientNotification.ts +5 -0
  41. package/apps/backend/src/protocol/ClientRequest.ts +46 -0
  42. package/apps/backend/src/protocol/CodexErrorInfo.ts +8 -0
  43. package/apps/backend/src/protocol/ContentBlock.ts +10 -0
  44. package/apps/backend/src/protocol/ContentItem.ts +5 -0
  45. package/apps/backend/src/protocol/ContextCompactedEvent.ts +5 -0
  46. package/apps/backend/src/protocol/ConversationGitInfo.ts +5 -0
  47. package/apps/backend/src/protocol/ConversationId.ts +5 -0
  48. package/apps/backend/src/protocol/ConversationSummary.ts +8 -0
  49. package/apps/backend/src/protocol/CreditsSnapshot.ts +5 -0
  50. package/apps/backend/src/protocol/CustomPrompt.ts +5 -0
  51. package/apps/backend/src/protocol/DeprecationNoticeEvent.ts +13 -0
  52. package/apps/backend/src/protocol/ElicitationRequestEvent.ts +6 -0
  53. package/apps/backend/src/protocol/EmbeddedResource.ts +13 -0
  54. package/apps/backend/src/protocol/EmbeddedResourceResource.ts +7 -0
  55. package/apps/backend/src/protocol/ErrorEvent.ts +6 -0
  56. package/apps/backend/src/protocol/EventMsg.ts +60 -0
  57. package/apps/backend/src/protocol/ExecApprovalRequestEvent.ts +32 -0
  58. package/apps/backend/src/protocol/ExecCommandApprovalParams.ts +12 -0
  59. package/apps/backend/src/protocol/ExecCommandApprovalResponse.ts +6 -0
  60. package/apps/backend/src/protocol/ExecCommandBeginEvent.ts +35 -0
  61. package/apps/backend/src/protocol/ExecCommandEndEvent.ts +59 -0
  62. package/apps/backend/src/protocol/ExecCommandOutputDeltaEvent.ts +18 -0
  63. package/apps/backend/src/protocol/ExecCommandSource.ts +5 -0
  64. package/apps/backend/src/protocol/ExecOneOffCommandParams.ts +6 -0
  65. package/apps/backend/src/protocol/ExecOneOffCommandResponse.ts +5 -0
  66. package/apps/backend/src/protocol/ExecOutputStream.ts +5 -0
  67. package/apps/backend/src/protocol/ExecPolicyAmendment.ts +12 -0
  68. package/apps/backend/src/protocol/ExitedReviewModeEvent.ts +6 -0
  69. package/apps/backend/src/protocol/FileChange.ts +5 -0
  70. package/apps/backend/src/protocol/ForcedLoginMethod.ts +5 -0
  71. package/apps/backend/src/protocol/FunctionCallOutputContentItem.ts +9 -0
  72. package/apps/backend/src/protocol/FunctionCallOutputPayload.ts +15 -0
  73. package/apps/backend/src/protocol/FuzzyFileSearchParams.ts +5 -0
  74. package/apps/backend/src/protocol/FuzzyFileSearchResponse.ts +6 -0
  75. package/apps/backend/src/protocol/FuzzyFileSearchResult.ts +8 -0
  76. package/apps/backend/src/protocol/GetAuthStatusParams.ts +5 -0
  77. package/apps/backend/src/protocol/GetAuthStatusResponse.ts +6 -0
  78. package/apps/backend/src/protocol/GetConversationSummaryParams.ts +6 -0
  79. package/apps/backend/src/protocol/GetConversationSummaryResponse.ts +6 -0
  80. package/apps/backend/src/protocol/GetHistoryEntryResponseEvent.ts +10 -0
  81. package/apps/backend/src/protocol/GetUserAgentResponse.ts +5 -0
  82. package/apps/backend/src/protocol/GetUserSavedConfigResponse.ts +6 -0
  83. package/apps/backend/src/protocol/GhostCommit.ts +8 -0
  84. package/apps/backend/src/protocol/GitDiffToRemoteParams.ts +5 -0
  85. package/apps/backend/src/protocol/GitDiffToRemoteResponse.ts +6 -0
  86. package/apps/backend/src/protocol/GitSha.ts +5 -0
  87. package/apps/backend/src/protocol/HistoryEntry.ts +5 -0
  88. package/apps/backend/src/protocol/ImageContent.ts +9 -0
  89. package/apps/backend/src/protocol/InitializeParams.ts +6 -0
  90. package/apps/backend/src/protocol/InitializeResponse.ts +5 -0
  91. package/apps/backend/src/protocol/InputItem.ts +5 -0
  92. package/apps/backend/src/protocol/InterruptConversationParams.ts +6 -0
  93. package/apps/backend/src/protocol/InterruptConversationResponse.ts +6 -0
  94. package/apps/backend/src/protocol/ItemCompletedEvent.ts +7 -0
  95. package/apps/backend/src/protocol/ItemStartedEvent.ts +7 -0
  96. package/apps/backend/src/protocol/ListConversationsParams.ts +5 -0
  97. package/apps/backend/src/protocol/ListConversationsResponse.ts +6 -0
  98. package/apps/backend/src/protocol/ListCustomPromptsResponseEvent.ts +9 -0
  99. package/apps/backend/src/protocol/ListSkillsResponseEvent.ts +9 -0
  100. package/apps/backend/src/protocol/LocalShellAction.ts +6 -0
  101. package/apps/backend/src/protocol/LocalShellExecAction.ts +5 -0
  102. package/apps/backend/src/protocol/LocalShellStatus.ts +5 -0
  103. package/apps/backend/src/protocol/LoginApiKeyParams.ts +5 -0
  104. package/apps/backend/src/protocol/LoginApiKeyResponse.ts +5 -0
  105. package/apps/backend/src/protocol/LoginChatGptCompleteNotification.ts +8 -0
  106. package/apps/backend/src/protocol/LoginChatGptResponse.ts +5 -0
  107. package/apps/backend/src/protocol/LogoutChatGptResponse.ts +5 -0
  108. package/apps/backend/src/protocol/McpAuthStatus.ts +5 -0
  109. package/apps/backend/src/protocol/McpInvocation.ts +18 -0
  110. package/apps/backend/src/protocol/McpListToolsResponseEvent.ts +25 -0
  111. package/apps/backend/src/protocol/McpStartupCompleteEvent.ts +6 -0
  112. package/apps/backend/src/protocol/McpStartupFailure.ts +5 -0
  113. package/apps/backend/src/protocol/McpStartupStatus.ts +5 -0
  114. package/apps/backend/src/protocol/McpStartupUpdateEvent.ts +14 -0
  115. package/apps/backend/src/protocol/McpToolCallBeginEvent.ts +10 -0
  116. package/apps/backend/src/protocol/McpToolCallEndEvent.ts +15 -0
  117. package/apps/backend/src/protocol/NetworkAccess.ts +8 -0
  118. package/apps/backend/src/protocol/NewConversationParams.ts +8 -0
  119. package/apps/backend/src/protocol/NewConversationResponse.ts +7 -0
  120. package/apps/backend/src/protocol/ParsedCommand.ts +12 -0
  121. package/apps/backend/src/protocol/PatchApplyBeginEvent.ts +23 -0
  122. package/apps/backend/src/protocol/PatchApplyEndEvent.ts +31 -0
  123. package/apps/backend/src/protocol/PlanItemArg.ts +6 -0
  124. package/apps/backend/src/protocol/PlanType.ts +5 -0
  125. package/apps/backend/src/protocol/Profile.ts +9 -0
  126. package/apps/backend/src/protocol/README.md +11 -0
  127. package/apps/backend/src/protocol/RateLimitSnapshot.ts +8 -0
  128. package/apps/backend/src/protocol/RateLimitWindow.ts +17 -0
  129. package/apps/backend/src/protocol/RawResponseItemEvent.ts +6 -0
  130. package/apps/backend/src/protocol/ReasoningContentDeltaEvent.ts +5 -0
  131. package/apps/backend/src/protocol/ReasoningEffort.ts +8 -0
  132. package/apps/backend/src/protocol/ReasoningItem.ts +5 -0
  133. package/apps/backend/src/protocol/ReasoningItemContent.ts +5 -0
  134. package/apps/backend/src/protocol/ReasoningItemReasoningSummary.ts +5 -0
  135. package/apps/backend/src/protocol/ReasoningRawContentDeltaEvent.ts +5 -0
  136. package/apps/backend/src/protocol/ReasoningSummary.ts +10 -0
  137. package/apps/backend/src/protocol/RemoveConversationListenerParams.ts +5 -0
  138. package/apps/backend/src/protocol/RemoveConversationSubscriptionResponse.ts +5 -0
  139. package/apps/backend/src/protocol/RequestId.ts +5 -0
  140. package/apps/backend/src/protocol/Resource.ts +9 -0
  141. package/apps/backend/src/protocol/ResourceLink.ts +11 -0
  142. package/apps/backend/src/protocol/ResourceTemplate.ts +9 -0
  143. package/apps/backend/src/protocol/ResponseItem.ts +17 -0
  144. package/apps/backend/src/protocol/ResumeConversationParams.ts +8 -0
  145. package/apps/backend/src/protocol/ResumeConversationResponse.ts +7 -0
  146. package/apps/backend/src/protocol/ReviewCodeLocation.ts +9 -0
  147. package/apps/backend/src/protocol/ReviewDecision.ts +9 -0
  148. package/apps/backend/src/protocol/ReviewFinding.ts +9 -0
  149. package/apps/backend/src/protocol/ReviewLineRange.ts +8 -0
  150. package/apps/backend/src/protocol/ReviewOutputEvent.ts +9 -0
  151. package/apps/backend/src/protocol/ReviewRequest.ts +9 -0
  152. package/apps/backend/src/protocol/ReviewTarget.ts +9 -0
  153. package/apps/backend/src/protocol/Role.ts +8 -0
  154. package/apps/backend/src/protocol/SandboxMode.ts +5 -0
  155. package/apps/backend/src/protocol/SandboxPolicy.ts +35 -0
  156. package/apps/backend/src/protocol/SandboxSettings.ts +6 -0
  157. package/apps/backend/src/protocol/SendUserMessageParams.ts +7 -0
  158. package/apps/backend/src/protocol/SendUserMessageResponse.ts +5 -0
  159. package/apps/backend/src/protocol/SendUserTurnParams.ts +11 -0
  160. package/apps/backend/src/protocol/SendUserTurnResponse.ts +5 -0
  161. package/apps/backend/src/protocol/ServerNotification.ts +36 -0
  162. package/apps/backend/src/protocol/ServerRequest.ts +13 -0
  163. package/apps/backend/src/protocol/SessionConfiguredEvent.ts +48 -0
  164. package/apps/backend/src/protocol/SessionConfiguredNotification.ts +8 -0
  165. package/apps/backend/src/protocol/SessionSource.ts +6 -0
  166. package/apps/backend/src/protocol/SetDefaultModelParams.ts +6 -0
  167. package/apps/backend/src/protocol/SetDefaultModelResponse.ts +5 -0
  168. package/apps/backend/src/protocol/SkillErrorInfo.ts +5 -0
  169. package/apps/backend/src/protocol/SkillMetadata.ts +6 -0
  170. package/apps/backend/src/protocol/SkillScope.ts +5 -0
  171. package/apps/backend/src/protocol/SkillsListEntry.ts +7 -0
  172. package/apps/backend/src/protocol/StepStatus.ts +5 -0
  173. package/apps/backend/src/protocol/StreamErrorEvent.ts +6 -0
  174. package/apps/backend/src/protocol/SubAgentSource.ts +5 -0
  175. package/apps/backend/src/protocol/TaskCompleteEvent.ts +5 -0
  176. package/apps/backend/src/protocol/TaskStartedEvent.ts +5 -0
  177. package/apps/backend/src/protocol/TerminalInteractionEvent.ts +17 -0
  178. package/apps/backend/src/protocol/TextContent.ts +9 -0
  179. package/apps/backend/src/protocol/TextResourceContents.ts +5 -0
  180. package/apps/backend/src/protocol/TokenCountEvent.ts +7 -0
  181. package/apps/backend/src/protocol/TokenUsage.ts +5 -0
  182. package/apps/backend/src/protocol/TokenUsageInfo.ts +6 -0
  183. package/apps/backend/src/protocol/Tool.ts +11 -0
  184. package/apps/backend/src/protocol/ToolAnnotations.ts +15 -0
  185. package/apps/backend/src/protocol/ToolInputSchema.ts +9 -0
  186. package/apps/backend/src/protocol/ToolOutputSchema.ts +10 -0
  187. package/apps/backend/src/protocol/Tools.ts +5 -0
  188. package/apps/backend/src/protocol/TurnAbortReason.ts +5 -0
  189. package/apps/backend/src/protocol/TurnAbortedEvent.ts +6 -0
  190. package/apps/backend/src/protocol/TurnDiffEvent.ts +5 -0
  191. package/apps/backend/src/protocol/TurnItem.ts +9 -0
  192. package/apps/backend/src/protocol/UndoCompletedEvent.ts +5 -0
  193. package/apps/backend/src/protocol/UndoStartedEvent.ts +5 -0
  194. package/apps/backend/src/protocol/UpdatePlanArgs.ts +6 -0
  195. package/apps/backend/src/protocol/UserInfoResponse.ts +5 -0
  196. package/apps/backend/src/protocol/UserInput.ts +8 -0
  197. package/apps/backend/src/protocol/UserMessageEvent.ts +5 -0
  198. package/apps/backend/src/protocol/UserMessageItem.ts +6 -0
  199. package/apps/backend/src/protocol/UserSavedConfig.ts +14 -0
  200. package/apps/backend/src/protocol/Verbosity.ts +9 -0
  201. package/apps/backend/src/protocol/ViewImageToolCallEvent.ts +13 -0
  202. package/apps/backend/src/protocol/WarningEvent.ts +5 -0
  203. package/apps/backend/src/protocol/WebSearchAction.ts +5 -0
  204. package/apps/backend/src/protocol/WebSearchBeginEvent.ts +5 -0
  205. package/apps/backend/src/protocol/WebSearchEndEvent.ts +5 -0
  206. package/apps/backend/src/protocol/WebSearchItem.ts +5 -0
  207. package/apps/backend/src/protocol/index.ts +198 -0
  208. package/apps/backend/src/protocol/serde_json/JsonValue.ts +5 -0
  209. package/apps/backend/src/protocol/v2/Account.ts +6 -0
  210. package/apps/backend/src/protocol/v2/AccountLoginCompletedNotification.ts +5 -0
  211. package/apps/backend/src/protocol/v2/AccountRateLimitsUpdatedNotification.ts +6 -0
  212. package/apps/backend/src/protocol/v2/AccountUpdatedNotification.ts +6 -0
  213. package/apps/backend/src/protocol/v2/AgentMessageDeltaNotification.ts +5 -0
  214. package/apps/backend/src/protocol/v2/ApprovalDecision.ts +6 -0
  215. package/apps/backend/src/protocol/v2/AskForApproval.ts +5 -0
  216. package/apps/backend/src/protocol/v2/CancelLoginAccountParams.ts +5 -0
  217. package/apps/backend/src/protocol/v2/CancelLoginAccountResponse.ts +6 -0
  218. package/apps/backend/src/protocol/v2/CancelLoginAccountStatus.ts +5 -0
  219. package/apps/backend/src/protocol/v2/CodexErrorInfo.ts +11 -0
  220. package/apps/backend/src/protocol/v2/CommandAction.ts +5 -0
  221. package/apps/backend/src/protocol/v2/CommandExecParams.ts +6 -0
  222. package/apps/backend/src/protocol/v2/CommandExecResponse.ts +5 -0
  223. package/apps/backend/src/protocol/v2/CommandExecutionOutputDeltaNotification.ts +5 -0
  224. package/apps/backend/src/protocol/v2/CommandExecutionRequestApprovalParams.ts +14 -0
  225. package/apps/backend/src/protocol/v2/CommandExecutionRequestApprovalResponse.ts +6 -0
  226. package/apps/backend/src/protocol/v2/CommandExecutionStatus.ts +5 -0
  227. package/apps/backend/src/protocol/v2/Config.ts +15 -0
  228. package/apps/backend/src/protocol/v2/ConfigBatchWriteParams.ts +10 -0
  229. package/apps/backend/src/protocol/v2/ConfigEdit.ts +7 -0
  230. package/apps/backend/src/protocol/v2/ConfigLayer.ts +7 -0
  231. package/apps/backend/src/protocol/v2/ConfigLayerMetadata.ts +6 -0
  232. package/apps/backend/src/protocol/v2/ConfigLayerSource.ts +6 -0
  233. package/apps/backend/src/protocol/v2/ConfigReadParams.ts +5 -0
  234. package/apps/backend/src/protocol/v2/ConfigReadResponse.ts +8 -0
  235. package/apps/backend/src/protocol/v2/ConfigValueWriteParams.ts +11 -0
  236. package/apps/backend/src/protocol/v2/ConfigWriteResponse.ts +12 -0
  237. package/apps/backend/src/protocol/v2/ContextCompactedNotification.ts +5 -0
  238. package/apps/backend/src/protocol/v2/CreditsSnapshot.ts +5 -0
  239. package/apps/backend/src/protocol/v2/DeprecationNoticeNotification.ts +13 -0
  240. package/apps/backend/src/protocol/v2/ErrorNotification.ts +6 -0
  241. package/apps/backend/src/protocol/v2/ExecPolicyAmendment.ts +5 -0
  242. package/apps/backend/src/protocol/v2/FeedbackUploadParams.ts +5 -0
  243. package/apps/backend/src/protocol/v2/FeedbackUploadResponse.ts +5 -0
  244. package/apps/backend/src/protocol/v2/FileChangeOutputDeltaNotification.ts +5 -0
  245. package/apps/backend/src/protocol/v2/FileChangeRequestApprovalParams.ts +14 -0
  246. package/apps/backend/src/protocol/v2/FileChangeRequestApprovalResponse.ts +6 -0
  247. package/apps/backend/src/protocol/v2/FileUpdateChange.ts +6 -0
  248. package/apps/backend/src/protocol/v2/GetAccountParams.ts +5 -0
  249. package/apps/backend/src/protocol/v2/GetAccountRateLimitsResponse.ts +6 -0
  250. package/apps/backend/src/protocol/v2/GetAccountResponse.ts +6 -0
  251. package/apps/backend/src/protocol/v2/GitInfo.ts +5 -0
  252. package/apps/backend/src/protocol/v2/ItemCompletedNotification.ts +6 -0
  253. package/apps/backend/src/protocol/v2/ItemStartedNotification.ts +6 -0
  254. package/apps/backend/src/protocol/v2/ListMcpServerStatusParams.ts +13 -0
  255. package/apps/backend/src/protocol/v2/ListMcpServerStatusResponse.ts +11 -0
  256. package/apps/backend/src/protocol/v2/LoginAccountParams.ts +5 -0
  257. package/apps/backend/src/protocol/v2/LoginAccountResponse.ts +9 -0
  258. package/apps/backend/src/protocol/v2/LogoutAccountResponse.ts +5 -0
  259. package/apps/backend/src/protocol/v2/McpAuthStatus.ts +5 -0
  260. package/apps/backend/src/protocol/v2/McpServerOauthLoginCompletedNotification.ts +5 -0
  261. package/apps/backend/src/protocol/v2/McpServerOauthLoginParams.ts +5 -0
  262. package/apps/backend/src/protocol/v2/McpServerOauthLoginResponse.ts +5 -0
  263. package/apps/backend/src/protocol/v2/McpServerStatus.ts +9 -0
  264. package/apps/backend/src/protocol/v2/McpToolCallError.ts +5 -0
  265. package/apps/backend/src/protocol/v2/McpToolCallProgressNotification.ts +5 -0
  266. package/apps/backend/src/protocol/v2/McpToolCallResult.ts +7 -0
  267. package/apps/backend/src/protocol/v2/McpToolCallStatus.ts +5 -0
  268. package/apps/backend/src/protocol/v2/MergeStrategy.ts +5 -0
  269. package/apps/backend/src/protocol/v2/Model.ts +7 -0
  270. package/apps/backend/src/protocol/v2/ModelListParams.ts +13 -0
  271. package/apps/backend/src/protocol/v2/ModelListResponse.ts +11 -0
  272. package/apps/backend/src/protocol/v2/NetworkAccess.ts +5 -0
  273. package/apps/backend/src/protocol/v2/OverriddenMetadata.ts +7 -0
  274. package/apps/backend/src/protocol/v2/PatchApplyStatus.ts +5 -0
  275. package/apps/backend/src/protocol/v2/PatchChangeKind.ts +5 -0
  276. package/apps/backend/src/protocol/v2/ProfileV2.ts +10 -0
  277. package/apps/backend/src/protocol/v2/RateLimitSnapshot.ts +8 -0
  278. package/apps/backend/src/protocol/v2/RateLimitWindow.ts +5 -0
  279. package/apps/backend/src/protocol/v2/RawResponseItemCompletedNotification.ts +6 -0
  280. package/apps/backend/src/protocol/v2/ReasoningEffortOption.ts +6 -0
  281. package/apps/backend/src/protocol/v2/ReasoningSummaryPartAddedNotification.ts +5 -0
  282. package/apps/backend/src/protocol/v2/ReasoningSummaryTextDeltaNotification.ts +5 -0
  283. package/apps/backend/src/protocol/v2/ReasoningTextDeltaNotification.ts +5 -0
  284. package/apps/backend/src/protocol/v2/ReviewDelivery.ts +5 -0
  285. package/apps/backend/src/protocol/v2/ReviewStartParams.ts +12 -0
  286. package/apps/backend/src/protocol/v2/ReviewStartResponse.ts +13 -0
  287. package/apps/backend/src/protocol/v2/ReviewTarget.ts +9 -0
  288. package/apps/backend/src/protocol/v2/SandboxMode.ts +5 -0
  289. package/apps/backend/src/protocol/v2/SandboxPolicy.ts +7 -0
  290. package/apps/backend/src/protocol/v2/SandboxWorkspaceWrite.ts +5 -0
  291. package/apps/backend/src/protocol/v2/SessionSource.ts +5 -0
  292. package/apps/backend/src/protocol/v2/SkillErrorInfo.ts +5 -0
  293. package/apps/backend/src/protocol/v2/SkillMetadata.ts +6 -0
  294. package/apps/backend/src/protocol/v2/SkillScope.ts +5 -0
  295. package/apps/backend/src/protocol/v2/SkillsListEntry.ts +7 -0
  296. package/apps/backend/src/protocol/v2/SkillsListParams.ts +13 -0
  297. package/apps/backend/src/protocol/v2/SkillsListResponse.ts +6 -0
  298. package/apps/backend/src/protocol/v2/TerminalInteractionNotification.ts +5 -0
  299. package/apps/backend/src/protocol/v2/Thread.ts +46 -0
  300. package/apps/backend/src/protocol/v2/ThreadArchiveParams.ts +5 -0
  301. package/apps/backend/src/protocol/v2/ThreadArchiveResponse.ts +5 -0
  302. package/apps/backend/src/protocol/v2/ThreadItem.ts +48 -0
  303. package/apps/backend/src/protocol/v2/ThreadListParams.ts +18 -0
  304. package/apps/backend/src/protocol/v2/ThreadListResponse.ts +11 -0
  305. package/apps/backend/src/protocol/v2/ThreadResumeParams.ts +35 -0
  306. package/apps/backend/src/protocol/v2/ThreadResumeResponse.ts +9 -0
  307. package/apps/backend/src/protocol/v2/ThreadStartParams.ts +15 -0
  308. package/apps/backend/src/protocol/v2/ThreadStartResponse.ts +9 -0
  309. package/apps/backend/src/protocol/v2/ThreadStartedNotification.ts +6 -0
  310. package/apps/backend/src/protocol/v2/ThreadTokenUsage.ts +6 -0
  311. package/apps/backend/src/protocol/v2/ThreadTokenUsageUpdatedNotification.ts +6 -0
  312. package/apps/backend/src/protocol/v2/TokenUsageBreakdown.ts +5 -0
  313. package/apps/backend/src/protocol/v2/ToolsV2.ts +5 -0
  314. package/apps/backend/src/protocol/v2/Turn.ts +18 -0
  315. package/apps/backend/src/protocol/v2/TurnCompletedNotification.ts +6 -0
  316. package/apps/backend/src/protocol/v2/TurnDiffUpdatedNotification.ts +9 -0
  317. package/apps/backend/src/protocol/v2/TurnError.ts +6 -0
  318. package/apps/backend/src/protocol/v2/TurnInterruptParams.ts +5 -0
  319. package/apps/backend/src/protocol/v2/TurnInterruptResponse.ts +5 -0
  320. package/apps/backend/src/protocol/v2/TurnPlanStep.ts +6 -0
  321. package/apps/backend/src/protocol/v2/TurnPlanStepStatus.ts +5 -0
  322. package/apps/backend/src/protocol/v2/TurnPlanUpdatedNotification.ts +6 -0
  323. package/apps/backend/src/protocol/v2/TurnStartParams.ts +34 -0
  324. package/apps/backend/src/protocol/v2/TurnStartResponse.ts +6 -0
  325. package/apps/backend/src/protocol/v2/TurnStartedNotification.ts +6 -0
  326. package/apps/backend/src/protocol/v2/TurnStatus.ts +5 -0
  327. package/apps/backend/src/protocol/v2/UserInput.ts +5 -0
  328. package/apps/backend/src/protocol/v2/WindowsWorldWritableWarningNotification.ts +5 -0
  329. package/apps/backend/src/protocol/v2/WriteStatus.ts +5 -0
  330. package/apps/backend/src/protocol/v2/index.ts +123 -0
  331. package/apps/backend/src/reviews/service.ts +27 -0
  332. package/apps/backend/src/reviews/store.ts +124 -0
  333. package/apps/backend/src/server.ts +531 -0
  334. package/apps/backend/src/services/profile-store.ts +114 -0
  335. package/apps/backend/src/services/supervisor.ts +102 -0
  336. package/apps/backend/src/thread-index/service.ts +75 -0
  337. package/apps/backend/src/thread-index/store.ts +195 -0
  338. package/apps/backend/src/ws/messages.ts +73 -0
  339. package/apps/backend/tsconfig.json +20 -0
  340. package/apps/web/README.md +24 -0
  341. package/apps/web/bun.lock +1062 -0
  342. package/apps/web/eslint.config.js +23 -0
  343. package/apps/web/index.html +16 -0
  344. package/apps/web/package.json +38 -0
  345. package/apps/web/src/app.tsx +83 -0
  346. package/apps/web/src/components/composer/slash-command-menu.tsx +47 -0
  347. package/apps/web/src/components/index.ts +2 -0
  348. package/apps/web/src/components/layout/account-usage-panel.tsx +167 -0
  349. package/apps/web/src/components/layout/analytics-view.tsx +296 -0
  350. package/apps/web/src/components/layout/index.ts +7 -0
  351. package/apps/web/src/components/layout/mobile-header.tsx +56 -0
  352. package/apps/web/src/components/layout/reviews-view.tsx +848 -0
  353. package/apps/web/src/components/layout/session-view.tsx +1374 -0
  354. package/apps/web/src/components/layout/settings-dialog.tsx +322 -0
  355. package/apps/web/src/components/layout/side-bar.tsx +417 -0
  356. package/apps/web/src/components/layout/thread-list.tsx +488 -0
  357. package/apps/web/src/components/layout/virtualized-message-list.tsx +748 -0
  358. package/apps/web/src/components/loading/startup-ascii.ts +652 -0
  359. package/apps/web/src/components/loading/startup-loader.tsx +37 -0
  360. package/apps/web/src/components/session-view/file-mention-menu.tsx +46 -0
  361. package/apps/web/src/components/session-view/session-auth-banner.tsx +61 -0
  362. package/apps/web/src/components/session-view/session-composer.tsx +328 -0
  363. package/apps/web/src/components/session-view/session-dialogs.tsx +280 -0
  364. package/apps/web/src/components/session-view/session-empty.tsx +47 -0
  365. package/apps/web/src/components/session-view/session-header.tsx +49 -0
  366. package/apps/web/src/components/ui/avatar.tsx +19 -0
  367. package/apps/web/src/components/ui/badge.tsx +21 -0
  368. package/apps/web/src/components/ui/button.tsx +47 -0
  369. package/apps/web/src/components/ui/collapsible-content.tsx +114 -0
  370. package/apps/web/src/components/ui/contribution-graph.tsx +182 -0
  371. package/apps/web/src/components/ui/dialog-box.tsx +203 -0
  372. package/apps/web/src/components/ui/icon-button.tsx +32 -0
  373. package/apps/web/src/components/ui/icons.tsx +187 -0
  374. package/apps/web/src/components/ui/index.tsx +15 -0
  375. package/apps/web/src/components/ui/input.tsx +43 -0
  376. package/apps/web/src/components/ui/markdown-stream.tsx +21 -0
  377. package/apps/web/src/components/ui/mobile-drawer.tsx +124 -0
  378. package/apps/web/src/components/ui/section-header.tsx +13 -0
  379. package/apps/web/src/components/ui/select.tsx +217 -0
  380. package/apps/web/src/components/ui/shimmer.tsx +138 -0
  381. package/apps/web/src/components/ui/status-dot.tsx +24 -0
  382. package/apps/web/src/config.ts +5 -0
  383. package/apps/web/src/hooks/index.ts +3 -0
  384. package/apps/web/src/hooks/use-analytics.ts +122 -0
  385. package/apps/web/src/hooks/use-hub-connection.ts +587 -0
  386. package/apps/web/src/hooks/use-mobile.ts +76 -0
  387. package/apps/web/src/hooks/use-thread-history.ts +210 -0
  388. package/apps/web/src/index.css +269 -0
  389. package/apps/web/src/main.tsx +10 -0
  390. package/apps/web/src/services/hub-client.ts +358 -0
  391. package/apps/web/src/store/index.ts +528 -0
  392. package/apps/web/src/types/index.ts +119 -0
  393. package/apps/web/src/utils/account-refresh.ts +168 -0
  394. package/apps/web/src/utils/approval-policy.ts +53 -0
  395. package/apps/web/src/utils/init-prompt.ts +41 -0
  396. package/apps/web/src/utils/item-format.ts +170 -0
  397. package/apps/web/src/utils/prompt-expander.ts +62 -0
  398. package/apps/web/src/utils/reasoning-summary.ts +48 -0
  399. package/apps/web/src/utils/slash-commands.ts +98 -0
  400. package/apps/web/tsconfig.app.json +28 -0
  401. package/apps/web/tsconfig.json +7 -0
  402. package/apps/web/tsconfig.node.json +26 -0
  403. package/apps/web/vite.config.ts +8 -0
  404. package/bin/better-codex.cjs +199 -0
  405. package/package.json +20 -0
@@ -0,0 +1,23 @@
1
+ import js from '@eslint/js'
2
+ import globals from 'globals'
3
+ import reactHooks from 'eslint-plugin-react-hooks'
4
+ import reactRefresh from 'eslint-plugin-react-refresh'
5
+ import tseslint from 'typescript-eslint'
6
+ import { defineConfig, globalIgnores } from 'eslint/config'
7
+
8
+ export default defineConfig([
9
+ globalIgnores(['dist']),
10
+ {
11
+ files: ['**/*.{ts,tsx}'],
12
+ extends: [
13
+ js.configs.recommended,
14
+ tseslint.configs.recommended,
15
+ reactHooks.configs.flat.recommended,
16
+ reactRefresh.configs.vite,
17
+ ],
18
+ languageOptions: {
19
+ ecmaVersion: 2020,
20
+ globals: globals.browser,
21
+ },
22
+ },
23
+ ])
@@ -0,0 +1,16 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <link rel="icon" type="image/svg+xml" href="/vite.svg" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <link rel="preconnect" href="https://fonts.googleapis.com">
8
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
9
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
10
+ <title>Codex</title>
11
+ </head>
12
+ <body>
13
+ <div id="root"></div>
14
+ <script type="module" src="/src/main.tsx"></script>
15
+ </body>
16
+ </html>
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "web",
3
+ "private": true,
4
+ "version": "0.0.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "tsc -b && vite build",
9
+ "lint": "eslint .",
10
+ "preview": "vite preview"
11
+ },
12
+ "dependencies": {
13
+ "@tanstack/react-virtual": "^3.13.14",
14
+ "react": "^19.2.0",
15
+ "react-dom": "^19.2.0",
16
+ "streamdown": "^1.6.10",
17
+ "zustand": "^5.0.9"
18
+ },
19
+ "devDependencies": {
20
+ "@eslint/js": "^9.39.1",
21
+ "@tailwindcss/vite": "^4.1.18",
22
+ "@types/node": "^24.10.1",
23
+ "@types/react": "^19.2.5",
24
+ "@types/react-dom": "^19.2.3",
25
+ "@vitejs/plugin-react": "^5.1.1",
26
+ "eslint": "^9.39.1",
27
+ "eslint-plugin-react-hooks": "^7.0.1",
28
+ "eslint-plugin-react-refresh": "^0.4.24",
29
+ "globals": "^16.5.0",
30
+ "tailwindcss": "^4.1.18",
31
+ "typescript": "~5.9.3",
32
+ "typescript-eslint": "^8.46.4",
33
+ "vite": "npm:rolldown-vite@7.2.5"
34
+ },
35
+ "overrides": {
36
+ "vite": "npm:rolldown-vite@7.2.5"
37
+ }
38
+ }
@@ -0,0 +1,83 @@
1
+ import { Sidebar, ThreadList, SessionView, AnalyticsView, ReviewsView } from './components'
2
+ import { StartupLoader } from './components/loading/startup-loader'
3
+ import { MobileHeader } from './components/layout/mobile-header'
4
+ import { MobileDrawer } from './components/ui'
5
+ import { useHubConnection } from './hooks/use-hub-connection'
6
+ import { useThreadHistory } from './hooks/use-thread-history'
7
+ import { useIsMobile, useDynamicViewportHeight } from './hooks/use-mobile'
8
+ import { useAppStore } from './store'
9
+
10
+ function App() {
11
+ useHubConnection()
12
+ useThreadHistory()
13
+ useDynamicViewportHeight()
14
+
15
+ const isMobile = useIsMobile()
16
+ const {
17
+ isMobileSidebarOpen,
18
+ isMobileThreadListOpen,
19
+ setMobileSidebarOpen,
20
+ setMobileThreadListOpen,
21
+ closeMobileDrawers,
22
+ setSelectedThreadId,
23
+ showAnalytics,
24
+ showReviews,
25
+ } = useAppStore()
26
+
27
+ const handleThreadSelect = (threadId: string) => {
28
+ if (threadId) {
29
+ setSelectedThreadId(threadId)
30
+ }
31
+ closeMobileDrawers()
32
+ }
33
+
34
+ if (isMobile) {
35
+ return (
36
+ <div
37
+ className="flex flex-col overflow-hidden bg-bg-primary"
38
+ style={{ height: 'calc(var(--vh, 1vh) * 100)' }}
39
+ >
40
+ <StartupLoader />
41
+
42
+ <MobileHeader />
43
+
44
+ {showAnalytics ? <AnalyticsView /> : showReviews ? <ReviewsView /> : <SessionView />}
45
+
46
+ <MobileDrawer
47
+ open={isMobileSidebarOpen}
48
+ onClose={() => setMobileSidebarOpen(false)}
49
+ side="left"
50
+ >
51
+ <Sidebar onNavigate={() => setMobileSidebarOpen(false)} />
52
+ </MobileDrawer>
53
+
54
+ <MobileDrawer
55
+ open={isMobileThreadListOpen}
56
+ onClose={() => setMobileThreadListOpen(false)}
57
+ side="right"
58
+ >
59
+ <ThreadList onThreadSelect={handleThreadSelect} />
60
+ </MobileDrawer>
61
+ </div>
62
+ )
63
+ }
64
+
65
+ return (
66
+ <div className="h-screen flex overflow-hidden bg-bg-primary">
67
+ <StartupLoader />
68
+ <Sidebar />
69
+ {showAnalytics ? (
70
+ <AnalyticsView />
71
+ ) : showReviews ? (
72
+ <ReviewsView />
73
+ ) : (
74
+ <>
75
+ <ThreadList />
76
+ <SessionView />
77
+ </>
78
+ )}
79
+ </div>
80
+ )
81
+ }
82
+
83
+ export default App
@@ -0,0 +1,47 @@
1
+ import type { SlashCommandDefinition } from '../../utils/slash-commands'
2
+
3
+ interface SlashCommandMenuProps {
4
+ commands: SlashCommandDefinition[]
5
+ activeIndex: number
6
+ isTaskRunning: boolean
7
+ onSelect: (command: SlashCommandDefinition) => void
8
+ onHover: (index: number) => void
9
+ }
10
+
11
+ export function SlashCommandMenu({
12
+ commands,
13
+ activeIndex,
14
+ isTaskRunning,
15
+ onSelect,
16
+ onHover,
17
+ }: SlashCommandMenuProps) {
18
+ if (commands.length === 0) {
19
+ return null
20
+ }
21
+
22
+ return (
23
+ <div className="absolute bottom-full left-0 mb-2 w-[320px] bg-bg-secondary border border-border rounded-xl shadow-xl overflow-hidden z-50">
24
+ <div className="max-h-[260px] overflow-y-auto py-1">
25
+ {commands.map((command, index) => {
26
+ const isActive = index === activeIndex
27
+ const isDisabled = isTaskRunning && !command.availableDuringTask
28
+ return (
29
+ <button
30
+ key={command.id}
31
+ type="button"
32
+ onClick={() => !isDisabled && onSelect(command)}
33
+ onMouseEnter={() => onHover(index)}
34
+ disabled={isDisabled}
35
+ className={`w-full text-left px-3 py-2 transition-colors ${
36
+ isActive ? 'bg-bg-elevated text-text-primary' : 'text-text-secondary'
37
+ } ${isDisabled ? 'opacity-50 cursor-not-allowed' : 'hover:bg-bg-hover'}`}
38
+ >
39
+ <div className="text-xs font-semibold">/{command.id}</div>
40
+ <div className="text-[10px] text-text-muted mt-0.5">{command.description}</div>
41
+ </button>
42
+ )
43
+ })}
44
+ </div>
45
+ </div>
46
+ )
47
+ }
@@ -0,0 +1,2 @@
1
+ export * from './ui'
2
+ export * from './layout'
@@ -0,0 +1,167 @@
1
+ import { useAppStore } from '../../store'
2
+ import { Icons, Badge } from '../ui'
3
+
4
+ export function AccountUsagePanel() {
5
+ const { accounts, selectedAccountId } = useAppStore()
6
+ const account = accounts.find((a) => a.id === selectedAccountId)
7
+
8
+ if (!account) {
9
+ return (
10
+ <div className="bg-bg-tertiary border border-border rounded-xl p-4">
11
+ <p className="text-xs text-text-muted">No account selected</p>
12
+ </div>
13
+ )
14
+ }
15
+
16
+ const { usage } = account
17
+
18
+ const formatResetTime = (resetsAt: number | null) => {
19
+ if (!resetsAt) return null
20
+ const now = Date.now() / 1000
21
+ const diff = resetsAt - now
22
+ if (diff <= 0) return 'Resetting...'
23
+ if (diff < 60) return `${Math.round(diff)}s`
24
+ if (diff < 3600) return `${Math.round(diff / 60)}m`
25
+ return `${Math.round(diff / 3600)}h`
26
+ }
27
+
28
+ const getRemainingColor = (percentLeft: number) => {
29
+ if (percentLeft <= 10) return 'text-accent-red'
30
+ if (percentLeft <= 30) return 'text-yellow-500'
31
+ return 'text-accent-green'
32
+ }
33
+
34
+ const getRemainingBarColor = (percentLeft: number) => {
35
+ if (percentLeft <= 10) return 'bg-accent-red'
36
+ if (percentLeft <= 30) return 'bg-yellow-500'
37
+ return 'bg-accent-green'
38
+ }
39
+
40
+ // Always show current rateLimit even without full usage data
41
+ const primaryUsed = usage?.primary?.usedPercent ?? account.rateLimit
42
+
43
+ return (
44
+ <div className="bg-bg-tertiary border border-border rounded-xl p-3">
45
+ <div className="flex items-center gap-2 mb-3">
46
+ <Icons.Grid className="w-3.5 h-3.5 text-text-muted" />
47
+ <h3 className="text-xs font-semibold text-text-primary">{account.name}</h3>
48
+ {(usage?.planType || account.plan !== 'Unknown') && (
49
+ <Badge variant="success">{usage?.planType || account.plan}</Badge>
50
+ )}
51
+ </div>
52
+
53
+ <div className="space-y-3">
54
+ <UsageBar
55
+ label="5 Hours"
56
+ usedPercent={primaryUsed}
57
+ windowMinutes={usage?.primary?.windowMinutes ?? null}
58
+ resetsAt={usage?.primary?.resetsAt ?? null}
59
+ formatResetTime={formatResetTime}
60
+ getRemainingColor={getRemainingColor}
61
+ getRemainingBarColor={getRemainingBarColor}
62
+ />
63
+
64
+ {usage?.secondary && (
65
+ <UsageBar
66
+ label="Weekly"
67
+ usedPercent={usage.secondary.usedPercent}
68
+ windowMinutes={usage.secondary.windowMinutes}
69
+ resetsAt={usage.secondary.resetsAt}
70
+ formatResetTime={formatResetTime}
71
+ getRemainingColor={getRemainingColor}
72
+ getRemainingBarColor={getRemainingBarColor}
73
+ />
74
+ )}
75
+
76
+ {usage?.credits && (
77
+ <div className="pt-2 border-t border-border">
78
+ <div className="flex items-center justify-between">
79
+ <span className="text-[10px] text-text-muted">Credits</span>
80
+ <div className="flex items-center gap-1.5">
81
+ {usage.credits.unlimited ? (
82
+ <Badge variant="success">Unlimited</Badge>
83
+ ) : usage.credits.balance ? (
84
+ <span className="text-xs font-medium text-text-primary">
85
+ ${usage.credits.balance}
86
+ </span>
87
+ ) : (
88
+ <span className={`text-[10px] ${usage.credits.hasCredits ? 'text-accent-green' : 'text-accent-red'}`}>
89
+ {usage.credits.hasCredits ? 'Available' : 'Exhausted'}
90
+ </span>
91
+ )}
92
+ </div>
93
+ </div>
94
+ </div>
95
+ )}
96
+
97
+ <div className="pt-2 border-t border-border space-y-1">
98
+ <div className="flex items-center justify-between">
99
+ <span className="text-[10px] text-text-muted">Status</span>
100
+ <span className={`text-[10px] font-medium ${
101
+ account.status === 'online' ? 'text-accent-green' :
102
+ account.status === 'degraded' ? 'text-yellow-500' : 'text-text-muted'
103
+ }`}>
104
+ {account.status}
105
+ </span>
106
+ </div>
107
+ {account.email && (
108
+ <div className="flex items-center justify-between">
109
+ <span className="text-[10px] text-text-muted">Email</span>
110
+ <span className="text-[10px] text-text-secondary truncate max-w-[120px]">{account.email}</span>
111
+ </div>
112
+ )}
113
+ </div>
114
+ </div>
115
+ </div>
116
+ )
117
+ }
118
+
119
+ function UsageBar({
120
+ label,
121
+ usedPercent,
122
+ windowMinutes,
123
+ resetsAt,
124
+ formatResetTime,
125
+ getRemainingColor,
126
+ getRemainingBarColor,
127
+ }: {
128
+ label: string
129
+ usedPercent: number
130
+ windowMinutes: number | null
131
+ resetsAt: number | null
132
+ formatResetTime: (resetsAt: number | null) => string | null
133
+ getRemainingColor: (percentLeft: number) => string
134
+ getRemainingBarColor: (percentLeft: number) => string
135
+ }) {
136
+ const resetTime = formatResetTime(resetsAt)
137
+ const percentLeft = Math.max(0, Math.min(100, 100 - usedPercent))
138
+
139
+ return (
140
+ <div>
141
+ <div className="flex items-center justify-between mb-1.5">
142
+ <span className="text-xs text-text-muted">{label}</span>
143
+ <div className="flex items-center gap-2">
144
+ <span className={`text-sm font-medium ${getRemainingColor(percentLeft)}`}>
145
+ {Math.round(percentLeft)}% left
146
+ </span>
147
+ {windowMinutes && (
148
+ <span className="text-[10px] text-text-muted">
149
+ / {windowMinutes}m window
150
+ </span>
151
+ )}
152
+ </div>
153
+ </div>
154
+ <div className="h-2 bg-bg-primary rounded-full overflow-hidden">
155
+ <div
156
+ className={`h-full transition-all duration-300 ${getRemainingBarColor(percentLeft)}`}
157
+ style={{ width: `${percentLeft}%` }}
158
+ />
159
+ </div>
160
+ {resetTime && (
161
+ <p className="text-[10px] text-text-muted mt-1">
162
+ Resets in {resetTime}
163
+ </p>
164
+ )}
165
+ </div>
166
+ )
167
+ }
@@ -0,0 +1,296 @@
1
+ import { useState, useMemo } from 'react'
2
+ import { useAnalytics, useMultipleMetrics, type AnalyticsMetric } from '../../hooks/use-analytics'
3
+ import { Icons, Select, SectionHeader } from '../ui'
4
+ import { useAppStore } from '../../store'
5
+
6
+ const CONTRIBUTION_METRICS: { value: AnalyticsMetric; label: string }[] = [
7
+ { value: 'turns_started', label: 'Turns' },
8
+ { value: 'turns_completed', label: 'Turns Completed' },
9
+ { value: 'threads_started', label: 'Sessions' },
10
+ { value: 'command_exec', label: 'Commands' },
11
+ ]
12
+
13
+ const YEARS = [2026, 2025, 2024]
14
+
15
+ export function AnalyticsView() {
16
+ const { accounts, selectedAccountId } = useAppStore()
17
+ const [selectedMetric, setSelectedMetric] = useState<AnalyticsMetric>('turns_started')
18
+ const [selectedYear, setSelectedYear] = useState(new Date().getFullYear())
19
+
20
+ const profileId = selectedAccountId ?? undefined
21
+
22
+ const now = new Date()
23
+ const isCurrentYear = selectedYear === now.getFullYear()
24
+ const yearStart = new Date(selectedYear, 0, 1)
25
+ const yearEnd = isCurrentYear ? now : new Date(selectedYear, 11, 31)
26
+ const daysDiff = Math.ceil((yearEnd.getTime() - yearStart.getTime()) / (1000 * 60 * 60 * 24)) + 1
27
+
28
+ const { data: graphData, loading } = useAnalytics(selectedMetric, profileId, undefined, isCurrentYear ? 365 : daysDiff)
29
+
30
+ const yearData = useMemo(() => {
31
+ return graphData.filter(d => d.date.startsWith(String(selectedYear)))
32
+ }, [graphData, selectedYear])
33
+
34
+ const { data: allMetrics } = useMultipleMetrics(
35
+ ['turns_started', 'threads_started', 'command_exec', 'approvals_requested_command'],
36
+ profileId,
37
+ 365
38
+ )
39
+
40
+ const totalTurns = (allMetrics.turns_started ?? []).reduce((sum, d) => sum + d.count, 0)
41
+ const totalSessions = (allMetrics.threads_started ?? []).reduce((sum, d) => sum + d.count, 0)
42
+ const totalCommands = (allMetrics.command_exec ?? []).reduce((sum, d) => sum + d.count, 0)
43
+ const totalApprovals = (allMetrics.approvals_requested_command ?? []).reduce((sum, d) => sum + d.count, 0)
44
+
45
+ const selectedAccount = accounts.find(a => a.id === selectedAccountId)
46
+ const totalForYear = yearData.reduce((sum, d) => sum + d.count, 0)
47
+
48
+ return (
49
+ <div className="flex-1 overflow-y-auto bg-bg-primary">
50
+ <div className="max-w-5xl mx-auto p-4 md:p-6 space-y-6">
51
+ <div>
52
+ <h1 className="text-xl font-semibold text-text-primary">Analytics</h1>
53
+ <p className="text-sm text-text-muted mt-1">
54
+ {selectedAccount
55
+ ? `Activity for ${selectedAccount.name}`
56
+ : 'Activity across all accounts'
57
+ }
58
+ </p>
59
+ </div>
60
+
61
+ <div className="bg-bg-secondary border border-border rounded-lg p-4 overflow-visible">
62
+ <div className="flex items-center justify-between gap-3 mb-4">
63
+ <div className="text-sm text-text-primary">
64
+ <span className="font-medium">{totalForYear.toLocaleString()}</span>
65
+ <span className="text-text-muted ml-1">
66
+ {CONTRIBUTION_METRICS.find(m => m.value === selectedMetric)?.label.toLowerCase()} in {selectedYear}
67
+ </span>
68
+ </div>
69
+ <div className="flex items-center gap-2 relative z-10">
70
+ <Select
71
+ value={selectedMetric}
72
+ onChange={(value) => setSelectedMetric(value as AnalyticsMetric)}
73
+ options={CONTRIBUTION_METRICS}
74
+ className="w-44"
75
+ />
76
+ </div>
77
+ </div>
78
+
79
+ {loading ? (
80
+ <div className="h-32 flex items-center justify-center text-text-muted">
81
+ Loading...
82
+ </div>
83
+ ) : (
84
+ <div className="flex gap-4">
85
+ <div className="hidden md:flex flex-col gap-1 text-xs text-text-muted">
86
+ {YEARS.map(year => (
87
+ <button
88
+ key={year}
89
+ onClick={() => setSelectedYear(year)}
90
+ className={`px-2 py-1 rounded text-left transition-colors ${
91
+ selectedYear === year
92
+ ? 'bg-bg-elevated text-text-primary font-medium'
93
+ : 'hover:text-text-secondary'
94
+ }`}
95
+ >
96
+ {year}
97
+ </button>
98
+ ))}
99
+ </div>
100
+
101
+ <div className="flex-1 overflow-x-auto">
102
+ <GitHubContributionGraph data={yearData} year={selectedYear} />
103
+ </div>
104
+ </div>
105
+ )}
106
+
107
+ <div className="flex md:hidden items-center justify-center gap-2 mt-4 pt-3 border-t border-border">
108
+ {YEARS.map(year => (
109
+ <button
110
+ key={year}
111
+ onClick={() => setSelectedYear(year)}
112
+ className={`px-3 py-1 rounded text-xs transition-colors ${
113
+ selectedYear === year
114
+ ? 'bg-bg-elevated text-text-primary font-medium border border-border'
115
+ : 'text-text-muted hover:text-text-secondary'
116
+ }`}
117
+ >
118
+ {year}
119
+ </button>
120
+ ))}
121
+ </div>
122
+ </div>
123
+
124
+ <div className="grid grid-cols-2 md:grid-cols-4 gap-3">
125
+ <StatBlock label="Turns" value={totalTurns} icon={<Icons.Bolt className="w-4 h-4" />} />
126
+ <StatBlock label="Sessions" value={totalSessions} icon={<Icons.Grid className="w-4 h-4" />} />
127
+ <StatBlock label="Commands" value={totalCommands} icon={<Icons.Terminal className="w-4 h-4" />} />
128
+ <StatBlock label="Approvals" value={totalApprovals} icon={<Icons.Check className="w-4 h-4" />} />
129
+ </div>
130
+
131
+ <div className="bg-bg-secondary border border-border rounded-lg overflow-hidden">
132
+ <div className="px-4 py-3 border-b border-border">
133
+ <SectionHeader>Recent Activity</SectionHeader>
134
+ </div>
135
+ <div className="divide-y divide-border">
136
+ {[...yearData].reverse().slice(0, 10).map((day) => (
137
+ <div key={day.date} className="flex items-center justify-between px-4 py-2.5 hover:bg-bg-hover">
138
+ <span className="text-sm text-text-primary">{formatDate(day.date)}</span>
139
+ <span className="text-sm text-text-muted">{day.count} {day.count === 1 ? 'contribution' : 'contributions'}</span>
140
+ </div>
141
+ ))}
142
+ {yearData.length === 0 && (
143
+ <div className="px-4 py-8 text-center text-text-muted text-sm">
144
+ No activity recorded for {selectedYear}
145
+ </div>
146
+ )}
147
+ </div>
148
+ </div>
149
+ </div>
150
+ </div>
151
+ )
152
+ }
153
+
154
+ function StatBlock({ label, value, icon }: { label: string; value: number; icon: React.ReactNode }) {
155
+ return (
156
+ <div className="bg-bg-secondary border border-border rounded-lg p-3 flex items-center gap-3">
157
+ <div className="p-2 bg-bg-hover rounded-lg text-text-muted">
158
+ {icon}
159
+ </div>
160
+ <div>
161
+ <div className="text-lg font-semibold text-text-primary">{value.toLocaleString()}</div>
162
+ <div className="text-xs text-text-muted">{label}</div>
163
+ </div>
164
+ </div>
165
+ )
166
+ }
167
+
168
+ interface DailyDataPoint {
169
+ date: string
170
+ count: number
171
+ }
172
+
173
+ function GitHubContributionGraph({ data, year }: { data: DailyDataPoint[]; year: number }) {
174
+ const { grid, monthLabels, maxCount } = useMemo(() => {
175
+ const dataMap = new Map<string, number>()
176
+ for (const point of data) {
177
+ dataMap.set(point.date, point.count)
178
+ }
179
+
180
+ const now = new Date()
181
+ const isCurrentYear = year === now.getFullYear()
182
+ const endDate = isCurrentYear ? now : new Date(year, 11, 31)
183
+ const startDate = new Date(year, 0, 1)
184
+
185
+ const startDayOfWeek = startDate.getDay()
186
+ const adjustedStart = new Date(startDate)
187
+ adjustedStart.setDate(adjustedStart.getDate() - startDayOfWeek)
188
+
189
+ const weeks: Array<Array<{ date: string; count: number; isInRange: boolean }>> = []
190
+ const monthLabels: Array<{ label: string; weekIndex: number }> = []
191
+ const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
192
+ let currentDate = new Date(adjustedStart)
193
+ let lastMonth = -1
194
+ let maxCount = 0
195
+
196
+ while (currentDate <= endDate || weeks.length < 53) {
197
+ const week: Array<{ date: string; count: number; isInRange: boolean }> = []
198
+
199
+ for (let d = 0; d < 7; d++) {
200
+ const dateKey = formatDateKey(currentDate)
201
+ const count = dataMap.get(dateKey) ?? 0
202
+ const inYear = currentDate.getFullYear() === year
203
+ const notFuture = currentDate <= now
204
+ const isInRange = inYear && notFuture
205
+
206
+ if (inYear && currentDate.getMonth() !== lastMonth && currentDate.getDate() <= 7) {
207
+ monthLabels.push({ label: months[currentDate.getMonth()], weekIndex: weeks.length })
208
+ lastMonth = currentDate.getMonth()
209
+ }
210
+
211
+ week.push({ date: dateKey, count, isInRange })
212
+ maxCount = Math.max(maxCount, count)
213
+
214
+ currentDate.setDate(currentDate.getDate() + 1)
215
+ }
216
+
217
+ weeks.push(week)
218
+ if (weeks.length >= 53) break
219
+ }
220
+
221
+ return { grid: weeks, monthLabels, maxCount }
222
+ }, [data, year])
223
+
224
+ const getColorClass = (count: number, isInRange: boolean): string => {
225
+ if (!isInRange) return 'bg-transparent'
226
+ if (count === 0) return 'bg-bg-elevated'
227
+ if (maxCount === 0) return 'bg-bg-elevated'
228
+ const ratio = count / maxCount
229
+ if (ratio <= 0.25) return 'bg-emerald-900/60'
230
+ if (ratio <= 0.5) return 'bg-emerald-700/70'
231
+ if (ratio <= 0.75) return 'bg-emerald-500/80'
232
+ return 'bg-emerald-400'
233
+ }
234
+
235
+ const days = ['', 'Mon', '', 'Wed', '', 'Fri', '']
236
+
237
+ return (
238
+ <div>
239
+ <div className="flex text-[10px] text-text-muted mb-1 ml-6">
240
+ {monthLabels.map((m, i) => (
241
+ <span
242
+ key={i}
243
+ className="absolute"
244
+ style={{ marginLeft: `${m.weekIndex * 13}px` }}
245
+ >
246
+ {m.label}
247
+ </span>
248
+ ))}
249
+ </div>
250
+
251
+ <div className="flex gap-[3px] mt-4">
252
+ <div className="flex flex-col gap-[3px] text-[10px] text-text-muted w-5">
253
+ {days.map((day, i) => (
254
+ <div key={i} className="h-[11px] flex items-center justify-end pr-1">
255
+ {day}
256
+ </div>
257
+ ))}
258
+ </div>
259
+
260
+ {grid.map((week, wi) => (
261
+ <div key={wi} className="flex flex-col gap-[3px]">
262
+ {week.map((day, di) => (
263
+ <div
264
+ key={di}
265
+ className={`w-[11px] h-[11px] rounded-sm ${getColorClass(day.count, day.isInRange)}`}
266
+ title={day.isInRange ? `${day.date}: ${day.count} contributions` : undefined}
267
+ />
268
+ ))}
269
+ </div>
270
+ ))}
271
+ </div>
272
+
273
+ <div className="flex items-center justify-end gap-1 mt-2 text-[10px] text-text-muted">
274
+ <span>Less</span>
275
+ <div className="w-[11px] h-[11px] rounded-sm bg-bg-elevated" />
276
+ <div className="w-[11px] h-[11px] rounded-sm bg-emerald-900/60" />
277
+ <div className="w-[11px] h-[11px] rounded-sm bg-emerald-700/70" />
278
+ <div className="w-[11px] h-[11px] rounded-sm bg-emerald-500/80" />
279
+ <div className="w-[11px] h-[11px] rounded-sm bg-emerald-400" />
280
+ <span>More</span>
281
+ </div>
282
+ </div>
283
+ )
284
+ }
285
+
286
+ function formatDateKey(date: Date): string {
287
+ const year = date.getFullYear()
288
+ const month = String(date.getMonth() + 1).padStart(2, '0')
289
+ const day = String(date.getDate()).padStart(2, '0')
290
+ return `${year}-${month}-${day}`
291
+ }
292
+
293
+ function formatDate(dateStr: string): string {
294
+ const date = new Date(dateStr + 'T00:00:00')
295
+ return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })
296
+ }
@@ -0,0 +1,7 @@
1
+ export { Sidebar } from './side-bar'
2
+ export { ThreadList } from './thread-list'
3
+ export { SessionView } from './session-view'
4
+ export { AnalyticsView } from './analytics-view'
5
+ export { ReviewsView } from './reviews-view'
6
+ export { VirtualizedMessageList } from './virtualized-message-list'
7
+ export { AccountUsagePanel } from './account-usage-panel'