bincode-cli 1.0.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 (300) hide show
  1. package/AGENTS.md +27 -0
  2. package/README.md +15 -0
  3. package/bin/bincode +98 -0
  4. package/bunfig.toml +4 -0
  5. package/package.json +124 -0
  6. package/parsers-config.ts +239 -0
  7. package/script/build.ts +167 -0
  8. package/script/postinstall.mjs +206 -0
  9. package/script/publish.ts +99 -0
  10. package/script/schema.ts +47 -0
  11. package/src/acp/README.md +164 -0
  12. package/src/acp/agent.ts +1051 -0
  13. package/src/acp/session.ts +101 -0
  14. package/src/acp/types.ts +22 -0
  15. package/src/agent/agent.ts +398 -0
  16. package/src/agent/generate.txt +75 -0
  17. package/src/agent/prompt/compaction.txt +12 -0
  18. package/src/agent/prompt/explore.txt +18 -0
  19. package/src/agent/prompt/summary.txt +10 -0
  20. package/src/agent/prompt/title.txt +36 -0
  21. package/src/auth/bineric-login.ts +506 -0
  22. package/src/auth/index.ts +70 -0
  23. package/src/bun/index.ts +114 -0
  24. package/src/bus/bus-event.ts +43 -0
  25. package/src/bus/global.ts +10 -0
  26. package/src/bus/index.ts +105 -0
  27. package/src/cli/auth-check.ts +61 -0
  28. package/src/cli/bootstrap.ts +21 -0
  29. package/src/cli/cmd/acp.ts +88 -0
  30. package/src/cli/cmd/agent.ts +256 -0
  31. package/src/cli/cmd/auth.ts +436 -0
  32. package/src/cli/cmd/cmd.ts +7 -0
  33. package/src/cli/cmd/debug/config.ts +15 -0
  34. package/src/cli/cmd/debug/file.ts +91 -0
  35. package/src/cli/cmd/debug/index.ts +43 -0
  36. package/src/cli/cmd/debug/lsp.ts +48 -0
  37. package/src/cli/cmd/debug/ripgrep.ts +83 -0
  38. package/src/cli/cmd/debug/scrap.ts +15 -0
  39. package/src/cli/cmd/debug/skill.ts +15 -0
  40. package/src/cli/cmd/debug/snapshot.ts +48 -0
  41. package/src/cli/cmd/export.ts +88 -0
  42. package/src/cli/cmd/generate.ts +38 -0
  43. package/src/cli/cmd/github.ts +1399 -0
  44. package/src/cli/cmd/import.ts +98 -0
  45. package/src/cli/cmd/login.ts +112 -0
  46. package/src/cli/cmd/logout.ts +38 -0
  47. package/src/cli/cmd/mcp.ts +654 -0
  48. package/src/cli/cmd/models.ts +77 -0
  49. package/src/cli/cmd/pr.ts +112 -0
  50. package/src/cli/cmd/run.ts +368 -0
  51. package/src/cli/cmd/serve.ts +31 -0
  52. package/src/cli/cmd/session.ts +106 -0
  53. package/src/cli/cmd/stats.ts +298 -0
  54. package/src/cli/cmd/tui/app.tsx +669 -0
  55. package/src/cli/cmd/tui/attach.ts +30 -0
  56. package/src/cli/cmd/tui/component/border.tsx +21 -0
  57. package/src/cli/cmd/tui/component/dialog-agent.tsx +31 -0
  58. package/src/cli/cmd/tui/component/dialog-command.tsx +123 -0
  59. package/src/cli/cmd/tui/component/dialog-mcp.tsx +86 -0
  60. package/src/cli/cmd/tui/component/dialog-model.tsx +223 -0
  61. package/src/cli/cmd/tui/component/dialog-provider.tsx +224 -0
  62. package/src/cli/cmd/tui/component/dialog-session-list.tsx +102 -0
  63. package/src/cli/cmd/tui/component/dialog-session-rename.tsx +31 -0
  64. package/src/cli/cmd/tui/component/dialog-status.tsx +162 -0
  65. package/src/cli/cmd/tui/component/dialog-tag.tsx +44 -0
  66. package/src/cli/cmd/tui/component/dialog-theme-list.tsx +50 -0
  67. package/src/cli/cmd/tui/component/logo.tsx +32 -0
  68. package/src/cli/cmd/tui/component/prompt/autocomplete.tsx +560 -0
  69. package/src/cli/cmd/tui/component/prompt/history.tsx +108 -0
  70. package/src/cli/cmd/tui/component/prompt/index.tsx +1052 -0
  71. package/src/cli/cmd/tui/context/args.tsx +14 -0
  72. package/src/cli/cmd/tui/context/directory.ts +13 -0
  73. package/src/cli/cmd/tui/context/exit.tsx +23 -0
  74. package/src/cli/cmd/tui/context/helper.tsx +25 -0
  75. package/src/cli/cmd/tui/context/keybind.tsx +101 -0
  76. package/src/cli/cmd/tui/context/kv.tsx +49 -0
  77. package/src/cli/cmd/tui/context/local.tsx +339 -0
  78. package/src/cli/cmd/tui/context/prompt.tsx +18 -0
  79. package/src/cli/cmd/tui/context/route.tsx +46 -0
  80. package/src/cli/cmd/tui/context/sdk.tsx +74 -0
  81. package/src/cli/cmd/tui/context/sync.tsx +372 -0
  82. package/src/cli/cmd/tui/context/theme/aura.json +69 -0
  83. package/src/cli/cmd/tui/context/theme/ayu.json +80 -0
  84. package/src/cli/cmd/tui/context/theme/bincode.json +245 -0
  85. package/src/cli/cmd/tui/context/theme/catppuccin-frappe.json +233 -0
  86. package/src/cli/cmd/tui/context/theme/catppuccin-macchiato.json +233 -0
  87. package/src/cli/cmd/tui/context/theme/catppuccin.json +112 -0
  88. package/src/cli/cmd/tui/context/theme/cobalt2.json +228 -0
  89. package/src/cli/cmd/tui/context/theme/cursor.json +249 -0
  90. package/src/cli/cmd/tui/context/theme/dracula.json +219 -0
  91. package/src/cli/cmd/tui/context/theme/everforest.json +241 -0
  92. package/src/cli/cmd/tui/context/theme/flexoki.json +237 -0
  93. package/src/cli/cmd/tui/context/theme/github.json +233 -0
  94. package/src/cli/cmd/tui/context/theme/gruvbox.json +95 -0
  95. package/src/cli/cmd/tui/context/theme/kanagawa.json +77 -0
  96. package/src/cli/cmd/tui/context/theme/lucent-orng.json +227 -0
  97. package/src/cli/cmd/tui/context/theme/material.json +235 -0
  98. package/src/cli/cmd/tui/context/theme/matrix.json +77 -0
  99. package/src/cli/cmd/tui/context/theme/mercury.json +252 -0
  100. package/src/cli/cmd/tui/context/theme/monokai.json +221 -0
  101. package/src/cli/cmd/tui/context/theme/nightowl.json +221 -0
  102. package/src/cli/cmd/tui/context/theme/nord.json +223 -0
  103. package/src/cli/cmd/tui/context/theme/one-dark.json +84 -0
  104. package/src/cli/cmd/tui/context/theme/orng.json +245 -0
  105. package/src/cli/cmd/tui/context/theme/palenight.json +222 -0
  106. package/src/cli/cmd/tui/context/theme/rosepine.json +234 -0
  107. package/src/cli/cmd/tui/context/theme/solarized.json +223 -0
  108. package/src/cli/cmd/tui/context/theme/synthwave84.json +226 -0
  109. package/src/cli/cmd/tui/context/theme/tokyonight.json +243 -0
  110. package/src/cli/cmd/tui/context/theme/vercel.json +245 -0
  111. package/src/cli/cmd/tui/context/theme/vesper.json +218 -0
  112. package/src/cli/cmd/tui/context/theme/zenburn.json +223 -0
  113. package/src/cli/cmd/tui/context/theme.tsx +1109 -0
  114. package/src/cli/cmd/tui/event.ts +40 -0
  115. package/src/cli/cmd/tui/routes/home.tsx +105 -0
  116. package/src/cli/cmd/tui/routes/session/dialog-fork-from-timeline.tsx +64 -0
  117. package/src/cli/cmd/tui/routes/session/dialog-message.tsx +109 -0
  118. package/src/cli/cmd/tui/routes/session/dialog-subagent.tsx +26 -0
  119. package/src/cli/cmd/tui/routes/session/dialog-timeline.tsx +47 -0
  120. package/src/cli/cmd/tui/routes/session/footer.tsx +88 -0
  121. package/src/cli/cmd/tui/routes/session/header.tsx +141 -0
  122. package/src/cli/cmd/tui/routes/session/index.tsx +1888 -0
  123. package/src/cli/cmd/tui/routes/session/sidebar.tsx +321 -0
  124. package/src/cli/cmd/tui/spawn.ts +60 -0
  125. package/src/cli/cmd/tui/thread.ts +120 -0
  126. package/src/cli/cmd/tui/ui/dialog-alert.tsx +57 -0
  127. package/src/cli/cmd/tui/ui/dialog-confirm.tsx +83 -0
  128. package/src/cli/cmd/tui/ui/dialog-help.tsx +38 -0
  129. package/src/cli/cmd/tui/ui/dialog-prompt.tsx +77 -0
  130. package/src/cli/cmd/tui/ui/dialog-select.tsx +330 -0
  131. package/src/cli/cmd/tui/ui/dialog.tsx +170 -0
  132. package/src/cli/cmd/tui/ui/spinner.ts +368 -0
  133. package/src/cli/cmd/tui/ui/toast.tsx +100 -0
  134. package/src/cli/cmd/tui/util/clipboard.ts +127 -0
  135. package/src/cli/cmd/tui/util/editor.ts +32 -0
  136. package/src/cli/cmd/tui/util/terminal.ts +114 -0
  137. package/src/cli/cmd/tui/worker.ts +63 -0
  138. package/src/cli/cmd/uninstall.ts +344 -0
  139. package/src/cli/cmd/upgrade.ts +67 -0
  140. package/src/cli/cmd/web.ts +84 -0
  141. package/src/cli/error.ts +55 -0
  142. package/src/cli/ui.ts +84 -0
  143. package/src/cli/upgrade.ts +25 -0
  144. package/src/command/index.ts +80 -0
  145. package/src/command/template/initialize.txt +10 -0
  146. package/src/command/template/review.txt +97 -0
  147. package/src/config/config.ts +995 -0
  148. package/src/config/markdown.ts +41 -0
  149. package/src/env/index.ts +26 -0
  150. package/src/file/ignore.ts +83 -0
  151. package/src/file/index.ts +328 -0
  152. package/src/file/ripgrep.ts +393 -0
  153. package/src/file/time.ts +64 -0
  154. package/src/file/watcher.ts +103 -0
  155. package/src/flag/flag.ts +46 -0
  156. package/src/format/formatter.ts +315 -0
  157. package/src/format/index.ts +137 -0
  158. package/src/global/index.ts +52 -0
  159. package/src/id/id.ts +73 -0
  160. package/src/ide/index.ts +76 -0
  161. package/src/index.ts +217 -0
  162. package/src/installation/index.ts +196 -0
  163. package/src/lsp/client.ts +229 -0
  164. package/src/lsp/index.ts +485 -0
  165. package/src/lsp/language.ts +116 -0
  166. package/src/lsp/server.ts +1895 -0
  167. package/src/mcp/auth.ts +135 -0
  168. package/src/mcp/index.ts +654 -0
  169. package/src/mcp/oauth-callback.ts +200 -0
  170. package/src/mcp/oauth-provider.ts +154 -0
  171. package/src/patch/index.ts +622 -0
  172. package/src/permission/index.ts +199 -0
  173. package/src/plugin/index.ts +101 -0
  174. package/src/project/bootstrap.ts +31 -0
  175. package/src/project/instance.ts +78 -0
  176. package/src/project/project.ts +221 -0
  177. package/src/project/state.ts +65 -0
  178. package/src/project/vcs.ts +76 -0
  179. package/src/provider/auth.ts +143 -0
  180. package/src/provider/models-macro.ts +11 -0
  181. package/src/provider/models.ts +106 -0
  182. package/src/provider/provider.ts +1071 -0
  183. package/src/provider/sdk/openai-compatible/src/README.md +5 -0
  184. package/src/provider/sdk/openai-compatible/src/index.ts +2 -0
  185. package/src/provider/sdk/openai-compatible/src/openai-compatible-provider.ts +101 -0
  186. package/src/provider/sdk/openai-compatible/src/responses/convert-to-openai-responses-input.ts +303 -0
  187. package/src/provider/sdk/openai-compatible/src/responses/map-openai-responses-finish-reason.ts +22 -0
  188. package/src/provider/sdk/openai-compatible/src/responses/openai-config.ts +18 -0
  189. package/src/provider/sdk/openai-compatible/src/responses/openai-error.ts +22 -0
  190. package/src/provider/sdk/openai-compatible/src/responses/openai-responses-api-types.ts +207 -0
  191. package/src/provider/sdk/openai-compatible/src/responses/openai-responses-language-model.ts +1713 -0
  192. package/src/provider/sdk/openai-compatible/src/responses/openai-responses-prepare-tools.ts +177 -0
  193. package/src/provider/sdk/openai-compatible/src/responses/openai-responses-settings.ts +1 -0
  194. package/src/provider/sdk/openai-compatible/src/responses/tool/code-interpreter.ts +88 -0
  195. package/src/provider/sdk/openai-compatible/src/responses/tool/file-search.ts +128 -0
  196. package/src/provider/sdk/openai-compatible/src/responses/tool/image-generation.ts +115 -0
  197. package/src/provider/sdk/openai-compatible/src/responses/tool/local-shell.ts +65 -0
  198. package/src/provider/sdk/openai-compatible/src/responses/tool/web-search-preview.ts +104 -0
  199. package/src/provider/sdk/openai-compatible/src/responses/tool/web-search.ts +103 -0
  200. package/src/provider/transform.ts +455 -0
  201. package/src/pty/index.ts +231 -0
  202. package/src/server/error.ts +36 -0
  203. package/src/server/project.ts +79 -0
  204. package/src/server/server.ts +2642 -0
  205. package/src/server/tui.ts +71 -0
  206. package/src/session/compaction.ts +223 -0
  207. package/src/session/index.ts +458 -0
  208. package/src/session/llm.ts +201 -0
  209. package/src/session/message-v2.ts +659 -0
  210. package/src/session/message.ts +189 -0
  211. package/src/session/processor.ts +409 -0
  212. package/src/session/prompt/anthropic-20250930.txt +166 -0
  213. package/src/session/prompt/anthropic.txt +104 -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/polaris.txt +106 -0
  224. package/src/session/prompt/qwen.txt +109 -0
  225. package/src/session/prompt.ts +1446 -0
  226. package/src/session/retry.ts +86 -0
  227. package/src/session/revert.ts +108 -0
  228. package/src/session/status.ts +76 -0
  229. package/src/session/summary.ts +194 -0
  230. package/src/session/system.ts +120 -0
  231. package/src/session/todo.ts +37 -0
  232. package/src/share/share-next.ts +194 -0
  233. package/src/share/share.ts +87 -0
  234. package/src/shell/shell.ts +67 -0
  235. package/src/skill/index.ts +1 -0
  236. package/src/skill/skill.ts +83 -0
  237. package/src/snapshot/index.ts +197 -0
  238. package/src/storage/storage.ts +226 -0
  239. package/src/tool/bash.ts +306 -0
  240. package/src/tool/bash.txt +158 -0
  241. package/src/tool/batch.ts +175 -0
  242. package/src/tool/batch.txt +24 -0
  243. package/src/tool/codesearch.ts +138 -0
  244. package/src/tool/codesearch.txt +12 -0
  245. package/src/tool/edit.ts +675 -0
  246. package/src/tool/edit.txt +10 -0
  247. package/src/tool/glob.ts +65 -0
  248. package/src/tool/glob.txt +6 -0
  249. package/src/tool/grep.ts +121 -0
  250. package/src/tool/grep.txt +8 -0
  251. package/src/tool/invalid.ts +17 -0
  252. package/src/tool/ls.ts +110 -0
  253. package/src/tool/ls.txt +1 -0
  254. package/src/tool/lsp-diagnostics.ts +26 -0
  255. package/src/tool/lsp-diagnostics.txt +1 -0
  256. package/src/tool/lsp-hover.ts +31 -0
  257. package/src/tool/lsp-hover.txt +1 -0
  258. package/src/tool/lsp.ts +87 -0
  259. package/src/tool/lsp.txt +19 -0
  260. package/src/tool/multiedit.ts +46 -0
  261. package/src/tool/multiedit.txt +41 -0
  262. package/src/tool/patch.ts +233 -0
  263. package/src/tool/patch.txt +1 -0
  264. package/src/tool/read.ts +219 -0
  265. package/src/tool/read.txt +12 -0
  266. package/src/tool/registry.ts +162 -0
  267. package/src/tool/skill.ts +100 -0
  268. package/src/tool/task.ts +136 -0
  269. package/src/tool/task.txt +60 -0
  270. package/src/tool/todo.ts +39 -0
  271. package/src/tool/todoread.txt +14 -0
  272. package/src/tool/todowrite.txt +167 -0
  273. package/src/tool/tool.ts +71 -0
  274. package/src/tool/webfetch.ts +187 -0
  275. package/src/tool/webfetch.txt +13 -0
  276. package/src/tool/websearch.ts +150 -0
  277. package/src/tool/websearch.txt +11 -0
  278. package/src/tool/write.ts +110 -0
  279. package/src/tool/write.txt +8 -0
  280. package/src/util/archive.ts +16 -0
  281. package/src/util/color.ts +19 -0
  282. package/src/util/context.ts +25 -0
  283. package/src/util/defer.ts +12 -0
  284. package/src/util/eventloop.ts +20 -0
  285. package/src/util/filesystem.ts +83 -0
  286. package/src/util/fn.ts +11 -0
  287. package/src/util/iife.ts +3 -0
  288. package/src/util/keybind.ts +102 -0
  289. package/src/util/lazy.ts +11 -0
  290. package/src/util/locale.ts +81 -0
  291. package/src/util/lock.ts +98 -0
  292. package/src/util/log.ts +180 -0
  293. package/src/util/queue.ts +32 -0
  294. package/src/util/rpc.ts +42 -0
  295. package/src/util/scrap.ts +10 -0
  296. package/src/util/signal.ts +12 -0
  297. package/src/util/timeout.ts +14 -0
  298. package/src/util/token.ts +7 -0
  299. package/src/util/wildcard.ts +54 -0
  300. package/tsconfig.json +16 -0
