chad-code 1.3.1

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 (338) hide show
  1. package/AGENTS.md +27 -0
  2. package/Dockerfile +18 -0
  3. package/README.md +15 -0
  4. package/README.npm.md +64 -0
  5. package/bin/chad-code +84 -0
  6. package/bunfig.toml +7 -0
  7. package/eslint.config.js +29 -0
  8. package/package.json +107 -0
  9. package/parsers-config.ts +253 -0
  10. package/script/build.ts +167 -0
  11. package/script/postinstall.mjs +122 -0
  12. package/script/publish-registries.ts +187 -0
  13. package/script/publish.ts +93 -0
  14. package/script/schema.ts +47 -0
  15. package/src/acp/README.md +164 -0
  16. package/src/acp/agent.ts +1086 -0
  17. package/src/acp/session.ts +101 -0
  18. package/src/acp/types.ts +22 -0
  19. package/src/agent/agent.ts +253 -0
  20. package/src/agent/generate.txt +75 -0
  21. package/src/agent/prompt/compaction.txt +12 -0
  22. package/src/agent/prompt/explore.txt +18 -0
  23. package/src/agent/prompt/summary.txt +11 -0
  24. package/src/agent/prompt/title.txt +36 -0
  25. package/src/auth/index.ts +70 -0
  26. package/src/bun/index.ts +130 -0
  27. package/src/bus/bus-event.ts +43 -0
  28. package/src/bus/global.ts +10 -0
  29. package/src/bus/index.ts +105 -0
  30. package/src/cli/bootstrap.ts +17 -0
  31. package/src/cli/cmd/acp.ts +69 -0
  32. package/src/cli/cmd/agent.ts +257 -0
  33. package/src/cli/cmd/auth.ts +132 -0
  34. package/src/cli/cmd/cmd.ts +7 -0
  35. package/src/cli/cmd/debug/agent.ts +28 -0
  36. package/src/cli/cmd/debug/config.ts +15 -0
  37. package/src/cli/cmd/debug/file.ts +91 -0
  38. package/src/cli/cmd/debug/index.ts +45 -0
  39. package/src/cli/cmd/debug/lsp.ts +48 -0
  40. package/src/cli/cmd/debug/ripgrep.ts +83 -0
  41. package/src/cli/cmd/debug/scrap.ts +15 -0
  42. package/src/cli/cmd/debug/skill.ts +15 -0
  43. package/src/cli/cmd/debug/snapshot.ts +48 -0
  44. package/src/cli/cmd/export.ts +88 -0
  45. package/src/cli/cmd/generate.ts +38 -0
  46. package/src/cli/cmd/github.ts +32 -0
  47. package/src/cli/cmd/import.ts +98 -0
  48. package/src/cli/cmd/mcp.ts +670 -0
  49. package/src/cli/cmd/models.ts +42 -0
  50. package/src/cli/cmd/pr.ts +112 -0
  51. package/src/cli/cmd/run.ts +374 -0
  52. package/src/cli/cmd/serve.ts +16 -0
  53. package/src/cli/cmd/session.ts +135 -0
  54. package/src/cli/cmd/stats.ts +402 -0
  55. package/src/cli/cmd/tui/app.tsx +705 -0
  56. package/src/cli/cmd/tui/attach.ts +32 -0
  57. package/src/cli/cmd/tui/component/border.tsx +21 -0
  58. package/src/cli/cmd/tui/component/dialog-agent.tsx +31 -0
  59. package/src/cli/cmd/tui/component/dialog-command.tsx +124 -0
  60. package/src/cli/cmd/tui/component/dialog-mcp.tsx +86 -0
  61. package/src/cli/cmd/tui/component/dialog-model.tsx +232 -0
  62. package/src/cli/cmd/tui/component/dialog-provider.tsx +228 -0
  63. package/src/cli/cmd/tui/component/dialog-session-list.tsx +115 -0
  64. package/src/cli/cmd/tui/component/dialog-session-rename.tsx +31 -0
  65. package/src/cli/cmd/tui/component/dialog-stash.tsx +86 -0
  66. package/src/cli/cmd/tui/component/dialog-status.tsx +162 -0
  67. package/src/cli/cmd/tui/component/dialog-tag.tsx +44 -0
  68. package/src/cli/cmd/tui/component/dialog-theme-list.tsx +50 -0
  69. package/src/cli/cmd/tui/component/did-you-know.tsx +85 -0
  70. package/src/cli/cmd/tui/component/logo.tsx +43 -0
  71. package/src/cli/cmd/tui/component/prompt/autocomplete.tsx +654 -0
  72. package/src/cli/cmd/tui/component/prompt/history.tsx +108 -0
  73. package/src/cli/cmd/tui/component/prompt/index.tsx +1078 -0
  74. package/src/cli/cmd/tui/component/prompt/stash.tsx +101 -0
  75. package/src/cli/cmd/tui/component/textarea-keybindings.ts +73 -0
  76. package/src/cli/cmd/tui/component/tips.ts +92 -0
  77. package/src/cli/cmd/tui/component/todo-item.tsx +32 -0
  78. package/src/cli/cmd/tui/context/args.tsx +14 -0
  79. package/src/cli/cmd/tui/context/directory.ts +13 -0
  80. package/src/cli/cmd/tui/context/exit.tsx +23 -0
  81. package/src/cli/cmd/tui/context/helper.tsx +25 -0
  82. package/src/cli/cmd/tui/context/keybind.tsx +101 -0
  83. package/src/cli/cmd/tui/context/kv.tsx +49 -0
  84. package/src/cli/cmd/tui/context/local.tsx +392 -0
  85. package/src/cli/cmd/tui/context/prompt.tsx +18 -0
  86. package/src/cli/cmd/tui/context/route.tsx +46 -0
  87. package/src/cli/cmd/tui/context/sdk.tsx +75 -0
  88. package/src/cli/cmd/tui/context/sync.tsx +384 -0
  89. package/src/cli/cmd/tui/context/theme/aura.json +69 -0
  90. package/src/cli/cmd/tui/context/theme/ayu.json +80 -0
  91. package/src/cli/cmd/tui/context/theme/catppuccin-frappe.json +233 -0
  92. package/src/cli/cmd/tui/context/theme/catppuccin-macchiato.json +233 -0
  93. package/src/cli/cmd/tui/context/theme/catppuccin.json +112 -0
  94. package/src/cli/cmd/tui/context/theme/chad.json +245 -0
  95. package/src/cli/cmd/tui/context/theme/cobalt2.json +228 -0
  96. package/src/cli/cmd/tui/context/theme/cursor.json +249 -0
  97. package/src/cli/cmd/tui/context/theme/dracula.json +219 -0
  98. package/src/cli/cmd/tui/context/theme/everforest.json +241 -0
  99. package/src/cli/cmd/tui/context/theme/flexoki.json +237 -0
  100. package/src/cli/cmd/tui/context/theme/github.json +233 -0
  101. package/src/cli/cmd/tui/context/theme/gruvbox.json +95 -0
  102. package/src/cli/cmd/tui/context/theme/kanagawa.json +77 -0
  103. package/src/cli/cmd/tui/context/theme/lucent-orng.json +227 -0
  104. package/src/cli/cmd/tui/context/theme/material.json +235 -0
  105. package/src/cli/cmd/tui/context/theme/matrix.json +77 -0
  106. package/src/cli/cmd/tui/context/theme/mercury.json +252 -0
  107. package/src/cli/cmd/tui/context/theme/monokai.json +221 -0
  108. package/src/cli/cmd/tui/context/theme/nightowl.json +221 -0
  109. package/src/cli/cmd/tui/context/theme/nord.json +223 -0
  110. package/src/cli/cmd/tui/context/theme/one-dark.json +84 -0
  111. package/src/cli/cmd/tui/context/theme/orng.json +245 -0
  112. package/src/cli/cmd/tui/context/theme/osaka-jade.json +93 -0
  113. package/src/cli/cmd/tui/context/theme/palenight.json +222 -0
  114. package/src/cli/cmd/tui/context/theme/rosepine.json +234 -0
  115. package/src/cli/cmd/tui/context/theme/solarized.json +223 -0
  116. package/src/cli/cmd/tui/context/theme/synthwave84.json +226 -0
  117. package/src/cli/cmd/tui/context/theme/tokyonight.json +243 -0
  118. package/src/cli/cmd/tui/context/theme/vercel.json +245 -0
  119. package/src/cli/cmd/tui/context/theme/vesper.json +218 -0
  120. package/src/cli/cmd/tui/context/theme/zenburn.json +223 -0
  121. package/src/cli/cmd/tui/context/theme.tsx +1137 -0
  122. package/src/cli/cmd/tui/event.ts +46 -0
  123. package/src/cli/cmd/tui/routes/home.tsx +138 -0
  124. package/src/cli/cmd/tui/routes/session/dialog-fork-from-timeline.tsx +64 -0
  125. package/src/cli/cmd/tui/routes/session/dialog-message.tsx +109 -0
  126. package/src/cli/cmd/tui/routes/session/dialog-subagent.tsx +26 -0
  127. package/src/cli/cmd/tui/routes/session/dialog-timeline.tsx +47 -0
  128. package/src/cli/cmd/tui/routes/session/footer.tsx +88 -0
  129. package/src/cli/cmd/tui/routes/session/header.tsx +125 -0
  130. package/src/cli/cmd/tui/routes/session/index.tsx +1814 -0
  131. package/src/cli/cmd/tui/routes/session/permission.tsx +416 -0
  132. package/src/cli/cmd/tui/routes/session/sidebar.tsx +318 -0
  133. package/src/cli/cmd/tui/spawn.ts +48 -0
  134. package/src/cli/cmd/tui/thread.ts +111 -0
  135. package/src/cli/cmd/tui/ui/dialog-alert.tsx +57 -0
  136. package/src/cli/cmd/tui/ui/dialog-confirm.tsx +83 -0
  137. package/src/cli/cmd/tui/ui/dialog-export-options.tsx +204 -0
  138. package/src/cli/cmd/tui/ui/dialog-help.tsx +38 -0
  139. package/src/cli/cmd/tui/ui/dialog-prompt.tsx +77 -0
  140. package/src/cli/cmd/tui/ui/dialog-select.tsx +345 -0
  141. package/src/cli/cmd/tui/ui/dialog.tsx +171 -0
  142. package/src/cli/cmd/tui/ui/link.tsx +28 -0
  143. package/src/cli/cmd/tui/ui/spinner.ts +368 -0
  144. package/src/cli/cmd/tui/ui/toast.tsx +100 -0
  145. package/src/cli/cmd/tui/util/clipboard.ts +127 -0
  146. package/src/cli/cmd/tui/util/editor.ts +32 -0
  147. package/src/cli/cmd/tui/util/signal.ts +7 -0
  148. package/src/cli/cmd/tui/util/terminal.ts +114 -0
  149. package/src/cli/cmd/tui/util/transcript.ts +98 -0
  150. package/src/cli/cmd/tui/worker.ts +68 -0
  151. package/src/cli/cmd/uninstall.ts +344 -0
  152. package/src/cli/cmd/upgrade.ts +67 -0
  153. package/src/cli/cmd/web.ts +73 -0
  154. package/src/cli/error.ts +56 -0
  155. package/src/cli/network.ts +53 -0
  156. package/src/cli/ui.ts +87 -0
  157. package/src/cli/upgrade.ts +25 -0
  158. package/src/command/index.ts +131 -0
  159. package/src/command/template/initialize.txt +10 -0
  160. package/src/command/template/review.txt +97 -0
  161. package/src/config/config.ts +1124 -0
  162. package/src/config/markdown.ts +41 -0
  163. package/src/env/index.ts +26 -0
  164. package/src/file/ignore.ts +83 -0
  165. package/src/file/index.ts +411 -0
  166. package/src/file/ripgrep.ts +402 -0
  167. package/src/file/time.ts +64 -0
  168. package/src/file/watcher.ts +117 -0
  169. package/src/flag/flag.ts +52 -0
  170. package/src/format/formatter.ts +359 -0
  171. package/src/format/index.ts +137 -0
  172. package/src/global/index.ts +55 -0
  173. package/src/id/id.ts +73 -0
  174. package/src/ide/index.ts +77 -0
  175. package/src/index.ts +159 -0
  176. package/src/installation/index.ts +198 -0
  177. package/src/lsp/client.ts +252 -0
  178. package/src/lsp/index.ts +485 -0
  179. package/src/lsp/language.ts +119 -0
  180. package/src/lsp/server.ts +2023 -0
  181. package/src/mcp/auth.ts +135 -0
  182. package/src/mcp/index.ts +874 -0
  183. package/src/mcp/oauth-callback.ts +200 -0
  184. package/src/mcp/oauth-provider.ts +154 -0
  185. package/src/patch/index.ts +622 -0
  186. package/src/permission/arity.ts +163 -0
  187. package/src/permission/index.ts +210 -0
  188. package/src/permission/next.ts +268 -0
  189. package/src/plugin/index.ts +106 -0
  190. package/src/project/bootstrap.ts +31 -0
  191. package/src/project/instance.ts +78 -0
  192. package/src/project/project.ts +263 -0
  193. package/src/project/state.ts +65 -0
  194. package/src/project/vcs.ts +76 -0
  195. package/src/provider/auth.ts +143 -0
  196. package/src/provider/models-macro.ts +4 -0
  197. package/src/provider/models.ts +77 -0
  198. package/src/provider/provider.ts +516 -0
  199. package/src/provider/transform.ts +114 -0
  200. package/src/pty/index.ts +212 -0
  201. package/src/server/error.ts +36 -0
  202. package/src/server/mdns.ts +57 -0
  203. package/src/server/project.ts +79 -0
  204. package/src/server/server.ts +2866 -0
  205. package/src/server/tui.ts +71 -0
  206. package/src/session/compaction.ts +225 -0
  207. package/src/session/index.ts +469 -0
  208. package/src/session/llm.ts +213 -0
  209. package/src/session/message-v2.ts +742 -0
  210. package/src/session/message.ts +189 -0
  211. package/src/session/processor.ts +402 -0
  212. package/src/session/prompt/anthropic-20250930.txt +166 -0
  213. package/src/session/prompt/anthropic.txt +105 -0
  214. package/src/session/prompt/anthropic_spoof.txt +1 -0
  215. package/src/session/prompt/beast.txt +147 -0
  216. package/src/session/prompt/build-switch.txt +5 -0
  217. package/src/session/prompt/codex.txt +318 -0
  218. package/src/session/prompt/copilot-gpt-5.txt +143 -0
  219. package/src/session/prompt/gemini.txt +155 -0
  220. package/src/session/prompt/max-steps.txt +16 -0
  221. package/src/session/prompt/plan-reminder-anthropic.txt +67 -0
  222. package/src/session/prompt/plan.txt +26 -0
  223. package/src/session/prompt/qwen.txt +109 -0
  224. package/src/session/prompt.ts +1621 -0
  225. package/src/session/retry.ts +90 -0
  226. package/src/session/revert.ts +108 -0
  227. package/src/session/status.ts +76 -0
  228. package/src/session/summary.ts +194 -0
  229. package/src/session/system.ts +108 -0
  230. package/src/session/todo.ts +37 -0
  231. package/src/share/share-next.ts +194 -0
  232. package/src/share/share.ts +23 -0
  233. package/src/shell/shell.ts +67 -0
  234. package/src/skill/index.ts +1 -0
  235. package/src/skill/skill.ts +124 -0
  236. package/src/snapshot/index.ts +197 -0
  237. package/src/storage/storage.ts +226 -0
  238. package/src/tool/bash.ts +262 -0
  239. package/src/tool/bash.txt +116 -0
  240. package/src/tool/batch.ts +175 -0
  241. package/src/tool/batch.txt +24 -0
  242. package/src/tool/codesearch.ts +132 -0
  243. package/src/tool/codesearch.txt +12 -0
  244. package/src/tool/edit.ts +655 -0
  245. package/src/tool/edit.txt +10 -0
  246. package/src/tool/glob.ts +75 -0
  247. package/src/tool/glob.txt +6 -0
  248. package/src/tool/grep.ts +132 -0
  249. package/src/tool/grep.txt +8 -0
  250. package/src/tool/invalid.ts +17 -0
  251. package/src/tool/ls.ts +119 -0
  252. package/src/tool/ls.txt +1 -0
  253. package/src/tool/lsp.ts +94 -0
  254. package/src/tool/lsp.txt +19 -0
  255. package/src/tool/multiedit.ts +46 -0
  256. package/src/tool/multiedit.txt +41 -0
  257. package/src/tool/patch.ts +210 -0
  258. package/src/tool/patch.txt +1 -0
  259. package/src/tool/read.ts +191 -0
  260. package/src/tool/read.txt +12 -0
  261. package/src/tool/registry.ts +137 -0
  262. package/src/tool/skill.ts +77 -0
  263. package/src/tool/task.ts +167 -0
  264. package/src/tool/task.txt +60 -0
  265. package/src/tool/todo.ts +53 -0
  266. package/src/tool/todoread.txt +14 -0
  267. package/src/tool/todowrite.txt +167 -0
  268. package/src/tool/tool.ts +73 -0
  269. package/src/tool/webfetch.ts +182 -0
  270. package/src/tool/webfetch.txt +13 -0
  271. package/src/tool/websearch.ts +144 -0
  272. package/src/tool/websearch.txt +11 -0
  273. package/src/tool/write.ts +84 -0
  274. package/src/tool/write.txt +8 -0
  275. package/src/util/archive.ts +16 -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 +83 -0
  281. package/src/util/fn.ts +11 -0
  282. package/src/util/iife.ts +3 -0
  283. package/src/util/keybind.ts +102 -0
  284. package/src/util/lazy.ts +18 -0
  285. package/src/util/locale.ts +81 -0
  286. package/src/util/lock.ts +98 -0
  287. package/src/util/log.ts +180 -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/src/worktree/index.ts +217 -0
  296. package/sst-env.d.ts +9 -0
  297. package/test/agent/agent.test.ts +448 -0
  298. package/test/bun.test.ts +53 -0
  299. package/test/cli/github-action.test.ts +129 -0
  300. package/test/cli/github-remote.test.ts +80 -0
  301. package/test/cli/tui/transcript.test.ts +297 -0
  302. package/test/config/agent-color.test.ts +66 -0
  303. package/test/config/config.test.ts +870 -0
  304. package/test/config/markdown.test.ts +89 -0
  305. package/test/file/ignore.test.ts +10 -0
  306. package/test/file/path-traversal.test.ts +115 -0
  307. package/test/fixture/fixture.ts +45 -0
  308. package/test/fixture/lsp/fake-lsp-server.js +77 -0
  309. package/test/ide/ide.test.ts +82 -0
  310. package/test/keybind.test.ts +421 -0
  311. package/test/lsp/client.test.ts +95 -0
  312. package/test/mcp/headers.test.ts +153 -0
  313. package/test/patch/patch.test.ts +348 -0
  314. package/test/permission/arity.test.ts +33 -0
  315. package/test/permission/next.test.ts +652 -0
  316. package/test/preload.ts +63 -0
  317. package/test/project/project.test.ts +120 -0
  318. package/test/provider/amazon-bedrock.test.ts +236 -0
  319. package/test/provider/provider.test.ts +2127 -0
  320. package/test/provider/transform.test.ts +980 -0
  321. package/test/server/session-select.test.ts +78 -0
  322. package/test/session/compaction.test.ts +251 -0
  323. package/test/session/message-v2.test.ts +570 -0
  324. package/test/session/retry.test.ts +131 -0
  325. package/test/session/revert-compact.test.ts +285 -0
  326. package/test/session/session.test.ts +71 -0
  327. package/test/skill/skill.test.ts +185 -0
  328. package/test/snapshot/snapshot.test.ts +939 -0
  329. package/test/tool/__snapshots__/tool.test.ts.snap +9 -0
  330. package/test/tool/bash.test.ts +232 -0
  331. package/test/tool/grep.test.ts +109 -0
  332. package/test/tool/patch.test.ts +261 -0
  333. package/test/tool/read.test.ts +167 -0
  334. package/test/util/iife.test.ts +36 -0
  335. package/test/util/lazy.test.ts +50 -0
  336. package/test/util/timeout.test.ts +21 -0
  337. package/test/util/wildcard.test.ts +55 -0
  338. package/tsconfig.json +16 -0
