cerebras-cli 1.0.5 → 1.0.138

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 (322) hide show
  1. package/AGENTS.md +27 -0
  2. package/Dockerfile +10 -0
  3. package/README.md +5 -3
  4. package/bin/{opencode.cjs → opencode} +4 -4
  5. package/bunfig.toml +4 -0
  6. package/package.json +89 -32
  7. package/parsers-config.ts +239 -0
  8. package/script/build.ts +151 -0
  9. package/script/postinstall.mjs +122 -0
  10. package/script/publish.ts +256 -0
  11. package/script/schema.ts +47 -0
  12. package/snake_game.py +111 -0
  13. package/src/acp/README.md +164 -0
  14. package/src/acp/agent.ts +812 -0
  15. package/src/acp/session.ts +70 -0
  16. package/src/acp/types.ts +22 -0
  17. package/src/agent/agent.ts +310 -0
  18. package/src/agent/generate.txt +75 -0
  19. package/src/auth/index.ts +70 -0
  20. package/src/bun/index.ts +152 -0
  21. package/src/bus/global.ts +10 -0
  22. package/src/bus/index.ts +142 -0
  23. package/src/cli/bootstrap.ts +17 -0
  24. package/src/cli/cmd/acp.ts +88 -0
  25. package/src/cli/cmd/agent.ts +165 -0
  26. package/src/cli/cmd/auth.ts +369 -0
  27. package/src/cli/cmd/cmd.ts +7 -0
  28. package/src/cli/cmd/debug/config.ts +15 -0
  29. package/src/cli/cmd/debug/file.ts +91 -0
  30. package/src/cli/cmd/debug/index.ts +43 -0
  31. package/src/cli/cmd/debug/lsp.ts +47 -0
  32. package/src/cli/cmd/debug/ripgrep.ts +83 -0
  33. package/src/cli/cmd/debug/scrap.ts +15 -0
  34. package/src/cli/cmd/debug/skill.ts +36 -0
  35. package/src/cli/cmd/debug/snapshot.ts +48 -0
  36. package/src/cli/cmd/export.ts +88 -0
  37. package/src/cli/cmd/generate.ts +38 -0
  38. package/src/cli/cmd/github.ts +1200 -0
  39. package/src/cli/cmd/import.ts +98 -0
  40. package/src/cli/cmd/mcp.ts +400 -0
  41. package/src/cli/cmd/models.ts +77 -0
  42. package/src/cli/cmd/pr.ts +112 -0
  43. package/src/cli/cmd/run.ts +342 -0
  44. package/src/cli/cmd/serve.ts +31 -0
  45. package/src/cli/cmd/session.ts +106 -0
  46. package/src/cli/cmd/stats.ts +298 -0
  47. package/src/cli/cmd/tui/app.tsx +833 -0
  48. package/src/cli/cmd/tui/attach.ts +25 -0
  49. package/src/cli/cmd/tui/component/border.tsx +21 -0
  50. package/src/cli/cmd/tui/component/cerebras-onboarding.tsx +225 -0
  51. package/src/cli/cmd/tui/component/dialog-agent.tsx +31 -0
  52. package/src/cli/cmd/tui/component/dialog-command.tsx +124 -0
  53. package/src/cli/cmd/tui/component/dialog-feedback.tsx +160 -0
  54. package/src/cli/cmd/tui/component/dialog-mcp.tsx +86 -0
  55. package/src/cli/cmd/tui/component/dialog-model.tsx +223 -0
  56. package/src/cli/cmd/tui/component/dialog-notification.tsx +78 -0
  57. package/src/cli/cmd/tui/component/dialog-provider.tsx +222 -0
  58. package/src/cli/cmd/tui/component/dialog-session-list.tsx +97 -0
  59. package/src/cli/cmd/tui/component/dialog-session-rename.tsx +31 -0
  60. package/src/cli/cmd/tui/component/dialog-status.tsx +114 -0
  61. package/src/cli/cmd/tui/component/dialog-tag.tsx +44 -0
  62. package/src/cli/cmd/tui/component/dialog-theme-list.tsx +50 -0
  63. package/src/cli/cmd/tui/component/logo.tsx +43 -0
  64. package/src/cli/cmd/tui/component/notification-banner.tsx +58 -0
  65. package/src/cli/cmd/tui/component/prompt/autocomplete.tsx +530 -0
  66. package/src/cli/cmd/tui/component/prompt/history.tsx +107 -0
  67. package/src/cli/cmd/tui/component/prompt/index.tsx +931 -0
  68. package/src/cli/cmd/tui/component/quickstart-onboarding.tsx +116 -0
  69. package/src/cli/cmd/tui/context/args.tsx +14 -0
  70. package/src/cli/cmd/tui/context/directory.ts +12 -0
  71. package/src/cli/cmd/tui/context/exit.tsx +23 -0
  72. package/src/cli/cmd/tui/context/helper.tsx +25 -0
  73. package/src/cli/cmd/tui/context/keybind.tsx +111 -0
  74. package/src/cli/cmd/tui/context/kv.tsx +49 -0
  75. package/src/cli/cmd/tui/context/local.tsx +338 -0
  76. package/src/cli/cmd/tui/context/prompt.tsx +18 -0
  77. package/src/cli/cmd/tui/context/route.tsx +45 -0
  78. package/src/cli/cmd/tui/context/sdk.tsx +75 -0
  79. package/src/cli/cmd/tui/context/sync.tsx +374 -0
  80. package/src/cli/cmd/tui/context/theme/aura.json +69 -0
  81. package/src/cli/cmd/tui/context/theme/ayu.json +80 -0
  82. package/src/cli/cmd/tui/context/theme/catppuccin-macchiato.json +233 -0
  83. package/src/cli/cmd/tui/context/theme/catppuccin.json +112 -0
  84. package/src/cli/cmd/tui/context/theme/cobalt2.json +228 -0
  85. package/src/cli/cmd/tui/context/theme/dracula.json +219 -0
  86. package/src/cli/cmd/tui/context/theme/everforest.json +241 -0
  87. package/src/cli/cmd/tui/context/theme/flexoki.json +237 -0
  88. package/src/cli/cmd/tui/context/theme/github.json +233 -0
  89. package/src/cli/cmd/tui/context/theme/gruvbox.json +95 -0
  90. package/src/cli/cmd/tui/context/theme/kanagawa.json +77 -0
  91. package/src/cli/cmd/tui/context/theme/material.json +235 -0
  92. package/src/cli/cmd/tui/context/theme/matrix.json +77 -0
  93. package/src/cli/cmd/tui/context/theme/mercury.json +252 -0
  94. package/src/cli/cmd/tui/context/theme/monokai.json +221 -0
  95. package/src/cli/cmd/tui/context/theme/nightowl.json +221 -0
  96. package/src/cli/cmd/tui/context/theme/nord.json +223 -0
  97. package/src/cli/cmd/tui/context/theme/one-dark.json +84 -0
  98. package/src/cli/cmd/tui/context/theme/orng.json +245 -0
  99. package/src/cli/cmd/tui/context/theme/palenight.json +222 -0
  100. package/src/cli/cmd/tui/context/theme/rosepine.json +234 -0
  101. package/src/cli/cmd/tui/context/theme/solarized.json +223 -0
  102. package/src/cli/cmd/tui/context/theme/synthwave84.json +226 -0
  103. package/src/cli/cmd/tui/context/theme/tokyonight.json +243 -0
  104. package/src/cli/cmd/tui/context/theme/vercel.json +245 -0
  105. package/src/cli/cmd/tui/context/theme/vesper.json +218 -0
  106. package/src/cli/cmd/tui/context/theme/zenburn.json +223 -0
  107. package/src/cli/cmd/tui/context/theme.tsx +1077 -0
  108. package/src/cli/cmd/tui/event.ts +39 -0
  109. package/src/cli/cmd/tui/routes/home.tsx +150 -0
  110. package/src/cli/cmd/tui/routes/session/dialog-message.tsx +93 -0
  111. package/src/cli/cmd/tui/routes/session/dialog-timeline.tsx +37 -0
  112. package/src/cli/cmd/tui/routes/session/footer.tsx +76 -0
  113. package/src/cli/cmd/tui/routes/session/header.tsx +181 -0
  114. package/src/cli/cmd/tui/routes/session/index.tsx +1695 -0
  115. package/src/cli/cmd/tui/routes/session/sidebar.tsx +686 -0
  116. package/src/cli/cmd/tui/spawn.ts +60 -0
  117. package/src/cli/cmd/tui/thread.ts +120 -0
  118. package/src/cli/cmd/tui/ui/dialog-alert.tsx +55 -0
  119. package/src/cli/cmd/tui/ui/dialog-confirm.tsx +81 -0
  120. package/src/cli/cmd/tui/ui/dialog-help.tsx +36 -0
  121. package/src/cli/cmd/tui/ui/dialog-prompt.tsx +75 -0
  122. package/src/cli/cmd/tui/ui/dialog-select.tsx +317 -0
  123. package/src/cli/cmd/tui/ui/dialog.tsx +170 -0
  124. package/src/cli/cmd/tui/ui/spinner.ts +368 -0
  125. package/src/cli/cmd/tui/ui/toast.tsx +100 -0
  126. package/src/cli/cmd/tui/util/clipboard.ts +127 -0
  127. package/src/cli/cmd/tui/util/editor.ts +32 -0
  128. package/src/cli/cmd/tui/util/terminal.ts +114 -0
  129. package/src/cli/cmd/tui/worker.ts +63 -0
  130. package/src/cli/cmd/uninstall.ts +344 -0
  131. package/src/cli/cmd/upgrade.ts +67 -0
  132. package/src/cli/cmd/web.ts +84 -0
  133. package/src/cli/error.ts +55 -0
  134. package/src/cli/ui.ts +84 -0
  135. package/src/cli/upgrade.ts +25 -0
  136. package/src/command/index.ts +79 -0
  137. package/src/command/template/initialize.txt +10 -0
  138. package/src/command/template/review.txt +73 -0
  139. package/src/config/config.ts +886 -0
  140. package/src/config/markdown.ts +41 -0
  141. package/src/env/index.ts +26 -0
  142. package/src/file/fzf.ts +124 -0
  143. package/src/file/ignore.ts +83 -0
  144. package/src/file/index.ts +326 -0
  145. package/src/file/ripgrep.ts +391 -0
  146. package/src/file/time.ts +38 -0
  147. package/src/file/watcher.ts +89 -0
  148. package/src/flag/flag.ts +29 -0
  149. package/src/format/formatter.ts +277 -0
  150. package/src/format/index.ts +137 -0
  151. package/src/global/index.ts +52 -0
  152. package/src/id/id.ts +73 -0
  153. package/src/ide/index.ts +75 -0
  154. package/src/index.ts +158 -0
  155. package/src/installation/index.ts +194 -0
  156. package/src/lsp/client.ts +215 -0
  157. package/src/lsp/index.ts +370 -0
  158. package/src/lsp/language.ts +111 -0
  159. package/src/lsp/server.ts +1327 -0
  160. package/src/mcp/auth.ts +82 -0
  161. package/src/mcp/index.ts +576 -0
  162. package/src/mcp/oauth-callback.ts +203 -0
  163. package/src/mcp/oauth-provider.ts +132 -0
  164. package/src/notification/index.ts +101 -0
  165. package/src/patch/index.ts +622 -0
  166. package/src/permission/index.ts +198 -0
  167. package/src/plugin/index.ts +95 -0
  168. package/src/project/bootstrap.ts +31 -0
  169. package/src/project/instance.ts +68 -0
  170. package/src/project/project.ts +133 -0
  171. package/src/project/state.ts +65 -0
  172. package/src/project/vcs.ts +77 -0
  173. package/src/provider/auth.ts +143 -0
  174. package/src/provider/models-macro.ts +11 -0
  175. package/src/provider/models.ts +93 -0
  176. package/src/provider/provider.ts +1005 -0
  177. package/src/provider/sdk/openai-compatible/src/README.md +5 -0
  178. package/src/provider/sdk/openai-compatible/src/index.ts +2 -0
  179. package/src/provider/sdk/openai-compatible/src/openai-compatible-provider.ts +100 -0
  180. package/src/provider/sdk/openai-compatible/src/responses/convert-to-openai-responses-input.ts +303 -0
  181. package/src/provider/sdk/openai-compatible/src/responses/map-openai-responses-finish-reason.ts +27 -0
  182. package/src/provider/sdk/openai-compatible/src/responses/openai-config.ts +18 -0
  183. package/src/provider/sdk/openai-compatible/src/responses/openai-error.ts +22 -0
  184. package/src/provider/sdk/openai-compatible/src/responses/openai-responses-api-types.ts +207 -0
  185. package/src/provider/sdk/openai-compatible/src/responses/openai-responses-language-model.ts +1713 -0
  186. package/src/provider/sdk/openai-compatible/src/responses/openai-responses-prepare-tools.ts +177 -0
  187. package/src/provider/sdk/openai-compatible/src/responses/openai-responses-settings.ts +1 -0
  188. package/src/provider/sdk/openai-compatible/src/responses/tool/code-interpreter.ts +88 -0
  189. package/src/provider/sdk/openai-compatible/src/responses/tool/file-search.ts +128 -0
  190. package/src/provider/sdk/openai-compatible/src/responses/tool/image-generation.ts +115 -0
  191. package/src/provider/sdk/openai-compatible/src/responses/tool/local-shell.ts +65 -0
  192. package/src/provider/sdk/openai-compatible/src/responses/tool/web-search-preview.ts +104 -0
  193. package/src/provider/sdk/openai-compatible/src/responses/tool/web-search.ts +103 -0
  194. package/src/provider/transform.ts +406 -0
  195. package/src/pty/index.ts +226 -0
  196. package/src/ratelimit/index.ts +185 -0
  197. package/src/server/error.ts +36 -0
  198. package/src/server/project.ts +50 -0
  199. package/src/server/server.ts +2463 -0
  200. package/src/server/tui.ts +71 -0
  201. package/src/session/compaction.ts +257 -0
  202. package/src/session/index.ts +470 -0
  203. package/src/session/message-v2.ts +641 -0
  204. package/src/session/message.ts +189 -0
  205. package/src/session/processor.ts +448 -0
  206. package/src/session/prompt/anthropic-20250930.txt +166 -0
  207. package/src/session/prompt/anthropic.txt +105 -0
  208. package/src/session/prompt/anthropic_spoof.txt +1 -0
  209. package/src/session/prompt/beast.txt +147 -0
  210. package/src/session/prompt/build-switch.txt +5 -0
  211. package/src/session/prompt/codex.txt +318 -0
  212. package/src/session/prompt/compaction.txt +12 -0
  213. package/src/session/prompt/copilot-gpt-5.txt +143 -0
  214. package/src/session/prompt/gemini.txt +155 -0
  215. package/src/session/prompt/max-steps.txt +16 -0
  216. package/src/session/prompt/plan-reminder-anthropic.txt +67 -0
  217. package/src/session/prompt/plan.txt +26 -0
  218. package/src/session/prompt/polaris.txt +107 -0
  219. package/src/session/prompt/qwen.txt +109 -0
  220. package/src/session/prompt/summarize.txt +4 -0
  221. package/src/session/prompt/title.txt +36 -0
  222. package/src/session/prompt.ts +1541 -0
  223. package/src/session/retry.ts +82 -0
  224. package/src/session/revert.ts +108 -0
  225. package/src/session/status.ts +75 -0
  226. package/src/session/summary.ts +203 -0
  227. package/src/session/system.ts +148 -0
  228. package/src/session/todo.ts +36 -0
  229. package/src/share/share-next.ts +195 -0
  230. package/src/share/share.ts +87 -0
  231. package/src/skill/index.ts +2 -0
  232. package/src/skill/skill.ts +138 -0
  233. package/src/snapshot/index.ts +197 -0
  234. package/src/storage/storage.ts +226 -0
  235. package/src/telemetry/index.ts +247 -0
  236. package/src/tool/bash.ts +365 -0
  237. package/src/tool/bash.txt +128 -0
  238. package/src/tool/batch.ts +173 -0
  239. package/src/tool/batch.txt +28 -0
  240. package/src/tool/codesearch.ts +138 -0
  241. package/src/tool/codesearch.txt +12 -0
  242. package/src/tool/edit.ts +674 -0
  243. package/src/tool/edit.txt +10 -0
  244. package/src/tool/glob.ts +65 -0
  245. package/src/tool/glob.txt +6 -0
  246. package/src/tool/grep.ts +120 -0
  247. package/src/tool/grep.txt +8 -0
  248. package/src/tool/invalid.ts +17 -0
  249. package/src/tool/ls.ts +110 -0
  250. package/src/tool/ls.txt +1 -0
  251. package/src/tool/lsp-diagnostics.ts +26 -0
  252. package/src/tool/lsp-diagnostics.txt +1 -0
  253. package/src/tool/lsp-hover.ts +31 -0
  254. package/src/tool/lsp-hover.txt +1 -0
  255. package/src/tool/multiedit.ts +46 -0
  256. package/src/tool/multiedit.txt +41 -0
  257. package/src/tool/patch.ts +233 -0
  258. package/src/tool/patch.txt +1 -0
  259. package/src/tool/read.ts +217 -0
  260. package/src/tool/read.txt +12 -0
  261. package/src/tool/registry.ts +150 -0
  262. package/src/tool/skill.ts +85 -0
  263. package/src/tool/task.ts +135 -0
  264. package/src/tool/task.txt +60 -0
  265. package/src/tool/todo.ts +39 -0
  266. package/src/tool/todoread.txt +14 -0
  267. package/src/tool/todowrite.txt +167 -0
  268. package/src/tool/tool.ts +66 -0
  269. package/src/tool/webfetch.ts +187 -0
  270. package/src/tool/webfetch.txt +14 -0
  271. package/src/tool/websearch.ts +150 -0
  272. package/src/tool/websearch.txt +11 -0
  273. package/src/tool/write.ts +99 -0
  274. package/src/tool/write.txt +8 -0
  275. package/src/types/shims.d.ts +3 -0
  276. package/src/util/color.ts +19 -0
  277. package/src/util/context.ts +25 -0
  278. package/src/util/defer.ts +12 -0
  279. package/src/util/eventloop.ts +20 -0
  280. package/src/util/filesystem.ts +69 -0
  281. package/src/util/fn.ts +11 -0
  282. package/src/util/iife.ts +3 -0
  283. package/src/util/keybind.ts +79 -0
  284. package/src/util/lazy.ts +11 -0
  285. package/src/util/locale.ts +81 -0
  286. package/src/util/lock.ts +98 -0
  287. package/src/util/log.ts +177 -0
  288. package/src/util/queue.ts +32 -0
  289. package/src/util/rpc.ts +42 -0
  290. package/src/util/scrap.ts +10 -0
  291. package/src/util/signal.ts +12 -0
  292. package/src/util/timeout.ts +14 -0
  293. package/src/util/token.ts +7 -0
  294. package/src/util/wildcard.ts +54 -0
  295. package/sst-env.d.ts +9 -0
  296. package/test/bun.test.ts +53 -0
  297. package/test/config/agent-color.test.ts +66 -0
  298. package/test/config/config.test.ts +503 -0
  299. package/test/config/markdown.test.ts +89 -0
  300. package/test/file/ignore.test.ts +10 -0
  301. package/test/fixture/fixture.ts +28 -0
  302. package/test/fixture/lsp/fake-lsp-server.js +77 -0
  303. package/test/ide/ide.test.ts +82 -0
  304. package/test/keybind.test.ts +317 -0
  305. package/test/lsp/client.test.ts +95 -0
  306. package/test/patch/patch.test.ts +348 -0
  307. package/test/preload.ts +38 -0
  308. package/test/project/project.test.ts +42 -0
  309. package/test/provider/provider.test.ts +1809 -0
  310. package/test/provider/transform.test.ts +305 -0
  311. package/test/session/retry.test.ts +61 -0
  312. package/test/session/session.test.ts +71 -0
  313. package/test/snapshot/snapshot.test.ts +939 -0
  314. package/test/tool/__snapshots__/tool.test.ts.snap +9 -0
  315. package/test/tool/bash.test.ts +55 -0
  316. package/test/tool/patch.test.ts +259 -0
  317. package/test/util/iife.test.ts +36 -0
  318. package/test/util/lazy.test.ts +50 -0
  319. package/test/util/timeout.test.ts +21 -0
  320. package/test/util/wildcard.test.ts +55 -0
  321. package/tsconfig.json +17 -0
  322. package/cerebras-cli-1.0.0.tgz +0 -0