@@ -0,0 +1,21 @@
1
+ export const EmptyBorder = {
2
+ topLeft: "",
3
+ bottomLeft: "",
4
+ vertical: "",
5
+ topRight: "",
6
+ bottomRight: "",
7
+ horizontal: " ",
8
+ bottomT: "",
9
+ topT: "",
10
+ cross: "",
11
+ leftT: "",
12
+ rightT: "",
13
+ }
14
+
15
+ export const SplitBorder = {
16
+ border: ["left" as const, "right" as const],
17
+ customBorderChars: {
18
+ ...EmptyBorder,
19
+ vertical: "┃",
20
+ },
21
+ }
@@ -0,0 +1,31 @@
1
+ import { createMemo } from "solid-js"
2
+ import { useLocal } from "@tui/context/local"
3
+ import { DialogSelect } from "@tui/ui/dialog-select"
4
+ import { useDialog } from "@tui/ui/dialog"
5
+
6
+ export function DialogAgent() {
7
+ const local = useLocal()
8
+ const dialog = useDialog()
9
+
10
+ const options = createMemo(() =>
11
+ local.agent.list().map((item) => {
12
+ return {
13
+ value: item.name,
14
+ title: item.name,
15
+ description: item.native ? "native" : item.description,
16
+ }
17
+ }),
18
+ )
19
+
20
+ return (
21
+ <DialogSelect
22
+ title="Select agent"
23
+ current={local.agent.current().name}
24
+ options={options()}
25
+ onSelect={(option) => {
26
+ local.agent.set(option.value)
27
+ dialog.clear()
28
+ }}
29
+ />
30
+ )
31
+ }
@@ -0,0 +1,123 @@
1
+ import { useDialog } from "@tui/ui/dialog"
2
+ import { DialogSelect, type DialogSelectOption, type DialogSelectRef } from "@tui/ui/dialog-select"
3
+ import {
4
+ createContext,
5
+ createMemo,
6
+ createSignal,
7
+ onCleanup,
8
+ useContext,
9
+ type Accessor,
10
+ type ParentProps,
11
+ } from "solid-js"
12
+ import { useKeyboard } from "@opentui/solid"
13
+ import { useKeybind } from "@tui/context/keybind"
14
+ import type { KeybindsConfig } from "@bincode-ai/sdk/v2"
15
+
16
+ type Context = ReturnType<typeof init>
17
+ const ctx = createContext<Context>()
18
+
19
+ export type CommandOption = DialogSelectOption & {
20
+ keybind?: keyof KeybindsConfig
21
+ suggested?: boolean
22
+ }
23
+
24
+ function init() {
25
+ const [registrations, setRegistrations] = createSignal<Accessor<CommandOption[]>[]>([])
26
+ const [suspendCount, setSuspendCount] = createSignal(0)
27
+ const dialog = useDialog()
28
+ const keybind = useKeybind()
29
+ const options = createMemo(() => {
30
+ const all = registrations().flatMap((x) => x())
31
+ const suggested = all.filter((x) => x.suggested)
32
+ return [
33
+ ...suggested.map((x) => ({
34
+ ...x,
35
+ category: "Suggested",
36
+ value: "suggested." + x.value,
37
+ })),
38
+ ...all,
39
+ ].map((x) => ({
40
+ ...x,
41
+ footer: x.keybind ? keybind.print(x.keybind) : undefined,
42
+ }))
43
+ })
44
+ const suspended = () => suspendCount() > 0
45
+
46
+ useKeyboard((evt) => {
47
+ if (suspended()) return
48
+ for (const option of options()) {
49
+ if (option.keybind && keybind.match(option.keybind, evt)) {
50
+ evt.preventDefault()
51
+ option.onSelect?.(dialog)
52
+ return
53
+ }
54
+ }
55
+ })
56
+
57
+ const result = {
58
+ trigger(name: string, source?: "prompt") {
59
+ for (const option of options()) {
60
+ if (option.value === name) {
61
+ option.onSelect?.(dialog, source)
62
+ return
63
+ }
64
+ }
65
+ },
66
+ keybinds(enabled: boolean) {
67
+ setSuspendCount((count) => count + (enabled ? -1 : 1))
68
+ },
69
+ suspended,
70
+ show() {
71
+ dialog.replace(() => <DialogCommand options={options()} />)
72
+ },
73
+ register(cb: () => CommandOption[]) {
74
+ const results = createMemo(cb)
75
+ setRegistrations((arr) => [results, ...arr])
76
+ onCleanup(() => {
77
+ setRegistrations((arr) => arr.filter((x) => x !== results))
78
+ })
79
+ },
80
+ get options() {
81
+ return options()
82
+ },
83
+ }
84
+ return result
85
+ }
86
+
87
+ export function useCommandDialog() {
88
+ const value = useContext(ctx)
89
+ if (!value) {
90
+ throw new Error("useCommandDialog must be used within a CommandProvider")
91
+ }
92
+ return value
93
+ }
94
+
95
+ export function CommandProvider(props: ParentProps) {
96
+ const value = init()
97
+ const dialog = useDialog()
98
+ const keybind = useKeybind()
99
+
100
+ useKeyboard((evt) => {
101
+ if (value.suspended()) return
102
+ if (dialog.stack.length > 0) return
103
+ if (evt.defaultPrevented) return
104
+ if (keybind.match("command_list", evt)) {
105
+ evt.preventDefault()
106
+ dialog.replace(() => <DialogCommand options={value.options} />)
107
+ return
108
+ }
109
+ })
110
+
111
+ return <ctx.Provider value={value}>{props.children}</ctx.Provider>
112
+ }
113
+
114
+ function DialogCommand(props: { options: CommandOption[] }) {
115
+ let ref: DialogSelectRef<string>
116
+ return (
117
+ <DialogSelect
118
+ ref={(r) => (ref = r)}
119
+ title="Commands"
120
+ options={props.options.filter((x) => !ref?.filter || !x.value.startsWith("suggested."))}
121
+ />
122
+ )
123
+ }
@@ -0,0 +1,86 @@
1
+ import { createMemo, createSignal } from "solid-js"
2
+ import { useLocal } from "@tui/context/local"
3
+ import { useSync } from "@tui/context/sync"
4
+ import { map, pipe, entries, sortBy } from "remeda"
5
+ import { DialogSelect, type DialogSelectRef, type DialogSelectOption } from "@tui/ui/dialog-select"
6
+ import { useTheme } from "../context/theme"
7
+ import { Keybind } from "@/util/keybind"
8
+ import { TextAttributes } from "@opentui/core"
9
+ import { useSDK } from "@tui/context/sdk"
10
+
11
+ function Status(props: { enabled: boolean; loading: boolean }) {
12
+ const { theme } = useTheme()
13
+ if (props.loading) {
14
+ return <span style={{ fg: theme.textMuted }}>⋯ Loading</span>
15
+ }
16
+ if (props.enabled) {
17
+ return <span style={{ fg: theme.success, attributes: TextAttributes.BOLD }}>✓ Enabled</span>
18
+ }
19
+ return <span style={{ fg: theme.textMuted }}>○ Disabled</span>
20
+ }
21
+
22
+ export function DialogMcp() {
23
+ const local = useLocal()
24
+ const sync = useSync()
25
+ const sdk = useSDK()
26
+ const [, setRef] = createSignal<DialogSelectRef<unknown>>()
27
+ const [loading, setLoading] = createSignal<string | null>(null)
28
+
29
+ const options = createMemo(() => {
30
+ // Track sync data and loading state to trigger re-render when they change
31
+ const mcpData = sync.data.mcp
32
+ const loadingMcp = loading()
33
+
34
+ return pipe(
35
+ mcpData ?? {},
36
+ entries(),
37
+ sortBy(([name]) => name),
38
+ map(([name, status]) => ({
39
+ value: name,
40
+ title: name,
41
+ description: status.status === "failed" ? "failed" : status.status,
42
+ footer: <Status enabled={local.mcp.isEnabled(name)} loading={loadingMcp === name} />,
43
+ category: undefined,
44
+ })),
45
+ )
46
+ })
47
+
48
+ const keybinds = createMemo(() => [
49
+ {
50
+ keybind: Keybind.parse("space")[0],
51
+ title: "toggle",
52
+ onTrigger: async (option: DialogSelectOption<string>) => {
53
+ // Prevent toggling while an operation is already in progress
54
+ if (loading() !== null) return
55
+
56
+ setLoading(option.value)
57
+ try {
58
+ await local.mcp.toggle(option.value)
59
+ // Refresh MCP status from server
60
+ const status = await sdk.client.mcp.status()
61
+ if (status.data) {
62
+ sync.set("mcp", status.data)
63
+ } else {
64
+ console.error("Failed to refresh MCP status: no data returned")
65
+ }
66
+ } catch (error) {
67
+ console.error("Failed to toggle MCP:", error)
68
+ } finally {
69
+ setLoading(null)
70
+ }
71
+ },
72
+ },
73
+ ])
74
+
75
+ return (
76
+ <DialogSelect
77
+ ref={setRef}
78
+ title="MCPs"
79
+ options={options()}
80
+ keybind={keybinds()}
81
+ onSelect={(option) => {
82
+ // Don't close on select, only on escape
83
+ }}
84
+ />
85
+ )
86
+ }
@@ -0,0 +1,223 @@
1
+ import { createMemo, createSignal } from "solid-js"
2
+ import { useLocal } from "@tui/context/local"
3
+ import { useSync } from "@tui/context/sync"
4
+ import { map, pipe, flatMap, entries, filter, sortBy, take } from "remeda"
5
+ import { DialogSelect, type DialogSelectRef } from "@tui/ui/dialog-select"
6
+ import { useDialog } from "@tui/ui/dialog"
7
+ import { createDialogProviderOptions, DialogProvider } from "./dialog-provider"
8
+ import { Keybind } from "@/util/keybind"
9
+
10
+ export function useConnected() {
11
+ const sync = useSync()
12
+ return createMemo(() =>
13
+ sync.data.provider.some((x) => x.id !== "bincode" || Object.values(x.models).some((y) => y.cost?.input !== 0)),
14
+ )
15
+ }
16
+
17
+ export function DialogModel(props: { providerID?: string }) {
18
+ const local = useLocal()
19
+ const sync = useSync()
20
+ const dialog = useDialog()
21
+ const [ref, setRef] = createSignal<DialogSelectRef<unknown>>()
22
+
23
+ const connected = useConnected()
24
+ const providers = createDialogProviderOptions()
25
+
26
+ const showExtra = createMemo(() => {
27
+ if (!connected()) return false
28
+ if (props.providerID) return false
29
+ return true
30
+ })
31
+
32
+ const options = createMemo(() => {
33
+ const query = ref()?.filter
34
+ const favorites = showExtra() ? local.model.favorite() : []
35
+ const recents = local.model.recent()
36
+
37
+ const recentList = showExtra()
38
+ ? recents
39
+ .filter(
40
+ (item) => !favorites.some((fav) => fav.providerID === item.providerID && fav.modelID === item.modelID),
41
+ )
42
+ .slice(0, 5)
43
+ : []
44
+
45
+ const favoriteOptions = !query
46
+ ? favorites.flatMap((item) => {
47
+ const provider = sync.data.provider.find((x) => x.id === item.providerID)
48
+ if (!provider) return []
49
+ const model = provider.models[item.modelID]
50
+ if (!model) return []
51
+ return [
52
+ {
53
+ key: item,
54
+ value: {
55
+ providerID: provider.id,
56
+ modelID: model.id,
57
+ },
58
+ title: model.name ?? item.modelID,
59
+ description: provider.name,
60
+ category: "Favorites",
61
+ disabled: provider.id === "bincode" && model.id.includes("-nano"),
62
+ footer: model.cost?.input === 0 && provider.id === "bincode" ? "Free" : undefined,
63
+ onSelect: () => {
64
+ dialog.clear()
65
+ local.model.set(
66
+ {
67
+ providerID: provider.id,
68
+ modelID: model.id,
69
+ },
70
+ { recent: true },
71
+ )
72
+ },
73
+ },
74
+ ]
75
+ })
76
+ : []
77
+
78
+ const recentOptions = !query
79
+ ? recentList.flatMap((item) => {
80
+ const provider = sync.data.provider.find((x) => x.id === item.providerID)
81
+ if (!provider) return []
82
+ const model = provider.models[item.modelID]
83
+ if (!model) return []
84
+ return [
85
+ {
86
+ key: item,
87
+ value: {
88
+ providerID: provider.id,
89
+ modelID: model.id,
90
+ },
91
+ title: model.name ?? item.modelID,
92
+ description: provider.name,
93
+ category: "Recent",
94
+ disabled: provider.id === "bincode" && model.id.includes("-nano"),
95
+ footer: model.cost?.input === 0 && provider.id === "bincode" ? "Free" : undefined,
96
+ onSelect: () => {
97
+ dialog.clear()
98
+ local.model.set(
99
+ {
100
+ providerID: provider.id,
101
+ modelID: model.id,
102
+ },
103
+ { recent: true },
104
+ )
105
+ },
106
+ },
107
+ ]
108
+ })
109
+ : []
110
+
111
+ return [
112
+ ...favoriteOptions,
113
+ ...recentOptions,
114
+ ...pipe(
115
+ sync.data.provider,
116
+ sortBy(
117
+ (provider) => provider.id !== "bincode",
118
+ (provider) => provider.name,
119
+ ),
120
+ flatMap((provider) =>
121
+ pipe(
122
+ provider.models,
123
+ entries(),
124
+ filter(([_, info]) => info.status !== "deprecated"),
125
+ filter(([_, info]) => (props.providerID ? info.providerID === props.providerID : true)),
126
+ map(([model, info]) => {
127
+ const value = {
128
+ providerID: provider.id,
129
+ modelID: model,
130
+ }
131
+ return {
132
+ value,
133
+ title: info.name ?? model,
134
+ description: favorites.some(
135
+ (item) => item.providerID === value.providerID && item.modelID === value.modelID,
136
+ )
137
+ ? "(Favorite)"
138
+ : undefined,
139
+ category: connected() ? provider.name : undefined,
140
+ disabled: provider.id === "bincode" && model.includes("-nano"),
141
+ footer: info.cost?.input === 0 && provider.id === "bincode" ? "Free" : undefined,
142
+ onSelect() {
143
+ dialog.clear()
144
+ local.model.set(
145
+ {
146
+ providerID: provider.id,
147
+ modelID: model,
148
+ },
149
+ { recent: true },
150
+ )
151
+ },
152
+ }
153
+ }),
154
+ filter((x) => {
155
+ if (query) return true
156
+ const value = x.value
157
+ const inFavorites = favorites.some(
158
+ (item) => item.providerID === value.providerID && item.modelID === value.modelID,
159
+ )
160
+ if (inFavorites) return false
161
+ const inRecents = recents.some(
162
+ (item) => item.providerID === value.providerID && item.modelID === value.modelID,
163
+ )
164
+ if (inRecents) return false
165
+ return true
166
+ }),
167
+ sortBy(
168
+ (x) => x.footer !== "Free",
169
+ (x) => x.title,
170
+ ),
171
+ ),
172
+ ),
173
+ ),
174
+ ...(!connected()
175
+ ? pipe(
176
+ providers(),
177
+ map((option) => {
178
+ return {
179
+ ...option,
180
+ category: "Popular providers",
181
+ }
182
+ }),
183
+ take(6),
184
+ )
185
+ : []),
186
+ ]
187
+ })
188
+
189
+ const provider = createMemo(() =>
190
+ props.providerID ? sync.data.provider.find((x) => x.id === props.providerID) : null,
191
+ )
192
+
193
+ const title = createMemo(() => {
194
+ if (provider()) return provider()!.name
195
+ return "Select model"
196
+ })
197
+
198
+ return (
199
+ <DialogSelect
200
+ keybind={[
201
+ {
202
+ keybind: Keybind.parse("ctrl+a")[0],
203
+ title: connected() ? "Connect provider" : "View all providers",
204
+ onTrigger() {
205
+ dialog.replace(() => <DialogProvider />)
206
+ },
207
+ },
208
+ {
209
+ keybind: Keybind.parse("ctrl+f")[0],
210
+ title: "Favorite",
211
+ disabled: !connected(),
212
+ onTrigger: (option) => {
213
+ local.model.toggleFavorite(option.value as { providerID: string; modelID: string })
214
+ },
215
+ },
216
+ ]}
217
+ ref={setRef}
218
+ title={title()}
219
+ current={local.model.current()}
220
+ options={options()}
221
+ />
222
+ )
223
+ }
@@ -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 "@bincode-ai/sdk/v2"
11
+ import { DialogModel } from "./dialog-model"
12
+
13
+ const PROVIDER_PRIORITY: Record<string, number> = {
14
+ bincode: 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
+ bincode: "(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 === "Bineric" ? (
199
+ <box gap={1}>
200
+ <text fg={theme.textMuted}>
201
+ Bincode gives you access to all the best coding 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://platform.bineric.com/</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
+ }