@clawpump/claw-agent 0.1.4 → 0.1.6

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 (1214) hide show
  1. package/agent/.dockerignore +67 -0
  2. package/agent/.envrc +1 -1
  3. package/agent/.gitattributes +8 -0
  4. package/agent/AGENTS.md +216 -4
  5. package/agent/CONTRIBUTING.md +46 -8
  6. package/agent/Dockerfile +78 -35
  7. package/agent/MANIFEST.in +2 -0
  8. package/agent/README.md +12 -5
  9. package/agent/README.ur-pk.md +261 -0
  10. package/agent/README.zh-CN.md +11 -8
  11. package/agent/SECURITY.md +5 -4
  12. package/agent/acp_adapter/provenance.py +127 -0
  13. package/agent/acp_adapter/server.py +112 -5
  14. package/agent/acp_adapter/session.py +1 -6
  15. package/agent/acp_registry/agent.json +2 -2
  16. package/agent/agent/account_usage.py +313 -1
  17. package/agent/agent/agent_init.py +140 -37
  18. package/agent/agent/agent_runtime_helpers.py +342 -83
  19. package/agent/agent/anthropic_adapter.py +320 -33
  20. package/agent/agent/auxiliary_client.py +525 -105
  21. package/agent/agent/background_review.py +157 -19
  22. package/agent/agent/bedrock_adapter.py +71 -6
  23. package/agent/agent/billing_view.py +295 -0
  24. package/agent/agent/chat_completion_helpers.py +229 -4
  25. package/agent/agent/codex_responses_adapter.py +86 -10
  26. package/agent/agent/codex_runtime.py +153 -1
  27. package/agent/agent/coding_context.py +738 -0
  28. package/agent/agent/context_compressor.py +392 -44
  29. package/agent/agent/context_references.py +34 -1
  30. package/agent/agent/conversation_compression.py +159 -22
  31. package/agent/agent/conversation_loop.py +643 -908
  32. package/agent/agent/copilot_acp_client.py +4 -11
  33. package/agent/agent/credential_pool.py +5 -3
  34. package/agent/agent/credits_tracker.py +794 -0
  35. package/agent/agent/curator.py +91 -18
  36. package/agent/agent/curator_backup.py +26 -10
  37. package/agent/agent/display.py +42 -1
  38. package/agent/agent/error_classifier.py +52 -3
  39. package/agent/agent/errors.py +3 -0
  40. package/agent/agent/file_safety.py +0 -17
  41. package/agent/agent/gemini_native_adapter.py +31 -1
  42. package/agent/agent/i18n.py +48 -4
  43. package/agent/agent/image_gen_provider.py +74 -5
  44. package/agent/agent/image_routing.py +29 -0
  45. package/agent/agent/insights.py +8 -17
  46. package/agent/agent/lsp/install.py +3 -0
  47. package/agent/agent/memory_manager.py +326 -31
  48. package/agent/agent/message_content.py +50 -0
  49. package/agent/agent/model_metadata.py +214 -3
  50. package/agent/agent/moonshot_schema.py +8 -1
  51. package/agent/agent/onboarding.py +60 -0
  52. package/agent/agent/prompt_builder.py +327 -37
  53. package/agent/agent/redact.py +1 -0
  54. package/agent/agent/runtime_cwd.py +34 -5
  55. package/agent/agent/secret_scope.py +205 -0
  56. package/agent/agent/secret_sources/bitwarden.py +34 -2
  57. package/agent/agent/skill_commands.py +90 -1
  58. package/agent/agent/skill_preprocessing.py +1 -0
  59. package/agent/agent/skill_utils.py +209 -36
  60. package/agent/agent/ssl_guard.py +94 -0
  61. package/agent/agent/system_prompt.py +133 -5
  62. package/agent/agent/tool_executor.py +496 -70
  63. package/agent/agent/transports/anthropic.py +83 -21
  64. package/agent/agent/transports/chat_completions.py +94 -5
  65. package/agent/agent/transports/codex.py +67 -2
  66. package/agent/agent/transports/codex_app_server.py +1 -0
  67. package/agent/agent/transports/codex_app_server_session.py +30 -0
  68. package/agent/agent/transports/types.py +12 -0
  69. package/agent/agent/turn_context.py +408 -0
  70. package/agent/agent/turn_finalizer.py +428 -0
  71. package/agent/agent/turn_retry_state.py +68 -0
  72. package/agent/agent/usage_pricing.py +3 -0
  73. package/agent/apps/bootstrap-installer/package.json +6 -5
  74. package/agent/apps/bootstrap-installer/src/routes/failure.tsx +12 -5
  75. package/agent/apps/bootstrap-installer/src/routes/progress.tsx +1 -3
  76. package/agent/apps/bootstrap-installer/src/store.ts +3 -2
  77. package/agent/apps/bootstrap-installer/src-tauri/src/bootstrap.rs +172 -7
  78. package/agent/apps/bootstrap-installer/src-tauri/src/events.rs +14 -1
  79. package/agent/apps/bootstrap-installer/src-tauri/src/paths.rs +29 -0
  80. package/agent/apps/bootstrap-installer/src-tauri/src/powershell.rs +93 -3
  81. package/agent/apps/bootstrap-installer/src-tauri/src/update.rs +695 -39
  82. package/agent/apps/bootstrap-installer/tsconfig.json +3 -4
  83. package/agent/apps/desktop/DESIGN.md +167 -0
  84. package/agent/apps/desktop/README.md +20 -16
  85. package/agent/apps/desktop/assets/icon.icns +0 -0
  86. package/agent/apps/desktop/assets/icon.ico +0 -0
  87. package/agent/apps/desktop/assets/icon.png +0 -0
  88. package/agent/apps/desktop/electron/backend-env.cjs +112 -0
  89. package/agent/apps/desktop/electron/backend-env.test.cjs +111 -0
  90. package/agent/apps/desktop/electron/backend-probes.test.cjs +3 -1
  91. package/agent/apps/desktop/electron/backend-ready.cjs +66 -0
  92. package/agent/apps/desktop/electron/bootstrap-platform.cjs +52 -0
  93. package/agent/apps/desktop/electron/bootstrap-platform.test.cjs +59 -1
  94. package/agent/apps/desktop/electron/bootstrap-runner.cjs +176 -38
  95. package/agent/apps/desktop/electron/bootstrap-runner.test.cjs +112 -1
  96. package/agent/apps/desktop/electron/connection-config.cjs +288 -0
  97. package/agent/apps/desktop/electron/connection-config.test.cjs +396 -0
  98. package/agent/apps/desktop/electron/dashboard-token.cjs +99 -0
  99. package/agent/apps/desktop/electron/dashboard-token.test.cjs +142 -0
  100. package/agent/apps/desktop/electron/desktop-uninstall.cjs +232 -0
  101. package/agent/apps/desktop/electron/desktop-uninstall.test.cjs +246 -0
  102. package/agent/apps/desktop/electron/entitlements.mac.inherit.plist +2 -0
  103. package/agent/apps/desktop/electron/fs-read-dir.cjs +109 -0
  104. package/agent/apps/desktop/electron/fs-read-dir.test.cjs +364 -0
  105. package/agent/apps/desktop/electron/gateway-ws-probe.cjs +188 -0
  106. package/agent/apps/desktop/electron/gateway-ws-probe.test.cjs +122 -0
  107. package/agent/apps/desktop/electron/git-root.cjs +54 -0
  108. package/agent/apps/desktop/electron/git-root.test.cjs +40 -0
  109. package/agent/apps/desktop/electron/git-worktrees.cjs +174 -0
  110. package/agent/apps/desktop/electron/hardening.cjs +123 -28
  111. package/agent/apps/desktop/electron/hardening.test.cjs +163 -0
  112. package/agent/apps/desktop/electron/main.cjs +3121 -331
  113. package/agent/apps/desktop/electron/oauth-net-request.cjs +20 -0
  114. package/agent/apps/desktop/electron/oauth-net-request.test.cjs +34 -0
  115. package/agent/apps/desktop/electron/preload.cjs +52 -2
  116. package/agent/apps/desktop/electron/session-windows.cjs +124 -0
  117. package/agent/apps/desktop/electron/session-windows.test.cjs +199 -0
  118. package/agent/apps/desktop/electron/update-rebuild.cjs +29 -0
  119. package/agent/apps/desktop/electron/update-rebuild.test.cjs +55 -0
  120. package/agent/apps/desktop/electron/update-remote.cjs +56 -0
  121. package/agent/apps/desktop/electron/update-remote.test.cjs +78 -0
  122. package/agent/apps/desktop/electron/vscode-marketplace.cjs +331 -0
  123. package/agent/apps/desktop/electron/vscode-marketplace.test.cjs +113 -0
  124. package/agent/apps/desktop/electron/windows-child-process.test.cjs +57 -0
  125. package/agent/apps/desktop/electron/windows-user-env.cjs +76 -0
  126. package/agent/apps/desktop/electron/windows-user-env.test.cjs +90 -0
  127. package/agent/apps/desktop/electron/workspace-cwd.cjs +38 -0
  128. package/agent/apps/desktop/electron/workspace-cwd.test.cjs +45 -0
  129. package/agent/apps/desktop/eslint.config.mjs +0 -3
  130. package/agent/apps/desktop/index.html +27 -2
  131. package/agent/apps/desktop/package.json +31 -11
  132. package/agent/apps/desktop/pr-assets/session-source-folders.png +0 -0
  133. package/agent/apps/desktop/public/apple-touch-icon.png +0 -0
  134. package/agent/apps/desktop/public/nous-girl.jpg +0 -0
  135. package/agent/apps/desktop/scripts/assert-dist-built.cjs +70 -0
  136. package/agent/apps/desktop/scripts/assert-dist-built.test.cjs +84 -0
  137. package/agent/apps/desktop/scripts/before-pack.cjs +78 -0
  138. package/agent/apps/desktop/scripts/before-pack.test.cjs +53 -0
  139. package/agent/apps/desktop/scripts/diag-scroll-reset.mjs +229 -0
  140. package/agent/apps/desktop/scripts/patch-electron-builder-mac-binary.cjs +64 -0
  141. package/agent/apps/desktop/scripts/run-electron-builder.cjs +57 -0
  142. package/agent/apps/desktop/src/app/agents/index.tsx +53 -45
  143. package/agent/apps/desktop/src/app/artifacts/index.tsx +102 -83
  144. package/agent/apps/desktop/src/app/chat/chat-drop-overlay.tsx +29 -8
  145. package/agent/apps/desktop/src/app/chat/chat-swap-overlay.tsx +47 -0
  146. package/agent/apps/desktop/src/app/chat/composer/attachments.tsx +81 -45
  147. package/agent/apps/desktop/src/app/chat/composer/completion-drawer.tsx +13 -24
  148. package/agent/apps/desktop/src/app/chat/composer/context-menu.tsx +138 -88
  149. package/agent/apps/desktop/src/app/chat/composer/controls.tsx +138 -90
  150. package/agent/apps/desktop/src/app/chat/composer/enter-submit-dom-race.test.tsx +218 -0
  151. package/agent/apps/desktop/src/app/chat/composer/focus.ts +32 -0
  152. package/agent/apps/desktop/src/app/chat/composer/help-hint.tsx +38 -25
  153. package/agent/apps/desktop/src/app/chat/composer/hooks/use-live-completion-adapter.ts +7 -0
  154. package/agent/apps/desktop/src/app/chat/composer/hooks/use-mic-recorder.ts +22 -12
  155. package/agent/apps/desktop/src/app/chat/composer/hooks/use-slash-completions.ts +142 -14
  156. package/agent/apps/desktop/src/app/chat/composer/hooks/use-voice-conversation.ts +14 -11
  157. package/agent/apps/desktop/src/app/chat/composer/hooks/use-voice-recorder.ts +9 -6
  158. package/agent/apps/desktop/src/app/chat/composer/ime-composition-dom-repro.test.tsx +108 -0
  159. package/agent/apps/desktop/src/app/chat/composer/index.tsx +930 -180
  160. package/agent/apps/desktop/src/app/chat/composer/inline-refs.ts +136 -32
  161. package/agent/apps/desktop/src/app/chat/composer/model-pill.tsx +86 -0
  162. package/agent/apps/desktop/src/app/chat/composer/queue-panel.tsx +54 -75
  163. package/agent/apps/desktop/src/app/chat/composer/rich-editor.test.ts +117 -1
  164. package/agent/apps/desktop/src/app/chat/composer/rich-editor.ts +117 -6
  165. package/agent/apps/desktop/src/app/chat/composer/slash-nav-dom-repro.test.tsx +186 -0
  166. package/agent/apps/desktop/src/app/chat/composer/status-stack/index.tsx +202 -0
  167. package/agent/apps/desktop/src/app/chat/composer/status-stack/status-row.tsx +155 -0
  168. package/agent/apps/desktop/src/app/chat/composer/text-utils.test.ts +104 -0
  169. package/agent/apps/desktop/src/app/chat/composer/text-utils.ts +37 -9
  170. package/agent/apps/desktop/src/app/chat/composer/trigger-popover.test.tsx +50 -0
  171. package/agent/apps/desktop/src/app/chat/composer/trigger-popover.tsx +105 -40
  172. package/agent/apps/desktop/src/app/chat/composer/types.ts +5 -0
  173. package/agent/apps/desktop/src/app/chat/composer/url-dialog.tsx +11 -15
  174. package/agent/apps/desktop/src/app/chat/composer/voice-activity.tsx +8 -4
  175. package/agent/apps/desktop/src/app/chat/hooks/use-composer-actions.test.ts +57 -0
  176. package/agent/apps/desktop/src/app/chat/hooks/use-composer-actions.ts +70 -16
  177. package/agent/apps/desktop/src/app/chat/hooks/use-file-drop-zone.ts +52 -16
  178. package/agent/apps/desktop/src/app/chat/index.tsx +234 -81
  179. package/agent/apps/desktop/src/app/chat/perf-probe.tsx +69 -21
  180. package/agent/apps/desktop/src/app/chat/right-rail/preview-console.tsx +44 -40
  181. package/agent/apps/desktop/src/app/chat/right-rail/preview-file.tsx +71 -25
  182. package/agent/apps/desktop/src/app/chat/right-rail/preview-pane.test.tsx +40 -1
  183. package/agent/apps/desktop/src/app/chat/right-rail/preview-pane.tsx +55 -53
  184. package/agent/apps/desktop/src/app/chat/right-rail/preview.tsx +35 -17
  185. package/agent/apps/desktop/src/app/chat/scroll-to-bottom-button.test.tsx +67 -0
  186. package/agent/apps/desktop/src/app/chat/scroll-to-bottom-button.tsx +74 -0
  187. package/agent/apps/desktop/src/app/chat/sidebar/cron-jobs-section.tsx +356 -0
  188. package/agent/apps/desktop/src/app/chat/sidebar/index.tsx +1189 -364
  189. package/agent/apps/desktop/src/app/chat/sidebar/load-more-row.tsx +30 -0
  190. package/agent/apps/desktop/src/app/chat/sidebar/order.test.ts +21 -0
  191. package/agent/apps/desktop/src/app/chat/sidebar/order.ts +17 -0
  192. package/agent/apps/desktop/src/app/chat/sidebar/profile-switcher.tsx +524 -0
  193. package/agent/apps/desktop/src/app/chat/sidebar/session-actions-menu.tsx +80 -45
  194. package/agent/apps/desktop/src/app/chat/sidebar/session-row.tsx +120 -25
  195. package/agent/apps/desktop/src/app/chat/sidebar/virtual-session-list.tsx +7 -13
  196. package/agent/apps/desktop/src/app/chat/sidebar/workspace-groups.test.ts +149 -0
  197. package/agent/apps/desktop/src/app/chat/sidebar/workspace-groups.ts +326 -0
  198. package/agent/apps/desktop/src/app/chat/thread-loading.ts +7 -2
  199. package/agent/apps/desktop/src/app/command-center/index.tsx +320 -581
  200. package/agent/apps/desktop/src/app/command-palette/index.tsx +681 -0
  201. package/agent/apps/desktop/src/app/command-palette/marketplace-theme-page.tsx +157 -0
  202. package/agent/apps/desktop/src/app/cron/index.tsx +392 -324
  203. package/agent/apps/desktop/src/app/cron/job-state.ts +29 -0
  204. package/agent/apps/desktop/src/app/desktop-controller.tsx +618 -123
  205. package/agent/apps/desktop/src/app/floating-hud.ts +22 -0
  206. package/agent/apps/desktop/src/app/gateway/hooks/use-gateway-boot.test.tsx +265 -0
  207. package/agent/apps/desktop/src/app/gateway/hooks/use-gateway-boot.ts +260 -14
  208. package/agent/apps/desktop/src/app/gateway/hooks/use-gateway-request.ts +48 -4
  209. package/agent/apps/desktop/src/app/hooks/use-keybinds.ts +270 -0
  210. package/agent/apps/desktop/src/app/hooks/use-refresh-hotkey.ts +45 -0
  211. package/agent/apps/desktop/src/app/layout-constants.ts +19 -0
  212. package/agent/apps/desktop/src/app/messaging/index.tsx +136 -241
  213. package/agent/apps/desktop/src/app/messaging/platform-icon.tsx +95 -0
  214. package/agent/apps/desktop/src/app/model-visibility-overlay.tsx +31 -0
  215. package/agent/apps/desktop/src/app/overlays/overlay-search-input.tsx +18 -62
  216. package/agent/apps/desktop/src/app/overlays/overlay-split-layout.tsx +59 -7
  217. package/agent/apps/desktop/src/app/overlays/overlay-view.tsx +9 -5
  218. package/agent/apps/desktop/src/app/page-search-shell.tsx +42 -20
  219. package/agent/apps/desktop/src/app/profiles/create-profile-dialog.tsx +165 -0
  220. package/agent/apps/desktop/src/app/profiles/delete-profile-dialog.tsx +65 -0
  221. package/agent/apps/desktop/src/app/profiles/index.tsx +174 -199
  222. package/agent/apps/desktop/src/app/profiles/rename-profile-dialog.tsx +125 -0
  223. package/agent/apps/desktop/src/app/right-sidebar/files/dnd-manager.ts +27 -0
  224. package/agent/apps/desktop/src/app/right-sidebar/files/ipc.test.ts +100 -0
  225. package/agent/apps/desktop/src/app/right-sidebar/files/ipc.ts +12 -18
  226. package/agent/apps/desktop/src/app/right-sidebar/files/remote-picker.tsx +177 -0
  227. package/agent/apps/desktop/src/app/right-sidebar/files/tree.tsx +35 -21
  228. package/agent/apps/desktop/src/app/right-sidebar/files/use-project-tree.test.ts +75 -3
  229. package/agent/apps/desktop/src/app/right-sidebar/files/use-project-tree.ts +152 -5
  230. package/agent/apps/desktop/src/app/right-sidebar/index.test.tsx +75 -0
  231. package/agent/apps/desktop/src/app/right-sidebar/index.tsx +166 -129
  232. package/agent/apps/desktop/src/app/right-sidebar/store.ts +19 -4
  233. package/agent/apps/desktop/src/app/right-sidebar/terminal/buffer.ts +65 -0
  234. package/agent/apps/desktop/src/app/right-sidebar/terminal/index.tsx +29 -34
  235. package/agent/apps/desktop/src/app/right-sidebar/terminal/persistent.tsx +18 -6
  236. package/agent/apps/desktop/src/app/right-sidebar/terminal/selection.ts +93 -32
  237. package/agent/apps/desktop/src/app/right-sidebar/terminal/use-terminal-session.ts +381 -119
  238. package/agent/apps/desktop/src/app/routes.ts +9 -0
  239. package/agent/apps/desktop/src/app/session/hooks/use-cwd-actions.ts +17 -7
  240. package/agent/apps/desktop/src/app/session/hooks/use-message-stream.ts +365 -47
  241. package/agent/apps/desktop/src/app/session/hooks/use-model-controls.test.tsx +198 -0
  242. package/agent/apps/desktop/src/app/session/hooks/use-model-controls.ts +70 -34
  243. package/agent/apps/desktop/src/app/session/hooks/use-prompt-actions.test.tsx +1061 -0
  244. package/agent/apps/desktop/src/app/session/hooks/use-prompt-actions.ts +1143 -165
  245. package/agent/apps/desktop/src/app/session/hooks/use-route-resume.test.tsx +341 -2
  246. package/agent/apps/desktop/src/app/session/hooks/use-route-resume.ts +176 -5
  247. package/agent/apps/desktop/src/app/session/hooks/use-session-actions.test.tsx +259 -0
  248. package/agent/apps/desktop/src/app/session/hooks/use-session-actions.ts +452 -149
  249. package/agent/apps/desktop/src/app/session/hooks/use-session-state-cache.test.tsx +327 -0
  250. package/agent/apps/desktop/src/app/session/hooks/use-session-state-cache.ts +133 -4
  251. package/agent/apps/desktop/src/app/session-picker-overlay.tsx +32 -0
  252. package/agent/apps/desktop/src/app/session-switcher.tsx +107 -0
  253. package/agent/apps/desktop/src/app/settings/about-settings.tsx +45 -36
  254. package/agent/apps/desktop/src/app/settings/appearance-settings.tsx +243 -162
  255. package/agent/apps/desktop/src/app/settings/config-settings.tsx +86 -66
  256. package/agent/apps/desktop/src/app/settings/constants.ts +459 -122
  257. package/agent/apps/desktop/src/app/settings/credential-key-ui.tsx +373 -0
  258. package/agent/apps/desktop/src/app/settings/env-credentials.tsx +198 -0
  259. package/agent/apps/desktop/src/app/settings/env-var-actions-menu.tsx +136 -0
  260. package/agent/apps/desktop/src/app/settings/field-copy.ts +56 -0
  261. package/agent/apps/desktop/src/app/settings/gateway-settings.tsx +385 -72
  262. package/agent/apps/desktop/src/app/settings/helpers.test.ts +156 -1
  263. package/agent/apps/desktop/src/app/settings/helpers.ts +30 -2
  264. package/agent/apps/desktop/src/app/settings/index.tsx +118 -84
  265. package/agent/apps/desktop/src/app/settings/keys-settings.tsx +62 -419
  266. package/agent/apps/desktop/src/app/settings/mcp-settings.tsx +65 -60
  267. package/agent/apps/desktop/src/app/settings/model-settings.test.tsx +129 -5
  268. package/agent/apps/desktop/src/app/settings/model-settings.tsx +370 -65
  269. package/agent/apps/desktop/src/app/settings/notifications-settings.tsx +150 -0
  270. package/agent/apps/desktop/src/app/settings/primitives.tsx +5 -11
  271. package/agent/apps/desktop/src/app/settings/provider-config-panel.test.tsx +142 -0
  272. package/agent/apps/desktop/src/app/settings/provider-config-panel.tsx +182 -0
  273. package/agent/apps/desktop/src/app/settings/providers-settings.test.tsx +171 -0
  274. package/agent/apps/desktop/src/app/settings/providers-settings.tsx +471 -0
  275. package/agent/apps/desktop/src/app/settings/sessions-settings.tsx +183 -71
  276. package/agent/apps/desktop/src/app/settings/toolset-config-panel.test.tsx +135 -1
  277. package/agent/apps/desktop/src/app/settings/toolset-config-panel.tsx +180 -57
  278. package/agent/apps/desktop/src/app/settings/types.ts +9 -6
  279. package/agent/apps/desktop/src/app/settings/uninstall-section.tsx +185 -0
  280. package/agent/apps/desktop/src/app/settings/use-deep-link-highlight.ts +60 -0
  281. package/agent/apps/desktop/src/app/shell/app-shell.tsx +59 -13
  282. package/agent/apps/desktop/src/app/shell/gateway-menu-panel.tsx +37 -32
  283. package/agent/apps/desktop/src/app/shell/hooks/use-overlay-routing.ts +6 -3
  284. package/agent/apps/desktop/src/app/shell/hooks/use-statusbar-items.tsx +212 -53
  285. package/agent/apps/desktop/src/app/shell/keybind-panel.tsx +215 -0
  286. package/agent/apps/desktop/src/app/shell/model-edit-submenu.test.tsx +84 -0
  287. package/agent/apps/desktop/src/app/shell/model-edit-submenu.tsx +244 -0
  288. package/agent/apps/desktop/src/app/shell/model-menu-panel.tsx +392 -0
  289. package/agent/apps/desktop/src/app/shell/statusbar-controls.tsx +23 -33
  290. package/agent/apps/desktop/src/app/shell/titlebar-controls.tsx +79 -95
  291. package/agent/apps/desktop/src/app/shell/titlebar.ts +8 -2
  292. package/agent/apps/desktop/src/app/skills/index.test.tsx +11 -0
  293. package/agent/apps/desktop/src/app/skills/index.tsx +79 -64
  294. package/agent/apps/desktop/src/app/types.ts +85 -0
  295. package/agent/apps/desktop/src/app/updates-overlay.tsx +110 -105
  296. package/agent/apps/desktop/src/components/assistant-ui/ansi-text.tsx +34 -0
  297. package/agent/apps/desktop/src/components/assistant-ui/block-direction.test.tsx +129 -0
  298. package/agent/apps/desktop/src/components/assistant-ui/clarify-tool.tsx +102 -81
  299. package/agent/apps/desktop/src/components/assistant-ui/directive-text.tsx +92 -15
  300. package/agent/apps/desktop/src/components/assistant-ui/markdown-text.test.ts +38 -0
  301. package/agent/apps/desktop/src/components/assistant-ui/markdown-text.tsx +304 -45
  302. package/agent/apps/desktop/src/components/assistant-ui/message-render-boundary.test.tsx +80 -0
  303. package/agent/apps/desktop/src/components/assistant-ui/message-render-boundary.tsx +48 -0
  304. package/agent/apps/desktop/src/components/assistant-ui/streaming.test.tsx +142 -90
  305. package/agent/apps/desktop/src/components/assistant-ui/thread-list.tsx +337 -0
  306. package/agent/apps/desktop/src/components/assistant-ui/thread.tsx +667 -190
  307. package/agent/apps/desktop/src/components/assistant-ui/tool-approval-group.test.tsx +299 -0
  308. package/agent/apps/desktop/src/components/assistant-ui/tool-approval.test.tsx +133 -0
  309. package/agent/apps/desktop/src/components/assistant-ui/tool-approval.tsx +239 -0
  310. package/agent/apps/desktop/src/components/assistant-ui/tool-fallback-model.test.ts +31 -0
  311. package/agent/apps/desktop/src/components/assistant-ui/tool-fallback-model.ts +152 -134
  312. package/agent/apps/desktop/src/components/assistant-ui/tool-fallback.tsx +142 -150
  313. package/agent/apps/desktop/src/components/assistant-ui/tooltip-icon-button.tsx +14 -12
  314. package/agent/apps/desktop/src/components/assistant-ui/user-message-edit.test.tsx +141 -0
  315. package/agent/apps/desktop/src/components/assistant-ui/user-message-text.tsx +152 -0
  316. package/agent/apps/desktop/src/components/boot-failure-overlay.tsx +150 -33
  317. package/agent/apps/desktop/src/components/boot-failure-reauth.test.ts +100 -0
  318. package/agent/apps/desktop/src/components/boot-failure-reauth.ts +81 -0
  319. package/agent/apps/desktop/src/components/brand-mark.tsx +19 -0
  320. package/agent/apps/desktop/src/components/chat/code-card.tsx +1 -1
  321. package/agent/apps/desktop/src/components/chat/composer-dock.ts +31 -0
  322. package/agent/apps/desktop/src/components/chat/diff-lines.tsx +1 -1
  323. package/agent/apps/desktop/src/components/chat/disclosure-row.tsx +13 -3
  324. package/agent/apps/desktop/src/components/chat/expandable-block.tsx +52 -0
  325. package/agent/apps/desktop/src/components/chat/generated-image-result.tsx +174 -0
  326. package/agent/apps/desktop/src/components/chat/image-generation-placeholder.tsx +70 -37
  327. package/agent/apps/desktop/src/components/chat/intro.tsx +8 -7
  328. package/agent/apps/desktop/src/components/chat/preview-attachment.tsx +4 -2
  329. package/agent/apps/desktop/src/components/chat/shiki-highlighter.test.ts +37 -0
  330. package/agent/apps/desktop/src/components/chat/shiki-highlighter.tsx +96 -22
  331. package/agent/apps/desktop/src/components/chat/status-row.tsx +70 -0
  332. package/agent/apps/desktop/src/components/chat/status-section.tsx +42 -0
  333. package/agent/apps/desktop/src/components/chat/terminal-output.tsx +54 -0
  334. package/agent/apps/desktop/src/components/chat/zoomable-image.tsx +70 -109
  335. package/agent/apps/desktop/src/components/desktop-install-overlay.tsx +154 -84
  336. package/agent/apps/desktop/src/components/desktop-onboarding-overlay.test.tsx +38 -8
  337. package/agent/apps/desktop/src/components/desktop-onboarding-overlay.tsx +789 -233
  338. package/agent/apps/desktop/src/components/error-boundary.tsx +77 -0
  339. package/agent/apps/desktop/src/components/gateway-connecting-overlay.test.tsx +144 -0
  340. package/agent/apps/desktop/src/components/gateway-connecting-overlay.tsx +7 -1
  341. package/agent/apps/desktop/src/components/haptics-provider.tsx +24 -0
  342. package/agent/apps/desktop/src/components/language-switcher.test.tsx +53 -0
  343. package/agent/apps/desktop/src/components/language-switcher.tsx +175 -0
  344. package/agent/apps/desktop/src/components/model-picker.tsx +42 -40
  345. package/agent/apps/desktop/src/components/model-visibility-dialog.tsx +166 -0
  346. package/agent/apps/desktop/src/components/notifications.tsx +48 -27
  347. package/agent/apps/desktop/src/components/pane-shell/index.ts +1 -1
  348. package/agent/apps/desktop/src/components/pane-shell/pane-shell.tsx +146 -9
  349. package/agent/apps/desktop/src/components/prompt-overlays.tsx +234 -0
  350. package/agent/apps/desktop/src/components/session-picker.tsx +108 -0
  351. package/agent/apps/desktop/src/components/ui/action-status.tsx +25 -0
  352. package/agent/apps/desktop/src/components/ui/badge.tsx +35 -0
  353. package/agent/apps/desktop/src/components/ui/button.tsx +37 -13
  354. package/agent/apps/desktop/src/components/ui/confirm-dialog.tsx +109 -0
  355. package/agent/apps/desktop/src/components/ui/control.ts +25 -0
  356. package/agent/apps/desktop/src/components/ui/copy-button.test.tsx +36 -0
  357. package/agent/apps/desktop/src/components/ui/copy-button.tsx +38 -27
  358. package/agent/apps/desktop/src/components/ui/dialog.tsx +39 -11
  359. package/agent/apps/desktop/src/components/ui/dropdown-menu.tsx +98 -24
  360. package/agent/apps/desktop/src/components/ui/error-state.tsx +50 -0
  361. package/agent/apps/desktop/src/components/ui/fade-text.tsx +9 -2
  362. package/agent/apps/desktop/src/components/ui/{braille-spinner.tsx → glyph-spinner.tsx} +15 -13
  363. package/agent/apps/desktop/src/components/ui/input.tsx +5 -2
  364. package/agent/apps/desktop/src/components/ui/kbd.tsx +83 -12
  365. package/agent/apps/desktop/src/components/ui/log-view.tsx +19 -0
  366. package/agent/apps/desktop/src/components/ui/pagination.tsx +12 -5
  367. package/agent/apps/desktop/src/components/ui/popover.tsx +44 -0
  368. package/agent/apps/desktop/src/components/ui/search-field.tsx +80 -0
  369. package/agent/apps/desktop/src/components/ui/segmented-control.tsx +51 -0
  370. package/agent/apps/desktop/src/components/ui/select.tsx +10 -3
  371. package/agent/apps/desktop/src/components/ui/sheet.tsx +8 -2
  372. package/agent/apps/desktop/src/components/ui/sidebar.tsx +18 -25
  373. package/agent/apps/desktop/src/components/ui/switch.tsx +38 -15
  374. package/agent/apps/desktop/src/components/ui/textarea.tsx +4 -11
  375. package/agent/apps/desktop/src/components/ui/tool-icon.tsx +65 -0
  376. package/agent/apps/desktop/src/components/ui/tooltip.tsx +31 -4
  377. package/agent/apps/desktop/src/fonts/JetBrainsMono-Bold.woff2 +0 -0
  378. package/agent/apps/desktop/src/fonts/JetBrainsMono-Italic.woff2 +0 -0
  379. package/agent/apps/desktop/src/fonts/JetBrainsMono-Regular.woff2 +0 -0
  380. package/agent/apps/desktop/src/global.d.ts +181 -4
  381. package/agent/apps/desktop/src/hermes.test.ts +60 -0
  382. package/agent/apps/desktop/src/hermes.ts +190 -13
  383. package/agent/apps/desktop/src/hooks/use-image-download.ts +85 -0
  384. package/agent/apps/desktop/src/hooks/use-resize-observer.ts +13 -4
  385. package/agent/apps/desktop/src/hooks/use-worktree-info.ts +68 -0
  386. package/agent/apps/desktop/src/i18n/catalog.ts +12 -0
  387. package/agent/apps/desktop/src/i18n/context.test.tsx +232 -0
  388. package/agent/apps/desktop/src/i18n/context.tsx +183 -0
  389. package/agent/apps/desktop/src/i18n/define-locale.ts +41 -0
  390. package/agent/apps/desktop/src/i18n/en.ts +1921 -0
  391. package/agent/apps/desktop/src/i18n/index.ts +20 -0
  392. package/agent/apps/desktop/src/i18n/ja.ts +2053 -0
  393. package/agent/apps/desktop/src/i18n/languages.test.ts +43 -0
  394. package/agent/apps/desktop/src/i18n/languages.ts +86 -0
  395. package/agent/apps/desktop/src/i18n/runtime.test.ts +75 -0
  396. package/agent/apps/desktop/src/i18n/runtime.ts +53 -0
  397. package/agent/apps/desktop/src/i18n/types.ts +1559 -0
  398. package/agent/apps/desktop/src/i18n/zh-hant.ts +1992 -0
  399. package/agent/apps/desktop/src/i18n/zh.ts +2099 -0
  400. package/agent/apps/desktop/src/lib/ansi.test.ts +123 -0
  401. package/agent/apps/desktop/src/lib/ansi.ts +186 -0
  402. package/agent/apps/desktop/src/lib/chat-messages.test.ts +79 -0
  403. package/agent/apps/desktop/src/lib/chat-messages.ts +68 -29
  404. package/agent/apps/desktop/src/lib/chat-runtime.test.ts +65 -1
  405. package/agent/apps/desktop/src/lib/chat-runtime.ts +39 -3
  406. package/agent/apps/desktop/src/lib/completion-sound.ts +519 -0
  407. package/agent/apps/desktop/src/lib/desktop-fs.test.ts +116 -0
  408. package/agent/apps/desktop/src/lib/desktop-fs.ts +113 -0
  409. package/agent/apps/desktop/src/lib/desktop-slash-commands.test.ts +89 -6
  410. package/agent/apps/desktop/src/lib/desktop-slash-commands.ts +270 -131
  411. package/agent/apps/desktop/src/lib/external-link.test.tsx +27 -0
  412. package/agent/apps/desktop/src/lib/external-link.tsx +9 -2
  413. package/agent/apps/desktop/src/lib/gateway-events.test.ts +27 -0
  414. package/agent/apps/desktop/src/lib/gateway-events.ts +16 -0
  415. package/agent/apps/desktop/src/lib/gateway-ws-url.test.ts +78 -0
  416. package/agent/apps/desktop/src/lib/gateway-ws-url.ts +91 -0
  417. package/agent/apps/desktop/src/lib/generated-images.test.ts +97 -0
  418. package/agent/apps/desktop/src/lib/generated-images.ts +116 -0
  419. package/agent/apps/desktop/src/lib/haptics.ts +17 -0
  420. package/agent/apps/desktop/src/lib/icons.ts +10 -2
  421. package/agent/apps/desktop/src/lib/keybinds/actions.ts +137 -0
  422. package/agent/apps/desktop/src/lib/keybinds/combo.test.ts +86 -0
  423. package/agent/apps/desktop/src/lib/keybinds/combo.ts +195 -0
  424. package/agent/apps/desktop/src/lib/local-preview.ts +23 -2
  425. package/agent/apps/desktop/src/lib/markdown-preprocess.ts +20 -7
  426. package/agent/apps/desktop/src/lib/media.remote.test.ts +90 -0
  427. package/agent/apps/desktop/src/lib/media.ts +40 -1
  428. package/agent/apps/desktop/src/lib/model-status-label.test.ts +59 -0
  429. package/agent/apps/desktop/src/lib/model-status-label.ts +122 -0
  430. package/agent/apps/desktop/src/lib/mutable-ref.ts +6 -0
  431. package/agent/apps/desktop/src/lib/profile-color.ts +58 -0
  432. package/agent/apps/desktop/src/lib/query-client.ts +13 -0
  433. package/agent/apps/desktop/src/lib/remend-tail.test.ts +105 -0
  434. package/agent/apps/desktop/src/lib/remend-tail.ts +108 -0
  435. package/agent/apps/desktop/src/lib/session-export.ts +6 -3
  436. package/agent/apps/desktop/src/lib/session-ids.test.ts +44 -0
  437. package/agent/apps/desktop/src/lib/session-ids.ts +26 -0
  438. package/agent/apps/desktop/src/lib/session-search.test.ts +66 -0
  439. package/agent/apps/desktop/src/lib/session-search.ts +21 -0
  440. package/agent/apps/desktop/src/lib/session-source.ts +126 -0
  441. package/agent/apps/desktop/src/lib/storage.test.ts +25 -0
  442. package/agent/apps/desktop/src/lib/storage.ts +35 -1
  443. package/agent/apps/desktop/src/lib/todos.test.ts +46 -1
  444. package/agent/apps/desktop/src/lib/todos.ts +37 -0
  445. package/agent/apps/desktop/src/lib/tool-result-summary.ts +5 -1
  446. package/agent/apps/desktop/src/lib/update-copy.test.ts +38 -0
  447. package/agent/apps/desktop/src/lib/update-copy.ts +44 -0
  448. package/agent/apps/desktop/src/lib/use-enter-animation.ts +2 -2
  449. package/agent/apps/desktop/src/lib/yolo-session.ts +50 -0
  450. package/agent/apps/desktop/src/main.tsx +19 -19
  451. package/agent/apps/desktop/src/store/boot.ts +4 -3
  452. package/agent/apps/desktop/src/store/clarify.test.ts +81 -0
  453. package/agent/apps/desktop/src/store/clarify.ts +50 -13
  454. package/agent/apps/desktop/src/store/command-palette.ts +20 -0
  455. package/agent/apps/desktop/src/store/compaction.test.ts +53 -0
  456. package/agent/apps/desktop/src/store/compaction.ts +38 -0
  457. package/agent/apps/desktop/src/store/completion-sound.ts +32 -0
  458. package/agent/apps/desktop/src/store/composer-input-history.test.ts +147 -0
  459. package/agent/apps/desktop/src/store/composer-input-history.ts +158 -0
  460. package/agent/apps/desktop/src/store/composer-queue.test.ts +68 -0
  461. package/agent/apps/desktop/src/store/composer-queue.ts +76 -0
  462. package/agent/apps/desktop/src/store/composer-status.test.ts +99 -0
  463. package/agent/apps/desktop/src/store/composer-status.ts +277 -0
  464. package/agent/apps/desktop/src/store/composer.test.ts +106 -0
  465. package/agent/apps/desktop/src/store/composer.ts +116 -0
  466. package/agent/apps/desktop/src/store/cron.ts +19 -0
  467. package/agent/apps/desktop/src/store/gateway.ts +280 -6
  468. package/agent/apps/desktop/src/store/keybinds.ts +143 -0
  469. package/agent/apps/desktop/src/store/layout.ts +107 -9
  470. package/agent/apps/desktop/src/store/model-presets.test.ts +51 -0
  471. package/agent/apps/desktop/src/store/model-presets.ts +86 -0
  472. package/agent/apps/desktop/src/store/model-visibility.test.ts +99 -0
  473. package/agent/apps/desktop/src/store/model-visibility.ts +161 -0
  474. package/agent/apps/desktop/src/store/native-notifications.test.ts +192 -0
  475. package/agent/apps/desktop/src/store/native-notifications.ts +203 -0
  476. package/agent/apps/desktop/src/store/notifications.ts +10 -7
  477. package/agent/apps/desktop/src/store/onboarding.test.ts +271 -1
  478. package/agent/apps/desktop/src/store/onboarding.ts +268 -38
  479. package/agent/apps/desktop/src/store/preview.ts +10 -1
  480. package/agent/apps/desktop/src/store/profile.test.ts +89 -0
  481. package/agent/apps/desktop/src/store/profile.ts +395 -0
  482. package/agent/apps/desktop/src/store/prompts.test.ts +127 -0
  483. package/agent/apps/desktop/src/store/prompts.ts +117 -0
  484. package/agent/apps/desktop/src/store/session-switcher.test.ts +115 -0
  485. package/agent/apps/desktop/src/store/session-switcher.ts +128 -0
  486. package/agent/apps/desktop/src/store/session-sync.ts +25 -0
  487. package/agent/apps/desktop/src/store/session.test.ts +268 -2
  488. package/agent/apps/desktop/src/store/session.ts +392 -18
  489. package/agent/apps/desktop/src/store/subagents.ts +3 -0
  490. package/agent/apps/desktop/src/store/system-actions.ts +48 -0
  491. package/agent/apps/desktop/src/store/thread-scroll.ts +58 -5
  492. package/agent/apps/desktop/src/store/todos.test.ts +47 -0
  493. package/agent/apps/desktop/src/store/todos.ts +64 -0
  494. package/agent/apps/desktop/src/store/tool-dismiss.ts +45 -0
  495. package/agent/apps/desktop/src/store/translucency.ts +38 -0
  496. package/agent/apps/desktop/src/store/updates.test.ts +187 -2
  497. package/agent/apps/desktop/src/store/updates.ts +268 -18
  498. package/agent/apps/desktop/src/store/windows.test.ts +143 -0
  499. package/agent/apps/desktop/src/store/windows.ts +115 -0
  500. package/agent/apps/desktop/src/styles.css +510 -119
  501. package/agent/apps/desktop/src/themes/color.ts +142 -0
  502. package/agent/apps/desktop/src/themes/context.tsx +128 -75
  503. package/agent/apps/desktop/src/themes/install.test.ts +119 -0
  504. package/agent/apps/desktop/src/themes/install.ts +95 -0
  505. package/agent/apps/desktop/src/themes/presets.test.ts +33 -0
  506. package/agent/apps/desktop/src/themes/presets.ts +13 -4
  507. package/agent/apps/desktop/src/themes/profile-theme.test.ts +41 -0
  508. package/agent/apps/desktop/src/themes/types.ts +35 -0
  509. package/agent/apps/desktop/src/themes/user-themes.test.ts +63 -0
  510. package/agent/apps/desktop/src/themes/user-themes.ts +122 -0
  511. package/agent/apps/desktop/src/themes/vscode.test.ts +171 -0
  512. package/agent/apps/desktop/src/themes/vscode.ts +343 -0
  513. package/agent/apps/desktop/src/types/hermes.ts +138 -1
  514. package/agent/apps/desktop/tsconfig.json +2 -2
  515. package/agent/apps/desktop/vite.config.ts +18 -0
  516. package/agent/apps/shared/package.json +1 -1
  517. package/agent/apps/shared/src/json-rpc-gateway.ts +63 -2
  518. package/agent/apps/shared/tsconfig.json +2 -2
  519. package/agent/cli-config.yaml.example +78 -1
  520. package/agent/cli.py +2294 -3146
  521. package/agent/cron/blueprint_catalog.py +713 -0
  522. package/agent/cron/jobs.py +226 -110
  523. package/agent/cron/scheduler.py +468 -193
  524. package/agent/cron/scheduler_provider.py +177 -0
  525. package/agent/cron/scripts/__init__.py +1 -0
  526. package/agent/cron/scripts/classify_items.py +226 -0
  527. package/agent/cron/suggestion_catalog.py +154 -0
  528. package/agent/cron/suggestions.py +257 -0
  529. package/agent/docs/chronos-managed-cron-contract.md +196 -0
  530. package/agent/docs/design/profile-builder.md +146 -0
  531. package/agent/docs/middleware/README.md +260 -0
  532. package/agent/docs/observability/README.md +316 -0
  533. package/agent/docs/plans/2026-06-09-003-fix-telegram-stream-overflow-continuations-plan.md +240 -0
  534. package/agent/docs/rca-ssl-cacert-post-git-pull.md +54 -0
  535. package/agent/docs/relay-connector-contract.md +285 -0
  536. package/agent/gateway/authz_mixin.py +536 -0
  537. package/agent/gateway/channel_directory.py +65 -3
  538. package/agent/gateway/config.py +222 -12
  539. package/agent/gateway/display_config.py +10 -0
  540. package/agent/gateway/hooks.py +17 -0
  541. package/agent/gateway/kanban_watchers.py +1146 -0
  542. package/agent/gateway/message_timestamps.py +166 -0
  543. package/agent/gateway/platforms/ADDING_A_PLATFORM.md +29 -0
  544. package/agent/gateway/platforms/api_server.py +216 -38
  545. package/agent/gateway/platforms/base.py +210 -58
  546. package/agent/gateway/platforms/email.py +122 -12
  547. package/agent/gateway/platforms/feishu.py +80 -11
  548. package/agent/gateway/platforms/feishu_meeting_invite.py +212 -0
  549. package/agent/gateway/platforms/matrix.py +1498 -297
  550. package/agent/gateway/platforms/qqbot/adapter.py +6 -0
  551. package/agent/gateway/platforms/signal.py +8 -0
  552. package/agent/gateway/platforms/slack.py +308 -12
  553. package/agent/gateway/platforms/telegram.py +831 -24
  554. package/agent/gateway/platforms/webhook.py +109 -21
  555. package/agent/gateway/platforms/weixin.py +113 -2
  556. package/agent/gateway/platforms/whatsapp.py +94 -288
  557. package/agent/gateway/platforms/whatsapp_cloud.py +1956 -0
  558. package/agent/gateway/platforms/whatsapp_common.py +367 -0
  559. package/agent/gateway/platforms/yuanbao.py +608 -191
  560. package/agent/gateway/platforms/yuanbao_proto.py +232 -23
  561. package/agent/gateway/relay/__init__.py +375 -0
  562. package/agent/gateway/relay/adapter.py +222 -0
  563. package/agent/gateway/relay/auth.py +168 -0
  564. package/agent/gateway/relay/descriptor.py +118 -0
  565. package/agent/gateway/relay/transport.py +101 -0
  566. package/agent/gateway/relay/ws_transport.py +327 -0
  567. package/agent/gateway/response_filters.py +53 -0
  568. package/agent/gateway/rich_sent_store.py +80 -0
  569. package/agent/gateway/run.py +2940 -5001
  570. package/agent/gateway/session.py +109 -8
  571. package/agent/gateway/session_context.py +22 -4
  572. package/agent/gateway/slash_commands.py +3854 -0
  573. package/agent/gateway/status.py +141 -21
  574. package/agent/gateway/stream_consumer.py +288 -31
  575. package/agent/hermes-already-has-routines.md +1 -1
  576. package/agent/hermes_cli/__init__.py +62 -17
  577. package/agent/hermes_cli/_parser.py +30 -0
  578. package/agent/hermes_cli/_subprocess_compat.py +61 -0
  579. package/agent/hermes_cli/active_sessions.py +320 -0
  580. package/agent/hermes_cli/auth.py +707 -59
  581. package/agent/hermes_cli/auth_commands.py +39 -22
  582. package/agent/hermes_cli/backup.py +109 -7
  583. package/agent/hermes_cli/banner.py +88 -0
  584. package/agent/hermes_cli/blueprint_cmd.py +318 -0
  585. package/agent/hermes_cli/clawpump_cli.py +3 -3
  586. package/agent/hermes_cli/cli_agent_setup_mixin.py +684 -0
  587. package/agent/hermes_cli/cli_commands_mixin.py +2293 -0
  588. package/agent/hermes_cli/commands.py +216 -91
  589. package/agent/hermes_cli/config.py +967 -130
  590. package/agent/hermes_cli/container_boot.py +76 -11
  591. package/agent/hermes_cli/cron.py +5 -11
  592. package/agent/hermes_cli/curator.py +21 -0
  593. package/agent/hermes_cli/dashboard_auth/__init__.py +2 -0
  594. package/agent/hermes_cli/dashboard_auth/base.py +62 -0
  595. package/agent/hermes_cli/dashboard_auth/cookies.py +32 -19
  596. package/agent/hermes_cli/dashboard_auth/login_page.py +156 -6
  597. package/agent/hermes_cli/dashboard_auth/middleware.py +28 -4
  598. package/agent/hermes_cli/dashboard_auth/prefix.py +46 -2
  599. package/agent/hermes_cli/dashboard_auth/public_paths.py +6 -0
  600. package/agent/hermes_cli/dashboard_auth/routes.py +158 -2
  601. package/agent/hermes_cli/dashboard_auth/ws_tickets.py +85 -11
  602. package/agent/hermes_cli/dashboard_register.py +427 -0
  603. package/agent/hermes_cli/debug.py +155 -50
  604. package/agent/hermes_cli/distribution.py +227 -0
  605. package/agent/hermes_cli/doctor.py +255 -14
  606. package/agent/hermes_cli/dump.py +60 -6
  607. package/agent/hermes_cli/env_loader.py +33 -0
  608. package/agent/hermes_cli/gateway.py +755 -103
  609. package/agent/hermes_cli/gateway_enroll.py +250 -0
  610. package/agent/hermes_cli/gateway_windows.py +254 -11
  611. package/agent/hermes_cli/gui_uninstall.py +285 -0
  612. package/agent/hermes_cli/inventory.py +105 -4
  613. package/agent/hermes_cli/kanban.py +58 -71
  614. package/agent/hermes_cli/kanban_db.py +391 -14
  615. package/agent/hermes_cli/kanban_decompose.py +2 -2
  616. package/agent/hermes_cli/kanban_specify.py +3 -1
  617. package/agent/hermes_cli/logs.py +2 -0
  618. package/agent/hermes_cli/main.py +2889 -5287
  619. package/agent/hermes_cli/managed_scope.py +214 -0
  620. package/agent/hermes_cli/managed_uv.py +254 -0
  621. package/agent/hermes_cli/mcp_catalog.py +6 -3
  622. package/agent/hermes_cli/mcp_config.py +145 -21
  623. package/agent/hermes_cli/mcp_security.py +96 -0
  624. package/agent/hermes_cli/mcp_startup.py +32 -3
  625. package/agent/hermes_cli/memory_providers.py +149 -0
  626. package/agent/hermes_cli/memory_setup.py +97 -42
  627. package/agent/hermes_cli/middleware.py +313 -0
  628. package/agent/hermes_cli/model_catalog.py +31 -0
  629. package/agent/hermes_cli/model_cost_guard.py +134 -0
  630. package/agent/hermes_cli/model_normalize.py +2 -1
  631. package/agent/hermes_cli/model_setup_flows.py +2759 -0
  632. package/agent/hermes_cli/model_switch.py +242 -27
  633. package/agent/hermes_cli/models.py +284 -44
  634. package/agent/hermes_cli/nous_account.py +33 -6
  635. package/agent/hermes_cli/nous_billing.py +406 -0
  636. package/agent/hermes_cli/nous_subscription.py +202 -5
  637. package/agent/hermes_cli/platforms.py +1 -0
  638. package/agent/hermes_cli/plugins.py +218 -18
  639. package/agent/hermes_cli/plugins_cmd.py +249 -105
  640. package/agent/hermes_cli/portal_cli.py +56 -16
  641. package/agent/hermes_cli/profile_distribution.py +6 -1
  642. package/agent/hermes_cli/profiles.py +283 -32
  643. package/agent/hermes_cli/provider_catalog.py +170 -0
  644. package/agent/hermes_cli/providers.py +4 -1
  645. package/agent/hermes_cli/pty_bridge.py +53 -4
  646. package/agent/hermes_cli/runtime_provider.py +216 -34
  647. package/agent/hermes_cli/secret_prompt.py +4 -4
  648. package/agent/hermes_cli/secrets_cli.py +24 -0
  649. package/agent/hermes_cli/send_cmd.py +28 -2
  650. package/agent/hermes_cli/service_manager.py +166 -19
  651. package/agent/hermes_cli/session_listing.py +97 -0
  652. package/agent/hermes_cli/setup.py +158 -94
  653. package/agent/hermes_cli/setup_whatsapp_cloud.py +541 -0
  654. package/agent/hermes_cli/skills_config.py +8 -2
  655. package/agent/hermes_cli/skills_hub.py +149 -7
  656. package/agent/hermes_cli/status.py +2 -2
  657. package/agent/hermes_cli/subcommands/__init__.py +18 -0
  658. package/agent/hermes_cli/subcommands/_shared.py +29 -0
  659. package/agent/hermes_cli/subcommands/acp.py +52 -0
  660. package/agent/hermes_cli/subcommands/auth.py +109 -0
  661. package/agent/hermes_cli/subcommands/backup.py +38 -0
  662. package/agent/hermes_cli/subcommands/claw.py +92 -0
  663. package/agent/hermes_cli/subcommands/config.py +49 -0
  664. package/agent/hermes_cli/subcommands/cron.py +163 -0
  665. package/agent/hermes_cli/subcommands/dashboard.py +143 -0
  666. package/agent/hermes_cli/subcommands/debug.py +77 -0
  667. package/agent/hermes_cli/subcommands/doctor.py +35 -0
  668. package/agent/hermes_cli/subcommands/dump.py +28 -0
  669. package/agent/hermes_cli/subcommands/gateway.py +332 -0
  670. package/agent/hermes_cli/subcommands/gui.py +63 -0
  671. package/agent/hermes_cli/subcommands/hooks.py +77 -0
  672. package/agent/hermes_cli/subcommands/import_cmd.py +31 -0
  673. package/agent/hermes_cli/subcommands/insights.py +25 -0
  674. package/agent/hermes_cli/subcommands/login.py +78 -0
  675. package/agent/hermes_cli/subcommands/logout.py +28 -0
  676. package/agent/hermes_cli/subcommands/logs.py +78 -0
  677. package/agent/hermes_cli/subcommands/mcp.py +108 -0
  678. package/agent/hermes_cli/subcommands/memory.py +53 -0
  679. package/agent/hermes_cli/subcommands/model.py +72 -0
  680. package/agent/hermes_cli/subcommands/pairing.py +36 -0
  681. package/agent/hermes_cli/subcommands/plugins.py +94 -0
  682. package/agent/hermes_cli/subcommands/postinstall.py +23 -0
  683. package/agent/hermes_cli/subcommands/profile.py +203 -0
  684. package/agent/hermes_cli/subcommands/prompt_size.py +36 -0
  685. package/agent/hermes_cli/subcommands/security.py +62 -0
  686. package/agent/hermes_cli/subcommands/setup.py +58 -0
  687. package/agent/hermes_cli/subcommands/skills.py +298 -0
  688. package/agent/hermes_cli/subcommands/slack.py +60 -0
  689. package/agent/hermes_cli/subcommands/status.py +28 -0
  690. package/agent/hermes_cli/subcommands/tools.py +95 -0
  691. package/agent/hermes_cli/subcommands/uninstall.py +41 -0
  692. package/agent/hermes_cli/subcommands/update.py +70 -0
  693. package/agent/hermes_cli/subcommands/version.py +18 -0
  694. package/agent/hermes_cli/subcommands/webhook.py +76 -0
  695. package/agent/hermes_cli/subcommands/whatsapp.py +22 -0
  696. package/agent/hermes_cli/suggestions_cmd.py +153 -0
  697. package/agent/hermes_cli/telegram_managed_bot.py +358 -0
  698. package/agent/hermes_cli/tips.py +3 -4
  699. package/agent/hermes_cli/tools_config.py +155 -28
  700. package/agent/hermes_cli/uninstall.py +231 -35
  701. package/agent/hermes_cli/web_server.py +6188 -975
  702. package/agent/hermes_cli/win_pty_bridge.py +179 -0
  703. package/agent/hermes_cli/write_approval_commands.py +209 -0
  704. package/agent/hermes_constants.py +164 -33
  705. package/agent/hermes_logging.py +74 -2
  706. package/agent/hermes_state.py +919 -106
  707. package/agent/hermes_time.py +20 -0
  708. package/agent/locales/af.yaml +23 -0
  709. package/agent/locales/de.yaml +23 -0
  710. package/agent/locales/en.yaml +20 -0
  711. package/agent/locales/es.yaml +23 -0
  712. package/agent/locales/fr.yaml +23 -0
  713. package/agent/locales/ga.yaml +23 -0
  714. package/agent/locales/hu.yaml +23 -0
  715. package/agent/locales/it.yaml +23 -0
  716. package/agent/locales/ja.yaml +23 -0
  717. package/agent/locales/ko.yaml +23 -0
  718. package/agent/locales/pt.yaml +23 -0
  719. package/agent/locales/ru.yaml +23 -0
  720. package/agent/locales/tr.yaml +23 -0
  721. package/agent/locales/uk.yaml +23 -0
  722. package/agent/locales/zh-hant.yaml +23 -0
  723. package/agent/locales/zh.yaml +23 -0
  724. package/agent/model_tools.py +204 -40
  725. package/agent/optional-mcps/clawpump/manifest.yaml +15 -5
  726. package/agent/optional-mcps/clawpump-stdio/manifest.yaml +14 -4
  727. package/agent/optional-mcps/unreal-engine/manifest.yaml +54 -0
  728. package/agent/optional-skills/blockchain/hyperliquid/SKILL.md +2 -2
  729. package/agent/optional-skills/blockchain/hyperliquid/scripts/hyperliquid_client.py +1 -1
  730. package/agent/optional-skills/creative/kanban-video-orchestrator/SKILL.md +1 -1
  731. package/agent/optional-skills/creative/kanban-video-orchestrator/assets/setup.sh.tmpl +4 -3
  732. package/agent/optional-skills/creative/kanban-video-orchestrator/references/kanban-setup.md +6 -4
  733. package/agent/optional-skills/creative/kanban-video-orchestrator/references/tool-matrix.md +2 -2
  734. package/agent/{skills/software-development → optional-skills/devops}/hermes-s6-container-supervision/SKILL.md +2 -0
  735. package/agent/optional-skills/devops/watchers/SKILL.md +1 -1
  736. package/agent/optional-skills/devops/watchers/scripts/watch_github.py +2 -1
  737. package/agent/optional-skills/payments/mpp-agent/SKILL.md +124 -0
  738. package/agent/optional-skills/payments/stripe-link-cli/SKILL.md +184 -0
  739. package/agent/optional-skills/payments/stripe-projects/SKILL.md +120 -0
  740. package/agent/optional-skills/productivity/canvas/SKILL.md +1 -1
  741. package/agent/optional-skills/productivity/canvas/scripts/canvas_api.py +4 -1
  742. package/agent/optional-skills/productivity/shop/SKILL.md +224 -0
  743. package/agent/optional-skills/productivity/shop/references/catalog-mcp.md +236 -0
  744. package/agent/optional-skills/productivity/shop/references/direct-api.md +278 -0
  745. package/agent/optional-skills/productivity/shop/references/legal.md +3 -0
  746. package/agent/optional-skills/productivity/shop/references/safety.md +36 -0
  747. package/agent/optional-skills/productivity/shopify/SKILL.md +1 -1
  748. package/agent/optional-skills/productivity/siyuan/SKILL.md +1 -1
  749. package/agent/optional-skills/productivity/telephony/SKILL.md +4 -4
  750. package/agent/optional-skills/productivity/telephony/scripts/telephony.py +15 -15
  751. package/agent/optional-skills/security/1password/SKILL.md +1 -1
  752. package/agent/{skills/red-teaming → optional-skills/security}/godmode/SKILL.md +3 -4
  753. package/agent/{skills/red-teaming → optional-skills/security}/godmode/scripts/auto_jailbreak.py +3 -1
  754. package/agent/optional-skills/software-development/rest-graphql-debug/SKILL.md +1 -1
  755. package/agent/{skills → optional-skills}/software-development/subagent-driven-development/SKILL.md +5 -5
  756. package/agent/package-lock.json +4082 -7907
  757. package/agent/package.json +18 -3
  758. package/agent/plugins/browser/firecrawl/provider.py +4 -1
  759. package/agent/plugins/cron/__init__.py +344 -0
  760. package/agent/plugins/cron/chronos/__init__.py +241 -0
  761. package/agent/plugins/cron/chronos/_nas_client.py +123 -0
  762. package/agent/plugins/cron/chronos/plugin.yaml +9 -0
  763. package/agent/plugins/cron/chronos/verify.py +103 -0
  764. package/agent/plugins/dashboard_auth/basic/__init__.py +491 -0
  765. package/agent/plugins/dashboard_auth/basic/plugin.yaml +7 -0
  766. package/agent/plugins/dashboard_auth/nous/__init__.py +12 -14
  767. package/agent/plugins/dashboard_auth/self_hosted/__init__.py +736 -0
  768. package/agent/plugins/dashboard_auth/self_hosted/plugin.yaml +8 -0
  769. package/agent/plugins/disk-cleanup/disk_cleanup.py +100 -20
  770. package/agent/plugins/google_meet/audio_bridge.py +4 -0
  771. package/agent/plugins/google_meet/meet_bot.py +7 -1
  772. package/agent/plugins/hermes-achievements/dashboard/dist/index.js +9 -15
  773. package/agent/plugins/image_gen/fal/__init__.py +35 -6
  774. package/agent/plugins/image_gen/krea/__init__.py +56 -13
  775. package/agent/plugins/image_gen/openai/__init__.py +122 -24
  776. package/agent/plugins/image_gen/openai-codex/__init__.py +28 -2
  777. package/agent/plugins/image_gen/xai/__init__.py +92 -12
  778. package/agent/plugins/kanban/dashboard/dist/index.js +63 -48
  779. package/agent/plugins/kanban/dashboard/plugin_api.py +39 -35
  780. package/agent/plugins/memory/__init__.py +48 -5
  781. package/agent/plugins/memory/byterover/__init__.py +1 -0
  782. package/agent/plugins/memory/hindsight/README.md +1 -1
  783. package/agent/plugins/memory/hindsight/__init__.py +138 -24
  784. package/agent/plugins/memory/hindsight/plugin.yaml +1 -1
  785. package/agent/plugins/memory/honcho/README.md +13 -10
  786. package/agent/plugins/memory/honcho/cli.py +247 -122
  787. package/agent/plugins/memory/honcho/client.py +112 -102
  788. package/agent/plugins/memory/openviking/README.md +12 -1
  789. package/agent/plugins/memory/openviking/__init__.py +2281 -107
  790. package/agent/plugins/memory/openviking/plugin.yaml +1 -2
  791. package/agent/plugins/memory/supermemory/README.md +22 -10
  792. package/agent/plugins/memory/supermemory/__init__.py +142 -37
  793. package/agent/plugins/memory/supermemory/plugin.yaml +1 -1
  794. package/agent/plugins/model-providers/anthropic/__init__.py +1 -0
  795. package/agent/plugins/model-providers/bedrock/__init__.py +1 -0
  796. package/agent/plugins/model-providers/copilot-acp/__init__.py +1 -0
  797. package/agent/plugins/model-providers/custom/__init__.py +8 -2
  798. package/agent/plugins/model-providers/kimi-coding/__init__.py +16 -7
  799. package/agent/plugins/model-providers/minimax/__init__.py +60 -8
  800. package/agent/plugins/model-providers/opencode-zen/__init__.py +12 -3
  801. package/agent/plugins/model-providers/openrouter/__init__.py +75 -4
  802. package/agent/plugins/model-providers/xiaomi/__init__.py +2 -0
  803. package/agent/plugins/model-providers/zai/__init__.py +1 -0
  804. package/agent/plugins/observability/langfuse/__init__.py +147 -14
  805. package/agent/plugins/observability/nemo_relay/README.md +559 -0
  806. package/agent/plugins/observability/nemo_relay/__init__.py +962 -0
  807. package/agent/plugins/observability/nemo_relay/plugin.yaml +20 -0
  808. package/agent/plugins/platforms/discord/adapter.py +932 -61
  809. package/agent/plugins/platforms/discord/voice_mixer.py +379 -0
  810. package/agent/plugins/platforms/google_chat/adapter.py +9 -3
  811. package/agent/plugins/platforms/google_chat/oauth.py +1 -1
  812. package/agent/plugins/platforms/homeassistant/__init__.py +3 -0
  813. package/agent/{gateway/platforms/homeassistant.py → plugins/platforms/homeassistant/adapter.py} +128 -0
  814. package/agent/plugins/platforms/homeassistant/plugin.yaml +22 -0
  815. package/agent/plugins/platforms/irc/adapter.py +4 -1
  816. package/agent/plugins/platforms/line/adapter.py +16 -1
  817. package/agent/plugins/platforms/mattermost/adapter.py +100 -24
  818. package/agent/plugins/platforms/photon/README.md +179 -0
  819. package/agent/plugins/platforms/photon/__init__.py +4 -0
  820. package/agent/plugins/platforms/photon/adapter.py +1586 -0
  821. package/agent/plugins/platforms/photon/auth.py +1046 -0
  822. package/agent/plugins/platforms/photon/cli.py +439 -0
  823. package/agent/plugins/platforms/photon/plugin.yaml +88 -0
  824. package/agent/plugins/platforms/photon/sidecar/README.md +52 -0
  825. package/agent/plugins/platforms/photon/sidecar/index.mjs +720 -0
  826. package/agent/plugins/platforms/photon/sidecar/package-lock.json +1730 -0
  827. package/agent/plugins/platforms/photon/sidecar/package.json +25 -0
  828. package/agent/plugins/platforms/photon/sidecar/patch-spectrum-mixed-attachments.mjs +155 -0
  829. package/agent/plugins/platforms/raft/__init__.py +3 -0
  830. package/agent/plugins/platforms/raft/adapter.py +774 -0
  831. package/agent/plugins/platforms/raft/plugin.yaml +19 -0
  832. package/agent/plugins/platforms/simplex/adapter.py +777 -220
  833. package/agent/plugins/platforms/simplex/plugin.yaml +21 -2
  834. package/agent/plugins/platforms/teams/adapter.py +175 -5
  835. package/agent/plugins/plugin_utils.py +135 -0
  836. package/agent/plugins/video_gen/fal/__init__.py +10 -3
  837. package/agent/plugins/web/searxng/provider.py +15 -2
  838. package/agent/plugins/web/xai/provider.py +2 -2
  839. package/agent/providers/base.py +22 -3
  840. package/agent/pyproject.toml +115 -21
  841. package/agent/run_agent.py +733 -39
  842. package/agent/scripts/build_skills_index.py +51 -19
  843. package/agent/scripts/check_subprocess_stdin.py +177 -0
  844. package/agent/scripts/contributor_audit.py +2 -0
  845. package/agent/scripts/docker_config_migrate.py +67 -0
  846. package/agent/scripts/install.cmd +3 -3
  847. package/agent/scripts/install.ps1 +580 -154
  848. package/agent/scripts/install.sh +402 -185
  849. package/agent/scripts/lib/node-bootstrap.sh +39 -4
  850. package/agent/scripts/release.py +183 -0
  851. package/agent/scripts/run_tests.sh +1 -0
  852. package/agent/scripts/run_tests_parallel.py +18 -23
  853. package/agent/scripts/whatsapp-bridge/bridge.js +25 -4
  854. package/agent/setup.py +59 -0
  855. package/agent/skills/autonomous-ai-agents/codex/SKILL.md +19 -0
  856. package/agent/skills/autonomous-ai-agents/hermes-agent/SKILL.md +10 -3
  857. package/agent/skills/{mcp/native-mcp/SKILL.md → autonomous-ai-agents/hermes-agent/references/native-mcp.md} +0 -13
  858. package/agent/skills/{devops/webhook-subscriptions/SKILL.md → autonomous-ai-agents/hermes-agent/references/webhooks.md} +1 -11
  859. package/agent/skills/clawpump/SKILL.md +53 -5
  860. package/agent/skills/devops/kanban-orchestrator/SKILL.md +1 -0
  861. package/agent/skills/devops/kanban-worker/SKILL.md +1 -0
  862. package/agent/skills/github/github-auth/SKILL.md +2 -2
  863. package/agent/skills/github/github-auth/scripts/gh-env.sh +2 -2
  864. package/agent/skills/github/github-code-review/SKILL.md +2 -2
  865. package/agent/skills/github/github-issues/SKILL.md +2 -2
  866. package/agent/skills/github/github-pr-workflow/SKILL.md +2 -2
  867. package/agent/skills/github/github-repo-management/SKILL.md +2 -2
  868. package/agent/skills/media/gif-search/SKILL.md +1 -1
  869. package/agent/skills/media/youtube-content/SKILL.md +10 -7
  870. package/agent/skills/media/youtube-content/scripts/fetch_transcript.py +3 -3
  871. package/agent/skills/note-taking/obsidian/SKILL.md +1 -1
  872. package/agent/skills/productivity/airtable/SKILL.md +2 -2
  873. package/agent/skills/productivity/google-workspace/scripts/setup.py +33 -7
  874. package/agent/skills/productivity/notion/SKILL.md +2 -2
  875. package/agent/skills/productivity/teams-meeting-pipeline/SKILL.md +1 -1
  876. package/agent/skills/research/llm-wiki/SKILL.md +1 -1
  877. package/agent/skills/social-media/xurl/SKILL.md +9 -0
  878. package/agent/skills/software-development/hermes-agent-skill-authoring/SKILL.md +1 -1
  879. package/agent/skills/software-development/plan/SKILL.md +285 -5
  880. package/agent/skills/software-development/requesting-code-review/SKILL.md +2 -2
  881. package/agent/skills/software-development/simplify-code/SKILL.md +212 -0
  882. package/agent/skills/software-development/spike/SKILL.md +2 -2
  883. package/agent/skills/software-development/systematic-debugging/SKILL.md +1 -1
  884. package/agent/skills/software-development/test-driven-development/SKILL.md +1 -1
  885. package/agent/tools/approval.py +302 -4
  886. package/agent/tools/async_delegation.py +386 -0
  887. package/agent/tools/blueprints.py +325 -0
  888. package/agent/tools/browser_cdp_tool.py +3 -3
  889. package/agent/tools/browser_tool.py +34 -6
  890. package/agent/tools/checkpoint_manager.py +31 -1
  891. package/agent/tools/clarify_tool.py +55 -5
  892. package/agent/tools/code_execution_tool.py +31 -14
  893. package/agent/tools/computer_use/cua_backend.py +81 -3
  894. package/agent/tools/computer_use/tool.py +79 -5
  895. package/agent/tools/computer_use/vision_routing.py +55 -3
  896. package/agent/tools/credential_files.py +31 -12
  897. package/agent/tools/cronjob_tools.py +30 -20
  898. package/agent/tools/delegate_tool.py +356 -31
  899. package/agent/tools/env_probe.py +1 -0
  900. package/agent/tools/environments/docker.py +163 -8
  901. package/agent/tools/environments/file_sync.py +2 -1
  902. package/agent/tools/environments/local.py +74 -23
  903. package/agent/tools/environments/singularity.py +4 -1
  904. package/agent/tools/environments/ssh.py +78 -11
  905. package/agent/tools/file_operations.py +277 -41
  906. package/agent/tools/file_tools.py +166 -28
  907. package/agent/tools/image_generation_tool.py +515 -29
  908. package/agent/tools/kanban_tools.py +99 -0
  909. package/agent/tools/lazy_deps.py +33 -2
  910. package/agent/tools/mcp_oauth.py +5 -5
  911. package/agent/tools/mcp_oauth_manager.py +7 -5
  912. package/agent/tools/mcp_tool.py +840 -33
  913. package/agent/tools/memory_tool.py +335 -38
  914. package/agent/tools/osv_check.py +15 -1
  915. package/agent/tools/process_registry.py +155 -11
  916. package/agent/tools/read_extract.py +248 -0
  917. package/agent/tools/read_terminal_tool.py +93 -0
  918. package/agent/tools/schema_sanitizer.py +38 -0
  919. package/agent/tools/send_message_tool.py +163 -49
  920. package/agent/tools/session_search_tool.py +189 -7
  921. package/agent/tools/skill_manager_tool.py +202 -3
  922. package/agent/tools/skill_usage.py +52 -4
  923. package/agent/tools/skills_hub.py +184 -44
  924. package/agent/tools/skills_sync.py +232 -5
  925. package/agent/tools/skills_tool.py +125 -11
  926. package/agent/tools/terminal_tool.py +148 -26
  927. package/agent/tools/tirith_security.py +2 -0
  928. package/agent/tools/todo_tool.py +32 -1
  929. package/agent/tools/transcription_tools.py +13 -5
  930. package/agent/tools/tts_tool.py +332 -38
  931. package/agent/tools/url_safety.py +52 -1
  932. package/agent/tools/vision_tools.py +124 -39
  933. package/agent/tools/voice_mode.py +4 -3
  934. package/agent/tools/web_tools.py +45 -15
  935. package/agent/tools/write_approval.py +493 -0
  936. package/agent/toolsets.py +34 -10
  937. package/agent/trajectory_compressor.py +81 -10
  938. package/agent/tui_gateway/entry.py +43 -6
  939. package/agent/tui_gateway/server.py +3335 -330
  940. package/agent/tui_gateway/slash_worker.py +61 -0
  941. package/agent/tui_gateway/ws.py +67 -9
  942. package/agent/ui-tui/eslint.config.mjs +0 -4
  943. package/agent/ui-tui/package.json +6 -6
  944. package/agent/ui-tui/packages/hermes-ink/package.json +1 -1
  945. package/agent/ui-tui/packages/hermes-ink/src/ink/app-mouse.test.ts +34 -1
  946. package/agent/ui-tui/packages/hermes-ink/src/ink/app-rawmode-mouse.test.ts +91 -0
  947. package/agent/ui-tui/packages/hermes-ink/src/ink/components/App.tsx +35 -2
  948. package/agent/ui-tui/packages/hermes-ink/src/ink/events/input-event.ts +4 -11
  949. package/agent/ui-tui/packages/hermes-ink/src/ink/parse-keypress.test.ts +23 -57
  950. package/agent/ui-tui/packages/hermes-ink/src/ink/parse-keypress.ts +11 -135
  951. package/agent/ui-tui/packages/hermes-ink/src/ink/termio/tokenize.test.ts +185 -0
  952. package/agent/ui-tui/packages/hermes-ink/src/ink/termio/tokenize.ts +37 -3
  953. package/agent/ui-tui/packages/hermes-ink/src/utils/execFileNoThrow.ts +5 -5
  954. package/agent/ui-tui/src/__tests__/appChromeStatusRule.test.tsx +217 -0
  955. package/agent/ui-tui/src/__tests__/appChromeStatusRuleDevCredits.test.tsx +73 -0
  956. package/agent/ui-tui/src/__tests__/approvalAction.test.ts +11 -0
  957. package/agent/ui-tui/src/__tests__/billingCommand.test.ts +301 -0
  958. package/agent/ui-tui/src/__tests__/blockLayout.test.ts +122 -0
  959. package/agent/ui-tui/src/__tests__/brandingMcpCount.test.ts +111 -0
  960. package/agent/ui-tui/src/__tests__/completionApply.test.ts +51 -0
  961. package/agent/ui-tui/src/__tests__/createGatewayEventHandler.test.ts +487 -2
  962. package/agent/ui-tui/src/__tests__/createSlashHandler.test.ts +54 -0
  963. package/agent/ui-tui/src/__tests__/creditsCommand.test.ts +144 -0
  964. package/agent/ui-tui/src/__tests__/gatewayClient.test.ts +120 -99
  965. package/agent/ui-tui/src/__tests__/gracefulExit.test.ts +11 -0
  966. package/agent/ui-tui/src/__tests__/memoryMonitor.test.ts +102 -0
  967. package/agent/ui-tui/src/__tests__/paths.test.ts +41 -1
  968. package/agent/ui-tui/src/__tests__/terminalModes.test.ts +22 -0
  969. package/agent/ui-tui/src/__tests__/text.test.ts +23 -0
  970. package/agent/ui-tui/src/__tests__/textInputFastEcho.test.ts +37 -0
  971. package/agent/ui-tui/src/__tests__/turnControllerNotice.test.ts +43 -0
  972. package/agent/ui-tui/src/__tests__/useInputHandlers.test.ts +38 -1
  973. package/agent/ui-tui/src/__tests__/virtualHeights.test.ts +8 -0
  974. package/agent/ui-tui/src/app/createGatewayEventHandler.ts +102 -7
  975. package/agent/ui-tui/src/app/interfaces.ts +64 -1
  976. package/agent/ui-tui/src/app/overlayStore.ts +18 -2
  977. package/agent/ui-tui/src/app/slash/commands/billing.ts +332 -0
  978. package/agent/ui-tui/src/app/slash/commands/core.ts +31 -2
  979. package/agent/ui-tui/src/app/slash/commands/credits.ts +57 -0
  980. package/agent/ui-tui/src/app/slash/commands/ops.ts +28 -0
  981. package/agent/ui-tui/src/app/slash/commands/session.ts +32 -4
  982. package/agent/ui-tui/src/app/slash/registry.ts +4 -0
  983. package/agent/ui-tui/src/app/turnController.ts +145 -2
  984. package/agent/ui-tui/src/app/uiStore.ts +2 -0
  985. package/agent/ui-tui/src/app/useInputHandlers.ts +42 -4
  986. package/agent/ui-tui/src/app/useMainApp.ts +54 -8
  987. package/agent/ui-tui/src/app/useSessionLifecycle.ts +40 -31
  988. package/agent/ui-tui/src/app/useSubmission.ts +23 -31
  989. package/agent/ui-tui/src/components/appChrome.tsx +112 -5
  990. package/agent/ui-tui/src/components/appLayout.tsx +9 -0
  991. package/agent/ui-tui/src/components/appOverlays.tsx +25 -1
  992. package/agent/ui-tui/src/components/billingOverlay.tsx +684 -0
  993. package/agent/ui-tui/src/components/branding.tsx +15 -3
  994. package/agent/ui-tui/src/components/messageLine.tsx +25 -3
  995. package/agent/ui-tui/src/components/pluginsHub.tsx +238 -0
  996. package/agent/ui-tui/src/components/prompts.tsx +31 -17
  997. package/agent/ui-tui/src/components/streamingAssistant.tsx +63 -55
  998. package/agent/ui-tui/src/components/textInput.tsx +16 -0
  999. package/agent/ui-tui/src/config/env.ts +12 -0
  1000. package/agent/ui-tui/src/config/limits.ts +13 -0
  1001. package/agent/ui-tui/src/domain/blockLayout.ts +146 -0
  1002. package/agent/ui-tui/src/domain/paths.ts +24 -0
  1003. package/agent/ui-tui/src/domain/slash.ts +40 -0
  1004. package/agent/ui-tui/src/entry.tsx +35 -4
  1005. package/agent/ui-tui/src/gatewayClient.ts +22 -10
  1006. package/agent/ui-tui/src/gatewayTypes.ts +130 -1
  1007. package/agent/ui-tui/src/lib/gracefulExit.ts +24 -4
  1008. package/agent/ui-tui/src/lib/memory.test.ts +162 -0
  1009. package/agent/ui-tui/src/lib/memory.ts +60 -1
  1010. package/agent/ui-tui/src/lib/memoryMonitor.ts +79 -4
  1011. package/agent/ui-tui/src/lib/osc52.ts +1 -1
  1012. package/agent/ui-tui/src/lib/text.test.ts +32 -1
  1013. package/agent/ui-tui/src/lib/text.ts +29 -2
  1014. package/agent/ui-tui/src/lib/virtualHeights.ts +13 -0
  1015. package/agent/ui-tui/src/types.ts +5 -0
  1016. package/agent/ui-tui/tsconfig.build.json +0 -1
  1017. package/agent/ui-tui/tsconfig.json +2 -1
  1018. package/agent/utils.py +66 -2
  1019. package/agent/uv.lock +308 -696
  1020. package/agent/web/index.html +2 -2
  1021. package/agent/web/package.json +11 -6
  1022. package/agent/web/public/claw-bg.webp +0 -0
  1023. package/agent/web/public/claw-logo.webp +0 -0
  1024. package/agent/web/src/App.tsx +138 -48
  1025. package/agent/web/src/components/AutomationBlueprints.tsx +225 -0
  1026. package/agent/web/src/components/Backdrop.tsx +15 -0
  1027. package/agent/web/src/components/ChatSessionList.tsx +260 -0
  1028. package/agent/web/src/components/ChatSidebar.tsx +262 -78
  1029. package/agent/web/src/components/ConfirmDialog.tsx +122 -0
  1030. package/agent/web/src/components/ModelPickerDialog.tsx +111 -16
  1031. package/agent/web/src/components/ModelReloadConfirm.tsx +40 -0
  1032. package/agent/web/src/components/ProfileScopeBanner.tsx +30 -0
  1033. package/agent/web/src/components/ProfileSwitcher.tsx +67 -0
  1034. package/agent/web/src/components/ReasoningPicker.tsx +167 -0
  1035. package/agent/web/src/components/SkillEditorDialog.tsx +215 -0
  1036. package/agent/web/src/components/ThemeSwitcher.tsx +119 -4
  1037. package/agent/web/src/components/ToolsetConfigDrawer.tsx +457 -0
  1038. package/agent/web/src/contexts/PageHeaderProvider.tsx +7 -4
  1039. package/agent/web/src/contexts/ProfileProvider.tsx +137 -0
  1040. package/agent/web/src/contexts/SystemActions.tsx +6 -8
  1041. package/agent/web/src/contexts/profile-context.ts +19 -0
  1042. package/agent/web/src/contexts/useProfileScope.ts +6 -0
  1043. package/agent/web/src/i18n/af.ts +5 -4
  1044. package/agent/web/src/i18n/de.ts +5 -4
  1045. package/agent/web/src/i18n/en.ts +58 -4
  1046. package/agent/web/src/i18n/es.ts +5 -3
  1047. package/agent/web/src/i18n/fr.ts +5 -3
  1048. package/agent/web/src/i18n/ga.ts +5 -4
  1049. package/agent/web/src/i18n/hu.ts +5 -4
  1050. package/agent/web/src/i18n/it.ts +5 -4
  1051. package/agent/web/src/i18n/ja.ts +5 -4
  1052. package/agent/web/src/i18n/ko.ts +5 -4
  1053. package/agent/web/src/i18n/pt.ts +5 -3
  1054. package/agent/web/src/i18n/ru.ts +5 -4
  1055. package/agent/web/src/i18n/tr.ts +5 -4
  1056. package/agent/web/src/i18n/types.ts +59 -1
  1057. package/agent/web/src/i18n/uk.ts +5 -3
  1058. package/agent/web/src/i18n/zh-hant.ts +5 -4
  1059. package/agent/web/src/i18n/zh.ts +5 -4
  1060. package/agent/web/src/index.css +2 -2
  1061. package/agent/web/src/lib/api.ts +819 -52
  1062. package/agent/web/src/lib/dashboard-flags.ts +16 -7
  1063. package/agent/web/src/lib/reasoning-effort.test.ts +48 -0
  1064. package/agent/web/src/lib/reasoning-effort.ts +36 -0
  1065. package/agent/web/src/lib/session-refresh.test.ts +21 -0
  1066. package/agent/web/src/lib/session-refresh.ts +26 -0
  1067. package/agent/web/src/pages/ChannelsPage.tsx +529 -68
  1068. package/agent/web/src/pages/ChatPage.tsx +249 -56
  1069. package/agent/web/src/pages/ConfigPage.tsx +11 -1
  1070. package/agent/web/src/pages/CronPage.tsx +219 -31
  1071. package/agent/web/src/pages/EnvPage.tsx +25 -6
  1072. package/agent/web/src/pages/FilesPage.tsx +525 -0
  1073. package/agent/web/src/pages/McpPage.tsx +80 -3
  1074. package/agent/web/src/pages/ModelsPage.tsx +97 -12
  1075. package/agent/web/src/pages/PluginsPage.tsx +1 -1
  1076. package/agent/web/src/pages/ProfileBuilderPage.tsx +611 -0
  1077. package/agent/web/src/pages/ProfilesPage.tsx +1038 -172
  1078. package/agent/web/src/pages/SessionsPage.tsx +144 -13
  1079. package/agent/web/src/pages/SkillsPage.tsx +851 -70
  1080. package/agent/web/src/pages/SystemPage.tsx +340 -4
  1081. package/agent/web/src/pages/WalletPage.tsx +401 -0
  1082. package/agent/web/src/pages/WebhooksPage.tsx +145 -15
  1083. package/agent/web/src/pages/X402Page.tsx +207 -0
  1084. package/agent/web/src/plugins/registry.ts +28 -11
  1085. package/agent/web/src/plugins/sdk.d.ts +160 -0
  1086. package/agent/web/src/themes/context.tsx +112 -5
  1087. package/agent/web/src/themes/fonts.ts +167 -0
  1088. package/agent/web/src/themes/index.ts +7 -0
  1089. package/agent/web/tsconfig.app.json +0 -1
  1090. package/agent/web/vite.config.ts +1 -8
  1091. package/agent/web/vitest.config.ts +16 -0
  1092. package/package.json +1 -1
  1093. package/agent/apps/desktop/package-lock.json +0 -18363
  1094. package/agent/apps/desktop/src/app/chat/composer/skin-slash-popover.tsx +0 -56
  1095. package/agent/apps/desktop/src/components/assistant-ui/thread-virtualizer.tsx +0 -382
  1096. package/agent/apps/desktop/src/components/assistant-ui/todo-tool.tsx +0 -109
  1097. package/agent/apps/desktop/src/components/chat/generated-image-context.tsx +0 -19
  1098. package/agent/optional-skills/productivity/shop-app/SKILL.md +0 -340
  1099. package/agent/skills/autonomous-ai-agents/kanban-codex-lane/SKILL.md +0 -277
  1100. package/agent/skills/autonomous-ai-agents/kanban-codex-lane/templates/pmb-codex-lane-prompt.md +0 -57
  1101. package/agent/skills/diagramming/DESCRIPTION.md +0 -3
  1102. package/agent/skills/domain/DESCRIPTION.md +0 -24
  1103. package/agent/skills/gifs/DESCRIPTION.md +0 -3
  1104. package/agent/skills/inference-sh/DESCRIPTION.md +0 -19
  1105. package/agent/skills/mcp/DESCRIPTION.md +0 -3
  1106. package/agent/skills/media/spotify/SKILL.md +0 -135
  1107. package/agent/skills/mlops/training/DESCRIPTION.md +0 -3
  1108. package/agent/skills/mlops/vector-databases/DESCRIPTION.md +0 -3
  1109. package/agent/skills/productivity/linear/SKILL.md +0 -380
  1110. package/agent/skills/productivity/linear/scripts/linear_api.py +0 -445
  1111. package/agent/skills/software-development/debugging-hermes-tui-commands/SKILL.md +0 -152
  1112. package/agent/skills/software-development/writing-plans/SKILL.md +0 -297
  1113. package/agent/ui-tui/package-lock.json +0 -7449
  1114. package/agent/ui-tui/packages/hermes-ink/package-lock.json +0 -1289
  1115. package/agent/web/package-lock.json +0 -8887
  1116. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/PORT_NOTES.md +0 -0
  1117. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/SKILL.md +0 -0
  1118. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/prompts/system.md +0 -0
  1119. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/palettes/macaron.md +0 -0
  1120. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/palettes/mono-ink.md +0 -0
  1121. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/palettes/neon.md +0 -0
  1122. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/palettes/warm.md +0 -0
  1123. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/prompt-construction.md +0 -0
  1124. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/style-presets.md +0 -0
  1125. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/blueprint.md +0 -0
  1126. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/chalkboard.md +0 -0
  1127. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/editorial.md +0 -0
  1128. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/elegant.md +0 -0
  1129. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/fantasy-animation.md +0 -0
  1130. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/flat-doodle.md +0 -0
  1131. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/flat.md +0 -0
  1132. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/ink-notes.md +0 -0
  1133. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/intuition-machine.md +0 -0
  1134. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/minimal.md +0 -0
  1135. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/nature.md +0 -0
  1136. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/notion.md +0 -0
  1137. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/pixel-art.md +0 -0
  1138. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/playful.md +0 -0
  1139. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/retro.md +0 -0
  1140. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/scientific.md +0 -0
  1141. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/screen-print.md +0 -0
  1142. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/sketch-notes.md +0 -0
  1143. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/sketch.md +0 -0
  1144. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/vector-illustration.md +0 -0
  1145. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/vintage.md +0 -0
  1146. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/warm.md +0 -0
  1147. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/watercolor.md +0 -0
  1148. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles.md +0 -0
  1149. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/usage.md +0 -0
  1150. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/workflow.md +0 -0
  1151. /package/agent/{skills → optional-skills}/creative/baoyu-comic/PORT_NOTES.md +0 -0
  1152. /package/agent/{skills → optional-skills}/creative/baoyu-comic/SKILL.md +0 -0
  1153. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/analysis-framework.md +0 -0
  1154. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/art-styles/chalk.md +0 -0
  1155. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/art-styles/ink-brush.md +0 -0
  1156. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/art-styles/ligne-claire.md +0 -0
  1157. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/art-styles/manga.md +0 -0
  1158. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/art-styles/minimalist.md +0 -0
  1159. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/art-styles/realistic.md +0 -0
  1160. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/auto-selection.md +0 -0
  1161. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/base-prompt.md +0 -0
  1162. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/character-template.md +0 -0
  1163. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/layouts/cinematic.md +0 -0
  1164. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/layouts/dense.md +0 -0
  1165. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/layouts/four-panel.md +0 -0
  1166. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/layouts/mixed.md +0 -0
  1167. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/layouts/splash.md +0 -0
  1168. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/layouts/standard.md +0 -0
  1169. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/layouts/webtoon.md +0 -0
  1170. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/ohmsha-guide.md +0 -0
  1171. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/partial-workflows.md +0 -0
  1172. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/presets/concept-story.md +0 -0
  1173. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/presets/four-panel.md +0 -0
  1174. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/presets/ohmsha.md +0 -0
  1175. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/presets/shoujo.md +0 -0
  1176. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/presets/wuxia.md +0 -0
  1177. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/storyboard-template.md +0 -0
  1178. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/tones/action.md +0 -0
  1179. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/tones/dramatic.md +0 -0
  1180. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/tones/energetic.md +0 -0
  1181. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/tones/neutral.md +0 -0
  1182. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/tones/romantic.md +0 -0
  1183. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/tones/vintage.md +0 -0
  1184. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/tones/warm.md +0 -0
  1185. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/workflow.md +0 -0
  1186. /package/agent/{skills → optional-skills}/creative/creative-ideation/SKILL.md +0 -0
  1187. /package/agent/{skills → optional-skills}/creative/creative-ideation/references/full-prompt-library.md +0 -0
  1188. /package/agent/{skills → optional-skills}/creative/pixel-art/ATTRIBUTION.md +0 -0
  1189. /package/agent/{skills → optional-skills}/creative/pixel-art/SKILL.md +0 -0
  1190. /package/agent/{skills → optional-skills}/creative/pixel-art/references/palettes.md +0 -0
  1191. /package/agent/{skills → optional-skills}/creative/pixel-art/scripts/__init__.py +0 -0
  1192. /package/agent/{skills → optional-skills}/creative/pixel-art/scripts/palettes.py +0 -0
  1193. /package/agent/{skills → optional-skills}/creative/pixel-art/scripts/pixel_art.py +0 -0
  1194. /package/agent/{skills → optional-skills}/creative/pixel-art/scripts/pixel_art_video.py +0 -0
  1195. /package/agent/{skills/mlops/inference → optional-skills/mlops}/obliteratus/SKILL.md +0 -0
  1196. /package/agent/{skills/mlops/inference → optional-skills/mlops}/obliteratus/references/analysis-modules.md +0 -0
  1197. /package/agent/{skills/mlops/inference → optional-skills/mlops}/obliteratus/references/methods-guide.md +0 -0
  1198. /package/agent/{skills/mlops/inference → optional-skills/mlops}/obliteratus/templates/abliteration-config.yaml +0 -0
  1199. /package/agent/{skills/mlops/inference → optional-skills/mlops}/obliteratus/templates/analysis-study.yaml +0 -0
  1200. /package/agent/{skills/mlops/inference → optional-skills/mlops}/obliteratus/templates/batch-abliteration.yaml +0 -0
  1201. /package/agent/{skills → optional-skills}/mlops/research/DESCRIPTION.md +0 -0
  1202. /package/agent/{skills → optional-skills}/mlops/research/dspy/SKILL.md +0 -0
  1203. /package/agent/{skills → optional-skills}/mlops/research/dspy/references/examples.md +0 -0
  1204. /package/agent/{skills → optional-skills}/mlops/research/dspy/references/modules.md +0 -0
  1205. /package/agent/{skills → optional-skills}/mlops/research/dspy/references/optimizers.md +0 -0
  1206. /package/agent/{skills/red-teaming → optional-skills/security}/godmode/references/jailbreak-templates.md +0 -0
  1207. /package/agent/{skills/red-teaming → optional-skills/security}/godmode/references/refusal-detection.md +0 -0
  1208. /package/agent/{skills/red-teaming → optional-skills/security}/godmode/scripts/godmode_race.py +0 -0
  1209. /package/agent/{skills/red-teaming → optional-skills/security}/godmode/scripts/load_godmode.py +0 -0
  1210. /package/agent/{skills/red-teaming → optional-skills/security}/godmode/scripts/parseltongue.py +0 -0
  1211. /package/agent/{skills/red-teaming → optional-skills/security}/godmode/templates/prefill-subtle.json +0 -0
  1212. /package/agent/{skills/red-teaming → optional-skills/security}/godmode/templates/prefill.json +0 -0
  1213. /package/agent/{skills → optional-skills}/software-development/subagent-driven-development/references/context-budget-discipline.md +0 -0
  1214. /package/agent/{skills → optional-skills}/software-development/subagent-driven-development/references/gates-taxonomy.md +0 -0
