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
package/README.md ADDED
@@ -0,0 +1,26 @@
1
+ # better-codex
2
+
3
+ Consumer-friendly launcher for the Codex Hub web UI.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm i -g better-codex
9
+ ```
10
+
11
+ Requires Bun: https://bun.sh
12
+
13
+ ## Run
14
+
15
+ ```bash
16
+ better-codex
17
+ ```
18
+
19
+ `better-codex web` works too (same command).
20
+
21
+ Options:
22
+ - `--root PATH` set the repo root (defaults to current dir or parent lookup).
23
+ - `--host 127.0.0.1` host for backend + UI.
24
+ - `--backend-port 7711` backend port.
25
+ - `--web-port 5173` UI port.
26
+ - `--open` open the UI in your browser after startup.
@@ -0,0 +1,46 @@
1
+ # codex-backend
2
+
3
+ Local backend that supervises multiple `codex app-server` processes and exposes a
4
+ WebSocket bridge for the web UI.
5
+
6
+ ## Setup
7
+
8
+ 1. Install dependencies
9
+ ```bash
10
+ bun install
11
+ ```
12
+ 2. Run the server
13
+ ```bash
14
+ bun run dev
15
+ ```
16
+
17
+ ## Protocol types
18
+
19
+ Generate TypeScript bindings from the installed Codex CLI:
20
+
21
+ ```bash
22
+ bun run generate:protocol
23
+ ```
24
+
25
+ ## Environment variables
26
+
27
+ - `CODEX_HUB_HOST` (default: `127.0.0.1`)
28
+ - `CODEX_HUB_PORT` (default: `7711`)
29
+ - `CODEX_HUB_TOKEN` (default: auto-generated at boot)
30
+ - `CODEX_HUB_DATA_DIR` (default: `~/.codex-hub`)
31
+ - `CODEX_HUB_PROFILES_DIR` (default: `~/.codex/profiles`)
32
+ - `CODEX_HUB_DEFAULT_CODEX_HOME` (default: `~/.codex`)
33
+ - `CODEX_BIN` (default: `codex`)
34
+ - `CODEX_FLAGS` (space-delimited)
35
+ - `CODEX_FLAGS_JSON` (JSON array, preferred)
36
+ - `CODEX_APP_SERVER_FLAGS`
37
+ - `CODEX_APP_SERVER_FLAGS_JSON`
38
+
39
+ ## Endpoints
40
+
41
+ - `GET /health`
42
+ - `GET /profiles`
43
+ - `POST /profiles`
44
+ - `POST /profiles/:profileId/start`
45
+ - `POST /profiles/:profileId/stop`
46
+ - `WS /ws?token=...`
@@ -0,0 +1,64 @@
1
+ {
2
+ "lockfileVersion": 1,
3
+ "configVersion": 1,
4
+ "workspaces": {
5
+ "": {
6
+ "name": "codex-backend",
7
+ "dependencies": {
8
+ "@elysiajs/cors": "^1.4.1",
9
+ "elysia": "1",
10
+ },
11
+ "devDependencies": {
12
+ "@types/bun": "1",
13
+ "typescript": "5",
14
+ },
15
+ },
16
+ },
17
+ "packages": {
18
+ "@borewit/text-codec": ["@borewit/text-codec@0.2.1", "", {}, "sha512-k7vvKPbf7J2fZ5klGRD9AeKfUvojuZIQ3BT5u7Jfv+puwXkUBUT5PVyMDfJZpy30CBDXGMgw7fguK/lpOMBvgw=="],
19
+
20
+ "@elysiajs/cors": ["@elysiajs/cors@1.4.1", "", { "peerDependencies": { "elysia": ">= 1.4.0" } }, "sha512-lQfad+F3r4mNwsxRKbXyJB8Jg43oAOXjRwn7sKUL6bcOW3KjUqUimTS+woNpO97efpzjtDE0tEjGk9DTw8lqTQ=="],
21
+
22
+ "@sinclair/typebox": ["@sinclair/typebox@0.34.46", "", {}, "sha512-kiW7CtS/NkdvTUjkjUJo7d5JsFfbJ14YjdhDk9KoEgK6nFjKNXZPrX0jfLA8ZlET4cFLHxOZ/0vFKOP+bOxIOQ=="],
23
+
24
+ "@tokenizer/inflate": ["@tokenizer/inflate@0.4.1", "", { "dependencies": { "debug": "^4.4.3", "token-types": "^6.1.1" } }, "sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA=="],
25
+
26
+ "@tokenizer/token": ["@tokenizer/token@0.3.0", "", {}, "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="],
27
+
28
+ "@types/bun": ["@types/bun@1.3.5", "", { "dependencies": { "bun-types": "1.3.5" } }, "sha512-RnygCqNrd3srIPEWBd5LFeUYG7plCoH2Yw9WaZGyNmdTEei+gWaHqydbaIRkIkcbXwhBT94q78QljxN0Sk838w=="],
29
+
30
+ "@types/node": ["@types/node@25.0.3", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA=="],
31
+
32
+ "bun-types": ["bun-types@1.3.5", "", { "dependencies": { "@types/node": "*" } }, "sha512-inmAYe2PFLs0SUbFOWSVD24sg1jFlMPxOjOSSCYqUgn4Hsc3rDc7dFvfVYjFPNHtov6kgUeulV4SxbuIV/stPw=="],
33
+
34
+ "cookie": ["cookie@1.1.1", "", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="],
35
+
36
+ "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
37
+
38
+ "elysia": ["elysia@1.4.19", "", { "dependencies": { "cookie": "^1.1.1", "exact-mirror": "0.2.5", "fast-decode-uri-component": "^1.0.1", "memoirist": "^0.4.0" }, "peerDependencies": { "@sinclair/typebox": ">= 0.34.0 < 1", "@types/bun": ">= 1.2.0", "file-type": ">= 20.0.0", "openapi-types": ">= 12.0.0", "typescript": ">= 5.0.0" }, "optionalPeers": ["@types/bun", "typescript"] }, "sha512-DZb9y8FnWyX5IuqY44SvqAV0DjJ15NeCWHrLdgXrKgTPDPsl3VNwWHqrEr9bmnOCpg1vh6QUvAX/tcxNj88jLA=="],
39
+
40
+ "exact-mirror": ["exact-mirror@0.2.5", "", { "peerDependencies": { "@sinclair/typebox": "^0.34.15" }, "optionalPeers": ["@sinclair/typebox"] }, "sha512-u8Wu2lO8nio5lKSJubOydsdNtQmH8ENba5m0nbQYmTvsjksXKYIS1nSShdDlO8Uem+kbo+N6eD5I03cpZ+QsRQ=="],
41
+
42
+ "fast-decode-uri-component": ["fast-decode-uri-component@1.0.1", "", {}, "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg=="],
43
+
44
+ "file-type": ["file-type@21.2.0", "", { "dependencies": { "@tokenizer/inflate": "^0.4.1", "strtok3": "^10.3.4", "token-types": "^6.1.1", "uint8array-extras": "^1.4.0" } }, "sha512-vCYBgFOrJQLoTzDyAXAL/RFfKnXXpUYt4+tipVy26nJJhT7ftgGETf2tAQF59EEL61i3MrorV/PG6tf7LJK7eg=="],
45
+
46
+ "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
47
+
48
+ "memoirist": ["memoirist@0.4.0", "", {}, "sha512-zxTgA0mSYELa66DimuNQDvyLq36AwDlTuVRbnQtB+VuTcKWm5Qc4z3WkSpgsFWHNhexqkIooqpv4hdcqrX5Nmg=="],
49
+
50
+ "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
51
+
52
+ "openapi-types": ["openapi-types@12.1.3", "", {}, "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw=="],
53
+
54
+ "strtok3": ["strtok3@10.3.4", "", { "dependencies": { "@tokenizer/token": "^0.3.0" } }, "sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg=="],
55
+
56
+ "token-types": ["token-types@6.1.2", "", { "dependencies": { "@borewit/text-codec": "^0.2.1", "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" } }, "sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww=="],
57
+
58
+ "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
59
+
60
+ "uint8array-extras": ["uint8array-extras@1.5.0", "", {}, "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A=="],
61
+
62
+ "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
63
+ }
64
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "codex-backend",
3
+ "type": "module",
4
+ "scripts": {
5
+ "dev": "bun --watch src/server.ts",
6
+ "generate:protocol": "bun scripts/generate-protocol.ts",
7
+ "start": "bun src/server.ts",
8
+ "typecheck": "tsc"
9
+ },
10
+ "devDependencies": {
11
+ "@types/bun": "1",
12
+ "typescript": "5"
13
+ },
14
+ "dependencies": {
15
+ "@elysiajs/cors": "^1.4.1",
16
+ "elysia": "1"
17
+ }
18
+ }
@@ -0,0 +1,32 @@
1
+ import { spawn } from 'node:child_process'
2
+ import { mkdir } from 'node:fs/promises'
3
+ import { join } from 'node:path'
4
+ import { loadConfig } from '../src/config'
5
+
6
+ const config = loadConfig()
7
+ const outDir = join(process.cwd(), 'src', 'protocol')
8
+
9
+ await mkdir(outDir, { recursive: true })
10
+
11
+ const args = [
12
+ ...config.codexArgs,
13
+ 'app-server',
14
+ 'generate-ts',
15
+ '--out',
16
+ outDir,
17
+ ]
18
+
19
+ const child = spawn(config.codexBin, args, {
20
+ stdio: 'inherit',
21
+ env: {
22
+ ...process.env,
23
+ },
24
+ })
25
+
26
+ const exitCode = await new Promise<number>((resolve) => {
27
+ child.on('exit', (code) => resolve(code ?? 1))
28
+ })
29
+
30
+ if (exitCode !== 0) {
31
+ process.exit(exitCode)
32
+ }
@@ -0,0 +1,219 @@
1
+ import { AnalyticsStore, type AnalyticsEvent, deriveDateKey } from './store'
2
+
3
+ type RpcEvent = {
4
+ profileId: string
5
+ method: string
6
+ params?: unknown
7
+ }
8
+
9
+ type RpcRequest = {
10
+ profileId: string
11
+ method: string
12
+ params?: unknown
13
+ }
14
+
15
+ type RpcResponse = {
16
+ profileId: string
17
+ method?: string
18
+ result?: unknown
19
+ error?: string
20
+ }
21
+
22
+ type ServerRequest = {
23
+ profileId: string
24
+ id: number
25
+ method: string
26
+ params?: unknown
27
+ }
28
+
29
+ type ApprovalDecision = {
30
+ decision?: string
31
+ acceptSettings?: { forSession?: boolean }
32
+ }
33
+
34
+ const now = () => Date.now()
35
+
36
+ export class AnalyticsService {
37
+ private readonly store: AnalyticsStore
38
+ private readonly pendingApprovals = new Map<number, { type: string }>()
39
+
40
+ constructor(store: AnalyticsStore) {
41
+ this.store = store
42
+ }
43
+
44
+ init(): void {
45
+ this.store.init()
46
+ }
47
+
48
+ getDailySeries(metric: string, profileId?: string, model?: string, days = 365) {
49
+ return this.store.getDailySeries(metric, profileId, model, days)
50
+ }
51
+
52
+ trackRpcEvent(event: RpcEvent): void {
53
+ const timestamp = now()
54
+ const base: AnalyticsEvent = {
55
+ occurredAt: timestamp,
56
+ dateKey: deriveDateKey(timestamp),
57
+ profileId: event.profileId,
58
+ eventType: `rpc.event:${event.method}`,
59
+ payload: event.params,
60
+ }
61
+ this.store.recordEvent(base)
62
+
63
+ if (event.method === 'thread/started' && event.params && typeof event.params === 'object') {
64
+ const { thread } = event.params as { thread?: { id?: string; modelProvider?: string; cwd?: string; createdAt?: number } }
65
+ if (thread?.id) {
66
+ this.store.upsertThreadMeta(thread.id, event.profileId, thread.modelProvider, thread.cwd, thread.createdAt ? thread.createdAt * 1000 : undefined)
67
+ this.store.incrementDaily('threads_started', event.profileId, thread.modelProvider, timestamp)
68
+ }
69
+ }
70
+
71
+ if (event.method === 'turn/started' && event.params && typeof event.params === 'object') {
72
+ const { threadId, turn } = event.params as { threadId?: string; turn?: { id?: string } }
73
+ if (threadId && turn?.id) {
74
+ this.store.upsertTurnMeta(turn.id, threadId, event.profileId, undefined, timestamp)
75
+ this.store.incrementDaily('turns_started', event.profileId, undefined, timestamp)
76
+ }
77
+ }
78
+
79
+ if (event.method === 'turn/completed' && event.params && typeof event.params === 'object') {
80
+ const { threadId, turn } = event.params as { threadId?: string; turn?: { id?: string; status?: string } }
81
+ if (threadId && turn?.id) {
82
+ this.store.upsertTurnMeta(turn.id, threadId, event.profileId, undefined, undefined, timestamp, turn.status)
83
+ this.store.incrementDaily('turns_completed', event.profileId, undefined, timestamp)
84
+ if (turn.status) {
85
+ this.store.incrementDaily(`turns_${turn.status}`, event.profileId, undefined, timestamp)
86
+ }
87
+ }
88
+ }
89
+
90
+ if (event.method === 'item/started' && event.params && typeof event.params === 'object') {
91
+ const { item } = event.params as { item?: { type?: string; id?: string } }
92
+ const itemType = item?.type
93
+ if (itemType) {
94
+ this.store.incrementDaily(`items_${itemType}`, event.profileId, undefined, timestamp)
95
+ }
96
+ }
97
+
98
+ if (event.method === 'item/completed' && event.params && typeof event.params === 'object') {
99
+ const { item } = event.params as { item?: { type?: string; id?: string } }
100
+ const itemType = item?.type
101
+ if (itemType) {
102
+ this.store.incrementDaily(`items_completed_${itemType}`, event.profileId, undefined, timestamp)
103
+ }
104
+ }
105
+
106
+ if (event.method === 'thread/tokenUsage/updated' && event.params && typeof event.params === 'object') {
107
+ const { threadId } = event.params as { threadId?: string }
108
+ if (threadId) {
109
+ this.store.recordTokenUsage(event.profileId, threadId, event.params, timestamp)
110
+ }
111
+ }
112
+ }
113
+
114
+ trackRpcRequest(request: RpcRequest): void {
115
+ const timestamp = now()
116
+ const base: AnalyticsEvent = {
117
+ occurredAt: timestamp,
118
+ dateKey: deriveDateKey(timestamp),
119
+ profileId: request.profileId,
120
+ eventType: `rpc.request:${request.method}`,
121
+ payload: request.params,
122
+ }
123
+ this.store.recordEvent(base)
124
+
125
+ if (request.method === 'turn/start') {
126
+ const params = (request.params ?? {}) as { model?: string; threadId?: string }
127
+ if (params.threadId && params.model) {
128
+ this.store.upsertThreadMeta(params.threadId, request.profileId, params.model)
129
+ }
130
+ }
131
+
132
+ if (request.method === 'command/exec') {
133
+ this.store.incrementDaily('command_exec', request.profileId, undefined, timestamp)
134
+ }
135
+
136
+ if (request.method === 'review/start') {
137
+ this.store.incrementDaily('reviews_started', request.profileId, undefined, timestamp)
138
+ }
139
+
140
+ if (request.method === 'account/login/start') {
141
+ const params = (request.params ?? {}) as { type?: string }
142
+ if (params.type) {
143
+ this.store.incrementDaily(`login_started_${params.type}`, request.profileId, undefined, timestamp)
144
+ }
145
+ }
146
+ }
147
+
148
+ trackRpcResponse(response: RpcResponse): void {
149
+ const timestamp = now()
150
+ const base: AnalyticsEvent = {
151
+ occurredAt: timestamp,
152
+ dateKey: deriveDateKey(timestamp),
153
+ profileId: response.profileId,
154
+ eventType: `rpc.response:${response.method ?? 'unknown'}`,
155
+ status: response.error ? 'error' : 'ok',
156
+ payload: response.error ?? response.result,
157
+ }
158
+ this.store.recordEvent(base)
159
+
160
+ if (response.method === 'thread/start' && response.result && typeof response.result === 'object') {
161
+ const { thread } = response.result as { thread?: { id?: string; modelProvider?: string; cwd?: string; createdAt?: number } }
162
+ if (thread?.id) {
163
+ this.store.upsertThreadMeta(thread.id, response.profileId, thread.modelProvider, thread.cwd, thread.createdAt ? thread.createdAt * 1000 : undefined)
164
+ }
165
+ }
166
+
167
+ if (response.method === 'thread/resume' && response.result && typeof response.result === 'object') {
168
+ const result = response.result as { thread?: { id?: string; modelProvider?: string; cwd?: string } }
169
+ if (result.thread?.id) {
170
+ this.store.upsertThreadMeta(result.thread.id, response.profileId, result.thread.modelProvider, result.thread.cwd)
171
+ }
172
+ }
173
+ }
174
+
175
+ trackServerRequest(request: ServerRequest): void {
176
+ const timestamp = now()
177
+ const base: AnalyticsEvent = {
178
+ occurredAt: timestamp,
179
+ dateKey: deriveDateKey(timestamp),
180
+ profileId: request.profileId,
181
+ eventType: `rpc.serverRequest:${request.method}`,
182
+ payload: request.params,
183
+ }
184
+ this.store.recordEvent(base)
185
+
186
+ if (request.method === 'item/commandExecution/requestApproval') {
187
+ this.pendingApprovals.set(request.id, { type: 'command' })
188
+ this.store.recordApprovalRequest(request.id, request.profileId, 'command', (request.params as { threadId?: string; itemId?: string })?.threadId, (request.params as { itemId?: string })?.itemId)
189
+ this.store.incrementDaily('approvals_requested_command', request.profileId, undefined, timestamp)
190
+ }
191
+
192
+ if (request.method === 'item/fileChange/requestApproval') {
193
+ this.pendingApprovals.set(request.id, { type: 'file' })
194
+ this.store.recordApprovalRequest(request.id, request.profileId, 'file', (request.params as { threadId?: string; itemId?: string })?.threadId, (request.params as { itemId?: string })?.itemId)
195
+ this.store.incrementDaily('approvals_requested_file', request.profileId, undefined, timestamp)
196
+ }
197
+ }
198
+
199
+ trackApprovalDecision(profileId: string, requestId: number, result?: unknown, error?: { message: string } | null): void {
200
+ const timestamp = now()
201
+ const decisionPayload = result as ApprovalDecision | undefined
202
+ const decision = error ? 'error' : decisionPayload?.decision ?? 'unknown'
203
+
204
+ this.store.recordEvent({
205
+ occurredAt: timestamp,
206
+ dateKey: deriveDateKey(timestamp),
207
+ profileId,
208
+ eventType: 'approval.decision',
209
+ status: decision,
210
+ payload: result ?? error,
211
+ })
212
+
213
+ if (this.pendingApprovals.has(requestId)) {
214
+ this.pendingApprovals.delete(requestId)
215
+ this.store.recordApprovalDecision(requestId, decision)
216
+ this.store.incrementDaily(`approvals_${decision}`, profileId, undefined, timestamp)
217
+ }
218
+ }
219
+ }
@@ -0,0 +1,284 @@
1
+ import { Database } from 'bun:sqlite'
2
+
3
+ export type AnalyticsEvent = {
4
+ occurredAt: number
5
+ dateKey: string
6
+ profileId: string
7
+ eventType: string
8
+ threadId?: string
9
+ turnId?: string
10
+ itemId?: string
11
+ model?: string
12
+ status?: string
13
+ payload?: unknown
14
+ }
15
+
16
+ const toDateKey = (timestampMs: number): string => {
17
+ const date = new Date(timestampMs)
18
+ const year = date.getUTCFullYear()
19
+ const month = String(date.getUTCMonth() + 1).padStart(2, '0')
20
+ const day = String(date.getUTCDate()).padStart(2, '0')
21
+ return `${year}-${month}-${day}`
22
+ }
23
+
24
+ export class AnalyticsStore {
25
+ private readonly db: Database
26
+
27
+ constructor(private readonly dbPath: string) {
28
+ this.db = new Database(dbPath)
29
+ }
30
+
31
+ init(): void {
32
+ this.db.exec(`
33
+ pragma journal_mode = wal;
34
+ create table if not exists analytics_events (
35
+ id integer primary key autoincrement,
36
+ occurred_at integer not null,
37
+ date_key text not null,
38
+ profile_id text not null,
39
+ thread_id text,
40
+ turn_id text,
41
+ item_id text,
42
+ model text,
43
+ event_type text not null,
44
+ status text,
45
+ payload text
46
+ );
47
+ create index if not exists analytics_events_date on analytics_events(date_key);
48
+ create index if not exists analytics_events_profile on analytics_events(profile_id);
49
+ create index if not exists analytics_events_thread on analytics_events(thread_id);
50
+ create index if not exists analytics_events_type on analytics_events(event_type);
51
+
52
+ create table if not exists analytics_daily (
53
+ date_key text not null,
54
+ metric text not null,
55
+ profile_id text not null,
56
+ model text,
57
+ count integer not null default 0,
58
+ primary key (date_key, metric, profile_id, model)
59
+ );
60
+
61
+ create table if not exists analytics_thread_meta (
62
+ thread_id text primary key,
63
+ profile_id text not null,
64
+ model text,
65
+ cwd text,
66
+ created_at integer
67
+ );
68
+
69
+ create table if not exists analytics_turn_meta (
70
+ turn_id text primary key,
71
+ thread_id text not null,
72
+ profile_id text not null,
73
+ model text,
74
+ started_at integer,
75
+ completed_at integer,
76
+ status text
77
+ );
78
+
79
+ create table if not exists analytics_token_usage (
80
+ id integer primary key autoincrement,
81
+ occurred_at integer not null,
82
+ date_key text not null,
83
+ profile_id text not null,
84
+ thread_id text not null,
85
+ payload text
86
+ );
87
+
88
+ create table if not exists analytics_approvals (
89
+ request_id integer primary key,
90
+ profile_id text not null,
91
+ thread_id text,
92
+ item_id text,
93
+ approval_type text not null,
94
+ requested_at integer not null,
95
+ decision text,
96
+ decided_at integer
97
+ );
98
+ `)
99
+ }
100
+
101
+ recordEvent(event: AnalyticsEvent): void {
102
+ const payload = event.payload ? safeStringify(event.payload) : null
103
+ this.db
104
+ .prepare(
105
+ `insert into analytics_events
106
+ (occurred_at, date_key, profile_id, thread_id, turn_id, item_id, model, event_type, status, payload)
107
+ values
108
+ ($occurred_at, $date_key, $profile_id, $thread_id, $turn_id, $item_id, $model, $event_type, $status, $payload)
109
+ `
110
+ )
111
+ .run({
112
+ $occurred_at: event.occurredAt,
113
+ $date_key: event.dateKey,
114
+ $profile_id: event.profileId,
115
+ $thread_id: event.threadId ?? null,
116
+ $turn_id: event.turnId ?? null,
117
+ $item_id: event.itemId ?? null,
118
+ $model: event.model ?? null,
119
+ $event_type: event.eventType,
120
+ $status: event.status ?? null,
121
+ $payload: payload,
122
+ })
123
+ }
124
+
125
+ incrementDaily(metric: string, profileId: string, model?: string, occurredAt = Date.now()): void {
126
+ const dateKey = toDateKey(occurredAt)
127
+ this.db
128
+ .prepare(
129
+ `insert into analytics_daily (date_key, metric, profile_id, model, count)
130
+ values ($date_key, $metric, $profile_id, $model, 0)
131
+ on conflict(date_key, metric, profile_id, model) do nothing
132
+ `
133
+ )
134
+ .run({
135
+ $date_key: dateKey,
136
+ $metric: metric,
137
+ $profile_id: profileId,
138
+ $model: model ?? null,
139
+ })
140
+
141
+ this.db
142
+ .prepare(
143
+ `update analytics_daily
144
+ set count = count + 1
145
+ where date_key = $date_key and metric = $metric and profile_id = $profile_id and model is $model
146
+ `
147
+ )
148
+ .run({
149
+ $date_key: dateKey,
150
+ $metric: metric,
151
+ $profile_id: profileId,
152
+ $model: model ?? null,
153
+ })
154
+ }
155
+
156
+ upsertThreadMeta(threadId: string, profileId: string, model?: string, cwd?: string, createdAt?: number): void {
157
+ this.db
158
+ .prepare(
159
+ `insert into analytics_thread_meta (thread_id, profile_id, model, cwd, created_at)
160
+ values ($thread_id, $profile_id, $model, $cwd, $created_at)
161
+ on conflict(thread_id) do update set
162
+ profile_id = excluded.profile_id,
163
+ model = coalesce(excluded.model, analytics_thread_meta.model),
164
+ cwd = coalesce(excluded.cwd, analytics_thread_meta.cwd),
165
+ created_at = coalesce(excluded.created_at, analytics_thread_meta.created_at)
166
+ `
167
+ )
168
+ .run({
169
+ $thread_id: threadId,
170
+ $profile_id: profileId,
171
+ $model: model ?? null,
172
+ $cwd: cwd ?? null,
173
+ $created_at: createdAt ?? null,
174
+ })
175
+ }
176
+
177
+ upsertTurnMeta(turnId: string, threadId: string, profileId: string, model?: string, startedAt?: number, completedAt?: number, status?: string): void {
178
+ this.db
179
+ .prepare(
180
+ `insert into analytics_turn_meta (turn_id, thread_id, profile_id, model, started_at, completed_at, status)
181
+ values ($turn_id, $thread_id, $profile_id, $model, $started_at, $completed_at, $status)
182
+ on conflict(turn_id) do update set
183
+ model = coalesce(excluded.model, analytics_turn_meta.model),
184
+ started_at = coalesce(excluded.started_at, analytics_turn_meta.started_at),
185
+ completed_at = coalesce(excluded.completed_at, analytics_turn_meta.completed_at),
186
+ status = coalesce(excluded.status, analytics_turn_meta.status)
187
+ `
188
+ )
189
+ .run({
190
+ $turn_id: turnId,
191
+ $thread_id: threadId,
192
+ $profile_id: profileId,
193
+ $model: model ?? null,
194
+ $started_at: startedAt ?? null,
195
+ $completed_at: completedAt ?? null,
196
+ $status: status ?? null,
197
+ })
198
+ }
199
+
200
+ recordTokenUsage(profileId: string, threadId: string, payload: unknown, occurredAt = Date.now()): void {
201
+ this.db
202
+ .prepare(
203
+ `insert into analytics_token_usage (occurred_at, date_key, profile_id, thread_id, payload)
204
+ values ($occurred_at, $date_key, $profile_id, $thread_id, $payload)
205
+ `
206
+ )
207
+ .run({
208
+ $occurred_at: occurredAt,
209
+ $date_key: toDateKey(occurredAt),
210
+ $profile_id: profileId,
211
+ $thread_id: threadId,
212
+ $payload: safeStringify(payload),
213
+ })
214
+ }
215
+
216
+ recordApprovalRequest(requestId: number, profileId: string, approvalType: string, threadId?: string, itemId?: string): void {
217
+ this.db
218
+ .prepare(
219
+ `insert into analytics_approvals (request_id, profile_id, thread_id, item_id, approval_type, requested_at)
220
+ values ($request_id, $profile_id, $thread_id, $item_id, $approval_type, $requested_at)
221
+ on conflict(request_id) do nothing
222
+ `
223
+ )
224
+ .run({
225
+ $request_id: requestId,
226
+ $profile_id: profileId,
227
+ $thread_id: threadId ?? null,
228
+ $item_id: itemId ?? null,
229
+ $approval_type: approvalType,
230
+ $requested_at: Date.now(),
231
+ })
232
+ }
233
+
234
+ recordApprovalDecision(requestId: number, decision: string): void {
235
+ this.db
236
+ .prepare(
237
+ `update analytics_approvals
238
+ set decision = $decision, decided_at = $decided_at
239
+ where request_id = $request_id
240
+ `
241
+ )
242
+ .run({
243
+ $decision: decision,
244
+ $decided_at: Date.now(),
245
+ $request_id: requestId,
246
+ })
247
+ }
248
+
249
+ getDailySeries(metric: string, profileId?: string, model?: string, days = 365): Array<{ date: string; count: number }> {
250
+ const cutoff = new Date()
251
+ cutoff.setUTCDate(cutoff.getUTCDate() - days + 1)
252
+ const cutoffKey = toDateKey(cutoff.getTime())
253
+
254
+ const rows = this.db
255
+ .prepare(
256
+ `select date_key as date, count
257
+ from analytics_daily
258
+ where metric = $metric
259
+ and date_key >= $cutoff
260
+ and ($profile_id is null or profile_id = $profile_id)
261
+ and ($model is null or model = $model)
262
+ order by date_key asc
263
+ `
264
+ )
265
+ .all({
266
+ $metric: metric,
267
+ $cutoff: cutoffKey,
268
+ $profile_id: profileId ?? null,
269
+ $model: model ?? null,
270
+ }) as Array<{ date: string; count: number }>
271
+
272
+ return rows
273
+ }
274
+ }
275
+
276
+ const safeStringify = (value: unknown): string => {
277
+ try {
278
+ return JSON.stringify(value)
279
+ } catch {
280
+ return String(value)
281
+ }
282
+ }
283
+
284
+ export const deriveDateKey = toDateKey