rird 1.0.200

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 (350) hide show
  1. package/AGENTS.md +27 -0
  2. package/Dockerfile +18 -0
  3. package/README.md +15 -0
  4. package/bin/opencode +336 -0
  5. package/bin/pty-wrapper.js +285 -0
  6. package/bunfig.toml +4 -0
  7. package/facebook_ads_library.png +0 -0
  8. package/nul`nif +0 -0
  9. package/package.json +111 -0
  10. package/parsers-config.ts +239 -0
  11. package/rird-1.0.199.tgz +0 -0
  12. package/script/build-windows.ts +54 -0
  13. package/script/build.ts +167 -0
  14. package/script/postinstall.mjs +544 -0
  15. package/script/publish-registries.ts +187 -0
  16. package/script/publish.ts +72 -0
  17. package/script/schema.ts +47 -0
  18. package/src/acp/README.md +164 -0
  19. package/src/acp/agent.ts +1063 -0
  20. package/src/acp/session.ts +101 -0
  21. package/src/acp/types.ts +22 -0
  22. package/src/agent/agent.ts +367 -0
  23. package/src/agent/generate.txt +75 -0
  24. package/src/agent/prompt/compaction.txt +12 -0
  25. package/src/agent/prompt/explore.txt +18 -0
  26. package/src/agent/prompt/summary.txt +10 -0
  27. package/src/agent/prompt/title.txt +36 -0
  28. package/src/auth/index.ts +70 -0
  29. package/src/bun/index.ts +114 -0
  30. package/src/bus/bus-event.ts +43 -0
  31. package/src/bus/global.ts +10 -0
  32. package/src/bus/index.ts +105 -0
  33. package/src/cli/bootstrap.ts +17 -0
  34. package/src/cli/cmd/acp.ts +88 -0
  35. package/src/cli/cmd/agent.ts +256 -0
  36. package/src/cli/cmd/auth.ts +391 -0
  37. package/src/cli/cmd/cmd.ts +7 -0
  38. package/src/cli/cmd/debug/config.ts +15 -0
  39. package/src/cli/cmd/debug/file.ts +91 -0
  40. package/src/cli/cmd/debug/index.ts +43 -0
  41. package/src/cli/cmd/debug/lsp.ts +48 -0
  42. package/src/cli/cmd/debug/ripgrep.ts +83 -0
  43. package/src/cli/cmd/debug/scrap.ts +15 -0
  44. package/src/cli/cmd/debug/skill.ts +15 -0
  45. package/src/cli/cmd/debug/snapshot.ts +48 -0
  46. package/src/cli/cmd/export.ts +88 -0
  47. package/src/cli/cmd/generate.ts +38 -0
  48. package/src/cli/cmd/github.ts +1400 -0
  49. package/src/cli/cmd/import.ts +98 -0
  50. package/src/cli/cmd/mcp.ts +654 -0
  51. package/src/cli/cmd/models.ts +77 -0
  52. package/src/cli/cmd/pr.ts +112 -0
  53. package/src/cli/cmd/run.ts +368 -0
  54. package/src/cli/cmd/serve.ts +31 -0
  55. package/src/cli/cmd/session.ts +106 -0
  56. package/src/cli/cmd/stats.ts +298 -0
  57. package/src/cli/cmd/tui/app.tsx +696 -0
  58. package/src/cli/cmd/tui/attach.ts +30 -0
  59. package/src/cli/cmd/tui/component/border.tsx +21 -0
  60. package/src/cli/cmd/tui/component/dialog-agent.tsx +31 -0
  61. package/src/cli/cmd/tui/component/dialog-command.tsx +124 -0
  62. package/src/cli/cmd/tui/component/dialog-mcp.tsx +86 -0
  63. package/src/cli/cmd/tui/component/dialog-model.tsx +245 -0
  64. package/src/cli/cmd/tui/component/dialog-provider.tsx +224 -0
  65. package/src/cli/cmd/tui/component/dialog-session-list.tsx +102 -0
  66. package/src/cli/cmd/tui/component/dialog-session-rename.tsx +31 -0
  67. package/src/cli/cmd/tui/component/dialog-stash.tsx +86 -0
  68. package/src/cli/cmd/tui/component/dialog-status.tsx +162 -0
  69. package/src/cli/cmd/tui/component/dialog-tag.tsx +44 -0
  70. package/src/cli/cmd/tui/component/dialog-theme-list.tsx +50 -0
  71. package/src/cli/cmd/tui/component/did-you-know.tsx +85 -0
  72. package/src/cli/cmd/tui/component/logo.tsx +35 -0
  73. package/src/cli/cmd/tui/component/prompt/autocomplete.tsx +574 -0
  74. package/src/cli/cmd/tui/component/prompt/history.tsx +108 -0
  75. package/src/cli/cmd/tui/component/prompt/index.tsx +1090 -0
  76. package/src/cli/cmd/tui/component/prompt/stash.tsx +101 -0
  77. package/src/cli/cmd/tui/component/tips.ts +27 -0
  78. package/src/cli/cmd/tui/component/todo-item.tsx +32 -0
  79. package/src/cli/cmd/tui/context/args.tsx +14 -0
  80. package/src/cli/cmd/tui/context/directory.ts +13 -0
  81. package/src/cli/cmd/tui/context/exit.tsx +23 -0
  82. package/src/cli/cmd/tui/context/helper.tsx +25 -0
  83. package/src/cli/cmd/tui/context/keybind.tsx +101 -0
  84. package/src/cli/cmd/tui/context/kv.tsx +49 -0
  85. package/src/cli/cmd/tui/context/local.tsx +354 -0
  86. package/src/cli/cmd/tui/context/prompt.tsx +18 -0
  87. package/src/cli/cmd/tui/context/route.tsx +46 -0
  88. package/src/cli/cmd/tui/context/sdk.tsx +74 -0
  89. package/src/cli/cmd/tui/context/sync.tsx +372 -0
  90. package/src/cli/cmd/tui/context/theme/aura.json +69 -0
  91. package/src/cli/cmd/tui/context/theme/ayu.json +80 -0
  92. package/src/cli/cmd/tui/context/theme/catppuccin-frappe.json +233 -0
  93. package/src/cli/cmd/tui/context/theme/catppuccin-macchiato.json +233 -0
  94. package/src/cli/cmd/tui/context/theme/catppuccin.json +112 -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/palenight.json +222 -0
  113. package/src/cli/cmd/tui/context/theme/rird.json +245 -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 +1109 -0
  122. package/src/cli/cmd/tui/event.ts +40 -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 +1864 -0
  131. package/src/cli/cmd/tui/routes/session/sidebar.tsx +318 -0
  132. package/src/cli/cmd/tui/spawn.ts +60 -0
  133. package/src/cli/cmd/tui/thread.ts +142 -0
  134. package/src/cli/cmd/tui/ui/dialog-alert.tsx +57 -0
  135. package/src/cli/cmd/tui/ui/dialog-confirm.tsx +83 -0
  136. package/src/cli/cmd/tui/ui/dialog-help.tsx +38 -0
  137. package/src/cli/cmd/tui/ui/dialog-prompt.tsx +77 -0
  138. package/src/cli/cmd/tui/ui/dialog-select.tsx +332 -0
  139. package/src/cli/cmd/tui/ui/dialog.tsx +170 -0
  140. package/src/cli/cmd/tui/ui/spinner.ts +368 -0
  141. package/src/cli/cmd/tui/ui/toast.tsx +100 -0
  142. package/src/cli/cmd/tui/util/clipboard.ts +127 -0
  143. package/src/cli/cmd/tui/util/editor.ts +32 -0
  144. package/src/cli/cmd/tui/util/terminal.ts +114 -0
  145. package/src/cli/cmd/tui/worker.ts +63 -0
  146. package/src/cli/cmd/uninstall.ts +344 -0
  147. package/src/cli/cmd/upgrade.ts +100 -0
  148. package/src/cli/cmd/web.ts +84 -0
  149. package/src/cli/error.ts +56 -0
  150. package/src/cli/ui.ts +84 -0
  151. package/src/cli/upgrade.ts +25 -0
  152. package/src/command/index.ts +80 -0
  153. package/src/command/template/initialize.txt +10 -0
  154. package/src/command/template/review.txt +97 -0
  155. package/src/config/config.ts +995 -0
  156. package/src/config/markdown.ts +41 -0
  157. package/src/env/index.ts +26 -0
  158. package/src/file/ignore.ts +83 -0
  159. package/src/file/index.ts +328 -0
  160. package/src/file/ripgrep.ts +393 -0
  161. package/src/file/time.ts +64 -0
  162. package/src/file/watcher.ts +103 -0
  163. package/src/flag/flag.ts +46 -0
  164. package/src/format/formatter.ts +315 -0
  165. package/src/format/index.ts +137 -0
  166. package/src/global/index.ts +52 -0
  167. package/src/id/id.ts +73 -0
  168. package/src/ide/index.ts +76 -0
  169. package/src/index.ts +240 -0
  170. package/src/installation/index.ts +239 -0
  171. package/src/lsp/client.ts +229 -0
  172. package/src/lsp/index.ts +485 -0
  173. package/src/lsp/language.ts +116 -0
  174. package/src/lsp/server.ts +1895 -0
  175. package/src/mcp/auth.ts +135 -0
  176. package/src/mcp/index.ts +690 -0
  177. package/src/mcp/oauth-callback.ts +200 -0
  178. package/src/mcp/oauth-provider.ts +154 -0
  179. package/src/patch/index.ts +622 -0
  180. package/src/permission/index.ts +199 -0
  181. package/src/plugin/index.ts +91 -0
  182. package/src/project/bootstrap.ts +31 -0
  183. package/src/project/instance.ts +78 -0
  184. package/src/project/project.ts +221 -0
  185. package/src/project/state.ts +65 -0
  186. package/src/project/vcs.ts +76 -0
  187. package/src/provider/auth.ts +143 -0
  188. package/src/provider/models-macro.ts +11 -0
  189. package/src/provider/models.ts +106 -0
  190. package/src/provider/provider.ts +1071 -0
  191. package/src/provider/sdk/openai-compatible/src/README.md +5 -0
  192. package/src/provider/sdk/openai-compatible/src/index.ts +2 -0
  193. package/src/provider/sdk/openai-compatible/src/openai-compatible-provider.ts +100 -0
  194. package/src/provider/sdk/openai-compatible/src/responses/convert-to-openai-responses-input.ts +303 -0
  195. package/src/provider/sdk/openai-compatible/src/responses/map-openai-responses-finish-reason.ts +22 -0
  196. package/src/provider/sdk/openai-compatible/src/responses/openai-config.ts +18 -0
  197. package/src/provider/sdk/openai-compatible/src/responses/openai-error.ts +22 -0
  198. package/src/provider/sdk/openai-compatible/src/responses/openai-responses-api-types.ts +207 -0
  199. package/src/provider/sdk/openai-compatible/src/responses/openai-responses-language-model.ts +1713 -0
  200. package/src/provider/sdk/openai-compatible/src/responses/openai-responses-prepare-tools.ts +177 -0
  201. package/src/provider/sdk/openai-compatible/src/responses/openai-responses-settings.ts +1 -0
  202. package/src/provider/sdk/openai-compatible/src/responses/tool/code-interpreter.ts +88 -0
  203. package/src/provider/sdk/openai-compatible/src/responses/tool/file-search.ts +128 -0
  204. package/src/provider/sdk/openai-compatible/src/responses/tool/image-generation.ts +115 -0
  205. package/src/provider/sdk/openai-compatible/src/responses/tool/local-shell.ts +65 -0
  206. package/src/provider/sdk/openai-compatible/src/responses/tool/web-search-preview.ts +104 -0
  207. package/src/provider/sdk/openai-compatible/src/responses/tool/web-search.ts +103 -0
  208. package/src/provider/transform.ts +455 -0
  209. package/src/pty/index.ts +231 -0
  210. package/src/security/guardrails.test.ts +341 -0
  211. package/src/security/guardrails.ts +558 -0
  212. package/src/security/index.ts +19 -0
  213. package/src/server/error.ts +36 -0
  214. package/src/server/project.ts +79 -0
  215. package/src/server/server.ts +2642 -0
  216. package/src/server/tui.ts +71 -0
  217. package/src/session/compaction.ts +223 -0
  218. package/src/session/index.ts +461 -0
  219. package/src/session/llm.ts +201 -0
  220. package/src/session/message-v2.ts +690 -0
  221. package/src/session/message.ts +189 -0
  222. package/src/session/processor.ts +409 -0
  223. package/src/session/prompt/act-switch.txt +5 -0
  224. package/src/session/prompt/anthropic-20250930.txt +166 -0
  225. package/src/session/prompt/anthropic.txt +85 -0
  226. package/src/session/prompt/anthropic_spoof.txt +1 -0
  227. package/src/session/prompt/beast.txt +103 -0
  228. package/src/session/prompt/codex.txt +304 -0
  229. package/src/session/prompt/copilot-gpt-5.txt +138 -0
  230. package/src/session/prompt/gemini.txt +85 -0
  231. package/src/session/prompt/max-steps.txt +16 -0
  232. package/src/session/prompt/plan-reminder-anthropic.txt +35 -0
  233. package/src/session/prompt/plan.txt +24 -0
  234. package/src/session/prompt/polaris.txt +84 -0
  235. package/src/session/prompt/qwen.txt +106 -0
  236. package/src/session/prompt.ts +1509 -0
  237. package/src/session/retry.ts +86 -0
  238. package/src/session/revert.ts +108 -0
  239. package/src/session/sensitive-filter.test.ts +327 -0
  240. package/src/session/sensitive-filter.ts +466 -0
  241. package/src/session/status.ts +76 -0
  242. package/src/session/summary.ts +194 -0
  243. package/src/session/system.ts +120 -0
  244. package/src/session/todo.ts +37 -0
  245. package/src/share/share-next.ts +194 -0
  246. package/src/share/share.ts +87 -0
  247. package/src/shell/shell.ts +67 -0
  248. package/src/skill/index.ts +1 -0
  249. package/src/skill/skill.ts +83 -0
  250. package/src/snapshot/index.ts +197 -0
  251. package/src/storage/storage.ts +226 -0
  252. package/src/tests/agent.test.ts +308 -0
  253. package/src/tests/build-guards.test.ts +267 -0
  254. package/src/tests/config.test.ts +664 -0
  255. package/src/tests/tool-registry.test.ts +589 -0
  256. package/src/tool/bash.ts +317 -0
  257. package/src/tool/bash.txt +158 -0
  258. package/src/tool/batch.ts +175 -0
  259. package/src/tool/batch.txt +24 -0
  260. package/src/tool/codesearch.ts +168 -0
  261. package/src/tool/codesearch.txt +12 -0
  262. package/src/tool/edit.ts +675 -0
  263. package/src/tool/edit.txt +10 -0
  264. package/src/tool/glob.ts +65 -0
  265. package/src/tool/glob.txt +6 -0
  266. package/src/tool/grep.ts +121 -0
  267. package/src/tool/grep.txt +8 -0
  268. package/src/tool/invalid.ts +17 -0
  269. package/src/tool/ls.ts +110 -0
  270. package/src/tool/ls.txt +1 -0
  271. package/src/tool/lsp-diagnostics.ts +26 -0
  272. package/src/tool/lsp-diagnostics.txt +1 -0
  273. package/src/tool/lsp-hover.ts +31 -0
  274. package/src/tool/lsp-hover.txt +1 -0
  275. package/src/tool/lsp.ts +87 -0
  276. package/src/tool/lsp.txt +19 -0
  277. package/src/tool/multiedit.ts +46 -0
  278. package/src/tool/multiedit.txt +41 -0
  279. package/src/tool/patch.ts +233 -0
  280. package/src/tool/patch.txt +1 -0
  281. package/src/tool/read.ts +219 -0
  282. package/src/tool/read.txt +12 -0
  283. package/src/tool/registry.ts +162 -0
  284. package/src/tool/skill.ts +100 -0
  285. package/src/tool/task.ts +136 -0
  286. package/src/tool/task.txt +51 -0
  287. package/src/tool/todo.ts +39 -0
  288. package/src/tool/todoread.txt +14 -0
  289. package/src/tool/todowrite.txt +167 -0
  290. package/src/tool/tool.ts +71 -0
  291. package/src/tool/webfetch.ts +198 -0
  292. package/src/tool/webfetch.txt +13 -0
  293. package/src/tool/websearch.ts +180 -0
  294. package/src/tool/websearch.txt +11 -0
  295. package/src/tool/write.ts +110 -0
  296. package/src/tool/write.txt +8 -0
  297. package/src/util/archive.ts +16 -0
  298. package/src/util/color.ts +19 -0
  299. package/src/util/context.ts +25 -0
  300. package/src/util/defer.ts +12 -0
  301. package/src/util/eventloop.ts +20 -0
  302. package/src/util/filesystem.ts +83 -0
  303. package/src/util/fn.ts +11 -0
  304. package/src/util/iife.ts +3 -0
  305. package/src/util/keybind.ts +102 -0
  306. package/src/util/lazy.ts +11 -0
  307. package/src/util/license.ts +325 -0
  308. package/src/util/locale.ts +81 -0
  309. package/src/util/lock.ts +98 -0
  310. package/src/util/log.ts +180 -0
  311. package/src/util/queue.ts +32 -0
  312. package/src/util/rpc.ts +42 -0
  313. package/src/util/scrap.ts +10 -0
  314. package/src/util/signal.ts +12 -0
  315. package/src/util/timeout.ts +14 -0
  316. package/src/util/token.ts +7 -0
  317. package/src/util/wildcard.ts +54 -0
  318. package/sst-env.d.ts +9 -0
  319. package/test/agent/agent.test.ts +146 -0
  320. package/test/bun.test.ts +53 -0
  321. package/test/cli/github-remote.test.ts +80 -0
  322. package/test/config/agent-color.test.ts +66 -0
  323. package/test/config/config.test.ts +535 -0
  324. package/test/config/markdown.test.ts +89 -0
  325. package/test/file/ignore.test.ts +10 -0
  326. package/test/fixture/fixture.ts +36 -0
  327. package/test/fixture/lsp/fake-lsp-server.js +77 -0
  328. package/test/ide/ide.test.ts +82 -0
  329. package/test/keybind.test.ts +421 -0
  330. package/test/lsp/client.test.ts +95 -0
  331. package/test/mcp/headers.test.ts +153 -0
  332. package/test/patch/patch.test.ts +348 -0
  333. package/test/preload.ts +57 -0
  334. package/test/project/project.test.ts +72 -0
  335. package/test/provider/provider.test.ts +1809 -0
  336. package/test/provider/transform.test.ts +411 -0
  337. package/test/session/retry.test.ts +111 -0
  338. package/test/session/session.test.ts +71 -0
  339. package/test/skill/skill.test.ts +131 -0
  340. package/test/snapshot/snapshot.test.ts +939 -0
  341. package/test/tool/__snapshots__/tool.test.ts.snap +9 -0
  342. package/test/tool/bash.test.ts +434 -0
  343. package/test/tool/grep.test.ts +108 -0
  344. package/test/tool/patch.test.ts +259 -0
  345. package/test/tool/read.test.ts +42 -0
  346. package/test/util/iife.test.ts +36 -0
  347. package/test/util/lazy.test.ts +50 -0
  348. package/test/util/timeout.test.ts +21 -0
  349. package/test/util/wildcard.test.ts +55 -0
  350. package/tsconfig.json +16 -0
@@ -0,0 +1,224 @@
1
+ import { createMemo, createSignal, onMount, Show } from "solid-js"
2
+ import { useSync } from "@tui/context/sync"
3
+ import { map, pipe, sortBy } from "remeda"
4
+ import { DialogSelect } from "@tui/ui/dialog-select"
5
+ import { useDialog } from "@tui/ui/dialog"
6
+ import { useSDK } from "../context/sdk"
7
+ import { DialogPrompt } from "../ui/dialog-prompt"
8
+ import { useTheme } from "../context/theme"
9
+ import { TextAttributes } from "@opentui/core"
10
+ import type { ProviderAuthAuthorization } from "@opencode-ai/sdk/v2"
11
+ import { DialogModel } from "./dialog-model"
12
+
13
+ const PROVIDER_PRIORITY: Record<string, number> = {
14
+ rird: 0,
15
+ anthropic: 1,
16
+ "github-copilot": 2,
17
+ openai: 3,
18
+ google: 4,
19
+ openrouter: 5,
20
+ }
21
+
22
+ export function createDialogProviderOptions() {
23
+ const sync = useSync()
24
+ const dialog = useDialog()
25
+ const sdk = useSDK()
26
+ const options = createMemo(() => {
27
+ return pipe(
28
+ sync.data.provider_next.all,
29
+ sortBy((x) => PROVIDER_PRIORITY[x.id] ?? 99),
30
+ map((provider) => ({
31
+ title: provider.name,
32
+ value: provider.id,
33
+ description: {
34
+ rird: "(Recommended)",
35
+ anthropic: "(Claude Max or API key)",
36
+ }[provider.id],
37
+ category: provider.id in PROVIDER_PRIORITY ? "Popular" : "Other",
38
+ async onSelect() {
39
+ const methods = sync.data.provider_auth[provider.id] ?? [
40
+ {
41
+ type: "api",
42
+ label: "API key",
43
+ },
44
+ ]
45
+ let index: number | null = 0
46
+ if (methods.length > 1) {
47
+ index = await new Promise<number | null>((resolve) => {
48
+ dialog.replace(
49
+ () => (
50
+ <DialogSelect
51
+ title="Select auth method"
52
+ options={methods.map((x, index) => ({
53
+ title: x.label,
54
+ value: index,
55
+ }))}
56
+ onSelect={(option) => resolve(option.value)}
57
+ />
58
+ ),
59
+ () => resolve(null),
60
+ )
61
+ })
62
+ }
63
+ if (index == null) return
64
+ const method = methods[index]
65
+ if (method.type === "oauth") {
66
+ const result = await sdk.client.provider.oauth.authorize({
67
+ providerID: provider.id,
68
+ method: index,
69
+ })
70
+ if (result.data?.method === "code") {
71
+ dialog.replace(() => (
72
+ <CodeMethod providerID={provider.id} title={method.label} index={index} authorization={result.data!} />
73
+ ))
74
+ }
75
+ if (result.data?.method === "auto") {
76
+ dialog.replace(() => (
77
+ <AutoMethod providerID={provider.id} title={method.label} index={index} authorization={result.data!} />
78
+ ))
79
+ }
80
+ }
81
+ if (method.type === "api") {
82
+ return dialog.replace(() => <ApiMethod providerID={provider.id} title={method.label} />)
83
+ }
84
+ },
85
+ })),
86
+ )
87
+ })
88
+ return options
89
+ }
90
+
91
+ export function DialogProvider() {
92
+ const options = createDialogProviderOptions()
93
+ return <DialogSelect title="Connect a provider" options={options()} />
94
+ }
95
+
96
+ interface AutoMethodProps {
97
+ index: number
98
+ providerID: string
99
+ title: string
100
+ authorization: ProviderAuthAuthorization
101
+ }
102
+ function AutoMethod(props: AutoMethodProps) {
103
+ const { theme } = useTheme()
104
+ const sdk = useSDK()
105
+ const dialog = useDialog()
106
+ const sync = useSync()
107
+
108
+ onMount(async () => {
109
+ const result = await sdk.client.provider.oauth.callback({
110
+ providerID: props.providerID,
111
+ method: props.index,
112
+ })
113
+ if (result.error) {
114
+ dialog.clear()
115
+ return
116
+ }
117
+ await sdk.client.instance.dispose()
118
+ await sync.bootstrap()
119
+ dialog.replace(() => <DialogModel providerID={props.providerID} />)
120
+ })
121
+
122
+ return (
123
+ <box paddingLeft={2} paddingRight={2} gap={1} paddingBottom={1}>
124
+ <box flexDirection="row" justifyContent="space-between">
125
+ <text attributes={TextAttributes.BOLD} fg={theme.text}>
126
+ {props.title}
127
+ </text>
128
+ <text fg={theme.textMuted}>esc</text>
129
+ </box>
130
+ <box gap={1}>
131
+ <text fg={theme.primary}>{props.authorization.url}</text>
132
+ <text fg={theme.textMuted}>{props.authorization.instructions}</text>
133
+ </box>
134
+ <text fg={theme.textMuted}>Waiting for authorization...</text>
135
+ </box>
136
+ )
137
+ }
138
+
139
+ interface CodeMethodProps {
140
+ index: number
141
+ title: string
142
+ providerID: string
143
+ authorization: ProviderAuthAuthorization
144
+ }
145
+ function CodeMethod(props: CodeMethodProps) {
146
+ const { theme } = useTheme()
147
+ const sdk = useSDK()
148
+ const sync = useSync()
149
+ const dialog = useDialog()
150
+ const [error, setError] = createSignal(false)
151
+
152
+ return (
153
+ <DialogPrompt
154
+ title={props.title}
155
+ placeholder="Authorization code"
156
+ onConfirm={async (value) => {
157
+ const { error } = await sdk.client.provider.oauth.callback({
158
+ providerID: props.providerID,
159
+ method: props.index,
160
+ code: value,
161
+ })
162
+ if (!error) {
163
+ await sdk.client.instance.dispose()
164
+ await sync.bootstrap()
165
+ dialog.replace(() => <DialogModel providerID={props.providerID} />)
166
+ return
167
+ }
168
+ setError(true)
169
+ }}
170
+ description={() => (
171
+ <box gap={1}>
172
+ <text fg={theme.textMuted}>{props.authorization.instructions}</text>
173
+ <text fg={theme.primary}>{props.authorization.url}</text>
174
+ <Show when={error()}>
175
+ <text fg={theme.error}>Invalid code</text>
176
+ </Show>
177
+ </box>
178
+ )}
179
+ />
180
+ )
181
+ }
182
+
183
+ interface ApiMethodProps {
184
+ providerID: string
185
+ title: string
186
+ }
187
+ function ApiMethod(props: ApiMethodProps) {
188
+ const dialog = useDialog()
189
+ const sdk = useSDK()
190
+ const sync = useSync()
191
+ const { theme } = useTheme()
192
+
193
+ return (
194
+ <DialogPrompt
195
+ title={props.title}
196
+ placeholder="API key"
197
+ description={
198
+ props.providerID === "rird" ? (
199
+ <box gap={1}>
200
+ <text fg={theme.textMuted}>
201
+ RIRD Mode gives you access to all the best AI models at the cheapest prices with a single API key.
202
+ </text>
203
+ <text fg={theme.text}>
204
+ Go to <span style={{ fg: theme.primary }}>https://rird.ai</span> to get a key
205
+ </text>
206
+ </box>
207
+ ) : undefined
208
+ }
209
+ onConfirm={async (value) => {
210
+ if (!value) return
211
+ sdk.client.auth.set({
212
+ providerID: props.providerID,
213
+ auth: {
214
+ type: "api",
215
+ key: value,
216
+ },
217
+ })
218
+ await sdk.client.instance.dispose()
219
+ await sync.bootstrap()
220
+ dialog.replace(() => <DialogModel providerID={props.providerID} />)
221
+ }}
222
+ />
223
+ )
224
+ }
@@ -0,0 +1,102 @@
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 { createEffect, createMemo, createSignal, onMount } 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 "opentui-spinner/solid"
12
+
13
+ export function DialogSessionList() {
14
+ const dialog = useDialog()
15
+ const sync = useSync()
16
+ const { theme } = useTheme()
17
+ const route = useRoute()
18
+ const sdk = useSDK()
19
+
20
+ const [toDelete, setToDelete] = createSignal<string>()
21
+
22
+ const deleteKeybind = "ctrl+d"
23
+
24
+ const currentSessionID = createMemo(() => (route.data.type === "session" ? route.data.sessionID : undefined))
25
+
26
+ const spinnerFrames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]
27
+
28
+ const options = createMemo(() => {
29
+ const today = new Date().toDateString()
30
+ return sync.data.session
31
+ .filter((x) => x.parentID === undefined)
32
+ .toSorted((a, b) => b.time.updated - a.time.updated)
33
+ .map((x) => {
34
+ const date = new Date(x.time.updated)
35
+ let category = date.toDateString()
36
+ if (category === today) {
37
+ category = "Today"
38
+ }
39
+ const isDeleting = toDelete() === x.id
40
+ const status = sync.data.session_status?.[x.id]
41
+ const isWorking = status?.type === "busy"
42
+ return {
43
+ title: isDeleting ? `Press ${deleteKeybind} again to confirm` : x.title,
44
+ bg: isDeleting ? theme.error : undefined,
45
+ value: x.id,
46
+ category,
47
+ footer: Locale.time(x.time.updated),
48
+ gutter: isWorking ? <spinner frames={spinnerFrames} interval={80} color={theme.primary} /> : undefined,
49
+ }
50
+ })
51
+ .slice(0, 150)
52
+ })
53
+
54
+ createEffect(() => {
55
+ console.log("session count", sync.data.session.length)
56
+ })
57
+
58
+ onMount(() => {
59
+ dialog.setSize("large")
60
+ })
61
+
62
+ return (
63
+ <DialogSelect
64
+ title="Sessions"
65
+ options={options()}
66
+ current={currentSessionID()}
67
+ onMove={() => {
68
+ setToDelete(undefined)
69
+ }}
70
+ onSelect={(option) => {
71
+ route.navigate({
72
+ type: "session",
73
+ sessionID: option.value,
74
+ })
75
+ dialog.clear()
76
+ }}
77
+ keybind={[
78
+ {
79
+ keybind: Keybind.parse(deleteKeybind)[0],
80
+ title: "delete",
81
+ onTrigger: async (option) => {
82
+ if (toDelete() === option.value) {
83
+ sdk.client.session.delete({
84
+ sessionID: option.value,
85
+ })
86
+ setToDelete(undefined)
87
+ return
88
+ }
89
+ setToDelete(option.value)
90
+ },
91
+ },
92
+ {
93
+ keybind: Keybind.parse("ctrl+r")[0],
94
+ title: "rename",
95
+ onTrigger: async (option) => {
96
+ dialog.replace(() => <DialogSessionRename session={option.value} />)
97
+ },
98
+ },
99
+ ]}
100
+ />
101
+ )
102
+ }
@@ -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: rird 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
+ }
@@ -0,0 +1,50 @@
1
+ import { DialogSelect, type DialogSelectRef } from "../ui/dialog-select"
2
+ import { useTheme } from "../context/theme"
3
+ import { useDialog } from "../ui/dialog"
4
+ import { onCleanup, onMount } from "solid-js"
5
+
6
+ export function DialogThemeList() {
7
+ const theme = useTheme()
8
+ const options = Object.keys(theme.all())
9
+ .sort((a, b) => a.localeCompare(b, undefined, { sensitivity: "base" }))
10
+ .map((value) => ({
11
+ title: value,
12
+ value: value,
13
+ }))
14
+ const dialog = useDialog()
15
+ let confirmed = false
16
+ let ref: DialogSelectRef<string>
17
+ const initial = theme.selected
18
+
19
+ onCleanup(() => {
20
+ if (!confirmed) theme.set(initial)
21
+ })
22
+
23
+ return (
24
+ <DialogSelect
25
+ title="Themes"
26
+ options={options}
27
+ current={initial}
28
+ onMove={(opt) => {
29
+ theme.set(opt.value)
30
+ }}
31
+ onSelect={(opt) => {
32
+ theme.set(opt.value)
33
+ confirmed = true
34
+ dialog.clear()
35
+ }}
36
+ ref={(r) => {
37
+ ref = r
38
+ }}
39
+ onFilter={(query) => {
40
+ if (query.length === 0) {
41
+ theme.set(initial)
42
+ return
43
+ }
44
+
45
+ const first = ref.filtered[0]
46
+ if (first) theme.set(first.value)
47
+ }}
48
+ />
49
+ )
50
+ }