@@ -4,8 +4,12 @@ import { useEffect, useMemo, useRef, useState } from 'react'
4
4
 
5
5
  import { ModelPickerDialog } from '@/components/model-picker'
6
6
  import { Button } from '@/components/ui/button'
7
+ import { Codicon } from '@/components/ui/codicon'
8
+ import { ErrorIcon } from '@/components/ui/error-state'
7
9
  import { Input } from '@/components/ui/input'
10
+ import { Loader } from '@/components/ui/loader'
8
11
  import { getGlobalModelOptions } from '@/hermes'
12
+ import { useI18n } from '@/i18n'
9
13
  import {
10
14
  Check,
11
15
  ChevronDown,
@@ -14,7 +18,6 @@ import {
14
18
  ExternalLink,
15
19
  KeyRound,
16
20
  Loader2,
17
- Sparkles,
18
21
  Terminal
19
22
  } from '@/lib/icons'
20
23
  import { isProviderSetupErrorMessage } from '@/lib/provider-setup-errors'
@@ -23,12 +26,17 @@ import { $desktopBoot, type DesktopBootState } from '@/store/boot'
23
26
  import {
24
27
  $desktopOnboarding,
25
28
  cancelOnboardingFlow,
29
+ clearPendingProviderOAuth,
26
30
  closeManualOnboarding,
27
31
  confirmOnboardingModel,
28
32
  copyDeviceCode,
29
33
  copyExternalCommand,
34
+ DEFAULT_MANUAL_ONBOARDING_REASON,
35
+ DEFAULT_ONBOARDING_REASON,
36
+ dismissFirstRunOnboarding,
30
37
  type OnboardingContext,
31
38
  type OnboardingFlow,
39
+ peekPendingProviderOAuth,
32
40
  recheckExternalSignin,
33
41
  refreshOnboarding,
34
42
  saveOnboardingApiKey,
@@ -38,7 +46,7 @@ import {
38
46
  startProviderOAuth,
39
47
  submitOnboardingCode
40
48
  } from '@/store/onboarding'
41
- import type { OAuthProvider } from '@/types/hermes'
49
+ import type { ModelOptionProvider, OAuthProvider } from '@/types/hermes'
42
50
 
43
51
  interface DesktopOnboardingOverlayProps {
44
52
  enabled: boolean
@@ -46,8 +54,8 @@ interface DesktopOnboardingOverlayProps {
46
54
  requestGateway: OnboardingContext['requestGateway']
47
55
  }
48
56
 
49
- interface ApiKeyOption {
50
- description: string
57
+ export interface ApiKeyOption {
58
+ description?: string
51
59
  docsUrl: string
52
60
  envKey: string
53
61
  id: string
@@ -56,76 +64,135 @@ interface ApiKeyOption {
56
64
  short?: string
57
65
  }
58
66
 
59
- const MIN_KEY_LENGTH = 8
60
-
61
67
  const API_KEY_OPTIONS: ApiKeyOption[] = [
62
68
  {
63
69
  id: 'openrouter',
64
70
  name: 'OpenRouter',
65
- short: 'one key, many models',
66
71
  envKey: 'OPENROUTER_API_KEY',
67
- description: 'Hosts hundreds of models behind a single key. Good default for new installs.',
68
72
  docsUrl: 'https://openrouter.ai/keys'
69
73
  },
70
74
  {
71
75
  id: 'openai',
72
76
  name: 'OpenAI',
73
- short: 'GPT-class models',
74
77
  envKey: 'OPENAI_API_KEY',
75
- description: 'Direct access to OpenAI models.',
76
78
  docsUrl: 'https://platform.openai.com/api-keys'
77
79
  },
78
80
  {
79
81
  id: 'gemini',
80
82
  name: 'Google Gemini',
81
- short: 'Gemini models',
82
83
  envKey: 'GEMINI_API_KEY',
83
- description: 'Direct access to Google Gemini models.',
84
84
  docsUrl: 'https://aistudio.google.com/app/apikey'
85
85
  },
86
86
  {
87
87
  id: 'xai',
88
88
  name: 'xAI Grok',
89
- short: 'Grok models',
90
89
  envKey: 'XAI_API_KEY',
91
- description: 'Direct access to xAI Grok models.',
92
90
  docsUrl: 'https://console.x.ai/'
93
91
  },
94
92
  {
95
93
  id: 'local',
96
94
  name: 'Local / custom endpoint',
97
- short: 'self-hosted',
98
95
  envKey: 'OPENAI_BASE_URL',
99
- description: 'Point Hermes at a local or self-hosted OpenAI-compatible endpoint (vLLM, llama.cpp, Ollama, etc).',
100
96
  docsUrl: 'https://github.com/NousResearch/hermes-agent#bring-your-own-endpoint',
101
97
  placeholder: 'http://127.0.0.1:8000/v1'
102
98
  }
103
99
  ]
104
100
 
101
+ // Build the FULL API-key provider catalog from the backend model options so the
102
+ // onboarding / Providers key form lists every `api_key` provider `hermes model`
103
+ // knows about — not just the hand-curated five. Curated entries keep their
104
+ // richer copy + placeholders and float to the top (recommended defaults); every
105
+ // other api_key provider is appended with a generic "paste {KEY}" affordance.
106
+ // OAuth / external providers are intentionally excluded here — they go through
107
+ // the OAuth picker / sign-in flow, not a pasted key.
108
+ function useApiKeyCatalog(): ApiKeyOption[] {
109
+ const [rows, setRows] = useState<ModelOptionProvider[]>([])
110
+
111
+ useEffect(() => {
112
+ let cancelled = false
113
+
114
+ // Best-effort — on failure the curated defaults still render. Wrapped in
115
+ // Promise.resolve().then so a synchronous throw (e.g. no desktop bridge in
116
+ // tests) is funneled into the same .catch instead of escaping.
117
+ void Promise.resolve()
118
+ .then(() => getGlobalModelOptions())
119
+ .then(res => {
120
+ if (!cancelled) {
121
+ setRows(res.providers ?? [])
122
+ }
123
+ })
124
+ .catch(() => {
125
+ // Ignore — fall back to the curated API_KEY_OPTIONS only.
126
+ })
127
+
128
+ return () => {
129
+ cancelled = true
130
+ }
131
+ }, [])
132
+
133
+ return useMemo(() => {
134
+ const curatedByEnv = new Map(API_KEY_OPTIONS.map(o => [o.envKey, o]))
135
+ const derived: ApiKeyOption[] = []
136
+ const seenEnv = new Set<string>(API_KEY_OPTIONS.map(o => o.envKey))
137
+
138
+ for (const row of rows) {
139
+ // Only api_key providers can be activated with a pasted key. Skip OAuth /
140
+ // external / managed flows and anything missing an env var to write to.
141
+ if (row.auth_type && row.auth_type !== 'api_key') {
142
+ continue
143
+ }
144
+
145
+ const envKey = row.key_env
146
+
147
+ if (!envKey || seenEnv.has(envKey)) {
148
+ continue
149
+ }
150
+
151
+ seenEnv.add(envKey)
152
+ derived.push({
153
+ id: row.slug,
154
+ name: row.name,
155
+ envKey,
156
+ description: `Direct API access to ${row.name}.`,
157
+ docsUrl: ''
158
+ })
159
+ }
160
+
161
+ // Curated first (recommended order), then the rest alphabetically so the
162
+ // long tail is scannable.
163
+ derived.sort((a, b) => a.name.localeCompare(b.name))
164
+
165
+ return [...API_KEY_OPTIONS.filter(o => curatedByEnv.has(o.envKey)), ...derived]
166
+ }, [rows])
167
+ }
168
+
105
169
  const PROVIDER_DISPLAY: Record<string, { order: number; title: string }> = {
106
170
  nous: { order: 0, title: 'Nous Portal' },
107
- anthropic: { order: 1, title: 'Anthropic Claude' },
108
- 'openai-codex': { order: 2, title: 'OpenAI Codex / ChatGPT' },
109
- 'minimax-oauth': { order: 3, title: 'MiniMax' },
110
- 'claude-code': { order: 4, title: 'Claude Code' },
111
- 'qwen-oauth': { order: 5, title: 'Qwen Code' }
171
+ 'openai-codex': { order: 1, title: 'OpenAI OAuth (ChatGPT)' },
172
+ 'minimax-oauth': { order: 2, title: 'MiniMax' },
173
+ 'qwen-oauth': { order: 3, title: 'Qwen Code' },
174
+ 'xai-oauth': { order: 4, title: 'xAI Grok' },
175
+ // Both Anthropic entries sit at the bottom: the API-key path first, then
176
+ // the subscription OAuth path (only works with extra usage credits).
177
+ anthropic: { order: 5, title: 'Anthropic API Key' },
178
+ 'claude-code': { order: 6, title: 'Anthropic OAuth: Required Extra Usage Credits to Use Subscription' }
112
179
  }
113
180
 
114
181
  const assetPath = (path: string) => `${import.meta.env.BASE_URL}${path.replace(/^\/+/, '')}`
115
182
 
116
- const FLOW_SUBTITLES: Record<OAuthProvider['flow'], string> = {
117
- pkce: 'Opens your browser to sign in, then continues here',
118
- device_code: 'Opens a verification page in your browser — Hermes connects automatically',
119
- external: 'Sign in once in your terminal, then come back to chat'
120
- }
121
-
122
- const providerTitle = (p: OAuthProvider) => PROVIDER_DISPLAY[p.id]?.title ?? p.name
183
+ export const providerTitle = (p: OAuthProvider) => PROVIDER_DISPLAY[p.id]?.title ?? p.name
123
184
  const orderOf = (p: OAuthProvider) => PROVIDER_DISPLAY[p.id]?.order ?? 99
124
185
 
125
- const sortProviders = (providers: OAuthProvider[]) =>
186
+ export const sortProviders = (providers: OAuthProvider[]) =>
126
187
  [...providers].sort((a, b) => orderOf(a) - orderOf(b) || a.name.localeCompare(b.name))
127
188
 
189
+ // Exit choreography, mirroring the gateway "connecting" overlay's timing:
190
+ // text-out (360ms: CONNECTED fades down, rest scrambles+fades) → hold (300ms)
191
+ // → surface-out (520ms, held back by [transition-delay:660ms]). Finalize after.
192
+ const ONBOARDING_EXIT_MS = 1180
193
+
128
194
  export function DesktopOnboardingOverlay({ enabled, onCompleted, requestGateway }: DesktopOnboardingOverlayProps) {
195
+ const { t } = useI18n()
129
196
  const onboarding = useStore($desktopOnboarding)
130
197
  const boot = useStore($desktopBoot)
131
198
  const ctxRef = useRef<OnboardingContext>({ requestGateway, onCompleted })
@@ -139,12 +206,65 @@ export function DesktopOnboardingOverlay({ enabled, onCompleted, requestGateway
139
206
  []
140
207
  )
141
208
 
209
+ // Cinematic exit on "Begin": dissolve the panel + overlay (revealing the chat
210
+ // behind), THEN finalize so the unmount lands after the fade — mirrors the
211
+ // connecting overlay's exit choreography instead of cutting instantly.
212
+ const [leaving, setLeaving] = useState(false)
213
+
214
+ const finalizeOnboarding = () => {
215
+ if (leaving) {
216
+ return
217
+ }
218
+
219
+ const reduce =
220
+ typeof window !== 'undefined' && window.matchMedia?.('(prefers-reduced-motion: reduce)').matches
221
+
222
+ if (reduce) {
223
+ confirmOnboardingModel(ctx)
224
+
225
+ return
226
+ }
227
+
228
+ setLeaving(true)
229
+ window.setTimeout(() => confirmOnboardingModel(ctx), ONBOARDING_EXIT_MS)
230
+ }
231
+
142
232
  useEffect(() => {
143
233
  if (enabled || onboarding.requested) {
144
234
  void refreshOnboarding(ctx)
145
235
  }
146
236
  }, [ctx, enabled, onboarding.requested])
147
237
 
238
+ // When the Providers settings page asked to connect a specific provider, the
239
+ // store stashed its id. Once the provider list has loaded and we're back at
240
+ // an idle picker, launch that exact OAuth flow so the user lands directly in
241
+ // sign-in instead of the picker they just came from.
242
+ useEffect(() => {
243
+ if (!onboarding.manual || onboarding.providers === null || onboarding.flow.status !== 'idle') {
244
+ return
245
+ }
246
+
247
+ const pendingId = peekPendingProviderOAuth()
248
+
249
+ if (!pendingId) {
250
+ return
251
+ }
252
+
253
+ const provider = onboarding.providers.find(p => p.id === pendingId)
254
+
255
+ if (provider) {
256
+ // Only clear once we've committed to launching it, so a failed/empty
257
+ // provider fetch doesn't silently drop the hand-off.
258
+ clearPendingProviderOAuth()
259
+ void startProviderOAuth(provider, ctx)
260
+ } else if (onboarding.providers.length > 0) {
261
+ // The list loaded but the id isn't a real provider — drop the stale
262
+ // hand-off. An empty list means the fetch isn't ready yet, so keep it
263
+ // and let a later refresh retry.
264
+ clearPendingProviderOAuth()
265
+ }
266
+ }, [ctx, onboarding.flow.status, onboarding.manual, onboarding.providers])
267
+
148
268
  // Mount from frame 1 so we replace the boot overlay seamlessly. The
149
269
  // configured field stays null until the runtime check resolves; only then
150
270
  // do we know whether to dismiss (true) or surface the picker (false).
@@ -154,48 +274,101 @@ export function DesktopOnboardingOverlay({ enabled, onCompleted, requestGateway
154
274
  return null
155
275
  }
156
276
 
277
+ // The user chose "I'll choose a provider later" on first run. Stay out of the
278
+ // way on every subsequent launch — they re-enter via Settings → Providers
279
+ // (manual mode), which sets manual=true and bypasses this gate.
280
+ if (onboarding.firstRunSkipped && !onboarding.manual) {
281
+ return null
282
+ }
283
+
157
284
  const { flow } = onboarding
285
+ // Show the launch reason only when it's a meaningful, caller-supplied prompt —
286
+ // suppress the generic defaults (useless noise) and provider-setup errors
287
+ // (those are surfaced by FlowPanel, not as a banner).
158
288
  const rawReason = onboarding.reason?.trim() || null
159
- const reason = rawReason && !isProviderSetupErrorMessage(rawReason) ? rawReason : null
289
+
290
+ const reason =
291
+ rawReason &&
292
+ !isProviderSetupErrorMessage(rawReason) &&
293
+ rawReason !== DEFAULT_ONBOARDING_REASON &&
294
+ rawReason !== DEFAULT_MANUAL_ONBOARDING_REASON
295
+ ? rawReason
296
+ : null
297
+
160
298
  // In manual mode the app is already configured, so the flow is "ready"
161
299
  // immediately — no runtime gate needed. Otherwise wait for the readiness
162
300
  // check (configured === false) before showing the picker.
163
301
  const ready = onboarding.manual || (enabled && onboarding.configured === false)
164
302
  const showPicker = flow.status === 'idle' || flow.status === 'success'
303
+ // The final "you're in" screen drops the card chrome and floats centered on
304
+ // the surface — same bare, cinematic treatment as the connecting overlay.
305
+ const bare = ready && !showPicker && flow.status === 'confirming_model'
165
306
 
166
307
  return (
167
- <div className="fixed inset-0 z-1300 flex items-center justify-center bg-(--ui-chat-surface-background) p-6">
168
- <div className="w-full max-w-[45rem] overflow-hidden rounded-xl border border-(--ui-stroke-secondary) bg-(--ui-chat-bubble-background) shadow-sm">
169
- <Header />
308
+ <div
309
+ className={cn(
310
+ 'fixed inset-0 z-1300 flex items-center justify-center bg-(--ui-chat-surface-background) p-6 transition-opacity duration-[520ms] ease-out',
311
+ // On the bare confirm screen, hold the surface (text-out + hold) so the
312
+ // per-element exit plays before it dissolves.
313
+ bare && leaving ? '[transition-delay:660ms]' : '',
314
+ leaving ? 'pointer-events-none opacity-0' : 'opacity-100'
315
+ )}
316
+ >
317
+ <div
318
+ className={cn(
319
+ 'relative w-full max-w-[45rem] transition-all duration-500 ease-out',
320
+ bare
321
+ ? ''
322
+ : 'overflow-hidden rounded-xl border border-(--stroke-nous) bg-(--ui-chat-bubble-background) shadow-nous',
323
+ // Bare confirm screen orchestrates its own per-element exit; the
324
+ // carded states use the simple lift/blur dissolve.
325
+ leaving && !bare
326
+ ? '-translate-y-1 scale-[0.985] opacity-0 blur-[2px]'
327
+ : 'translate-y-0 scale-100 opacity-100 blur-0'
328
+ )}
329
+ >
330
+ {showPicker || !ready ? <Header /> : null}
331
+ {onboarding.manual ? (
332
+ <Button
333
+ aria-label={t.common.close}
334
+ className="absolute right-3 top-3 z-10 text-(--ui-text-tertiary) hover:bg-(--chrome-action-hover) hover:text-foreground"
335
+ onClick={() => closeManualOnboarding()}
336
+ size="icon-sm"
337
+ variant="ghost"
338
+ >
339
+ <Codicon name="close" size="1rem" />
340
+ </Button>
341
+ ) : null}
170
342
  <div className="grid gap-3 p-5">
171
- {onboarding.manual ? (
172
- <div className="flex justify-end">
173
- <button
174
- className="text-xs font-medium text-muted-foreground transition hover:text-foreground"
175
- onClick={() => closeManualOnboarding()}
176
- type="button"
177
- >
178
- Close
179
- </button>
180
- </div>
181
- ) : null}
182
343
  {reason ? <ReasonNotice reason={reason} /> : null}
183
- {ready ? showPicker ? <Picker ctx={ctx} /> : <FlowPanel ctx={ctx} flow={flow} /> : <Preparing boot={boot} />}
344
+ {ready ? (
345
+ showPicker ? (
346
+ <Picker ctx={ctx} />
347
+ ) : (
348
+ <FlowPanel ctx={ctx} flow={flow} leaving={leaving} onBegin={finalizeOnboarding} />
349
+ )
350
+ ) : (
351
+ <Preparing boot={boot} />
352
+ )}
184
353
  </div>
185
354
  </div>
186
355
  </div>
187
356
  )
188
357
  }
189
358
 
359
+ // The launch reason is a prompt ("why am I seeing this"), not an error. Only
360
+ // rendered for meaningful caller-supplied reasons (defaults are filtered out
361
+ // upstream), so it never shows the generic "no provider configured" noise.
190
362
  function ReasonNotice({ reason }: { reason: string }) {
191
363
  return (
192
- <div className="rounded-2xl border border-destructive/30 bg-destructive/10 px-4 py-3 text-sm text-destructive">
364
+ <div className="rounded-2xl border border-(--ui-stroke-tertiary) bg-(--ui-bg-tertiary)/40 px-4 py-3 text-sm text-muted-foreground">
193
365
  {reason}
194
366
  </div>
195
367
  )
196
368
  }
197
369
 
198
370
  function Preparing({ boot }: { boot: DesktopBootState }) {
371
+ const { t } = useI18n()
199
372
  const progress = Math.max(2, Math.min(100, Math.round(boot.progress)))
200
373
  const hasError = Boolean(boot.error)
201
374
  const installing = boot.phase.startsWith('runtime.')
@@ -203,9 +376,7 @@ function Preparing({ boot }: { boot: DesktopBootState }) {
203
376
  return (
204
377
  <div className="grid gap-3" role="status">
205
378
  <p className="text-sm text-muted-foreground">
206
- {installing
207
- ? 'Hermes is finishing install. This usually takes under a minute on first run.'
208
- : 'Starting Hermes…'}
379
+ {installing ? t.onboarding.preparingInstall : t.onboarding.starting}
209
380
  </p>
210
381
  <div className="h-2 overflow-hidden rounded-full bg-muted">
211
382
  <div
@@ -226,25 +397,17 @@ function Preparing({ boot }: { boot: DesktopBootState }) {
226
397
  }
227
398
 
228
399
  function Header() {
400
+ const { t } = useI18n()
401
+
229
402
  return (
230
- <div className="border-b border-(--ui-stroke-tertiary) bg-(--ui-chat-bubble-background) px-5 py-4">
231
- <div className="flex items-start gap-3">
232
- <div className="flex size-9 shrink-0 items-center justify-center rounded-lg bg-(--ui-bg-tertiary) text-(--ui-text-tertiary)">
233
- <Sparkles className="size-5" />
234
- </div>
235
- <div>
236
- <h2 className="text-[0.9375rem] font-semibold tracking-tight">Let's get you setup with Hermes Agent</h2>
237
- <p className="mt-1 max-w-xl text-[0.8125rem] leading-5 text-(--ui-text-tertiary)">
238
- Connect a model provider to start chatting. Most options take one click.
239
- </p>
240
- </div>
241
- </div>
403
+ <div className="bg-(--ui-chat-bubble-background) px-5 pt-5 pb-1">
404
+ <h2 className="text-[0.9375rem] font-semibold tracking-tight">{t.onboarding.headerTitle}</h2>
405
+ <p className="mt-1 max-w-xl text-[0.8125rem] leading-5 text-(--ui-text-tertiary)">{t.onboarding.headerDesc}</p>
242
406
  </div>
243
407
  )
244
408
  }
245
409
 
246
- const FEATURED_ID = 'nous'
247
- const FEATURED_PITCH = 'One subscription, 300+ frontier models — the recommended way to run Hermes'
410
+ export const FEATURED_ID = 'nous'
248
411
  const SHOW_ALL_KEY = 'hermes-onboarding-show-all-v1'
249
412
 
250
413
  const readShowAll = () => {
@@ -266,17 +429,38 @@ const persistShowAll = (value: boolean) => {
266
429
  }
267
430
 
268
431
  export function Picker({ ctx }: { ctx: OnboardingContext }) {
269
- const { mode, providers } = useStore($desktopOnboarding)
432
+ const { t } = useI18n()
433
+ const { localEndpoint, manual, mode, providers } = useStore($desktopOnboarding)
270
434
  const [showAll, setShowAll] = useState(readShowAll)
271
435
  const ordered = useMemo(() => (providers ? sortProviders(providers) : []), [providers])
272
436
  const hasOauth = ordered.length > 0
437
+ const apiKeyOptions = useApiKeyCatalog()
273
438
 
274
- if (mode === 'apikey' || !hasOauth) {
275
- return <ApiKeyForm canGoBack={hasOauth} ctx={ctx} />
439
+ // localEndpoint forces the key form regardless of `mode` (which a manual
440
+ // provider refresh may flip back to 'oauth'); it preselects the local option
441
+ // and hides the "back to sign in" link since the user came specifically to
442
+ // configure a custom endpoint.
443
+ if (localEndpoint || mode === 'apikey' || !hasOauth) {
444
+ return (
445
+ <div className="grid gap-3">
446
+ <ApiKeyForm
447
+ canGoBack={hasOauth && !localEndpoint}
448
+ initialEnvKey={localEndpoint ? 'OPENAI_BASE_URL' : undefined}
449
+ onBack={() => setOnboardingMode('oauth')}
450
+ onSave={(envKey, value, name, apiKey) => saveOnboardingApiKey(envKey, value, name, ctx, apiKey)}
451
+ options={apiKeyOptions}
452
+ />
453
+ {manual ? null : (
454
+ <div className="flex justify-center border-t border-(--ui-stroke-tertiary) pt-3">
455
+ <ChooseLaterLink />
456
+ </div>
457
+ )}
458
+ </div>
459
+ )
276
460
  }
277
461
 
278
462
  if (providers === null) {
279
- return <Status>Looking up providers...</Status>
463
+ return <Status>{t.onboarding.lookingUpProviders}</Status>
280
464
  }
281
465
 
282
466
  const select = (p: OAuthProvider) => void startProviderOAuth(p, ctx)
@@ -289,134 +473,241 @@ export function Picker({ ctx }: { ctx: OnboardingContext }) {
289
473
 
290
474
  return (
291
475
  <div className="grid gap-2">
292
- {featured ? <FeaturedProviderRow onSelect={select} provider={featured} /> : null}
293
- {showRest ? (
294
- <>
295
- {rest.map(p => (
296
- <ProviderRow key={p.id} onSelect={select} provider={p} />
297
- ))}
298
- <KeyProviderRow onClick={() => setOnboardingMode('apikey')} />
299
- </>
300
- ) : null}
476
+ <div className="grid max-h-[60dvh] gap-2 overflow-y-auto p-1">
477
+ {featured ? <FeaturedProviderRow onSelect={select} provider={featured} /> : null}
478
+ {showRest ? (
479
+ <>
480
+ {rest.map(p => (
481
+ <ProviderRow key={p.id} onSelect={select} provider={p} />
482
+ ))}
483
+ <KeyProviderRow onClick={() => setOnboardingMode('apikey')} />
484
+ </>
485
+ ) : null}
486
+ </div>
301
487
  {collapsible ? (
302
- <button
303
- className="flex items-center justify-center gap-1.5 pt-1 text-xs font-medium text-muted-foreground transition hover:text-foreground"
488
+ <Button
489
+ className="mt-1 self-center font-medium"
304
490
  onClick={() => setShowAll(persistShowAll(!showAll))}
491
+ size="xs"
305
492
  type="button"
493
+ variant="text"
306
494
  >
307
- {showAll ? 'Collapse' : 'Other providers'}
495
+ {showAll ? t.onboarding.collapse : t.onboarding.otherProviders}
308
496
  <ChevronDown className={cn('size-3.5 transition', showAll && 'rotate-180')} />
309
- </button>
497
+ </Button>
310
498
  ) : null}
311
- <div className="flex justify-end pt-1">
312
- <button
313
- className="text-xs font-medium text-muted-foreground hover:text-foreground"
499
+ <div className="flex items-center justify-between gap-3 pt-1">
500
+ {/* First run only: let the user defer the choice and land in the app.
501
+ In manual mode the overlay already has a close affordance, so the
502
+ "choose later" escape would be redundant — hide it. */}
503
+ {manual ? <span /> : <ChooseLaterLink />}
504
+ <Button
505
+ className="-mr-2 font-medium"
314
506
  onClick={() => setOnboardingMode('apikey')}
507
+ size="xs"
315
508
  type="button"
509
+ variant="text"
316
510
  >
317
- I have an API key
318
- </button>
511
+ {t.onboarding.haveApiKey}
512
+ </Button>
319
513
  </div>
320
514
  </div>
321
515
  )
322
516
  }
323
517
 
324
- function FeaturedProviderRow({
518
+ // "I'll choose a provider later" — dismisses the first-run picker and persists
519
+ // the skip so it never re-nags. The user connects a provider any time from
520
+ // Settings → Providers. Rendered only on the unconfigured first-run flow.
521
+ function ChooseLaterLink() {
522
+ const { t } = useI18n()
523
+
524
+ return (
525
+ <Button
526
+ className="font-medium"
527
+ onClick={() => dismissFirstRunOnboarding()}
528
+ size="xs"
529
+ type="button"
530
+ variant="text"
531
+ >
532
+ {t.onboarding.chooseLater}
533
+ </Button>
534
+ )
535
+ }
536
+
537
+ export function FeaturedProviderRow({
325
538
  onSelect,
326
539
  provider
327
540
  }: {
328
541
  onSelect: (provider: OAuthProvider) => void
329
542
  provider: OAuthProvider
330
543
  }) {
544
+ const { t } = useI18n()
331
545
  const loggedIn = provider.status?.logged_in
332
546
 
333
547
  return (
334
548
  <button
335
- className={cn(
336
- 'group flex w-full items-center justify-between gap-4 rounded-2xl border-2 border-primary/50 bg-primary/5 p-4 text-left transition hover:border-primary hover:bg-primary/10',
337
- loggedIn && 'border-primary'
338
- )}
549
+ className="group relative flex w-full items-center justify-between gap-4 rounded-[8px] bg-primary/[0.06] px-3 py-2.5 text-left transition-colors hover:bg-primary/10"
339
550
  onClick={() => onSelect(provider)}
340
551
  type="button"
341
552
  >
553
+ <span aria-hidden className="arc-border arc-reverse arc-nous" />
342
554
  <div className="min-w-0">
343
555
  <div className="flex items-center gap-2">
344
556
  <img alt="" className="size-5 shrink-0 rounded" src={assetPath('apple-touch-icon.png')} />
345
- <span className="text-base font-semibold">{providerTitle(provider)}</span>
557
+ <span className="text-[length:var(--conversation-text-font-size)] font-semibold">
558
+ {providerTitle(provider)}
559
+ </span>
346
560
  {loggedIn ? (
347
561
  <ConnectedTag />
348
562
  ) : (
349
563
  <span className="inline-flex items-center gap-1.5 bg-primary px-2 py-0.5 text-[0.64rem] font-semibold uppercase tracking-[0.16em] text-primary-foreground">
350
564
  <span aria-hidden="true" className="dither inline-block size-2 shrink-0" />
351
- Recommended
565
+ {t.onboarding.recommended}
352
566
  </span>
353
567
  )}
354
568
  </div>
355
- <p className="mt-1 text-xs leading-5 text-muted-foreground">{FEATURED_PITCH}</p>
569
+ <p className="mt-1 text-xs leading-5 text-muted-foreground">{t.onboarding.featuredPitch}</p>
356
570
  </div>
357
- <ChevronRight className="size-5 shrink-0 text-primary transition group-hover:translate-x-0.5" />
571
+ <ChevronRight className="size-4 shrink-0 text-primary transition group-hover:translate-x-0.5" />
358
572
  </button>
359
573
  )
360
574
  }
361
575
 
362
576
  function ConnectedTag() {
577
+ const { t } = useI18n()
578
+
363
579
  return (
364
580
  <span className="inline-flex items-center gap-1 bg-primary/10 px-2 py-0.5 text-xs font-medium text-primary">
365
581
  <Check className="size-3" />
366
- Connected
582
+ {t.onboarding.connected}
367
583
  </span>
368
584
  )
369
585
  }
370
586
 
371
- function KeyProviderRow({ onClick }: { onClick: () => void }) {
587
+ const PROVIDER_ROW_CLASS =
588
+ 'group flex w-full items-center justify-between gap-3 rounded-[6px] px-3 py-2.5 text-left transition-colors hover:bg-(--ui-control-hover-background)'
589
+
590
+ export function KeyProviderRow({ onClick }: { onClick: () => void }) {
591
+ const { t } = useI18n()
592
+
372
593
  return (
373
- <button
374
- className="group flex w-full items-center justify-between gap-3 rounded-2xl border border-border bg-background/60 p-3 text-left transition hover:border-primary/40 hover:bg-accent/40"
375
- onClick={onClick}
376
- type="button"
377
- >
594
+ <button className={PROVIDER_ROW_CLASS} onClick={onClick} type="button">
378
595
  <div className="min-w-0">
379
- <span className="text-sm font-semibold">OpenRouter</span>
380
- <p className="mt-1 text-xs leading-5 text-muted-foreground">One key, hundreds of models — a solid default</p>
596
+ <span className="text-[length:var(--conversation-text-font-size)] font-semibold">OpenRouter</span>
597
+ <p className="mt-1 text-xs leading-5 text-muted-foreground">{t.onboarding.openRouterPitch}</p>
381
598
  </div>
382
599
  <ChevronRight className="size-4 text-muted-foreground transition group-hover:text-foreground" />
383
600
  </button>
384
601
  )
385
602
  }
386
603
 
387
- function ProviderRow({ onSelect, provider }: { onSelect: (provider: OAuthProvider) => void; provider: OAuthProvider }) {
604
+ export function ProviderRow({
605
+ onSelect,
606
+ provider
607
+ }: {
608
+ onSelect: (provider: OAuthProvider) => void
609
+ provider: OAuthProvider
610
+ }) {
611
+ const { t } = useI18n()
388
612
  const loggedIn = provider.status?.logged_in
389
613
  const Trail = provider.flow === 'external' ? Terminal : ChevronRight
390
614
 
391
615
  return (
392
- <button
393
- className={cn(
394
- 'group flex w-full items-center justify-between gap-3 rounded-2xl border border-border bg-background/60 p-3 text-left transition hover:border-primary/40 hover:bg-accent/40',
395
- loggedIn && 'border-primary/30'
396
- )}
397
- onClick={() => onSelect(provider)}
398
- type="button"
399
- >
616
+ <button className={PROVIDER_ROW_CLASS} onClick={() => onSelect(provider)} type="button">
400
617
  <div className="min-w-0">
401
618
  <div className="flex items-center gap-2">
402
- <span className="text-sm font-semibold">{providerTitle(provider)}</span>
619
+ <span className="text-[length:var(--conversation-text-font-size)] font-semibold">
620
+ {providerTitle(provider)}
621
+ </span>
403
622
  {loggedIn ? <ConnectedTag /> : null}
404
623
  </div>
405
- <p className="mt-1 text-xs leading-5 text-muted-foreground">{FLOW_SUBTITLES[provider.flow]}</p>
624
+ <p className="mt-1 text-xs leading-5 text-muted-foreground">{t.onboarding.flowSubtitles[provider.flow]}</p>
406
625
  </div>
407
626
  <Trail className="size-4 text-muted-foreground transition group-hover:text-foreground" />
408
627
  </button>
409
628
  )
410
629
  }
411
630
 
412
- function ApiKeyForm({ canGoBack, ctx }: { canGoBack: boolean; ctx: OnboardingContext }) {
413
- const [option, setOption] = useState<ApiKeyOption>(API_KEY_OPTIONS[0])
631
+ // Presentational two-column key picker. Onboarding feeds it its curated
632
+ // options + a ctx-bound save; the Providers settings page feeds it the full
633
+ // provider catalog + a setEnvVar-backed save (plus `isSet`/`onClear` so it can
634
+ // double as a manage surface). Keep it free of store/ctx coupling so both
635
+ // surfaces render the identical form.
636
+ export function ApiKeyForm({
637
+ canGoBack,
638
+ initialEnvKey,
639
+ isSet,
640
+ onBack,
641
+ onClear,
642
+ onSave,
643
+ options = API_KEY_OPTIONS,
644
+ redactedValue
645
+ }: {
646
+ canGoBack: boolean
647
+ /** Preselect a specific option by env key (e.g. 'OPENAI_BASE_URL' to land on
648
+ * the local / custom endpoint form). Falls back to the first option. */
649
+ initialEnvKey?: string
650
+ isSet?: (envKey: string) => boolean
651
+ onBack: () => void
652
+ onClear?: (envKey: string) => void
653
+ onSave: (
654
+ envKey: string,
655
+ value: string,
656
+ name: string,
657
+ apiKey?: string
658
+ ) => Promise<{ message?: string; ok: boolean }>
659
+ options?: ApiKeyOption[]
660
+ redactedValue?: (envKey: string) => null | string | undefined
661
+ }) {
662
+ const { t } = useI18n()
663
+
664
+ const [option, setOption] = useState<ApiKeyOption>(
665
+ () => options.find(o => o.envKey === initialEnvKey) ?? options[0]
666
+ )
667
+
414
668
  const [value, setValue] = useState('')
669
+ // Optional endpoint API key, only used by the local / custom endpoint option
670
+ // (whose `value` is the base URL). Cleared whenever the option changes.
671
+ const [localKey, setLocalKey] = useState('')
415
672
  const [saving, setSaving] = useState(false)
416
673
  const [error, setError] = useState<null | string>(null)
674
+ // `options` can change at runtime when callers filter the catalog (e.g. the
675
+ // Providers page wiring its search into this grid). Keep the selection valid
676
+ // by snapping back to the first remaining option when the current one drops.
677
+ useEffect(() => {
678
+ if (options.length > 0 && !options.some(o => o.envKey === option.envKey)) {
679
+ setOption(options[0])
680
+ setValue('')
681
+ setLocalKey('')
682
+ setError(null)
683
+ }
684
+ }, [option.envKey, options])
685
+ // The catalog grid can be tall, leaving the entry field far below the fold.
686
+ // On selection we scroll the field into view and focus it so it's always
687
+ // obvious where to paste next.
688
+ const entryRef = useRef<HTMLDivElement>(null)
689
+
690
+ const pick = (o: ApiKeyOption) => {
691
+ setOption(o)
692
+ setValue('')
693
+ setLocalKey('')
694
+ setError(null)
695
+ requestAnimationFrame(() => {
696
+ entryRef.current?.scrollIntoView({ behavior: 'smooth', block: 'center' })
697
+ entryRef.current?.querySelector('input')?.focus()
698
+ })
699
+ }
417
700
 
418
701
  const isLocal = option.envKey === 'OPENAI_BASE_URL'
419
- const canSave = value.trim().length >= (isLocal ? 1 : MIN_KEY_LENGTH)
702
+ const alreadySet = isSet?.(option.envKey) ?? false
703
+ // When set, surface the backend's redacted value (e.g. "sk-12…wxyz") as the
704
+ // placeholder so users can eyeball that the right key is in place.
705
+ const currentRedacted = alreadySet ? (redactedValue?.(option.envKey) ?? null) : null
706
+ // Only require a non-empty value — no length/format validation, so a short
707
+ // or unusual key can't block the user from continuing.
708
+ const canSave = value.trim().length >= 1
709
+ const optionCopy = t.onboarding.apiKeyOptions[option.id]
710
+ const optionDescription = optionCopy?.description ?? option.description
420
711
 
421
712
  const submit = async () => {
422
713
  if (!canSave || saving) {
@@ -425,12 +716,13 @@ function ApiKeyForm({ canGoBack, ctx }: { canGoBack: boolean; ctx: OnboardingCon
425
716
 
426
717
  setSaving(true)
427
718
  setError(null)
428
- const result = await saveOnboardingApiKey(option.envKey, value, option.name, ctx)
719
+ const result = await onSave(option.envKey, value, option.name, isLocal ? localKey : undefined)
429
720
 
430
721
  if (result.ok) {
431
722
  setValue('')
723
+ setLocalKey('')
432
724
  } else {
433
- setError(result.message ?? 'Could not save credential.')
725
+ setError(result.message ?? t.onboarding.couldNotSave)
434
726
  }
435
727
 
436
728
  setSaving(false)
@@ -439,44 +731,44 @@ function ApiKeyForm({ canGoBack, ctx }: { canGoBack: boolean; ctx: OnboardingCon
439
731
  return (
440
732
  <div className="grid gap-4">
441
733
  {canGoBack ? (
442
- <button
443
- className="-mt-1 flex items-center gap-1 self-start text-xs font-medium text-muted-foreground hover:text-foreground"
444
- onClick={() => setOnboardingMode('oauth')}
734
+ <Button
735
+ className="-mt-1 self-start font-medium"
736
+ onClick={onBack}
737
+ size="xs"
445
738
  type="button"
739
+ variant="text"
446
740
  >
447
741
  <ChevronLeft className="size-3" />
448
- Back to sign in
449
- </button>
742
+ {t.onboarding.backToSignIn}
743
+ </Button>
450
744
  ) : null}
451
745
 
452
- <div className="grid gap-2 sm:grid-cols-2">
453
- {API_KEY_OPTIONS.map(o => (
746
+ <div className="grid max-h-[42dvh] gap-2 overflow-y-auto p-1 sm:grid-cols-2">
747
+ {options.map(o => (
454
748
  <button
455
749
  className={cn(
456
750
  'rounded-2xl border bg-background/60 p-3 text-left transition hover:bg-accent/50',
457
- option.id === o.id ? 'border-primary ring-2 ring-primary/20' : 'border-border'
751
+ option.envKey === o.envKey ? 'border-primary ring-2 ring-primary/20' : 'border-transparent'
458
752
  )}
459
- key={o.id}
460
- onClick={() => {
461
- setOption(o)
462
- setValue('')
463
- setError(null)
464
- }}
753
+ key={o.envKey}
754
+ onClick={() => pick(o)}
465
755
  type="button"
466
756
  >
467
757
  <div className="flex items-center justify-between gap-2">
468
758
  <span className="text-sm font-medium">{o.name}</span>
469
- {option.id === o.id ? <Check className="size-4 text-primary" /> : null}
759
+ {isSet?.(o.envKey) ? <Check className="size-3.5 text-muted-foreground" /> : null}
470
760
  </div>
471
- {o.short ? <p className="mt-1 text-xs text-muted-foreground">{o.short}</p> : null}
761
+ {(t.onboarding.apiKeyOptions[o.id]?.short ?? o.short) ? (
762
+ <p className="mt-1 text-xs text-muted-foreground">{t.onboarding.apiKeyOptions[o.id]?.short ?? o.short}</p>
763
+ ) : null}
472
764
  </button>
473
765
  ))}
474
766
  </div>
475
767
 
476
- <div className="grid gap-2">
768
+ <div className="grid scroll-mt-4 gap-2" ref={entryRef}>
477
769
  <div className="flex items-center justify-between gap-3">
478
- <p className="text-sm leading-6 text-muted-foreground">{option.description}</p>
479
- {option.docsUrl ? <DocsLink href={option.docsUrl}>Get a key</DocsLink> : null}
770
+ <p className="text-sm leading-6 text-muted-foreground">{optionDescription}</p>
771
+ {option.docsUrl ? <DocsLink href={option.docsUrl}>{t.onboarding.getKey}</DocsLink> : null}
480
772
  </div>
481
773
  <Input
482
774
  autoComplete="off"
@@ -484,56 +776,86 @@ function ApiKeyForm({ canGoBack, ctx }: { canGoBack: boolean; ctx: OnboardingCon
484
776
  className="font-mono"
485
777
  onChange={e => setValue(e.target.value)}
486
778
  onKeyDown={e => e.key === 'Enter' && void submit()}
487
- placeholder={option.placeholder || 'Paste API key'}
779
+ placeholder={
780
+ currentRedacted ??
781
+ (alreadySet ? t.onboarding.replaceCurrent : option.placeholder || t.onboarding.pasteApiKey)
782
+ }
488
783
  type={isLocal ? 'text' : 'password'}
489
784
  value={value}
490
785
  />
786
+ {isLocal ? (
787
+ <Input
788
+ autoComplete="off"
789
+ className="font-mono"
790
+ onChange={e => setLocalKey(e.target.value)}
791
+ onKeyDown={e => e.key === 'Enter' && void submit()}
792
+ placeholder={t.onboarding.localApiKeyPlaceholder}
793
+ type="password"
794
+ value={localKey}
795
+ />
796
+ ) : null}
491
797
  {error ? <p className="text-xs text-destructive">{error}</p> : null}
492
798
  </div>
493
799
 
494
- <div className="flex justify-end">
800
+ <div className="flex items-center justify-between gap-3">
801
+ <div>
802
+ {alreadySet && onClear ? (
803
+ <Button onClick={() => onClear(option.envKey)} size="sm" variant="ghost">
804
+ {t.common.remove}
805
+ </Button>
806
+ ) : null}
807
+ </div>
495
808
  <Button disabled={!canSave || saving} onClick={() => void submit()}>
496
- {saving ? <Loader2 className="size-4 animate-spin" /> : <KeyRound className="size-4" />}
497
- {saving ? 'Connecting' : 'Connect'}
809
+ {saving ? <Loader2 className="animate-spin" /> : <KeyRound />}
810
+ {saving ? t.onboarding.connecting : alreadySet ? t.onboarding.update : t.common.connect}
498
811
  </Button>
499
812
  </div>
500
813
  </div>
501
814
  )
502
815
  }
503
816
 
504
- function FlowPanel({ ctx, flow }: { ctx: OnboardingContext; flow: OnboardingFlow }) {
817
+ function FlowPanel({
818
+ ctx,
819
+ flow,
820
+ leaving,
821
+ onBegin
822
+ }: {
823
+ ctx: OnboardingContext
824
+ flow: OnboardingFlow
825
+ leaving: boolean
826
+ onBegin: () => void
827
+ }) {
828
+ const { t } = useI18n()
505
829
  const title = 'provider' in flow && flow.provider ? providerTitle(flow.provider) : ''
506
830
 
507
831
  if (flow.status === 'starting') {
508
- return <Status>Starting sign-in for {title}...</Status>
832
+ return <Status>{t.onboarding.startingSignIn(title)}</Status>
509
833
  }
510
834
 
511
835
  if (flow.status === 'submitting') {
512
- return <Status>Verifying your code with {title}...</Status>
836
+ return <Status>{t.onboarding.verifyingCode(title)}</Status>
513
837
  }
514
838
 
515
839
  if (flow.status === 'success') {
516
840
  return (
517
- <div className="flex items-center gap-2 rounded-2xl border border-primary/30 bg-primary/10 px-4 py-3 text-sm text-primary">
518
- <Check className="size-4" />
519
- {title} connected. Picking a default model...
520
- </div>
841
+ <DecodedLabel text={t.onboarding.connectedPicking(title)} />
521
842
  )
522
843
  }
523
844
 
524
845
  if (flow.status === 'confirming_model') {
525
- return <ConfirmingModelPanel ctx={ctx} flow={flow} />
846
+ return <ConfirmingModelPanel flow={flow} leaving={leaving} onBegin={onBegin} />
526
847
  }
527
848
 
528
849
  if (flow.status === 'error') {
529
850
  return (
530
851
  <div className="grid gap-3">
531
- <div className="rounded-2xl border border-destructive/30 bg-destructive/10 px-4 py-3 text-sm text-destructive">
532
- {flow.message || 'Sign-in failed. Try again.'}
852
+ <div className="flex items-center gap-1.5 text-sm text-destructive">
853
+ <ErrorIcon className="shrink-0" size="0.875rem" />
854
+ <span>{flow.message || t.onboarding.signInFailed}</span>
533
855
  </div>
534
856
  <div className="flex justify-end">
535
857
  <Button onClick={cancelOnboardingFlow} variant="outline">
536
- Pick a different provider
858
+ {t.onboarding.pickDifferentProvider}
537
859
  </Button>
538
860
  </div>
539
861
  </div>
@@ -542,45 +864,58 @@ function FlowPanel({ ctx, flow }: { ctx: OnboardingContext; flow: OnboardingFlow
542
864
 
543
865
  if (flow.status === 'awaiting_user') {
544
866
  return (
545
- <Step title={`Sign in with ${title}`}>
867
+ <Step title={t.onboarding.signInWith(title)}>
546
868
  <ol className="list-decimal space-y-1 pl-5 text-sm text-muted-foreground">
547
- <li>We opened {title} in your browser.</li>
548
- <li>Authorize Hermes there.</li>
549
- <li>Copy the authorization code and paste it below.</li>
869
+ <li>{t.onboarding.openedBrowser(title)}</li>
870
+ <li>{t.onboarding.authorizeThere}</li>
871
+ <li>{t.onboarding.copyAuthCode}</li>
550
872
  </ol>
551
873
  <Input
552
874
  autoFocus
553
875
  onChange={e => setOnboardingCode(e.target.value)}
554
876
  onKeyDown={e => e.key === 'Enter' && void submitOnboardingCode(ctx)}
555
- placeholder="Paste authorization code"
877
+ placeholder={t.onboarding.pasteAuthCode}
556
878
  value={flow.code}
557
879
  />
558
- <FlowFooter left={<DocsLink href={flow.start.auth_url}>Re-open authorization page</DocsLink>}>
880
+ <FlowFooter left={<DocsLink href={flow.start.auth_url}>{t.onboarding.reopenAuthPage}</DocsLink>}>
559
881
  <CancelBtn />
560
882
  <Button disabled={!flow.code.trim()} onClick={() => void submitOnboardingCode(ctx)}>
561
- Continue
883
+ {t.common.continue}
562
884
  </Button>
563
885
  </FlowFooter>
564
886
  </Step>
565
887
  )
566
888
  }
567
889
 
890
+ if (flow.status === 'awaiting_browser') {
891
+ return (
892
+ <Step title={t.onboarding.signInWith(title)}>
893
+ <p className="text-sm text-muted-foreground">{t.onboarding.autoBrowser(title)}</p>
894
+ <FlowFooter left={<DocsLink href={flow.start.auth_url}>{t.onboarding.reopenSignInPage}</DocsLink>}>
895
+ <span className="flex items-center gap-2 text-xs text-muted-foreground">
896
+ <Loader2 className="size-3 animate-spin" />
897
+ {t.onboarding.waitingAuthorize}
898
+ </span>
899
+ <CancelBtn size="sm" />
900
+ </FlowFooter>
901
+ </Step>
902
+ )
903
+ }
904
+
568
905
  if (flow.status === 'external_pending') {
569
906
  return (
570
- <Step title={`Sign in with ${title}`}>
571
- <p className="text-sm text-muted-foreground">
572
- {title} signs in through its own CLI. Run this command in a terminal, then come back and pick "I've signed
573
- in":
574
- </p>
907
+ <Step title={t.onboarding.signInWith(title)}>
908
+ <p className="text-sm text-muted-foreground">{t.onboarding.externalPending(title)}</p>
575
909
  <CodeBlock copied={flow.copied} onCopy={() => void copyExternalCommand()} text={flow.provider.cli_command} />
576
910
  <FlowFooter
577
- left={flow.provider.docs_url ? <DocsLink href={flow.provider.docs_url}>{title} docs</DocsLink> : null}
911
+ left={
912
+ flow.provider.docs_url ? (
913
+ <DocsLink href={flow.provider.docs_url}>{t.onboarding.docs(title)}</DocsLink>
914
+ ) : null
915
+ }
578
916
  >
579
917
  <CancelBtn />
580
- <Button onClick={() => void recheckExternalSignin(ctx)}>
581
- <Check className="size-4" />
582
- I've signed in
583
- </Button>
918
+ <Button onClick={() => void recheckExternalSignin(ctx)}>{t.onboarding.signedIn}</Button>
584
919
  </FlowFooter>
585
920
  </Step>
586
921
  )
@@ -591,13 +926,13 @@ function FlowPanel({ ctx, flow }: { ctx: OnboardingContext; flow: OnboardingFlow
591
926
  }
592
927
 
593
928
  return (
594
- <Step title={`Sign in with ${title}`}>
595
- <p className="text-sm text-muted-foreground">We opened {title} in your browser. Enter this code there:</p>
596
- <CodeBlock copied={flow.copied} large onCopy={() => void copyDeviceCode()} text={flow.start.user_code} />
597
- <FlowFooter left={<DocsLink href={flow.start.verification_url}>Re-open verification page</DocsLink>}>
929
+ <Step title={t.onboarding.signInWith(title)}>
930
+ <p className="text-sm text-muted-foreground">{t.onboarding.deviceCodeOpened(title)}</p>
931
+ <DeviceCode code={flow.start.user_code} copied={flow.copied} onCopy={() => void copyDeviceCode()} />
932
+ <FlowFooter left={<DocsLink href={flow.start.verification_url}>{t.onboarding.reopenVerification}</DocsLink>}>
598
933
  <span className="flex items-center gap-2 text-xs text-muted-foreground">
599
934
  <Loader2 className="size-3 animate-spin" />
600
- Waiting for you to authorize...
935
+ {t.onboarding.waitingAuthorize}
601
936
  </span>
602
937
  <CancelBtn size="sm" />
603
938
  </FlowFooter>
@@ -614,22 +949,53 @@ function Step({ children, title }: { children: React.ReactNode; title: string })
614
949
  )
615
950
  }
616
951
 
617
- function CodeBlock({
618
- copied,
619
- large,
620
- onCopy,
621
- text
622
- }: {
623
- copied: boolean
624
- large?: boolean
625
- onCopy: () => void
626
- text: string
627
- }) {
952
+ // Device-code display: OTP-style — each character in its own readonly cell.
953
+ // The whole row is the copy button (no side button, no checkmark); on copy the
954
+ // cells flash emerald for feedback. Dashes render as quiet separators.
955
+ function DeviceCode({ code, copied, onCopy }: { code: string; copied: boolean; onCopy: () => void }) {
956
+ const { t } = useI18n()
957
+
628
958
  return (
629
- <div className="flex items-center justify-between gap-3 rounded-2xl border border-border bg-secondary/30 px-4 py-3">
630
- <code className={cn('font-mono', large ? 'text-2xl tracking-[0.4em]' : 'text-sm')}>{text}</code>
959
+ <button
960
+ aria-label={t.onboarding.copy}
961
+ className="group flex w-full items-center justify-center gap-1.5"
962
+ onClick={onCopy}
963
+ type="button"
964
+ >
965
+ {[...code].map((ch, i) =>
966
+ ch === '-' || ch === ' ' ? (
967
+ <span className="w-1.5 text-center text-lg text-muted-foreground" key={i}>
968
+
969
+ </span>
970
+ ) : (
971
+ <span
972
+ className={cn(
973
+ 'flex size-10 items-center justify-center rounded-md border font-mono text-xl font-semibold uppercase transition-colors',
974
+ copied
975
+ ? 'border-primary/50 text-primary'
976
+ : 'border-(--stroke-nous) text-foreground group-hover:border-(--ui-stroke-secondary)'
977
+ )}
978
+ key={i}
979
+ >
980
+ {ch}
981
+ </span>
982
+ )
983
+ )}
984
+ </button>
985
+ )
986
+ }
987
+
988
+ function CodeBlock({ copied, onCopy, text }: { copied: boolean; onCopy: () => void; text: string }) {
989
+ const { t } = useI18n()
990
+
991
+ return (
992
+ <div className="flex items-center justify-between gap-3 rounded-md border border-(--stroke-nous) px-3 py-2">
993
+ <code className="min-w-0 flex-1 truncate font-mono text-sm">
994
+ <span className="mr-2 select-none text-muted-foreground">$</span>
995
+ {text}
996
+ </code>
631
997
  <Button onClick={onCopy} size="sm" variant="outline">
632
- {copied ? <Check className="size-4" /> : 'Copy'}
998
+ {copied ? t.common.copied : t.onboarding.copy}
633
999
  </Button>
634
1000
  </div>
635
1001
  )
@@ -645,20 +1011,193 @@ function FlowFooter({ children, left }: { children: React.ReactNode; left?: Reac
645
1011
  }
646
1012
 
647
1013
  function CancelBtn({ size = 'default' }: { size?: 'default' | 'sm' }) {
1014
+ const { t } = useI18n()
1015
+
648
1016
  return (
649
1017
  <Button onClick={cancelOnboardingFlow} size={size} variant="ghost">
650
- Cancel
1018
+ {t.common.cancel}
651
1019
  </Button>
652
1020
  )
653
1021
  }
654
1022
 
1023
+ // Borrowed from the gateway "connecting" overlay: a mono, letter-spaced label
1024
+ // that decodes left-to-right from scrambled glyphs into the real text, with a
1025
+ // blinking block cursor. Ties onboarding's success moment to that same motif.
1026
+ // Cuneiform glyphs (array, since each is a surrogate pair) for the scramble.
1027
+ // Hero "X CONNECTED" decode uses the SAME ascii map as the connecting overlay.
1028
+ const ASCII_GLYPHS = [...'/\\|-_=+<>~:*']
1029
+ const pickAscii = () => ASCII_GLYPHS[(Math.random() * ASCII_GLYPHS.length) | 0]
1030
+ // Cuneiform is reserved for the subtle "other text" (model name + BEGIN) easter egg.
1031
+ const SCRAMBLE_GLYPHS = [...'𒀀𒀁𒀂𒀅𒀊𒀖𒀜𒀭𒀲𒀸𒁀𒁉𒁒𒁕𒁹𒂊𒃻𒄆𒄴𒅀𒆍𒇽𒈨𒉡']
1032
+ const GLYPH_SET = new Set(SCRAMBLE_GLYPHS)
1033
+ const pickGlyph = () => SCRAMBLE_GLYPHS[(Math.random() * SCRAMBLE_GLYPHS.length) | 0]
1034
+ // How many trailing characters of each word scramble during decode-in.
1035
+ const DECODE_TAIL = 4
1036
+
1037
+ // Renders text where cuneiform scramble-glyphs are dropped to a smaller em-size
1038
+ // (resolved Latin chars stay full size) — keeps the easter-egg glyphs subtle.
1039
+ function GlyphText({ text }: { text: string }) {
1040
+ return (
1041
+ <>
1042
+ {Array.from(text, (ch, i) =>
1043
+ GLYPH_SET.has(ch) ? (
1044
+ <span className="text-[0.62em]" key={i}>
1045
+ {ch}
1046
+ </span>
1047
+ ) : (
1048
+ ch
1049
+ )
1050
+ )}
1051
+ </>
1052
+ )
1053
+ }
1054
+
1055
+ function useDecoded(text: string): string {
1056
+ const [out, setOut] = useState(text)
1057
+
1058
+ useEffect(() => {
1059
+ if (typeof window !== 'undefined' && window.matchMedia?.('(prefers-reduced-motion: reduce)').matches) {
1060
+ setOut(text)
1061
+
1062
+ return
1063
+ }
1064
+
1065
+ // Each WORD keeps its head static and only churns its tail (last few chars),
1066
+ // resolving left-to-right across all tails — same anchor-the-prefix trick the
1067
+ // connecting overlay uses ("CONN" static, "ECTING" churns), applied per word
1068
+ // so both the provider and "CONNECTED" decode and time stays constant.
1069
+ const chars = [...text]
1070
+ const scrambleable = chars.map(() => false)
1071
+
1072
+ for (let i = 0; i < chars.length; ) {
1073
+ if (!/[a-z0-9]/i.test(chars[i])) {
1074
+ i += 1
1075
+
1076
+ continue
1077
+ }
1078
+
1079
+ let j = i
1080
+
1081
+ while (j < chars.length && /[a-z0-9]/i.test(chars[j])) {
1082
+ j += 1
1083
+ }
1084
+
1085
+ for (let k = Math.max(i, j - DECODE_TAIL); k < j; k += 1) {
1086
+ scrambleable[k] = true
1087
+ }
1088
+
1089
+ i = j
1090
+ }
1091
+
1092
+ const tailIndices = chars.map((_, idx) => idx).filter(idx => scrambleable[idx])
1093
+ let resolved = 0
1094
+
1095
+ const id = window.setInterval(() => {
1096
+ resolved += 0.5
1097
+ const settled = new Set(tailIndices.slice(0, Math.floor(resolved)))
1098
+
1099
+ setOut(chars.map((ch, idx) => (scrambleable[idx] && !settled.has(idx) ? pickAscii() : ch)).join(''))
1100
+
1101
+ if (Math.floor(resolved) >= tailIndices.length) {
1102
+ window.clearInterval(id)
1103
+ }
1104
+ }, 45)
1105
+
1106
+ return () => window.clearInterval(id)
1107
+ }, [text])
1108
+
1109
+ return out
1110
+ }
1111
+
1112
+ // Continuously scrambles alphanumeric chars while `active` (used on exit so the
1113
+ // model name / button decay into ascii noise as they fade).
1114
+ function useScramble(text: string, active: boolean): string {
1115
+ const [out, setOut] = useState(text)
1116
+
1117
+ useEffect(() => {
1118
+ if (!active) {
1119
+ setOut(text)
1120
+
1121
+ return
1122
+ }
1123
+
1124
+ const id = window.setInterval(() => {
1125
+ setOut(Array.from(text, ch => (/[a-z0-9]/i.test(ch) ? pickGlyph() : ch)).join(''))
1126
+ }, 45)
1127
+
1128
+ return () => window.clearInterval(id)
1129
+ }, [text, active])
1130
+
1131
+ return out
1132
+ }
1133
+
1134
+ function DecodedLabel({ leaving, text }: { leaving?: boolean; text: string }) {
1135
+ const decoded = useDecoded(text.toUpperCase())
1136
+
1137
+ return (
1138
+ <span
1139
+ className={cn(
1140
+ 'inline-flex items-center font-mono text-xs font-semibold uppercase tracking-[0.28em] tabular-nums text-primary transition duration-[360ms] ease-out',
1141
+ leaving ? 'translate-y-2 opacity-0 saturate-0' : 'translate-y-0 opacity-100 saturate-100'
1142
+ )}
1143
+ >
1144
+ <GlyphText text={decoded} />
1145
+ <span
1146
+ aria-hidden="true"
1147
+ className="dither ml-1.5 -mr-[0.875rem] inline-block size-2 shrink-0 -translate-y-px rounded-[1px] text-primary"
1148
+ style={{ animation: 'ob-decode-cursor 1s step-end infinite' }}
1149
+ />
1150
+ <style>{'@keyframes ob-decode-cursor { 0%, 49% { opacity: 1 } 50%, 100% { opacity: 0 } }'}</style>
1151
+ </span>
1152
+ )
1153
+ }
1154
+
1155
+ // Terminal-flavored CTA to match the connecting overlay's hacker aesthetic:
1156
+ // mono, uppercase, letter-spaced, wrapped in primary brackets that light up on
1157
+ // hover. The whole onboarding "you're in" moment leans into this motif.
1158
+ function HackeryButton({
1159
+ disabled,
1160
+ label,
1161
+ loading,
1162
+ onClick
1163
+ }: {
1164
+ disabled?: boolean
1165
+ label: React.ReactNode
1166
+ loading?: boolean
1167
+ onClick: () => void
1168
+ }) {
1169
+ return (
1170
+ <button
1171
+ className={cn(
1172
+ 'group inline-flex items-center gap-2 rounded-md border border-(--stroke-nous) px-6 py-2.5',
1173
+ 'font-mono text-xs font-semibold uppercase text-primary',
1174
+ 'transition-all duration-150 hover:border-primary/60 hover:bg-primary/[0.06]',
1175
+ 'disabled:pointer-events-none disabled:opacity-50'
1176
+ )}
1177
+ disabled={disabled}
1178
+ onClick={onClick}
1179
+ type="button"
1180
+ >
1181
+ <span className="text-primary/40 transition-colors group-hover:text-primary">[</span>
1182
+ {loading ? <Loader2 className="size-3 animate-spin" /> : null}
1183
+ <span className="-mr-[0.25em] pl-[0.25em] tracking-[0.25em]">{label}</span>
1184
+ <span className="text-primary/40 transition-colors group-hover:text-primary">]</span>
1185
+ </button>
1186
+ )
1187
+ }
1188
+
655
1189
  function ConfirmingModelPanel({
656
- ctx,
657
- flow
1190
+ flow,
1191
+ leaving,
1192
+ onBegin
658
1193
  }: {
659
- ctx: OnboardingContext
660
1194
  flow: Extract<OnboardingFlow, { status: 'confirming_model' }>
1195
+ leaving: boolean
1196
+ onBegin: () => void
661
1197
  }) {
1198
+ const { t } = useI18n()
1199
+ const scrambledModel = useScramble(flow.currentModel, leaving)
1200
+ const scrambledBegin = useScramble(t.onboarding.startChatting, leaving)
662
1201
  // Local state controls whether the model picker dialog is open.
663
1202
  // We reuse the existing ModelPickerDialog component (the same picker
664
1203
  // available from the chat shell) rather than building an inline
@@ -672,53 +1211,70 @@ function ConfirmingModelPanel({
672
1211
  queryKey: ['onboarding-model-options', flow.providerSlug],
673
1212
  queryFn: () => getGlobalModelOptions()
674
1213
  })
1214
+
675
1215
  const providerRow = options.data?.providers?.find(
676
1216
  p => String(p.slug).toLowerCase() === flow.providerSlug.toLowerCase()
677
1217
  )
1218
+
678
1219
  const price = providerRow?.pricing?.[flow.currentModel]
679
1220
  const freeTier = providerRow?.free_tier
680
1221
 
681
1222
  return (
682
- <div className="grid gap-4">
683
- <div className="flex items-center gap-2 rounded-2xl border border-primary/30 bg-primary/10 px-4 py-3 text-sm text-primary">
684
- <Check className="size-4 shrink-0" />
685
- <span>{flow.label} connected.</span>
686
- </div>
687
-
688
- <div className="grid gap-3 rounded-2xl border border-border bg-background/60 p-4">
689
- <div className="flex flex-wrap items-center justify-between gap-3">
690
- <div className="min-w-0">
691
- <div className="flex items-center gap-2">
692
- <p className="text-xs uppercase tracking-wide text-muted-foreground">Default model</p>
693
- {freeTier === true && (
694
- <span className="rounded-sm bg-emerald-500/15 px-1 py-0.5 text-[0.6rem] font-semibold uppercase tracking-wide text-emerald-600 dark:text-emerald-400">
695
- Free tier
696
- </span>
697
- )}
698
- {freeTier === false && (
699
- <span className="rounded-sm bg-primary/15 px-1 py-0.5 text-[0.6rem] font-semibold uppercase tracking-wide text-primary">
700
- Pro
701
- </span>
702
- )}
703
- </div>
704
- <p className="mt-1 truncate font-mono text-sm">{flow.currentModel}</p>
705
- {price && (price.input || price.output) && (
706
- <p className="mt-1 font-mono text-xs text-muted-foreground">
707
- {price.free ? 'Free' : `${price.input || '?'} in / ${price.output || '?'} out per Mtok`}
708
- </p>
709
- )}
710
- </div>
711
- <Button disabled={flow.saving} onClick={() => setPickerOpen(true)} size="sm" variant="outline">
712
- Change
713
- </Button>
1223
+ <div className="grid place-items-center gap-7 py-6 text-center">
1224
+ <DecodedLabel leaving={leaving} text={t.onboarding.connectedProvider(flow.label)} />
1225
+
1226
+ <div
1227
+ className={cn(
1228
+ 'grid justify-items-center gap-1.5 transition duration-[360ms] ease-out',
1229
+ leaving ? 'opacity-0 saturate-0' : 'opacity-100 saturate-100'
1230
+ )}
1231
+ >
1232
+ <div className="flex items-center gap-2">
1233
+ <span className="font-mono text-[0.625rem] uppercase tracking-[0.2em] text-muted-foreground">
1234
+ {t.onboarding.defaultModel}
1235
+ </span>
1236
+ {freeTier === true && (
1237
+ <span className="rounded-sm bg-emerald-500/15 px-1 py-0.5 text-[0.6rem] font-semibold uppercase tracking-wide text-emerald-600 dark:text-emerald-400">
1238
+ {t.onboarding.freeTier}
1239
+ </span>
1240
+ )}
1241
+ {freeTier === false && (
1242
+ <span className="rounded-sm bg-primary/15 px-1 py-0.5 text-[0.6rem] font-semibold uppercase tracking-wide text-primary">
1243
+ {t.onboarding.pro}
1244
+ </span>
1245
+ )}
714
1246
  </div>
1247
+ <p className="font-mono text-base">
1248
+ <GlyphText text={scrambledModel} />
1249
+ </p>
1250
+ {price && (price.input || price.output) && (
1251
+ <p className="font-mono text-xs text-muted-foreground">
1252
+ {price.free ? t.onboarding.free : t.onboarding.price(price.input || '?', price.output || '?')}
1253
+ </p>
1254
+ )}
1255
+ <Button
1256
+ className="mt-0.5 text-xs"
1257
+ disabled={flow.saving}
1258
+ onClick={() => setPickerOpen(true)}
1259
+ size="inline"
1260
+ variant="text"
1261
+ >
1262
+ {t.onboarding.change}
1263
+ </Button>
715
1264
  </div>
716
1265
 
717
- <div className="flex justify-end">
718
- <Button disabled={flow.saving} onClick={() => confirmOnboardingModel(ctx)}>
719
- {flow.saving ? <Loader2 className="size-4 animate-spin" /> : <Sparkles className="size-4" />}
720
- Start chatting
721
- </Button>
1266
+ <div
1267
+ className={cn(
1268
+ 'transition duration-[360ms] ease-out',
1269
+ leaving ? 'opacity-0 saturate-0' : 'opacity-100 saturate-100'
1270
+ )}
1271
+ >
1272
+ <HackeryButton
1273
+ disabled={flow.saving}
1274
+ label={<GlyphText text={scrambledBegin} />}
1275
+ loading={flow.saving}
1276
+ onClick={onBegin}
1277
+ />
722
1278
  </div>
723
1279
 
724
1280
  {/*
@@ -746,7 +1302,7 @@ function ConfirmingModelPanel({
746
1302
 
747
1303
  function DocsLink({ children, href }: { children: React.ReactNode; href: string }) {
748
1304
  return (
749
- <Button asChild size="xs" variant="ghost">
1305
+ <Button asChild size="xs" variant="text">
750
1306
  <a href={href} rel="noreferrer" target="_blank">
751
1307
  <ExternalLink className="size-3" />
752
1308
  {children}
@@ -757,8 +1313,8 @@ function DocsLink({ children, href }: { children: React.ReactNode; href: string
757
1313
 
758
1314
  function Status({ children }: { children: React.ReactNode }) {
759
1315
  return (
760
- <div className="flex items-center gap-3 rounded-2xl bg-muted/30 px-4 py-6 text-sm text-muted-foreground">
761
- <Loader2 className="size-4 animate-spin" />
1316
+ <div className="flex items-center gap-2.5 py-1 text-sm text-muted-foreground" role="status">
1317
+ <Loader className="size-7" type="lemniscate-bloom" />
762
1318
  {children}
763
1319
  </div>
764
1320
  )