heyhank 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 (199) hide show
  1. package/README.md +40 -0
  2. package/bin/cli.ts +168 -0
  3. package/bin/ctl.ts +528 -0
  4. package/bin/generate-token.ts +28 -0
  5. package/dist/apple-touch-icon.png +0 -0
  6. package/dist/assets/AgentsPage-BPhirnCe.js +7 -0
  7. package/dist/assets/AssistantPage-DJ-cMQfb.js +1 -0
  8. package/dist/assets/CronManager-DDbz-yiT.js +1 -0
  9. package/dist/assets/HelpPage-DMfkzERp.js +1 -0
  10. package/dist/assets/IntegrationsPage-CrOitCmJ.js +1 -0
  11. package/dist/assets/MediaPage-CE5rdvkC.js +1 -0
  12. package/dist/assets/PlatformDashboard-Do6F0O2p.js +1 -0
  13. package/dist/assets/Playground-Fc5cdc5p.js +109 -0
  14. package/dist/assets/ProcessPanel-CslEiZkI.js +2 -0
  15. package/dist/assets/PromptsPage-D2EhsdNO.js +4 -0
  16. package/dist/assets/RunsPage-C5BZF5Rx.js +1 -0
  17. package/dist/assets/SandboxManager-a1AVI5q2.js +8 -0
  18. package/dist/assets/SettingsPage-DirhjQrJ.js +51 -0
  19. package/dist/assets/SocialMediaPage-DBuM28vD.js +1 -0
  20. package/dist/assets/TailscalePage-CHiFhZXF.js +1 -0
  21. package/dist/assets/TelephonyPage-x0VV0fOo.js +1 -0
  22. package/dist/assets/TerminalPage-Drwyrnfd.js +1 -0
  23. package/dist/assets/gemini-audio-t-TSU-To.js +17 -0
  24. package/dist/assets/gemini-live-client-C7rqAW7G.js +166 -0
  25. package/dist/assets/index-C8M_PUmX.css +32 -0
  26. package/dist/assets/index-CEqZnThB.js +204 -0
  27. package/dist/assets/sw-register-LSSpj6RU.js +1 -0
  28. package/dist/assets/time-ago-B6r_l9u1.js +1 -0
  29. package/dist/assets/workbox-window.prod.es5-BIl4cyR9.js +2 -0
  30. package/dist/favicon-32-original.png +0 -0
  31. package/dist/favicon-32.png +0 -0
  32. package/dist/favicon.ico +0 -0
  33. package/dist/favicon.svg +8 -0
  34. package/dist/fonts/MesloLGSNerdFontMono-Bold.woff2 +0 -0
  35. package/dist/fonts/MesloLGSNerdFontMono-Regular.woff2 +0 -0
  36. package/dist/heyhank-mascot-poster.png +0 -0
  37. package/dist/heyhank-mascot.mp4 +0 -0
  38. package/dist/heyhank-mascot.webm +0 -0
  39. package/dist/icon-192-original.png +0 -0
  40. package/dist/icon-192.png +0 -0
  41. package/dist/icon-512-original.png +0 -0
  42. package/dist/icon-512.png +0 -0
  43. package/dist/index.html +21 -0
  44. package/dist/logo-192.png +0 -0
  45. package/dist/logo-512.png +0 -0
  46. package/dist/logo-codex.svg +14 -0
  47. package/dist/logo-docker.svg +4 -0
  48. package/dist/logo-original.png +0 -0
  49. package/dist/logo.png +0 -0
  50. package/dist/logo.svg +14 -0
  51. package/dist/manifest.json +24 -0
  52. package/dist/push-sw.js +34 -0
  53. package/dist/sw.js +1 -0
  54. package/dist/workbox-d2a0910a.js +1 -0
  55. package/package.json +109 -0
  56. package/server/agent-cron-migrator.ts +85 -0
  57. package/server/agent-executor.ts +357 -0
  58. package/server/agent-store.ts +185 -0
  59. package/server/agent-timeout.ts +107 -0
  60. package/server/agent-types.ts +122 -0
  61. package/server/ai-validation-settings.ts +37 -0
  62. package/server/ai-validator.ts +181 -0
  63. package/server/anthropic-provider-migration.ts +48 -0
  64. package/server/assistant-store.ts +272 -0
  65. package/server/auth-manager.ts +150 -0
  66. package/server/auto-approve.ts +153 -0
  67. package/server/auto-namer.ts +36 -0
  68. package/server/backend-adapter.ts +54 -0
  69. package/server/cache-headers.ts +61 -0
  70. package/server/calendar-service.ts +434 -0
  71. package/server/claude-adapter.ts +889 -0
  72. package/server/claude-container-auth.ts +30 -0
  73. package/server/claude-session-discovery.ts +157 -0
  74. package/server/claude-session-history.ts +410 -0
  75. package/server/cli-launcher.ts +1303 -0
  76. package/server/codex-adapter.ts +3027 -0
  77. package/server/codex-container-auth.ts +24 -0
  78. package/server/codex-home.ts +27 -0
  79. package/server/codex-ws-proxy.cjs +226 -0
  80. package/server/commands-discovery.ts +81 -0
  81. package/server/constants.ts +7 -0
  82. package/server/container-manager.ts +1053 -0
  83. package/server/cost-tracker.ts +222 -0
  84. package/server/cron-scheduler.ts +243 -0
  85. package/server/cron-store.ts +148 -0
  86. package/server/cron-types.ts +63 -0
  87. package/server/email-service.ts +354 -0
  88. package/server/env-manager.ts +161 -0
  89. package/server/event-bus-types.ts +75 -0
  90. package/server/event-bus.ts +124 -0
  91. package/server/execution-store.ts +170 -0
  92. package/server/federation/node-connection.ts +190 -0
  93. package/server/federation/node-manager.ts +366 -0
  94. package/server/federation/node-store.ts +86 -0
  95. package/server/federation/node-types.ts +121 -0
  96. package/server/fs-utils.ts +15 -0
  97. package/server/git-utils.ts +421 -0
  98. package/server/github-pr.ts +379 -0
  99. package/server/google-media.ts +342 -0
  100. package/server/image-pull-manager.ts +279 -0
  101. package/server/index.ts +491 -0
  102. package/server/internal-ai.ts +237 -0
  103. package/server/kill-switch.ts +99 -0
  104. package/server/llm-providers.ts +342 -0
  105. package/server/logger.ts +259 -0
  106. package/server/mcp-registry.ts +401 -0
  107. package/server/message-bus.ts +271 -0
  108. package/server/message-delivery.ts +128 -0
  109. package/server/metrics-collector.ts +350 -0
  110. package/server/metrics-types.ts +108 -0
  111. package/server/middleware/managed-auth.ts +195 -0
  112. package/server/novnc-proxy.ts +99 -0
  113. package/server/path-resolver.ts +186 -0
  114. package/server/paths.ts +13 -0
  115. package/server/pr-poller.ts +162 -0
  116. package/server/prompt-manager.ts +211 -0
  117. package/server/protocol/claude-upstream/README.md +19 -0
  118. package/server/protocol/claude-upstream/sdk.d.ts.txt +1943 -0
  119. package/server/protocol/codex-upstream/ClientNotification.ts.txt +5 -0
  120. package/server/protocol/codex-upstream/ClientRequest.ts.txt +60 -0
  121. package/server/protocol/codex-upstream/README.md +18 -0
  122. package/server/protocol/codex-upstream/ServerNotification.ts.txt +41 -0
  123. package/server/protocol/codex-upstream/ServerRequest.ts.txt +16 -0
  124. package/server/protocol/codex-upstream/v2/DynamicToolCallParams.ts.txt +6 -0
  125. package/server/protocol/codex-upstream/v2/DynamicToolCallResponse.ts.txt +6 -0
  126. package/server/protocol-monitor.ts +50 -0
  127. package/server/provider-manager.ts +111 -0
  128. package/server/provider-registry.ts +393 -0
  129. package/server/push-notifications.ts +221 -0
  130. package/server/recorder.ts +374 -0
  131. package/server/recording-hub/compat-validator.ts +284 -0
  132. package/server/recording-hub/diagnostics.ts +299 -0
  133. package/server/recording-hub/hub-config.ts +19 -0
  134. package/server/recording-hub/hub-routes.ts +236 -0
  135. package/server/recording-hub/hub-store.ts +265 -0
  136. package/server/recording-hub/replay-adapter.ts +207 -0
  137. package/server/relay-client.ts +320 -0
  138. package/server/reminder-scheduler.ts +38 -0
  139. package/server/replay.ts +78 -0
  140. package/server/routes/agent-routes.ts +264 -0
  141. package/server/routes/assistant-routes.ts +90 -0
  142. package/server/routes/cron-routes.ts +103 -0
  143. package/server/routes/env-routes.ts +95 -0
  144. package/server/routes/federation-routes.ts +76 -0
  145. package/server/routes/fs-routes.ts +622 -0
  146. package/server/routes/git-routes.ts +97 -0
  147. package/server/routes/llm-routes.ts +166 -0
  148. package/server/routes/media-routes.ts +135 -0
  149. package/server/routes/metrics-routes.ts +13 -0
  150. package/server/routes/platform-routes.ts +1379 -0
  151. package/server/routes/prompt-routes.ts +67 -0
  152. package/server/routes/provider-routes.ts +109 -0
  153. package/server/routes/sandbox-routes.ts +127 -0
  154. package/server/routes/settings-routes.ts +285 -0
  155. package/server/routes/skills-routes.ts +100 -0
  156. package/server/routes/socialmedia-routes.ts +208 -0
  157. package/server/routes/system-routes.ts +228 -0
  158. package/server/routes/tailscale-routes.ts +22 -0
  159. package/server/routes/telephony-routes.ts +259 -0
  160. package/server/routes.ts +1379 -0
  161. package/server/sandbox-manager.ts +168 -0
  162. package/server/service.ts +718 -0
  163. package/server/session-creation-service.ts +457 -0
  164. package/server/session-git-info.ts +104 -0
  165. package/server/session-names.ts +67 -0
  166. package/server/session-orchestrator.ts +824 -0
  167. package/server/session-state-machine.ts +207 -0
  168. package/server/session-store.ts +146 -0
  169. package/server/session-types.ts +511 -0
  170. package/server/settings-manager.ts +149 -0
  171. package/server/shared-context.ts +157 -0
  172. package/server/socialmedia/adapter.ts +15 -0
  173. package/server/socialmedia/adapters/ayrshare-adapter.ts +169 -0
  174. package/server/socialmedia/adapters/buffer-adapter.ts +299 -0
  175. package/server/socialmedia/adapters/postiz-adapter.ts +298 -0
  176. package/server/socialmedia/manager.ts +227 -0
  177. package/server/socialmedia/store.ts +98 -0
  178. package/server/socialmedia/types.ts +89 -0
  179. package/server/tailscale-manager.ts +451 -0
  180. package/server/telephony/audio-bridge.ts +331 -0
  181. package/server/telephony/call-manager.ts +457 -0
  182. package/server/telephony/call-types.ts +108 -0
  183. package/server/telephony/telephony-store.ts +119 -0
  184. package/server/terminal-manager.ts +240 -0
  185. package/server/update-checker.ts +192 -0
  186. package/server/usage-limits.ts +225 -0
  187. package/server/web-push.d.ts +51 -0
  188. package/server/worktree-tracker.ts +84 -0
  189. package/server/ws-auth.ts +41 -0
  190. package/server/ws-bridge-browser-ingest.ts +72 -0
  191. package/server/ws-bridge-browser.ts +112 -0
  192. package/server/ws-bridge-cli-ingest.ts +81 -0
  193. package/server/ws-bridge-codex.ts +266 -0
  194. package/server/ws-bridge-controls.ts +20 -0
  195. package/server/ws-bridge-persist.ts +66 -0
  196. package/server/ws-bridge-publish.ts +79 -0
  197. package/server/ws-bridge-replay.ts +61 -0
  198. package/server/ws-bridge-types.ts +121 -0
  199. package/server/ws-bridge.ts +1240 -0