@@ -0,0 +1,833 @@
1
+ import { render, useKeyboard, useRenderer, useTerminalDimensions } from "@opentui/solid"
2
+ import { Clipboard } from "@tui/util/clipboard"
3
+ import { TextAttributes } from "@opentui/core"
4
+ import { RouteProvider, useRoute } from "@tui/context/route"
5
+ import { Switch, Match, createEffect, untrack, ErrorBoundary, createSignal, onMount, batch, Show, on } from "solid-js"
6
+ import { Installation } from "@/installation"
7
+ import { Global } from "@/global"
8
+ import { Flag } from "@/flag/flag"
9
+ import { DialogProvider, useDialog } from "@tui/ui/dialog"
10
+ import { DialogProvider as DialogProviderList } from "@tui/component/dialog-provider"
11
+ import { CerebrasOnboarding } from "@tui/component/cerebras-onboarding"
12
+ import { QuickStartOnboarding } from "@tui/component/quickstart-onboarding"
13
+ import { SDKProvider, useSDK } from "@tui/context/sdk"
14
+ import { SyncProvider, useSync } from "@tui/context/sync"
15
+ import { LocalProvider, useLocal } from "@tui/context/local"
16
+ import { DialogModel, useConnected } from "@tui/component/dialog-model"
17
+ import { DialogMcp } from "@tui/component/dialog-mcp"
18
+ import { DialogStatus } from "@tui/component/dialog-status"
19
+ import { DialogThemeList } from "@tui/component/dialog-theme-list"
20
+ import { DialogHelp } from "./ui/dialog-help"
21
+ import { DialogFeedback, type FeedbackMetadata } from "./component/dialog-feedback"
22
+ import { CommandProvider, useCommandDialog } from "@tui/component/dialog-command"
23
+ import { DialogAgent } from "@tui/component/dialog-agent"
24
+ import { DialogSessionList } from "@tui/component/dialog-session-list"
25
+ import { KeybindProvider } from "@tui/context/keybind"
26
+ import { ThemeProvider, useTheme } from "@tui/context/theme"
27
+ import { Home } from "@tui/routes/home"
28
+ import { Session } from "@tui/routes/session"
29
+ import { PromptHistoryProvider } from "./component/prompt/history"
30
+ import { DialogAlert } from "./ui/dialog-alert"
31
+ import { ToastProvider, useToast } from "./ui/toast"
32
+ import { ExitProvider, useExit } from "./context/exit"
33
+ import { Session as SessionApi } from "@/session"
34
+ import { SessionStatus } from "@/session/status"
35
+
36
+ // Rate limit state
37
+ let isInRetryState = false
38
+ let rateLimitHandlerRegistered = false
39
+ import { TuiEvent } from "./event"
40
+ import { KVProvider, useKV } from "./context/kv"
41
+ import { Provider } from "@/provider/provider"
42
+ import { Identifier } from "@/id/id"
43
+ import { ArgsProvider, useArgs, type Args } from "./context/args"
44
+ import open from "open"
45
+ import { PromptRefProvider, usePromptRef } from "./context/prompt"
46
+
47
+ import { Notification } from "@/notification"
48
+ import { FullscreenNotification } from "@tui/component/dialog-notification"
49
+ import { NotificationBanner } from "@tui/component/notification-banner"
50
+
51
+ async function getTerminalBackgroundColor(): Promise<"dark" | "light"> {
52
+ // can't set raw mode if not a TTY
53
+ if (!process.stdin.isTTY) return "dark"
54
+
55
+ return new Promise((resolve) => {
56
+ let timeout: NodeJS.Timeout
57
+
58
+ const cleanup = () => {
59
+ process.stdin.setRawMode(false)
60
+ process.stdin.removeListener("data", handler)
61
+ clearTimeout(timeout)
62
+ }
63
+
64
+ const handler = (data: Buffer) => {
65
+ const str = data.toString()
66
+ const match = str.match(/\x1b]11;([^\x07\x1b]+)/)
67
+ if (match) {
68
+ cleanup()
69
+ const color = match[1]
70
+ // Parse RGB values from color string
71
+ // Formats: rgb:RR/GG/BB or #RRGGBB or rgb(R,G,B)
72
+ let r = 0,
73
+ g = 0,
74
+ b = 0
75
+
76
+ if (color.startsWith("rgb:")) {
77
+ const parts = color.substring(4).split("/")
78
+ r = parseInt(parts[0], 16) >> 8 // Convert 16-bit to 8-bit
79
+ g = parseInt(parts[1], 16) >> 8 // Convert 16-bit to 8-bit
80
+ b = parseInt(parts[2], 16) >> 8 // Convert 16-bit to 8-bit
81
+ } else if (color.startsWith("#")) {
82
+ r = parseInt(color.substring(1, 3), 16)
83
+ g = parseInt(color.substring(3, 5), 16)
84
+ b = parseInt(color.substring(5, 7), 16)
85
+ } else if (color.startsWith("rgb(")) {
86
+ const parts = color.substring(4, color.length - 1).split(",")
87
+ r = parseInt(parts[0])
88
+ g = parseInt(parts[1])
89
+ b = parseInt(parts[2])
90
+ }
91
+
92
+ // Calculate luminance using relative luminance formula
93
+ const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255
94
+
95
+ // Determine if dark or light based on luminance threshold
96
+ resolve(luminance > 0.5 ? "light" : "dark")
97
+ }
98
+ }
99
+
100
+ process.stdin.setRawMode(true)
101
+ process.stdin.on("data", handler)
102
+ process.stdout.write("\x1b]11;?\x07")
103
+
104
+ timeout = setTimeout(() => {
105
+ cleanup()
106
+ resolve("dark")
107
+ }, 1000)
108
+ })
109
+ }
110
+
111
+ export function tui(input: { url: string; args: Args; onExit?: () => Promise<void> }) {
112
+ // promise to prevent immediate exit
113
+ return new Promise<void>(async (resolve) => {
114
+ const mode = await getTerminalBackgroundColor()
115
+ const onExit = async () => {
116
+ await input.onExit?.()
117
+ resolve()
118
+ }
119
+
120
+ render(
121
+ () => {
122
+ return (
123
+ <ErrorBoundary fallback={(error, reset) => <ErrorComponent error={error} reset={reset} onExit={onExit} />}>
124
+ <ArgsProvider {...input.args}>
125
+ <ExitProvider onExit={onExit}>
126
+ <KVProvider>
127
+ <ToastProvider>
128
+ <RouteProvider>
129
+ <SDKProvider url={input.url}>
130
+ <SyncProvider>
131
+ <ThemeProvider mode={mode}>
132
+ <LocalProvider>
133
+ <KeybindProvider>
134
+ <DialogProvider>
135
+ <CommandProvider>
136
+ <PromptHistoryProvider>
137
+ <PromptRefProvider>
138
+ <App />
139
+ </PromptRefProvider>
140
+ </PromptHistoryProvider>
141
+ </CommandProvider>
142
+ </DialogProvider>
143
+ </KeybindProvider>
144
+ </LocalProvider>
145
+ </ThemeProvider>
146
+ </SyncProvider>
147
+ </SDKProvider>
148
+ </RouteProvider>
149
+ </ToastProvider>
150
+ </KVProvider>
151
+ </ExitProvider>
152
+ </ArgsProvider>
153
+ </ErrorBoundary>
154
+ )
155
+ },
156
+ {
157
+ targetFps: 60,
158
+ gatherStats: false,
159
+ exitOnCtrlC: false,
160
+ useKittyKeyboard: true,
161
+ },
162
+ )
163
+ })
164
+ }
165
+
166
+ function App() {
167
+ const route = useRoute()
168
+ const dimensions = useTerminalDimensions()
169
+ const renderer = useRenderer()
170
+ renderer.disableStdoutInterception()
171
+ const dialog = useDialog()
172
+ const local = useLocal()
173
+ const kv = useKV()
174
+ const command = useCommandDialog()
175
+ const sdk = useSDK()
176
+ const { event } = sdk
177
+ const toast = useToast()
178
+ const { theme, mode, setMode } = useTheme()
179
+ const sync = useSync()
180
+ const exit = useExit()
181
+ const promptRef = usePromptRef()
182
+ const [bannerNotification, setBannerNotification] = createSignal<import("@/notification").Notification | null>(null)
183
+ const [fullscreenNotification, setFullscreenNotification] = createSignal<import("@/notification").Notification | null>(null)
184
+ const [showOnboarding, setShowOnboarding] = createSignal(false)
185
+ const [showQuickStart, setShowQuickStart] = createSignal(false)
186
+
187
+ createEffect(() => {
188
+ console.log(JSON.stringify(route.data))
189
+ })
190
+
191
+ // Update terminal window title based on current route and session
192
+ createEffect(() => {
193
+ if (route.data.type === "home") {
194
+ renderer.setTerminalTitle("opencode")
195
+ return
196
+ }
197
+
198
+ if (route.data.type === "session") {
199
+ const session = sync.session.get(route.data.sessionID)
200
+ if (!session || SessionApi.isDefaultTitle(session.title)) {
201
+ renderer.setTerminalTitle("opencode")
202
+ return
203
+ }
204
+
205
+ // Truncate title to 40 chars max
206
+ const title = session.title.length > 40 ? session.title.slice(0, 37) + "..." : session.title
207
+ renderer.setTerminalTitle(`oc | ${title}`)
208
+ }
209
+ })
210
+
211
+ const args = useArgs()
212
+ onMount(() => {
213
+ batch(() => {
214
+ if (args.agent) local.agent.set(args.agent)
215
+ if (args.model) {
216
+ const { providerID, modelID } = Provider.parseModel(args.model)
217
+ if (!providerID || !modelID)
218
+ return toast.show({
219
+ variant: "warning",
220
+ message: `Invalid model format: ${args.model}`,
221
+ duration: 3000,
222
+ })
223
+ local.model.set({ providerID, modelID }, { recent: true })
224
+ }
225
+ if (args.sessionID) {
226
+ route.navigate({
227
+ type: "session",
228
+ sessionID: args.sessionID,
229
+ })
230
+ }
231
+ })
232
+
233
+ })
234
+
235
+ // Check for notifications (but not for first-time users who will see onboarding)
236
+ let notificationChecked = false
237
+ createEffect(() => {
238
+ if (notificationChecked) return
239
+ if (!kv.ready) return
240
+
241
+ // Skip notifications for first-time users - they'll see onboarding instead
242
+ const hasSeenOnboarding = kv.get("hasSeenCerebrasOnboarding", false)
243
+ if (!hasSeenOnboarding) return
244
+
245
+ notificationChecked = true
246
+ Notification.check().then((notif) => {
247
+ if (!notif) return
248
+
249
+ if (notif.display === "fullscreen") {
250
+ setFullscreenNotification(notif)
251
+ } else if (notif.display === "banner") {
252
+ setBannerNotification(notif)
253
+ } else {
254
+ // Toast notification
255
+ toast.show({
256
+ variant: notif.type === "critical" ? "error" : notif.type === "warning" ? "warning" : "info",
257
+ title: notif.title,
258
+ message: notif.message,
259
+ duration: 8000,
260
+ })
261
+ Notification.markSeen(notif.id)
262
+ }
263
+ })
264
+ })
265
+
266
+ let continued = false
267
+ createEffect(() => {
268
+ if (continued || sync.status !== "complete" || !args.continue) return
269
+ const match = sync.data.session.at(0)?.id
270
+ if (match) {
271
+ continued = true
272
+ route.navigate({ type: "session", sessionID: match })
273
+ }
274
+ })
275
+
276
+ // Show Cerebras onboarding for first-time users
277
+ let onboardingTriggered = false
278
+ createEffect(() => {
279
+ if (onboardingTriggered) return
280
+ if (sync.status !== "complete") return
281
+ if (!kv.ready) return
282
+
283
+ const cerebrasConnected = sync.data.provider.some((p) => p.id === "cerebras")
284
+ const hasSeenOnboarding = kv.get("hasSeenCerebrasOnboarding", false)
285
+
286
+ if (!cerebrasConnected && !hasSeenOnboarding) {
287
+ onboardingTriggered = true
288
+ setShowOnboarding(true)
289
+ }
290
+ })
291
+
292
+ // Show quick start after Cerebras onboarding (or if already set up)
293
+ let quickStartTriggered = false
294
+ createEffect(() => {
295
+ if (quickStartTriggered) return
296
+ if (showOnboarding()) return // Wait for Cerebras onboarding to finish
297
+ if (sync.status !== "complete") return
298
+ if (!kv.ready) return
299
+
300
+ const hasSeenQuickStart = kv.get("hasSeenQuickStart", false)
301
+ const cerebrasConnected = sync.data.provider.some((p) => p.id === "cerebras")
302
+
303
+ // Show quick start for users who just completed Cerebras setup or already have it
304
+ if (cerebrasConnected && !hasSeenQuickStart) {
305
+ quickStartTriggered = true
306
+ setShowQuickStart(true)
307
+ }
308
+ })
309
+
310
+ // Handle quick start prompt selection - submit to new session
311
+ const handleQuickStartSelect = async (prompt: string) => {
312
+ setShowQuickStart(false)
313
+
314
+ // Create a new session and submit the prompt
315
+ const selectedModel = local.model.current()
316
+ if (!selectedModel) return
317
+
318
+ const sessionID = await sdk.client.session.create({}).then((x) => x.data!.id)
319
+ const messageID = Identifier.ascending("message")
320
+
321
+ // Submit the prompt
322
+ sdk.client.session.prompt({
323
+ sessionID,
324
+ ...selectedModel,
325
+ messageID,
326
+ agent: local.agent.current().name,
327
+ model: selectedModel,
328
+ parts: [
329
+ {
330
+ id: Identifier.ascending("part"),
331
+ type: "text",
332
+ text: prompt,
333
+ },
334
+ ],
335
+ })
336
+
337
+ // Navigate to the session
338
+ setTimeout(() => {
339
+ route.navigate({ type: "session", sessionID })
340
+ }, 50)
341
+ }
342
+
343
+ const connected = useConnected()
344
+ command.register(() => [
345
+ {
346
+ title: "Switch session",
347
+ value: "session.list",
348
+ keybind: "session_list",
349
+ category: "Session",
350
+ suggested: sync.data.session.length > 0,
351
+ onSelect: () => {
352
+ dialog.replace(() => <DialogSessionList />)
353
+ },
354
+ },
355
+ {
356
+ title: "New session",
357
+ suggested: route.data.type === "session",
358
+ value: "session.new",
359
+ keybind: "session_new",
360
+ category: "Session",
361
+ onSelect: () => {
362
+ const current = promptRef.current
363
+ // Don't require focus - if there's any text, preserve it
364
+ const currentPrompt = current?.current?.input ? current.current : undefined
365
+ route.navigate({
366
+ type: "home",
367
+ initialPrompt: currentPrompt,
368
+ })
369
+ dialog.clear()
370
+ },
371
+ },
372
+ {
373
+ title: "Switch model",
374
+ value: "model.list",
375
+ keybind: "model_list",
376
+ suggested: true,
377
+ category: "Agent",
378
+ onSelect: () => {
379
+ dialog.replace(() => <DialogModel />)
380
+ },
381
+ },
382
+ {
383
+ title: "Model cycle",
384
+ disabled: true,
385
+ value: "model.cycle_recent",
386
+ keybind: "model_cycle_recent",
387
+ category: "Agent",
388
+ onSelect: () => {
389
+ local.model.cycle(1)
390
+ },
391
+ },
392
+ {
393
+ title: "Model cycle reverse",
394
+ disabled: true,
395
+ value: "model.cycle_recent_reverse",
396
+ keybind: "model_cycle_recent_reverse",
397
+ category: "Agent",
398
+ onSelect: () => {
399
+ local.model.cycle(-1)
400
+ },
401
+ },
402
+ {
403
+ title: "Switch agent",
404
+ value: "agent.list",
405
+ keybind: "agent_list",
406
+ category: "Agent",
407
+ onSelect: () => {
408
+ dialog.replace(() => <DialogAgent />)
409
+ },
410
+ },
411
+ {
412
+ title: "Toggle MCPs",
413
+ value: "mcp.list",
414
+ category: "Agent",
415
+ onSelect: () => {
416
+ dialog.replace(() => <DialogMcp />)
417
+ },
418
+ },
419
+ {
420
+ title: "Agent cycle",
421
+ value: "agent.cycle",
422
+ keybind: "agent_cycle",
423
+ category: "Agent",
424
+ disabled: true,
425
+ onSelect: () => {
426
+ local.agent.move(1)
427
+ },
428
+ },
429
+ {
430
+ title: "Agent cycle reverse",
431
+ value: "agent.cycle.reverse",
432
+ keybind: "agent_cycle_reverse",
433
+ category: "Agent",
434
+ disabled: true,
435
+ onSelect: () => {
436
+ local.agent.move(-1)
437
+ },
438
+ },
439
+ {
440
+ title: "Connect provider",
441
+ value: "provider.connect",
442
+ suggested: !connected(),
443
+ onSelect: () => {
444
+ dialog.replace(() => <DialogProviderList />)
445
+ },
446
+ category: "Provider",
447
+ },
448
+ {
449
+ title: "View status",
450
+ keybind: "status_view",
451
+ value: "opencode.status",
452
+ onSelect: () => {
453
+ dialog.replace(() => <DialogStatus />)
454
+ },
455
+ category: "System",
456
+ },
457
+ {
458
+ title: "Switch theme",
459
+ value: "theme.switch",
460
+ onSelect: () => {
461
+ dialog.replace(() => <DialogThemeList />)
462
+ },
463
+ category: "System",
464
+ },
465
+ {
466
+ title: "Toggle appearance",
467
+ value: "theme.switch_mode",
468
+ onSelect: (dialog) => {
469
+ setMode(mode() === "dark" ? "light" : "dark")
470
+ dialog.clear()
471
+ },
472
+ category: "System",
473
+ },
474
+ {
475
+ title: "Help",
476
+ value: "help.show",
477
+ onSelect: () => {
478
+ dialog.replace(() => <DialogHelp />)
479
+ },
480
+ category: "System",
481
+ },
482
+ {
483
+ title: "Send feedback",
484
+ value: "feedback.send",
485
+ onSelect: () => {
486
+ dialog.replace(() => <DialogFeedback onClose={() => dialog.clear()} />)
487
+ },
488
+ category: "System",
489
+ },
490
+ {
491
+ title: "Open docs",
492
+ value: "docs.open",
493
+ onSelect: () => {
494
+ open("https://opencode.ai/docs").catch(() => {})
495
+ dialog.clear()
496
+ },
497
+ category: "System",
498
+ },
499
+ {
500
+ title: "Exit the app",
501
+ value: "app.exit",
502
+ onSelect: () => exit(),
503
+ category: "System",
504
+ },
505
+ {
506
+ title: "Toggle debug panel",
507
+ category: "System",
508
+ value: "app.debug",
509
+ onSelect: (dialog) => {
510
+ renderer.toggleDebugOverlay()
511
+ dialog.clear()
512
+ },
513
+ },
514
+ {
515
+ title: "Toggle console",
516
+ category: "System",
517
+ value: "app.fps",
518
+ onSelect: (dialog) => {
519
+ renderer.console.toggle()
520
+ dialog.clear()
521
+ },
522
+ },
523
+ {
524
+ title: "Suspend terminal",
525
+ value: "terminal.suspend",
526
+ keybind: "terminal_suspend",
527
+ category: "System",
528
+ onSelect: () => {
529
+ process.once("SIGCONT", () => {
530
+ renderer.resume()
531
+ })
532
+
533
+ renderer.suspend()
534
+ // pid=0 means send the signal to all processes in the process group
535
+ process.kill(0, "SIGTSTP")
536
+ },
537
+ },
538
+ ])
539
+
540
+
541
+ event.on(TuiEvent.CommandExecute.type, (evt) => {
542
+ command.trigger(evt.properties.command)
543
+ })
544
+
545
+ event.on(TuiEvent.ToastShow.type, (evt) => {
546
+ toast.show({
547
+ title: evt.properties.title,
548
+ message: evt.properties.message,
549
+ variant: evt.properties.variant,
550
+ duration: evt.properties.duration,
551
+ })
552
+ })
553
+
554
+ event.on(SessionApi.Event.Deleted.type, (evt) => {
555
+ if (route.data.type === "session" && route.data.sessionID === evt.properties.info.id) {
556
+ dialog.clear()
557
+ route.navigate({ type: "home" })
558
+ toast.show({
559
+ variant: "info",
560
+ message: "The current session was deleted",
561
+ })
562
+ }
563
+ })
564
+
565
+ event.on(SessionApi.Event.Error.type, (evt) => {
566
+ const error = evt.properties.error
567
+ const message = (() => {
568
+ if (!error) return "An error occured"
569
+
570
+ if (typeof error === "object") {
571
+ const data = error.data
572
+ if ("message" in data && typeof data.message === "string") {
573
+ return data.message
574
+ }
575
+ }
576
+ return String(error)
577
+ })()
578
+
579
+ // Don't show error toast for retryable errors (rate limits) - we show a custom PayGo toast instead
580
+ const isRetryable = error && typeof error === "object" && error.data?.isRetryable === true
581
+ if (isRetryable) return
582
+
583
+ toast.show({
584
+ variant: "error",
585
+ message,
586
+ duration: 5000,
587
+ })
588
+
589
+ // For non-retryable errors, prompt user to report
590
+ // Gather metadata for feedback form
591
+ const currentModel = local.model.current()
592
+
593
+ // Extract error information
594
+ let errorName: string | undefined
595
+ let errorMessage: string | undefined
596
+ let errorData: unknown
597
+
598
+ if (error && typeof error === "object") {
599
+ errorName = error.name
600
+ if (error.data && typeof error.data === "object") {
601
+ errorMessage = "message" in error.data && typeof error.data.message === "string"
602
+ ? error.data.message
603
+ : undefined
604
+ // Include full error data but ensure it's serializable
605
+ errorData = {
606
+ ...error.data,
607
+ // Ensure statusCode, isRetryable, etc. are included
608
+ statusCode: "statusCode" in error.data ? error.data.statusCode : undefined,
609
+ isRetryable: "isRetryable" in error.data ? error.data.isRetryable : undefined,
610
+ responseHeaders: "responseHeaders" in error.data ? error.data.responseHeaders : undefined,
611
+ responseBody: "responseBody" in error.data ? error.data.responseBody : undefined,
612
+ }
613
+ }
614
+ }
615
+
616
+ const metadata: FeedbackMetadata = {
617
+ error: errorName ? {
618
+ name: errorName,
619
+ message: errorMessage,
620
+ data: errorData,
621
+ } : undefined,
622
+ sessionID: evt.properties.sessionID,
623
+ providerID: currentModel?.providerID,
624
+ modelID: currentModel?.modelID,
625
+ }
626
+
627
+ setTimeout(() => {
628
+ dialog.replace(() => <DialogFeedback onClose={() => dialog.clear()} metadata={metadata} />)
629
+ }, 500)
630
+ })
631
+
632
+ event.on(Installation.Event.Updated.type, (evt) => {
633
+ toast.show({
634
+ variant: "success",
635
+ title: "Update Complete",
636
+ message: `OpenCode updated to v${evt.properties.version}`,
637
+ duration: 5000,
638
+ })
639
+ })
640
+
641
+ event.on(Installation.Event.UpdateAvailable.type, (evt) => {
642
+ toast.show({
643
+ variant: "info",
644
+ title: "Update Available",
645
+ message: `OpenCode v${evt.properties.version} is available. Run 'opencode upgrade' to update manually.`,
646
+ duration: 10000,
647
+ })
648
+ })
649
+
650
+ // Rate limit handling - track retry state, suggest PayGo with exponential backoff (persisted across sessions)
651
+ if (!rateLimitHandlerRegistered) {
652
+ rateLimitHandlerRegistered = true
653
+
654
+ event.on(SessionStatus.Event.Status.type, (evt) => {
655
+ const { status } = evt.properties
656
+ const wasInRetry = isInRetryState
657
+ isInRetryState = status.type === "retry"
658
+
659
+ // Show toast when first entering retry
660
+ if (isInRetryState && !wasInRetry) {
661
+ // Get persisted values from KV store (defaults: count=0, nextAt=1)
662
+ const rateLimitCount = kv.get("rateLimitCount", 0) + 1
663
+ const nextPayGoSuggestionAt = kv.get("nextPayGoSuggestionAt", 1)
664
+
665
+ // Update the count
666
+ kv.set("rateLimitCount", rateLimitCount)
667
+
668
+ // Show PayGo suggestion with exponential backoff (1st, 2nd, 4th, 8th, etc)
669
+ if (rateLimitCount >= nextPayGoSuggestionAt) {
670
+ kv.set("nextPayGoSuggestionAt", nextPayGoSuggestionAt * 2) // Double for next time
671
+ toast.show({
672
+ variant: "warning",
673
+ title: "Rate Limited",
674
+ message: "Press Ctrl+U to upgrade to PayGo for unlimited requests",
675
+ duration: 60000,
676
+ })
677
+ } else {
678
+ toast.show({
679
+ variant: "info",
680
+ title: "🥤 Rate Limited",
681
+ message: "Press Ctrl+G to play a game while you wait!",
682
+ duration: 30000,
683
+ })
684
+ }
685
+ }
686
+ })
687
+ }
688
+
689
+ // Listen for Ctrl+G during rate limit to open game, Ctrl+U for upgrade
690
+ useKeyboard((evt) => {
691
+ if (isInRetryState) {
692
+ if (evt.name === "g" && evt.ctrl) {
693
+ // Diet Coke game
694
+ open("https://diet-coke.netlify.app/")
695
+ } else if (evt.name === "u" && evt.ctrl) {
696
+ // Open PayGo upgrade page
697
+ open("https://cloud.cerebras.ai?utm-source=cli-paygo")
698
+ }
699
+ }
700
+ })
701
+
702
+ return (
703
+ <box
704
+ width={dimensions().width}
705
+ height={dimensions().height}
706
+ backgroundColor={theme.background}
707
+ onMouseUp={async () => {
708
+ if (Flag.OPENCODE_EXPERIMENTAL_DISABLE_COPY_ON_SELECT) {
709
+ renderer.clearSelection()
710
+ return
711
+ }
712
+ const text = renderer.getSelection()?.getSelectedText()
713
+ if (text && text.length > 0) {
714
+ const base64 = Buffer.from(text).toString("base64")
715
+ const osc52 = `\x1b]52;c;${base64}\x07`
716
+ const finalOsc52 = process.env["TMUX"] ? `\x1bPtmux;\x1b${osc52}\x1b\\` : osc52
717
+ /* @ts-expect-error */
718
+ renderer.writeOut(finalOsc52)
719
+ await Clipboard.copy(text)
720
+ .then(() => toast.show({ message: "Copied to clipboard", variant: "info" }))
721
+ .catch(toast.error)
722
+ renderer.clearSelection()
723
+ }
724
+ }}
725
+ >
726
+ <Show
727
+ when={!showOnboarding()}
728
+ fallback={
729
+ <CerebrasOnboarding onComplete={() => setShowOnboarding(false)} />
730
+ }
731
+ >
732
+ <Show
733
+ when={!showQuickStart()}
734
+ fallback={
735
+ <QuickStartOnboarding
736
+ onSelect={handleQuickStartSelect}
737
+ onSkip={() => setShowQuickStart(false)}
738
+ />
739
+ }
740
+ >
741
+ <Show
742
+ when={!fullscreenNotification()}
743
+ fallback={
744
+ <FullscreenNotification
745
+ notification={fullscreenNotification()!}
746
+ onClose={() => {
747
+ Notification.markSeen(fullscreenNotification()!.id)
748
+ setFullscreenNotification(null)
749
+ }}
750
+ />
751
+ }
752
+ >
753
+ <Show when={bannerNotification()}>
754
+ {(notif) => (
755
+ <NotificationBanner
756
+ notification={notif()}
757
+ onDismiss={() => {
758
+ Notification.markSeen(notif().id)
759
+ setBannerNotification(null)
760
+ }}
761
+ />
762
+ )}
763
+ </Show>
764
+ <Switch>
765
+ <Match when={route.data.type === "home"}>
766
+ <Home />
767
+ </Match>
768
+ <Match when={route.data.type === "session"}>
769
+ <Session />
770
+ </Match>
771
+ </Switch>
772
+ </Show>
773
+ </Show>
774
+ </Show>
775
+ </box>
776
+ )
777
+ }
778
+
779
+ function ErrorComponent(props: { error: Error; reset: () => void; onExit: () => Promise<void> }) {
780
+ const term = useTerminalDimensions()
781
+ useKeyboard((evt) => {
782
+ if (evt.ctrl && evt.name === "c") {
783
+ props.onExit()
784
+ }
785
+ })
786
+ const [copied, setCopied] = createSignal(false)
787
+
788
+ const issueURL = new URL("https://github.com/sst/opencode/issues/new?template=bug-report.yml")
789
+
790
+ if (props.error.message) {
791
+ issueURL.searchParams.set("title", `opentui: fatal: ${props.error.message}`)
792
+ }
793
+
794
+ if (props.error.stack) {
795
+ issueURL.searchParams.set(
796
+ "description",
797
+ "```\n" + props.error.stack.substring(0, 6000 - issueURL.toString().length) + "...\n```",
798
+ )
799
+ }
800
+
801
+ issueURL.searchParams.set("opencode-version", Installation.VERSION)
802
+
803
+ const copyIssueURL = () => {
804
+ Clipboard.copy(issueURL.toString()).then(() => {
805
+ setCopied(true)
806
+ })
807
+ }
808
+
809
+ return (
810
+ <box flexDirection="column" gap={1}>
811
+ <box flexDirection="row" gap={1} alignItems="center">
812
+ <text attributes={TextAttributes.BOLD}>Please report an issue.</text>
813
+ <box onMouseUp={copyIssueURL} backgroundColor="#565f89" padding={1}>
814
+ <text attributes={TextAttributes.BOLD}>Copy issue URL (exception info pre-filled)</text>
815
+ </box>
816
+ {copied() && <text>Successfully copied</text>}
817
+ </box>
818
+ <box flexDirection="row" gap={2} alignItems="center">
819
+ <text>A fatal error occurred!</text>
820
+ <box onMouseUp={props.reset} backgroundColor="#565f89" padding={1}>
821
+ <text>Reset TUI</text>
822
+ </box>
823
+ <box onMouseUp={props.onExit} backgroundColor="#565f89" padding={1}>
824
+ <text>Exit</text>
825
+ </box>
826
+ </box>
827
+ <scrollbox height={Math.floor(term().height * 0.7)}>
828
+ <text>{props.error.stack}</text>
829
+ </scrollbox>
830
+ <text>{props.error.message}</text>
831
+ </box>
832
+ )
833
+ }