@@ -0,0 +1,228 @@
1
+ import { createMemo, createSignal, onMount, Show } from "solid-js"
2
+ import { useSync } from "@tui/context/sync"
3
+ import { useLocal } from "@tui/context/local"
4
+ import { map, pipe, sortBy } from "remeda"
5
+ import { DialogSelect } from "@tui/ui/dialog-select"
6
+ import { useDialog } from "@tui/ui/dialog"
7
+ import { useSDK } from "../context/sdk"
8
+ import { DialogPrompt } from "../ui/dialog-prompt"
9
+ import { Link } from "../ui/link"
10
+ import { useTheme } from "../context/theme"
11
+ import { TextAttributes } from "@opentui/core"
12
+ import type { ProviderAuthAuthorization } from "@opencode-ai/sdk/v2"
13
+ import { DialogModel } from "./dialog-model"
14
+
15
+ const PROVIDER_PRIORITY: Record<string, number> = {
16
+ corethink: 0,
17
+ }
18
+
19
+ export function createDialogProviderOptions() {
20
+ const sync = useSync()
21
+ const dialog = useDialog()
22
+ const sdk = useSDK()
23
+ const options = createMemo(() => {
24
+ return pipe(
25
+ sync.data.provider_next.all,
26
+ sortBy((x) => PROVIDER_PRIORITY[x.id] ?? 99),
27
+ map((provider) => ({
28
+ title: provider.name,
29
+ value: provider.id,
30
+ description: provider.id === "corethink" ? "(Recommended)" : undefined,
31
+ category: provider.id in PROVIDER_PRIORITY ? "Popular" : "Other",
32
+ async onSelect() {
33
+ const methods = sync.data.provider_auth[provider.id] ?? [
34
+ {
35
+ type: "api",
36
+ label: "API key",
37
+ },
38
+ ]
39
+ let index: number | null = 0
40
+ if (methods.length > 1) {
41
+ index = await new Promise<number | null>((resolve) => {
42
+ dialog.replace(
43
+ () => (
44
+ <DialogSelect
45
+ title="Select auth method"
46
+ options={methods.map((x, index) => ({
47
+ title: x.label,
48
+ value: index,
49
+ }))}
50
+ onSelect={(option) => resolve(option.value)}
51
+ />
52
+ ),
53
+ () => resolve(null),
54
+ )
55
+ })
56
+ }
57
+ if (index == null) return
58
+ const method = methods[index]
59
+ if (method.type === "oauth") {
60
+ const result = await sdk.client.provider.oauth.authorize({
61
+ providerID: provider.id,
62
+ method: index,
63
+ })
64
+ if (result.data?.method === "code") {
65
+ dialog.replace(() => (
66
+ <CodeMethod providerID={provider.id} title={method.label} index={index} authorization={result.data!} />
67
+ ))
68
+ }
69
+ if (result.data?.method === "auto") {
70
+ dialog.replace(() => (
71
+ <AutoMethod providerID={provider.id} title={method.label} index={index} authorization={result.data!} />
72
+ ))
73
+ }
74
+ }
75
+ if (method.type === "api") {
76
+ return dialog.replace(() => <ApiMethod providerID={provider.id} title={method.label} />)
77
+ }
78
+ },
79
+ })),
80
+ )
81
+ })
82
+ return options
83
+ }
84
+
85
+ export function DialogProvider() {
86
+ // Directly show Chadcode API key input instead of provider selection
87
+ return <ApiMethod providerID="corethink" title="Enter your Chadcode API key" />
88
+ }
89
+
90
+ interface AutoMethodProps {
91
+ index: number
92
+ providerID: string
93
+ title: string
94
+ authorization: ProviderAuthAuthorization
95
+ }
96
+ function AutoMethod(props: AutoMethodProps) {
97
+ const { theme } = useTheme()
98
+ const sdk = useSDK()
99
+ const dialog = useDialog()
100
+ const sync = useSync()
101
+
102
+ onMount(async () => {
103
+ const result = await sdk.client.provider.oauth.callback({
104
+ providerID: props.providerID,
105
+ method: props.index,
106
+ })
107
+ if (result.error) {
108
+ dialog.clear()
109
+ return
110
+ }
111
+ await sdk.client.instance.dispose()
112
+ await sync.bootstrap()
113
+ dialog.replace(() => <DialogModel providerID={props.providerID} />)
114
+ })
115
+
116
+ return (
117
+ <box paddingLeft={2} paddingRight={2} gap={1} paddingBottom={1}>
118
+ <box flexDirection="row" justifyContent="space-between">
119
+ <text attributes={TextAttributes.BOLD} fg={theme.text}>
120
+ {props.title}
121
+ </text>
122
+ <text fg={theme.textMuted}>esc</text>
123
+ </box>
124
+ <box gap={1}>
125
+ <Link href={props.authorization.url} fg={theme.primary} />
126
+ <text fg={theme.textMuted}>{props.authorization.instructions}</text>
127
+ </box>
128
+ <text fg={theme.textMuted}>Waiting for authorization...</text>
129
+ </box>
130
+ )
131
+ }
132
+
133
+ interface CodeMethodProps {
134
+ index: number
135
+ title: string
136
+ providerID: string
137
+ authorization: ProviderAuthAuthorization
138
+ }
139
+ function CodeMethod(props: CodeMethodProps) {
140
+ const { theme } = useTheme()
141
+ const sdk = useSDK()
142
+ const sync = useSync()
143
+ const dialog = useDialog()
144
+ const [error, setError] = createSignal(false)
145
+
146
+ return (
147
+ <DialogPrompt
148
+ title={props.title}
149
+ placeholder="Authorization code"
150
+ onConfirm={async (value) => {
151
+ const { error } = await sdk.client.provider.oauth.callback({
152
+ providerID: props.providerID,
153
+ method: props.index,
154
+ code: value,
155
+ })
156
+ if (!error) {
157
+ await sdk.client.instance.dispose()
158
+ await sync.bootstrap()
159
+ dialog.replace(() => <DialogModel providerID={props.providerID} />)
160
+ return
161
+ }
162
+ setError(true)
163
+ }}
164
+ description={() => (
165
+ <box gap={1}>
166
+ <text fg={theme.textMuted}>{props.authorization.instructions}</text>
167
+ <Link href={props.authorization.url} fg={theme.primary} />
168
+ <Show when={error()}>
169
+ <text fg={theme.error}>Invalid code</text>
170
+ </Show>
171
+ </box>
172
+ )}
173
+ />
174
+ )
175
+ }
176
+
177
+ interface ApiMethodProps {
178
+ providerID: string
179
+ title: string
180
+ }
181
+ function ApiMethod(props: ApiMethodProps) {
182
+ const dialog = useDialog()
183
+ const sdk = useSDK()
184
+ const sync = useSync()
185
+ const { theme } = useTheme()
186
+ const local = useLocal()
187
+
188
+ return (
189
+ <DialogPrompt
190
+ title={props.title}
191
+ placeholder="API key"
192
+ description={
193
+ props.providerID === "corethink" ? (
194
+ <box gap={1}>
195
+ <text fg={theme.textMuted}>
196
+ Chadcode provides AI-powered code assistance. Get your API key from the Chadcode dashboard.
197
+ </text>
198
+ <text fg={theme.text}>
199
+ API keys should start with <span style={{ fg: theme.primary }}>sk_</span>
200
+ </text>
201
+ </box>
202
+ ) : undefined
203
+ }
204
+ onConfirm={async (value) => {
205
+ if (!value) return
206
+ await sdk.client.auth.set({
207
+ providerID: props.providerID,
208
+ auth: {
209
+ type: "api",
210
+ key: value,
211
+ },
212
+ })
213
+ await sdk.client.instance.dispose()
214
+ await sync.bootstrap()
215
+ // Auto-select corethink model and close dialog
216
+ if (props.providerID === "corethink") {
217
+ local.model.set(
218
+ { providerID: "corethink", modelID: "corethink" },
219
+ { recent: true }
220
+ )
221
+ dialog.clear()
222
+ } else {
223
+ dialog.replace(() => <DialogModel providerID={props.providerID} />)
224
+ }
225
+ }}
226
+ />
227
+ )
228
+ }
@@ -0,0 +1,115 @@
1
+ import { useDialog } from "@tui/ui/dialog"
2
+ import { DialogSelect } from "@tui/ui/dialog-select"
3
+ import { useRoute } from "@tui/context/route"
4
+ import { useSync } from "@tui/context/sync"
5
+ import { createMemo, createSignal, createResource, onMount, Show } from "solid-js"
6
+ import { Locale } from "@/util/locale"
7
+ import { Keybind } from "@/util/keybind"
8
+ import { useTheme } from "../context/theme"
9
+ import { useSDK } from "../context/sdk"
10
+ import { DialogSessionRename } from "./dialog-session-rename"
11
+ import { useKV } from "../context/kv"
12
+ import { createDebouncedSignal } from "../util/signal"
13
+ import "opentui-spinner/solid"
14
+
15
+ export function DialogSessionList() {
16
+ const dialog = useDialog()
17
+ const sync = useSync()
18
+ const { theme } = useTheme()
19
+ const route = useRoute()
20
+ const sdk = useSDK()
21
+ const kv = useKV()
22
+
23
+ const [toDelete, setToDelete] = createSignal<string>()
24
+ const [search, setSearch] = createDebouncedSignal("", 150)
25
+
26
+ const [searchResults] = createResource(search, async (query) => {
27
+ if (!query) return undefined
28
+ const result = await sdk.client.session.list({ search: query, limit: 30 })
29
+ return result.data ?? []
30
+ })
31
+
32
+ const deleteKeybind = "ctrl+d"
33
+
34
+ const currentSessionID = createMemo(() => (route.data.type === "session" ? route.data.sessionID : undefined))
35
+
36
+ const spinnerFrames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]
37
+
38
+ const sessions = createMemo(() => searchResults() ?? sync.data.session)
39
+
40
+ const options = createMemo(() => {
41
+ const today = new Date().toDateString()
42
+ return sessions()
43
+ .filter((x) => x.parentID === undefined)
44
+ .toSorted((a, b) => b.time.updated - a.time.updated)
45
+ .map((x) => {
46
+ const date = new Date(x.time.updated)
47
+ let category = date.toDateString()
48
+ if (category === today) {
49
+ category = "Today"
50
+ }
51
+ const isDeleting = toDelete() === x.id
52
+ const status = sync.data.session_status?.[x.id]
53
+ const isWorking = status?.type === "busy"
54
+ return {
55
+ title: isDeleting ? `Press ${deleteKeybind} again to confirm` : x.title,
56
+ bg: isDeleting ? theme.error : undefined,
57
+ value: x.id,
58
+ category,
59
+ footer: Locale.time(x.time.updated),
60
+ gutter: isWorking ? (
61
+ <Show when={kv.get("animations_enabled", true)} fallback={<text fg={theme.textMuted}>[⋯]</text>}>
62
+ <spinner frames={spinnerFrames} interval={80} color={theme.primary} />
63
+ </Show>
64
+ ) : undefined,
65
+ }
66
+ })
67
+ })
68
+
69
+ onMount(() => {
70
+ dialog.setSize("large")
71
+ })
72
+
73
+ return (
74
+ <DialogSelect
75
+ title="Sessions"
76
+ options={options()}
77
+ skipFilter={true}
78
+ current={currentSessionID()}
79
+ onFilter={setSearch}
80
+ onMove={() => {
81
+ setToDelete(undefined)
82
+ }}
83
+ onSelect={(option) => {
84
+ route.navigate({
85
+ type: "session",
86
+ sessionID: option.value,
87
+ })
88
+ dialog.clear()
89
+ }}
90
+ keybind={[
91
+ {
92
+ keybind: Keybind.parse(deleteKeybind)[0],
93
+ title: "delete",
94
+ onTrigger: async (option) => {
95
+ if (toDelete() === option.value) {
96
+ sdk.client.session.delete({
97
+ sessionID: option.value,
98
+ })
99
+ setToDelete(undefined)
100
+ return
101
+ }
102
+ setToDelete(option.value)
103
+ },
104
+ },
105
+ {
106
+ keybind: Keybind.parse("ctrl+r")[0],
107
+ title: "rename",
108
+ onTrigger: async (option) => {
109
+ dialog.replace(() => <DialogSessionRename session={option.value} />)
110
+ },
111
+ },
112
+ ]}
113
+ />
114
+ )
115
+ }
@@ -0,0 +1,31 @@
1
+ import { DialogPrompt } from "@tui/ui/dialog-prompt"
2
+ import { useDialog } from "@tui/ui/dialog"
3
+ import { useSync } from "@tui/context/sync"
4
+ import { createMemo } from "solid-js"
5
+ import { useSDK } from "../context/sdk"
6
+
7
+ interface DialogSessionRenameProps {
8
+ session: string
9
+ }
10
+
11
+ export function DialogSessionRename(props: DialogSessionRenameProps) {
12
+ const dialog = useDialog()
13
+ const sync = useSync()
14
+ const sdk = useSDK()
15
+ const session = createMemo(() => sync.session.get(props.session))
16
+
17
+ return (
18
+ <DialogPrompt
19
+ title="Rename Session"
20
+ value={session()?.title}
21
+ onConfirm={(value) => {
22
+ sdk.client.session.update({
23
+ sessionID: props.session,
24
+ title: value,
25
+ })
26
+ dialog.clear()
27
+ }}
28
+ onCancel={() => dialog.clear()}
29
+ />
30
+ )
31
+ }
@@ -0,0 +1,86 @@
1
+ import { useDialog } from "@tui/ui/dialog"
2
+ import { DialogSelect } from "@tui/ui/dialog-select"
3
+ import { createMemo, createSignal } from "solid-js"
4
+ import { Locale } from "@/util/locale"
5
+ import { Keybind } from "@/util/keybind"
6
+ import { useTheme } from "../context/theme"
7
+ import { usePromptStash, type StashEntry } from "./prompt/stash"
8
+
9
+ function getRelativeTime(timestamp: number): string {
10
+ const now = Date.now()
11
+ const diff = now - timestamp
12
+ const seconds = Math.floor(diff / 1000)
13
+ const minutes = Math.floor(seconds / 60)
14
+ const hours = Math.floor(minutes / 60)
15
+ const days = Math.floor(hours / 24)
16
+
17
+ if (seconds < 60) return "just now"
18
+ if (minutes < 60) return `${minutes}m ago`
19
+ if (hours < 24) return `${hours}h ago`
20
+ if (days < 7) return `${days}d ago`
21
+ return Locale.datetime(timestamp)
22
+ }
23
+
24
+ function getStashPreview(input: string, maxLength: number = 50): string {
25
+ const firstLine = input.split("\n")[0].trim()
26
+ return Locale.truncate(firstLine, maxLength)
27
+ }
28
+
29
+ export function DialogStash(props: { onSelect: (entry: StashEntry) => void }) {
30
+ const dialog = useDialog()
31
+ const stash = usePromptStash()
32
+ const { theme } = useTheme()
33
+
34
+ const [toDelete, setToDelete] = createSignal<number>()
35
+
36
+ const options = createMemo(() => {
37
+ const entries = stash.list()
38
+ // Show most recent first
39
+ return entries
40
+ .map((entry, index) => {
41
+ const isDeleting = toDelete() === index
42
+ const lineCount = (entry.input.match(/\n/g)?.length ?? 0) + 1
43
+ return {
44
+ title: isDeleting ? "Press ctrl+d again to confirm" : getStashPreview(entry.input),
45
+ bg: isDeleting ? theme.error : undefined,
46
+ value: index,
47
+ description: getRelativeTime(entry.timestamp),
48
+ footer: lineCount > 1 ? `~${lineCount} lines` : undefined,
49
+ }
50
+ })
51
+ .toReversed()
52
+ })
53
+
54
+ return (
55
+ <DialogSelect
56
+ title="Stash"
57
+ options={options()}
58
+ onMove={() => {
59
+ setToDelete(undefined)
60
+ }}
61
+ onSelect={(option) => {
62
+ const entries = stash.list()
63
+ const entry = entries[option.value]
64
+ if (entry) {
65
+ stash.remove(option.value)
66
+ props.onSelect(entry)
67
+ }
68
+ dialog.clear()
69
+ }}
70
+ keybind={[
71
+ {
72
+ keybind: Keybind.parse("ctrl+d")[0],
73
+ title: "delete",
74
+ onTrigger: (option) => {
75
+ if (toDelete() === option.value) {
76
+ stash.remove(option.value)
77
+ setToDelete(undefined)
78
+ return
79
+ }
80
+ setToDelete(option.value)
81
+ },
82
+ },
83
+ ]}
84
+ />
85
+ )
86
+ }
@@ -0,0 +1,162 @@
1
+ import { TextAttributes } from "@opentui/core"
2
+ import { useTheme } from "../context/theme"
3
+ import { useSync } from "@tui/context/sync"
4
+ import { For, Match, Switch, Show, createMemo } from "solid-js"
5
+
6
+ export type DialogStatusProps = {}
7
+
8
+ export function DialogStatus() {
9
+ const sync = useSync()
10
+ const { theme } = useTheme()
11
+
12
+ const enabledFormatters = createMemo(() => sync.data.formatter.filter((f) => f.enabled))
13
+
14
+ const plugins = createMemo(() => {
15
+ const list = sync.data.config.plugin ?? []
16
+ const result = list.map((value) => {
17
+ if (value.startsWith("file://")) {
18
+ const path = value.substring("file://".length)
19
+ const parts = path.split("/")
20
+ const filename = parts.pop() || path
21
+ if (!filename.includes(".")) return { name: filename }
22
+ const basename = filename.split(".")[0]
23
+ if (basename === "index") {
24
+ const dirname = parts.pop()
25
+ const name = dirname || basename
26
+ return { name }
27
+ }
28
+ return { name: basename }
29
+ }
30
+ const index = value.lastIndexOf("@")
31
+ if (index <= 0) return { name: value, version: "latest" }
32
+ const name = value.substring(0, index)
33
+ const version = value.substring(index + 1)
34
+ return { name, version }
35
+ })
36
+ return result.toSorted((a, b) => a.name.localeCompare(b.name))
37
+ })
38
+
39
+ return (
40
+ <box paddingLeft={2} paddingRight={2} gap={1} paddingBottom={1}>
41
+ <box flexDirection="row" justifyContent="space-between">
42
+ <text fg={theme.text} attributes={TextAttributes.BOLD}>
43
+ Status
44
+ </text>
45
+ <text fg={theme.textMuted}>esc</text>
46
+ </box>
47
+ <Show when={Object.keys(sync.data.mcp).length > 0} fallback={<text fg={theme.text}>No MCP Servers</text>}>
48
+ <box>
49
+ <text fg={theme.text}>{Object.keys(sync.data.mcp).length} MCP Servers</text>
50
+ <For each={Object.entries(sync.data.mcp)}>
51
+ {([key, item]) => (
52
+ <box flexDirection="row" gap={1}>
53
+ <text
54
+ flexShrink={0}
55
+ style={{
56
+ fg: (
57
+ {
58
+ connected: theme.success,
59
+ failed: theme.error,
60
+ disabled: theme.textMuted,
61
+ needs_auth: theme.warning,
62
+ needs_client_registration: theme.error,
63
+ } as Record<string, typeof theme.success>
64
+ )[item.status],
65
+ }}
66
+ >
67
+
68
+ </text>
69
+ <text fg={theme.text} wrapMode="word">
70
+ <b>{key}</b>{" "}
71
+ <span style={{ fg: theme.textMuted }}>
72
+ <Switch fallback={item.status}>
73
+ <Match when={item.status === "connected"}>Connected</Match>
74
+ <Match when={item.status === "failed" && item}>{(val) => val().error}</Match>
75
+ <Match when={item.status === "disabled"}>Disabled in configuration</Match>
76
+ <Match when={(item.status as string) === "needs_auth"}>
77
+ Needs authentication (run: opencode mcp auth {key})
78
+ </Match>
79
+ <Match when={(item.status as string) === "needs_client_registration" && item}>
80
+ {(val) => (val() as { error: string }).error}
81
+ </Match>
82
+ </Switch>
83
+ </span>
84
+ </text>
85
+ </box>
86
+ )}
87
+ </For>
88
+ </box>
89
+ </Show>
90
+ {sync.data.lsp.length > 0 && (
91
+ <box>
92
+ <text fg={theme.text}>{sync.data.lsp.length} LSP Servers</text>
93
+ <For each={sync.data.lsp}>
94
+ {(item) => (
95
+ <box flexDirection="row" gap={1}>
96
+ <text
97
+ flexShrink={0}
98
+ style={{
99
+ fg: {
100
+ connected: theme.success,
101
+ error: theme.error,
102
+ }[item.status],
103
+ }}
104
+ >
105
+
106
+ </text>
107
+ <text fg={theme.text} wrapMode="word">
108
+ <b>{item.id}</b> <span style={{ fg: theme.textMuted }}>{item.root}</span>
109
+ </text>
110
+ </box>
111
+ )}
112
+ </For>
113
+ </box>
114
+ )}
115
+ <Show when={enabledFormatters().length > 0} fallback={<text fg={theme.text}>No Formatters</text>}>
116
+ <box>
117
+ <text fg={theme.text}>{enabledFormatters().length} Formatters</text>
118
+ <For each={enabledFormatters()}>
119
+ {(item) => (
120
+ <box flexDirection="row" gap={1}>
121
+ <text
122
+ flexShrink={0}
123
+ style={{
124
+ fg: theme.success,
125
+ }}
126
+ >
127
+
128
+ </text>
129
+ <text wrapMode="word" fg={theme.text}>
130
+ <b>{item.name}</b>
131
+ </text>
132
+ </box>
133
+ )}
134
+ </For>
135
+ </box>
136
+ </Show>
137
+ <Show when={plugins().length > 0} fallback={<text fg={theme.text}>No Plugins</text>}>
138
+ <box>
139
+ <text fg={theme.text}>{plugins().length} Plugins</text>
140
+ <For each={plugins()}>
141
+ {(item) => (
142
+ <box flexDirection="row" gap={1}>
143
+ <text
144
+ flexShrink={0}
145
+ style={{
146
+ fg: theme.success,
147
+ }}
148
+ >
149
+
150
+ </text>
151
+ <text wrapMode="word" fg={theme.text}>
152
+ <b>{item.name}</b>
153
+ {item.version && <span style={{ fg: theme.textMuted }}> @{item.version}</span>}
154
+ </text>
155
+ </box>
156
+ )}
157
+ </For>
158
+ </box>
159
+ </Show>
160
+ </box>
161
+ )
162
+ }
@@ -0,0 +1,44 @@
1
+ import { createMemo, createResource } from "solid-js"
2
+ import { DialogSelect } from "@tui/ui/dialog-select"
3
+ import { useDialog } from "@tui/ui/dialog"
4
+ import { useSDK } from "@tui/context/sdk"
5
+ import { createStore } from "solid-js/store"
6
+
7
+ export function DialogTag(props: { onSelect?: (value: string) => void }) {
8
+ const sdk = useSDK()
9
+ const dialog = useDialog()
10
+
11
+ const [store] = createStore({
12
+ filter: "",
13
+ })
14
+
15
+ const [files] = createResource(
16
+ () => [store.filter],
17
+ async () => {
18
+ const result = await sdk.client.find.files({
19
+ query: store.filter,
20
+ })
21
+ if (result.error) return []
22
+ const sliced = (result.data ?? []).slice(0, 5)
23
+ return sliced
24
+ },
25
+ )
26
+
27
+ const options = createMemo(() =>
28
+ (files() ?? []).map((file) => ({
29
+ value: file,
30
+ title: file,
31
+ })),
32
+ )
33
+
34
+ return (
35
+ <DialogSelect
36
+ title="Autocomplete"
37
+ options={options()}
38
+ onSelect={(option) => {
39
+ props.onSelect?.(option.value)
40
+ dialog.clear()
41
+ }}
42
+ />
43
+ )
44
+ }