@@ -0,0 +1,511 @@
1
+ // Types for the WebSocket bridge between Claude Code CLI and the browser
2
+
3
+ import type { SessionPhase } from "./session-state-machine.js";
4
+
5
+ // ─── CLI Message Types (NDJSON from Claude Code CLI) ──────────────────────────
6
+
7
+ export interface CLISystemInitMessage {
8
+ type: "system";
9
+ subtype: "init";
10
+ cwd: string;
11
+ session_id: string;
12
+ tools: string[];
13
+ mcp_servers: { name: string; status: string }[];
14
+ model: string;
15
+ permissionMode: string;
16
+ apiKeySource: string;
17
+ claude_code_version: string;
18
+ slash_commands: string[];
19
+ agents?: string[];
20
+ skills?: string[];
21
+ output_style: string;
22
+ uuid: string;
23
+ }
24
+
25
+ export interface CLISystemStatusMessage {
26
+ type: "system";
27
+ subtype: "status";
28
+ status: "compacting" | null;
29
+ permissionMode?: string;
30
+ uuid: string;
31
+ session_id: string;
32
+ }
33
+
34
+ export interface CLICompactBoundaryMessage {
35
+ type: "system";
36
+ subtype: "compact_boundary";
37
+ compact_metadata: {
38
+ trigger: "manual" | "auto";
39
+ pre_tokens: number;
40
+ };
41
+ uuid: string;
42
+ session_id: string;
43
+ }
44
+
45
+ export interface CLITaskNotificationMessage {
46
+ type: "system";
47
+ subtype: "task_notification";
48
+ task_id: string;
49
+ status: "completed" | "failed" | "stopped";
50
+ output_file: string;
51
+ summary: string;
52
+ uuid: string;
53
+ session_id: string;
54
+ }
55
+
56
+ export interface CLIFilesPersistedMessage {
57
+ type: "system";
58
+ subtype: "files_persisted";
59
+ files: { filename: string; file_id: string }[];
60
+ failed: { filename: string; error: string }[];
61
+ processed_at: string;
62
+ uuid: string;
63
+ session_id: string;
64
+ }
65
+
66
+ export interface CLIHookStartedMessage {
67
+ type: "system";
68
+ subtype: "hook_started";
69
+ hook_id: string;
70
+ hook_name: string;
71
+ hook_event: string;
72
+ uuid: string;
73
+ session_id: string;
74
+ }
75
+
76
+ export interface CLIHookProgressMessage {
77
+ type: "system";
78
+ subtype: "hook_progress";
79
+ hook_id: string;
80
+ hook_name: string;
81
+ hook_event: string;
82
+ stdout: string;
83
+ stderr: string;
84
+ output: string;
85
+ uuid: string;
86
+ session_id: string;
87
+ }
88
+
89
+ export interface CLIHookResponseMessage {
90
+ type: "system";
91
+ subtype: "hook_response";
92
+ hook_id: string;
93
+ hook_name: string;
94
+ hook_event: string;
95
+ output: string;
96
+ stdout: string;
97
+ stderr: string;
98
+ exit_code?: number;
99
+ outcome: "success" | "error" | "cancelled";
100
+ uuid: string;
101
+ session_id: string;
102
+ }
103
+
104
+ export type CLISystemMessage =
105
+ | CLISystemInitMessage
106
+ | CLISystemStatusMessage
107
+ | CLICompactBoundaryMessage
108
+ | CLITaskNotificationMessage
109
+ | CLIFilesPersistedMessage
110
+ | CLIHookStartedMessage
111
+ | CLIHookProgressMessage
112
+ | CLIHookResponseMessage;
113
+
114
+ export interface CLIAssistantMessage {
115
+ type: "assistant";
116
+ message: {
117
+ id: string;
118
+ type: "message";
119
+ role: "assistant";
120
+ model: string;
121
+ content: ContentBlock[];
122
+ stop_reason: string | null;
123
+ usage: {
124
+ input_tokens: number;
125
+ output_tokens: number;
126
+ cache_creation_input_tokens: number;
127
+ cache_read_input_tokens: number;
128
+ };
129
+ };
130
+ parent_tool_use_id: string | null;
131
+ error?: string;
132
+ uuid: string;
133
+ session_id: string;
134
+ }
135
+
136
+ export interface CLIResultMessage {
137
+ type: "result";
138
+ subtype: "success" | "error_during_execution" | "error_max_turns" | "error_max_budget_usd" | "error_max_structured_output_retries";
139
+ is_error: boolean;
140
+ result?: string;
141
+ errors?: string[];
142
+ duration_ms: number;
143
+ duration_api_ms: number;
144
+ num_turns: number;
145
+ total_cost_usd: number;
146
+ stop_reason: string | null;
147
+ usage: {
148
+ input_tokens: number;
149
+ output_tokens: number;
150
+ cache_creation_input_tokens: number;
151
+ cache_read_input_tokens: number;
152
+ };
153
+ modelUsage?: Record<string, {
154
+ inputTokens: number;
155
+ outputTokens: number;
156
+ cacheReadInputTokens: number;
157
+ cacheCreationInputTokens: number;
158
+ contextWindow: number;
159
+ maxOutputTokens: number;
160
+ costUSD: number;
161
+ }>;
162
+ total_lines_added?: number;
163
+ total_lines_removed?: number;
164
+ uuid: string;
165
+ session_id: string;
166
+ }
167
+
168
+ export interface CLIStreamEventMessage {
169
+ type: "stream_event";
170
+ event: unknown;
171
+ parent_tool_use_id: string | null;
172
+ uuid: string;
173
+ session_id: string;
174
+ }
175
+
176
+ export interface CLIToolProgressMessage {
177
+ type: "tool_progress";
178
+ tool_use_id: string;
179
+ tool_name: string;
180
+ parent_tool_use_id: string | null;
181
+ elapsed_time_seconds: number;
182
+ uuid: string;
183
+ session_id: string;
184
+ }
185
+
186
+ export interface CLIToolUseSummaryMessage {
187
+ type: "tool_use_summary";
188
+ summary: string;
189
+ preceding_tool_use_ids: string[];
190
+ uuid: string;
191
+ session_id: string;
192
+ }
193
+
194
+ export interface CLIControlRequestMessage {
195
+ type: "control_request";
196
+ request_id: string;
197
+ request: {
198
+ subtype: "can_use_tool";
199
+ tool_name: string;
200
+ input: Record<string, unknown>;
201
+ permission_suggestions?: PermissionUpdate[];
202
+ description?: string;
203
+ tool_use_id: string;
204
+ agent_id?: string;
205
+ title?: string;
206
+ display_name?: string;
207
+ blocked_path?: string;
208
+ decision_reason?: string;
209
+ };
210
+ }
211
+
212
+ export interface CLIKeepAliveMessage {
213
+ type: "keep_alive";
214
+ }
215
+
216
+ export interface CLIAuthStatusMessage {
217
+ type: "auth_status";
218
+ isAuthenticating: boolean;
219
+ output: string[];
220
+ error?: string;
221
+ uuid: string;
222
+ session_id: string;
223
+ }
224
+
225
+ export interface CLIControlResponseMessage {
226
+ type: "control_response";
227
+ response: {
228
+ subtype: "success" | "error";
229
+ request_id: string;
230
+ response?: Record<string, unknown>;
231
+ error?: string;
232
+ };
233
+ }
234
+
235
+ /** CLI echoes user messages back (including subagent tool_result blocks). */
236
+ export interface CLIUserEchoMessage {
237
+ type: "user";
238
+ message: { role: string; content: unknown };
239
+ uuid?: string;
240
+ session_id?: string;
241
+ }
242
+
243
+ /** Rate-limit status from Claude API (allowed/throttled). */
244
+ export interface CLIRateLimitEventMessage {
245
+ type: "rate_limit_event";
246
+ rate_limit_info: Record<string, unknown>;
247
+ uuid?: string;
248
+ }
249
+
250
+ /** CLI cancels a pending control_request (e.g. permission revoked). */
251
+ export interface CLIControlCancelRequestMessage {
252
+ type: "control_cancel_request";
253
+ request_id: string;
254
+ }
255
+
256
+ /** Simplified assistant text (streamlined output mode). @internal */
257
+ export interface CLIStreamlinedTextMessage {
258
+ type: "streamlined_text";
259
+ text: string;
260
+ session_id: string;
261
+ uuid: string;
262
+ }
263
+
264
+ /** Simplified tool use summary (e.g. "Read 2 files, wrote 1 file"). @internal */
265
+ export interface CLIStreamlinedToolUseSummaryMessage {
266
+ type: "streamlined_tool_use_summary";
267
+ tool_summary: string;
268
+ session_id: string;
269
+ uuid: string;
270
+ }
271
+
272
+ /** Predicted next user prompts (enabled via promptSuggestions in initialize). */
273
+ export interface CLIPromptSuggestionMessage {
274
+ type: "prompt_suggestion";
275
+ suggestions: string[];
276
+ session_id: string;
277
+ uuid: string;
278
+ }
279
+
280
+ export type CLIMessage =
281
+ | CLISystemMessage
282
+ | CLIAssistantMessage
283
+ | CLIResultMessage
284
+ | CLIStreamEventMessage
285
+ | CLIToolProgressMessage
286
+ | CLIToolUseSummaryMessage
287
+ | CLIControlRequestMessage
288
+ | CLIControlResponseMessage
289
+ | CLIKeepAliveMessage
290
+ | CLIAuthStatusMessage
291
+ | CLIUserEchoMessage
292
+ | CLIRateLimitEventMessage
293
+ | CLIControlCancelRequestMessage
294
+ | CLIStreamlinedTextMessage
295
+ | CLIStreamlinedToolUseSummaryMessage
296
+ | CLIPromptSuggestionMessage;
297
+
298
+ // ─── Content Block Types ──────────────────────────────────────────────────────
299
+
300
+ export type ContentBlock =
301
+ | { type: "text"; text: string }
302
+ | { type: "tool_use"; id: string; name: string; input: Record<string, unknown> }
303
+ | { type: "tool_result"; tool_use_id: string; content: string | ContentBlock[]; is_error?: boolean }
304
+ | { type: "thinking"; thinking: string; budget_tokens?: number };
305
+
306
+ // ─── Browser Message Types (browser <-> bridge) ──────────────────────────────
307
+
308
+ /** Messages the browser sends to the bridge */
309
+ export type BrowserOutgoingMessage =
310
+ | { type: "user_message"; content: string; session_id?: string; images?: { media_type: string; data: string }[]; client_msg_id?: string }
311
+ | { type: "permission_response"; request_id: string; behavior: "allow" | "deny"; updated_input?: Record<string, unknown>; updated_permissions?: PermissionUpdate[]; message?: string; client_msg_id?: string }
312
+ | { type: "session_subscribe"; last_seq: number }
313
+ | { type: "session_ack"; last_seq: number }
314
+ | { type: "interrupt"; client_msg_id?: string }
315
+ | { type: "set_model"; model: string; client_msg_id?: string }
316
+ | { type: "set_permission_mode"; mode: string; client_msg_id?: string }
317
+ | { type: "mcp_get_status"; client_msg_id?: string }
318
+ | { type: "mcp_toggle"; serverName: string; enabled: boolean; client_msg_id?: string }
319
+ | { type: "mcp_reconnect"; serverName: string; client_msg_id?: string }
320
+ | { type: "mcp_set_servers"; servers: Record<string, McpServerConfig>; client_msg_id?: string }
321
+ | { type: "set_ai_validation"; aiValidationEnabled?: boolean | null; aiValidationAutoApprove?: boolean | null; aiValidationAutoDeny?: boolean | null; client_msg_id?: string }
322
+ | { type: "end_session"; reason?: string; client_msg_id?: string }
323
+ | { type: "stop_task"; task_id: string; client_msg_id?: string }
324
+ | { type: "update_environment_variables"; variables: Record<string, string>; client_msg_id?: string };
325
+
326
+ /** Messages the bridge sends to the browser */
327
+ export type BrowserIncomingMessageBase =
328
+ | { type: "session_init"; session: SessionState }
329
+ | { type: "session_update"; session: Partial<SessionState> }
330
+ | { type: "assistant"; message: CLIAssistantMessage["message"]; parent_tool_use_id: string | null; timestamp?: number }
331
+ | { type: "stream_event"; event: unknown; parent_tool_use_id: string | null }
332
+ | {
333
+ type: "system_event";
334
+ event:
335
+ | Pick<CLICompactBoundaryMessage, "subtype" | "compact_metadata" | "uuid" | "session_id">
336
+ | Pick<CLITaskNotificationMessage, "subtype" | "task_id" | "status" | "output_file" | "summary" | "uuid" | "session_id">
337
+ | Pick<CLIFilesPersistedMessage, "subtype" | "files" | "failed" | "processed_at" | "uuid" | "session_id">
338
+ | Pick<CLIHookStartedMessage, "subtype" | "hook_id" | "hook_name" | "hook_event" | "uuid" | "session_id">
339
+ | Pick<CLIHookProgressMessage, "subtype" | "hook_id" | "hook_name" | "hook_event" | "stdout" | "stderr" | "output" | "uuid" | "session_id">
340
+ | Pick<CLIHookResponseMessage, "subtype" | "hook_id" | "hook_name" | "hook_event" | "output" | "stdout" | "stderr" | "exit_code" | "outcome" | "uuid" | "session_id">;
341
+ timestamp?: number;
342
+ }
343
+ | { type: "result"; data: CLIResultMessage }
344
+ | { type: "permission_request"; request: PermissionRequest }
345
+ | { type: "permission_cancelled"; request_id: string }
346
+ | { type: "permission_auto_resolved"; request: PermissionRequest; behavior: "allow" | "deny"; reason: string }
347
+ | { type: "tool_progress"; tool_use_id: string; tool_name: string; elapsed_time_seconds: number }
348
+ | { type: "tool_use_summary"; summary: string; tool_use_ids: string[] }
349
+ | { type: "status_change"; status: "compacting" | "idle" | "running" | null }
350
+ | { type: "auth_status"; isAuthenticating: boolean; output: string[]; error?: string }
351
+ | { type: "error"; message: string }
352
+ | { type: "cli_disconnected" }
353
+ | { type: "cli_connected" }
354
+ | { type: "user_message"; content: string; timestamp: number; id?: string }
355
+ | { type: "message_history"; messages: BrowserIncomingMessage[] }
356
+ | { type: "event_replay"; events: BufferedBrowserEvent[] }
357
+ | { type: "session_name_update"; name: string }
358
+ | { type: "pr_status_update"; pr: import("./github-pr.js").GitHubPRInfo | null; available: boolean }
359
+ | { type: "mcp_status"; servers: McpServerDetail[] }
360
+ | { type: "session_phase"; phase: SessionPhase; previousPhase: SessionPhase }
361
+ | { type: "prompt_suggestion"; suggestions: string[] }
362
+ | { type: "streamlined_text"; text: string }
363
+ | { type: "streamlined_tool_use_summary"; tool_summary: string };
364
+
365
+ export type BrowserIncomingMessage = BrowserIncomingMessageBase & { seq?: number };
366
+
367
+ export type ReplayableBrowserIncomingMessage = Exclude<BrowserIncomingMessageBase, { type: "event_replay" }>;
368
+
369
+ export interface BufferedBrowserEvent {
370
+ seq: number;
371
+ message: ReplayableBrowserIncomingMessage;
372
+ }
373
+
374
+ // ─── Session State ────────────────────────────────────────────────────────────
375
+
376
+ export type BackendType = "claude" | "codex" | "ollama" | "openrouter" | "gemini";
377
+
378
+ export interface SessionState {
379
+ session_id: string;
380
+ backend_type?: BackendType;
381
+ model: string;
382
+ cwd: string;
383
+ tools: string[];
384
+ permissionMode: string;
385
+ claude_code_version: string;
386
+ mcp_servers: { name: string; status: string }[];
387
+ agents: string[];
388
+ slash_commands: string[];
389
+ skills: string[];
390
+ total_cost_usd: number;
391
+ num_turns: number;
392
+ context_used_percent: number;
393
+ is_compacting: boolean;
394
+ git_branch: string;
395
+ is_worktree: boolean;
396
+ is_containerized: boolean;
397
+ repo_root: string;
398
+ git_ahead: number;
399
+ git_behind: number;
400
+ total_lines_added: number;
401
+ total_lines_removed: number;
402
+ // Codex-specific token details (forwarded from thread/tokenUsage/updated)
403
+ codex_token_details?: {
404
+ inputTokens: number;
405
+ outputTokens: number;
406
+ cachedInputTokens: number;
407
+ reasoningOutputTokens: number;
408
+ modelContextWindow: number;
409
+ };
410
+ // Codex-specific rate limits (forwarded from account/rateLimits/updated)
411
+ codex_rate_limits?: {
412
+ primary: { usedPercent: number; windowDurationMins: number; resetsAt: number } | null;
413
+ secondary: { usedPercent: number; windowDurationMins: number; resetsAt: number } | null;
414
+ };
415
+ /** If this session was spawned by a cron job */
416
+ cronJobId?: string;
417
+ /** Human-readable name of the cron job that spawned this session */
418
+ cronJobName?: string;
419
+ /** If this session was spawned by an agent */
420
+ agentId?: string;
421
+ /** Human-readable name of the agent that spawned this session */
422
+ agentName?: string;
423
+ /** Per-session AI validation override. null/undefined = use global default */
424
+ aiValidationEnabled?: boolean | null;
425
+ /** Per-session auto-approve override. null/undefined = use global default */
426
+ aiValidationAutoApprove?: boolean | null;
427
+ /** Per-session auto-deny override. null/undefined = use global default */
428
+ aiValidationAutoDeny?: boolean | null;
429
+ /** Federation: remote node ID if this session originates from a peer */
430
+ nodeId?: string;
431
+ /** Federation: remote node display name */
432
+ nodeName?: string;
433
+ }
434
+
435
+ // ─── MCP Types ───────────────────────────────────────────────────────────────
436
+
437
+ export interface McpServerConfig {
438
+ type: "stdio" | "sse" | "http" | "sdk";
439
+ command?: string;
440
+ args?: string[];
441
+ env?: Record<string, string>;
442
+ url?: string;
443
+ }
444
+
445
+ export interface McpServerDetail {
446
+ name: string;
447
+ status: "connected" | "failed" | "disabled" | "connecting";
448
+ serverInfo?: unknown;
449
+ error?: string;
450
+ config: { type: string; url?: string; command?: string; args?: string[] };
451
+ scope: string;
452
+ tools?: { name: string; annotations?: { readOnly?: boolean; destructive?: boolean; openWorld?: boolean } }[];
453
+ }
454
+
455
+ // ─── Permission Request ──────────────────────────────────────────────────────
456
+
457
+ // ─── Permission Rule Types ───────────────────────────────────────────────────
458
+
459
+ export type PermissionDestination = "userSettings" | "projectSettings" | "localSettings" | "session" | "cliArg";
460
+
461
+ export type PermissionUpdate =
462
+ | { type: "addRules"; rules: { toolName: string; ruleContent?: string }[]; behavior: "allow" | "deny" | "ask"; destination: PermissionDestination }
463
+ | { type: "replaceRules"; rules: { toolName: string; ruleContent?: string }[]; behavior: "allow" | "deny" | "ask"; destination: PermissionDestination }
464
+ | { type: "removeRules"; rules: { toolName: string; ruleContent?: string }[]; behavior: "allow" | "deny" | "ask"; destination: PermissionDestination }
465
+ | { type: "setMode"; mode: string; destination: PermissionDestination }
466
+ | { type: "addDirectories"; directories: string[]; destination: PermissionDestination }
467
+ | { type: "removeDirectories"; directories: string[]; destination: PermissionDestination };
468
+
469
+ export interface AiValidationInfo {
470
+ verdict: "safe" | "dangerous" | "uncertain";
471
+ reason: string;
472
+ ruleBasedOnly: boolean;
473
+ }
474
+
475
+ export interface PermissionRequest {
476
+ request_id: string;
477
+ tool_name: string;
478
+ input: Record<string, unknown>;
479
+ permission_suggestions?: PermissionUpdate[];
480
+ description?: string;
481
+ tool_use_id: string;
482
+ agent_id?: string;
483
+ title?: string;
484
+ display_name?: string;
485
+ blocked_path?: string;
486
+ decision_reason?: string;
487
+ timestamp: number;
488
+ ai_validation?: AiValidationInfo;
489
+ }
490
+
491
+ // ─── Session Creation Progress (SSE streaming) ──────────────────────────────
492
+
493
+ export type CreationStepId =
494
+ | "resolving_env"
495
+ | "fetching_git"
496
+ | "checkout_branch"
497
+ | "pulling_git"
498
+ | "creating_worktree"
499
+ | "pulling_image"
500
+ | "building_image"
501
+ | "creating_container"
502
+ | "copying_workspace"
503
+ | "running_init_script"
504
+ | "launching_cli";
505
+
506
+ export interface CreationProgressEvent {
507
+ step: CreationStepId;
508
+ label: string;
509
+ status: "in_progress" | "done" | "error";
510
+ detail?: string;
511
+ }
@@ -0,0 +1,149 @@
1
+ import {
2
+ mkdirSync,
3
+ readFileSync,
4
+ writeFileSync,
5
+ existsSync,
6
+ } from "node:fs";
7
+ import { join, dirname } from "node:path";
8
+ import { HEYHANK_HOME } from "./paths.js";
9
+
10
+ export const DEFAULT_ANTHROPIC_MODEL = "claude-sonnet-4-6";
11
+
12
+ export type UpdateChannel = "stable" | "prerelease";
13
+
14
+ export interface HeyHankSettings {
15
+ anthropicApiKey: string;
16
+ anthropicModel: string;
17
+ /** OAuth token obtained via `claude setup-token` — injected as CLAUDE_CODE_OAUTH_TOKEN */
18
+ claudeCodeOAuthToken: string;
19
+ /** OpenAI API key for Codex — injected as OPENAI_API_KEY */
20
+ openaiApiKey: string;
21
+ /** Whether the onboarding wizard has been completed */
22
+ onboardingCompleted: boolean;
23
+ /** Gemini API key for voice chat */
24
+ geminiApiKey: string;
25
+ /** Gemini Live voice name (e.g. Kore, Puck, Charon, Fenrir, Aoede, Leda, Orus, Zephyr) */
26
+ geminiVoice: string;
27
+ /** Custom name for the voice assistant (e.g. "Jarvis", "Friday") */
28
+ assistantName: string;
29
+ /** User's display name so the assistant knows who it's talking to */
30
+ userName: string;
31
+ editorTabEnabled: boolean;
32
+ /** Provider ID for internal AI features (auto-renaming, AI validation). Empty = auto-detect. */
33
+ internalAiProvider: string;
34
+ aiValidationEnabled: boolean;
35
+ aiValidationAutoApprove: boolean;
36
+ aiValidationAutoDeny: boolean;
37
+ publicUrl: string;
38
+ updateChannel: UpdateChannel;
39
+ dockerAutoUpdate: boolean;
40
+ updatedAt: number;
41
+ }
42
+
43
+ const DEFAULT_PATH = join(HEYHANK_HOME, "settings.json");
44
+
45
+ let loaded = false;
46
+ let filePath = DEFAULT_PATH;
47
+ let settings: HeyHankSettings = {
48
+ anthropicApiKey: "",
49
+ anthropicModel: DEFAULT_ANTHROPIC_MODEL,
50
+ claudeCodeOAuthToken: "",
51
+ openaiApiKey: "",
52
+ onboardingCompleted: false,
53
+ geminiApiKey: "",
54
+ geminiVoice: "Kore",
55
+ assistantName: "",
56
+ userName: "",
57
+ editorTabEnabled: false,
58
+ internalAiProvider: "",
59
+ aiValidationEnabled: false,
60
+ aiValidationAutoApprove: true,
61
+ aiValidationAutoDeny: false,
62
+ publicUrl: "",
63
+ updateChannel: "stable",
64
+ dockerAutoUpdate: false,
65
+ updatedAt: 0,
66
+ };
67
+
68
+ function normalize(raw: Partial<HeyHankSettings> | null | undefined): HeyHankSettings {
69
+ return {
70
+ anthropicApiKey: typeof raw?.anthropicApiKey === "string" ? raw.anthropicApiKey : "",
71
+ anthropicModel:
72
+ typeof raw?.anthropicModel === "string" && raw.anthropicModel.trim()
73
+ ? raw.anthropicModel === "claude-sonnet-4.6" ? DEFAULT_ANTHROPIC_MODEL : raw.anthropicModel
74
+ : DEFAULT_ANTHROPIC_MODEL,
75
+ claudeCodeOAuthToken: typeof raw?.claudeCodeOAuthToken === "string" ? raw.claudeCodeOAuthToken : "",
76
+ openaiApiKey: typeof raw?.openaiApiKey === "string" ? raw.openaiApiKey : "",
77
+ onboardingCompleted: typeof raw?.onboardingCompleted === "boolean" ? raw.onboardingCompleted : false,
78
+ geminiApiKey: typeof raw?.geminiApiKey === "string" ? raw.geminiApiKey : "",
79
+ geminiVoice: typeof raw?.geminiVoice === "string" && raw.geminiVoice.trim() ? raw.geminiVoice : "Kore",
80
+ assistantName: typeof raw?.assistantName === "string" ? raw.assistantName.trim() : "",
81
+ userName: typeof raw?.userName === "string" ? raw.userName.trim() : "",
82
+ editorTabEnabled: typeof raw?.editorTabEnabled === "boolean" ? raw.editorTabEnabled : false,
83
+ internalAiProvider: typeof raw?.internalAiProvider === "string" ? raw.internalAiProvider.trim() : "",
84
+ aiValidationEnabled: typeof raw?.aiValidationEnabled === "boolean" ? raw.aiValidationEnabled : false,
85
+ aiValidationAutoApprove: typeof raw?.aiValidationAutoApprove === "boolean" ? raw.aiValidationAutoApprove : true,
86
+ aiValidationAutoDeny: typeof raw?.aiValidationAutoDeny === "boolean" ? raw.aiValidationAutoDeny : false,
87
+ publicUrl: typeof raw?.publicUrl === "string" ? raw.publicUrl.trim().replace(/\/+$/, "") : "",
88
+ updateChannel: raw?.updateChannel === "prerelease" ? "prerelease" : "stable",
89
+ dockerAutoUpdate: typeof raw?.dockerAutoUpdate === "boolean" ? raw.dockerAutoUpdate : false,
90
+ updatedAt: typeof raw?.updatedAt === "number" ? raw.updatedAt : 0,
91
+ };
92
+ }
93
+
94
+ function ensureLoaded(): void {
95
+ if (loaded) return;
96
+ try {
97
+ if (existsSync(filePath)) {
98
+ const raw = readFileSync(filePath, "utf-8");
99
+ settings = normalize(JSON.parse(raw) as Partial<HeyHankSettings>);
100
+ }
101
+ } catch {
102
+ settings = normalize(null);
103
+ }
104
+ loaded = true;
105
+ }
106
+
107
+ function persist(): void {
108
+ mkdirSync(dirname(filePath), { recursive: true });
109
+ writeFileSync(filePath, JSON.stringify(settings, null, 2), { encoding: "utf-8", mode: 0o600 });
110
+ }
111
+
112
+ export function getSettings(): HeyHankSettings {
113
+ ensureLoaded();
114
+ return { ...settings };
115
+ }
116
+
117
+ export function updateSettings(
118
+ patch: Partial<Pick<HeyHankSettings, "anthropicApiKey" | "anthropicModel" | "claudeCodeOAuthToken" | "openaiApiKey" | "onboardingCompleted" | "geminiApiKey" | "geminiVoice" | "assistantName" | "userName" | "editorTabEnabled" | "internalAiProvider" | "aiValidationEnabled" | "aiValidationAutoApprove" | "aiValidationAutoDeny" | "publicUrl" | "updateChannel" | "dockerAutoUpdate">>,
119
+ ): HeyHankSettings {
120
+ ensureLoaded();
121
+ settings = normalize({
122
+ anthropicApiKey: patch.anthropicApiKey ?? settings.anthropicApiKey,
123
+ anthropicModel: patch.anthropicModel ?? settings.anthropicModel,
124
+ claudeCodeOAuthToken: patch.claudeCodeOAuthToken ?? settings.claudeCodeOAuthToken,
125
+ openaiApiKey: patch.openaiApiKey ?? settings.openaiApiKey,
126
+ onboardingCompleted: patch.onboardingCompleted ?? settings.onboardingCompleted,
127
+ geminiApiKey: patch.geminiApiKey ?? settings.geminiApiKey,
128
+ geminiVoice: patch.geminiVoice ?? settings.geminiVoice,
129
+ assistantName: patch.assistantName ?? settings.assistantName,
130
+ userName: patch.userName ?? settings.userName,
131
+ editorTabEnabled: patch.editorTabEnabled ?? settings.editorTabEnabled,
132
+ internalAiProvider: patch.internalAiProvider ?? settings.internalAiProvider,
133
+ aiValidationEnabled: patch.aiValidationEnabled ?? settings.aiValidationEnabled,
134
+ aiValidationAutoApprove: patch.aiValidationAutoApprove ?? settings.aiValidationAutoApprove,
135
+ aiValidationAutoDeny: patch.aiValidationAutoDeny ?? settings.aiValidationAutoDeny,
136
+ publicUrl: patch.publicUrl ?? settings.publicUrl,
137
+ updateChannel: patch.updateChannel ?? settings.updateChannel,
138
+ dockerAutoUpdate: patch.dockerAutoUpdate ?? settings.dockerAutoUpdate,
139
+ updatedAt: Date.now(),
140
+ });
141
+ persist();
142
+ return { ...settings };
143
+ }
144
+
145
+ export function _resetForTest(customPath?: string): void {
146
+ loaded = false;
147
+ filePath = customPath || DEFAULT_PATH;
148
+ settings = normalize(null);
149
+ }