@100xprompt/brain 0.0.0-stage → 0.1.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 (260) hide show
  1. package/LICENSE +48 -0
  2. package/README.md +235 -2
  3. package/package.json +48 -4
  4. package/src/adapters/aider/index.ts +8 -0
  5. package/src/adapters/aider/reader.ts +129 -0
  6. package/src/adapters/aider/records.ts +220 -0
  7. package/src/adapters/aider/writer.ts +269 -0
  8. package/src/adapters/amazon-q-cli/index.ts +8 -0
  9. package/src/adapters/amazon-q-cli/reader.ts +84 -0
  10. package/src/adapters/amazon-q-cli/records.ts +191 -0
  11. package/src/adapters/amazon-q-cli/writer.ts +370 -0
  12. package/src/adapters/amazon-q-ide/index.ts +9 -0
  13. package/src/adapters/amazon-q-ide/reader.ts +131 -0
  14. package/src/adapters/amazon-q-ide/records.ts +141 -0
  15. package/src/adapters/amazon-q-ide/writer.ts +325 -0
  16. package/src/adapters/antigravity/index.ts +9 -0
  17. package/src/adapters/antigravity/proto.ts +194 -0
  18. package/src/adapters/antigravity/reader.ts +424 -0
  19. package/src/adapters/antigravity/records.ts +236 -0
  20. package/src/adapters/antigravity/writer.ts +463 -0
  21. package/src/adapters/auggie/index.ts +36 -0
  22. package/src/adapters/claude-code/index.ts +8 -0
  23. package/src/adapters/claude-code/reader.ts +639 -0
  24. package/src/adapters/claude-code/records.ts +88 -0
  25. package/src/adapters/claude-code/writer.ts +329 -0
  26. package/src/adapters/cline/index.ts +12 -0
  27. package/src/adapters/cline/reader.ts +432 -0
  28. package/src/adapters/cline/records.ts +151 -0
  29. package/src/adapters/cline/vscdb.ts +56 -0
  30. package/src/adapters/cline/writer.ts +656 -0
  31. package/src/adapters/cline/xml.ts +177 -0
  32. package/src/adapters/cline4/index.ts +9 -0
  33. package/src/adapters/cline4/reader.ts +179 -0
  34. package/src/adapters/cline4/records.ts +92 -0
  35. package/src/adapters/cline4/writer.ts +361 -0
  36. package/src/adapters/codebuff/index.ts +8 -0
  37. package/src/adapters/codebuff/project.ts +207 -0
  38. package/src/adapters/codebuff/reader.ts +215 -0
  39. package/src/adapters/codebuff/records.ts +173 -0
  40. package/src/adapters/codebuff/writer.ts +383 -0
  41. package/src/adapters/codex/index.ts +8 -0
  42. package/src/adapters/codex/native.ts +239 -0
  43. package/src/adapters/codex/reader.ts +747 -0
  44. package/src/adapters/codex/records.ts +114 -0
  45. package/src/adapters/codex/writer.ts +479 -0
  46. package/src/adapters/continue/index.ts +9 -0
  47. package/src/adapters/continue/reader.ts +68 -0
  48. package/src/adapters/continue/records.ts +418 -0
  49. package/src/adapters/continue/writer.ts +162 -0
  50. package/src/adapters/copilot-cli/index.ts +9 -0
  51. package/src/adapters/copilot-cli/reader.ts +186 -0
  52. package/src/adapters/copilot-cli/records.ts +241 -0
  53. package/src/adapters/copilot-cli/writer.ts +353 -0
  54. package/src/adapters/crush/index.ts +8 -0
  55. package/src/adapters/crush/reader.ts +180 -0
  56. package/src/adapters/crush/records.ts +169 -0
  57. package/src/adapters/crush/writer.ts +333 -0
  58. package/src/adapters/cursor-cli/index.ts +405 -0
  59. package/src/adapters/cursor-ide/agentkv.ts +95 -0
  60. package/src/adapters/cursor-ide/index.ts +501 -0
  61. package/src/adapters/cursor-ide/transcripts.ts +122 -0
  62. package/src/adapters/cursor-ide/workspaces.ts +119 -0
  63. package/src/adapters/cursor-ide/writer.ts +170 -0
  64. package/src/adapters/devin/index.ts +8 -0
  65. package/src/adapters/devin/reader.ts +305 -0
  66. package/src/adapters/devin/records.ts +277 -0
  67. package/src/adapters/devin/writer.ts +523 -0
  68. package/src/adapters/factory-droid/index.ts +12 -0
  69. package/src/adapters/factory-droid/reader.ts +184 -0
  70. package/src/adapters/factory-droid/records.ts +79 -0
  71. package/src/adapters/factory-droid/writer.ts +350 -0
  72. package/src/adapters/gemini-cli/exchanges.ts +228 -0
  73. package/src/adapters/gemini-cli/index.ts +396 -0
  74. package/src/adapters/gemini-cli/records.ts +521 -0
  75. package/src/adapters/gemini-cli/writer.ts +354 -0
  76. package/src/adapters/goose/index.ts +8 -0
  77. package/src/adapters/goose/reader.ts +138 -0
  78. package/src/adapters/goose/records.ts +199 -0
  79. package/src/adapters/goose/writer.ts +340 -0
  80. package/src/adapters/grok-cli/image.ts +44 -0
  81. package/src/adapters/grok-cli/index.ts +8 -0
  82. package/src/adapters/grok-cli/reader.ts +302 -0
  83. package/src/adapters/grok-cli/records.ts +172 -0
  84. package/src/adapters/grok-cli/writer.ts +329 -0
  85. package/src/adapters/hermes/index.ts +8 -0
  86. package/src/adapters/hermes/reader.ts +249 -0
  87. package/src/adapters/hermes/records.ts +285 -0
  88. package/src/adapters/hermes/writer.ts +452 -0
  89. package/src/adapters/iflow-cli/index.ts +72 -0
  90. package/src/adapters/iflow-cli/records.ts +215 -0
  91. package/src/adapters/iflow-cli/writer.ts +204 -0
  92. package/src/adapters/index.ts +112 -0
  93. package/src/adapters/kilo-cli/index.ts +21 -0
  94. package/src/adapters/kilo-code/index.ts +11 -0
  95. package/src/adapters/kilo-code/writer.ts +92 -0
  96. package/src/adapters/kimi/index.ts +9 -0
  97. package/src/adapters/kimi/reader.ts +92 -0
  98. package/src/adapters/kimi/records.ts +221 -0
  99. package/src/adapters/kimi/writer.ts +367 -0
  100. package/src/adapters/kiro-cli/index.ts +8 -0
  101. package/src/adapters/kiro-cli/reader.ts +87 -0
  102. package/src/adapters/kiro-cli/records.ts +260 -0
  103. package/src/adapters/kiro-cli/writer.ts +305 -0
  104. package/src/adapters/kiro-ide/index.ts +8 -0
  105. package/src/adapters/kiro-ide/writer.ts +52 -0
  106. package/src/adapters/letta/index.ts +8 -0
  107. package/src/adapters/letta/reader.ts +177 -0
  108. package/src/adapters/letta/records.ts +119 -0
  109. package/src/adapters/letta/writer.ts +413 -0
  110. package/src/adapters/mastracode/index.ts +8 -0
  111. package/src/adapters/mastracode/reader.ts +323 -0
  112. package/src/adapters/mastracode/records.ts +336 -0
  113. package/src/adapters/mastracode/writer.ts +385 -0
  114. package/src/adapters/mistral-vibe/index.ts +9 -0
  115. package/src/adapters/mistral-vibe/reader.ts +62 -0
  116. package/src/adapters/mistral-vibe/records.ts +104 -0
  117. package/src/adapters/mistral-vibe/writer.ts +359 -0
  118. package/src/adapters/muse/index.ts +10 -0
  119. package/src/adapters/muse/reader.ts +332 -0
  120. package/src/adapters/muse/records.ts +104 -0
  121. package/src/adapters/muse/writer.ts +387 -0
  122. package/src/adapters/omp/index.ts +13 -0
  123. package/src/adapters/omp/reader.ts +314 -0
  124. package/src/adapters/omp/records.ts +153 -0
  125. package/src/adapters/omp/writer.ts +216 -0
  126. package/src/adapters/opencode/ids.ts +52 -0
  127. package/src/adapters/opencode/index.ts +31 -0
  128. package/src/adapters/opencode/note.ts +8 -0
  129. package/src/adapters/opencode/reader.ts +452 -0
  130. package/src/adapters/opencode/tools.ts +174 -0
  131. package/src/adapters/opencode/v2.ts +367 -0
  132. package/src/adapters/opencode/writer.ts +379 -0
  133. package/src/adapters/openhands/index.ts +8 -0
  134. package/src/adapters/openhands/reader.ts +222 -0
  135. package/src/adapters/openhands/records.ts +136 -0
  136. package/src/adapters/openhands/writer.ts +404 -0
  137. package/src/adapters/pearai/index.ts +10 -0
  138. package/src/adapters/pi/index.ts +28 -0
  139. package/src/adapters/pi/reader.ts +261 -0
  140. package/src/adapters/pi/records.ts +99 -0
  141. package/src/adapters/pi/writer.ts +362 -0
  142. package/src/adapters/qoder-cli/index.ts +8 -0
  143. package/src/adapters/qoder-cli/reader.ts +83 -0
  144. package/src/adapters/qoder-cli/records.ts +45 -0
  145. package/src/adapters/qoder-cli/writer.ts +72 -0
  146. package/src/adapters/qwen-code/index.ts +84 -0
  147. package/src/adapters/qwen-code/records.ts +194 -0
  148. package/src/adapters/qwen-code/writer.ts +224 -0
  149. package/src/adapters/tabnine-cli/index.ts +8 -0
  150. package/src/adapters/tabnine-cli/reader.ts +106 -0
  151. package/src/adapters/tabnine-cli/records.ts +33 -0
  152. package/src/adapters/tabnine-cli/writer.ts +156 -0
  153. package/src/adapters/types.ts +73 -0
  154. package/src/adapters/void/index.ts +9 -0
  155. package/src/adapters/void/reader.ts +48 -0
  156. package/src/adapters/void/records.ts +285 -0
  157. package/src/adapters/void/writer.ts +93 -0
  158. package/src/adapters/vscode-copilot/index.ts +9 -0
  159. package/src/adapters/vscode-copilot/reader.ts +212 -0
  160. package/src/adapters/vscode-copilot/records.ts +388 -0
  161. package/src/adapters/vscode-copilot/shared.ts +68 -0
  162. package/src/adapters/vscode-copilot/writer.ts +546 -0
  163. package/src/adapters/zed/index.ts +8 -0
  164. package/src/adapters/zed/reader.ts +198 -0
  165. package/src/adapters/zed/records.ts +87 -0
  166. package/src/adapters/zed/writer.ts +341 -0
  167. package/src/brief-writers.ts +71 -0
  168. package/src/brief.ts +238 -0
  169. package/src/cli.ts +351 -0
  170. package/src/dashboard/cli.ts +148 -0
  171. package/src/dashboard/conversions.ts +170 -0
  172. package/src/dashboard/report.ts +261 -0
  173. package/src/dashboard/send.ts +213 -0
  174. package/src/dashboard/state.ts +95 -0
  175. package/src/fsutil.ts +133 -0
  176. package/src/gateway/common.ts +232 -0
  177. package/src/gateway/connect.ts +847 -0
  178. package/src/gateway/connectors/aider.ts +356 -0
  179. package/src/gateway/connectors/amazon-q-cli.ts +6 -0
  180. package/src/gateway/connectors/amazon-q-ide.ts +6 -0
  181. package/src/gateway/connectors/antigravity.ts +6 -0
  182. package/src/gateway/connectors/auggie.ts +6 -0
  183. package/src/gateway/connectors/cline-sdk.ts +85 -0
  184. package/src/gateway/connectors/cline.ts +34 -0
  185. package/src/gateway/connectors/codebuff.ts +6 -0
  186. package/src/gateway/connectors/continue.ts +579 -0
  187. package/src/gateway/connectors/copilot-cli.ts +138 -0
  188. package/src/gateway/connectors/crush.ts +313 -0
  189. package/src/gateway/connectors/cursor-cli.ts +6 -0
  190. package/src/gateway/connectors/cursor.ts +6 -0
  191. package/src/gateway/connectors/devin.ts +6 -0
  192. package/src/gateway/connectors/factory-droid.ts +308 -0
  193. package/src/gateway/connectors/goose.ts +79 -0
  194. package/src/gateway/connectors/grok-cli.ts +82 -0
  195. package/src/gateway/connectors/hermes.ts +230 -0
  196. package/src/gateway/connectors/iflow-cli.ts +50 -0
  197. package/src/gateway/connectors/index.ts +86 -0
  198. package/src/gateway/connectors/kilo-cli.ts +434 -0
  199. package/src/gateway/connectors/kilo-code.ts +32 -0
  200. package/src/gateway/connectors/kimi-code.ts +241 -0
  201. package/src/gateway/connectors/kiro-cli.ts +6 -0
  202. package/src/gateway/connectors/kiro-ide.ts +6 -0
  203. package/src/gateway/connectors/letta-code.ts +170 -0
  204. package/src/gateway/connectors/mastracode.ts +91 -0
  205. package/src/gateway/connectors/mistral-vibe.ts +495 -0
  206. package/src/gateway/connectors/muse.ts +27 -0
  207. package/src/gateway/connectors/omp.ts +279 -0
  208. package/src/gateway/connectors/openhands.ts +296 -0
  209. package/src/gateway/connectors/pearai.ts +52 -0
  210. package/src/gateway/connectors/pi.ts +357 -0
  211. package/src/gateway/connectors/qoder-cli.ts +6 -0
  212. package/src/gateway/connectors/qwen-code.ts +411 -0
  213. package/src/gateway/connectors/roo.ts +43 -0
  214. package/src/gateway/connectors/tabnine-cli.ts +6 -0
  215. package/src/gateway/connectors/void.ts +6 -0
  216. package/src/gateway/connectors/vscode-copilot.ts +286 -0
  217. package/src/gateway/connectors/windsurf.ts +6 -0
  218. package/src/gateway/connectors/zed.ts +29 -0
  219. package/src/gateway/signin.ts +323 -0
  220. package/src/handoff.ts +104 -0
  221. package/src/hooks.ts +455 -0
  222. package/src/memory/audit.ts +514 -0
  223. package/src/memory/blobs.ts +94 -0
  224. package/src/memory/cli.ts +117 -0
  225. package/src/memory/distill.ts +381 -0
  226. package/src/memory/externalize.ts +435 -0
  227. package/src/memory/lookups.ts +70 -0
  228. package/src/memory/mcp-install.ts +401 -0
  229. package/src/memory/mcp-server.ts +296 -0
  230. package/src/memory/prepare.ts +179 -0
  231. package/src/memory/preview.ts +154 -0
  232. package/src/memory/project.ts +35 -0
  233. package/src/memory/redact.ts +72 -0
  234. package/src/memory/relevance.ts +187 -0
  235. package/src/memory/repomap.ts +516 -0
  236. package/src/memory/store.ts +270 -0
  237. package/src/memory/sync.ts +166 -0
  238. package/src/memory/tidy.ts +193 -0
  239. package/src/memory/types.ts +118 -0
  240. package/src/org/admin-ops.ts +96 -0
  241. package/src/org/auth.ts +112 -0
  242. package/src/org/backup.ts +115 -0
  243. package/src/org/cli.ts +438 -0
  244. package/src/org/client.ts +404 -0
  245. package/src/org/config.ts +44 -0
  246. package/src/org/crypto.ts +537 -0
  247. package/src/org/db.ts +435 -0
  248. package/src/org/graph.ts +318 -0
  249. package/src/org/oidc.ts +435 -0
  250. package/src/org/policy.ts +398 -0
  251. package/src/org/server.ts +920 -0
  252. package/src/org/sso.ts +316 -0
  253. package/src/org/tls.ts +66 -0
  254. package/src/org/types.ts +193 -0
  255. package/src/self.ts +68 -0
  256. package/src/session/attachments.ts +53 -0
  257. package/src/session/peekcache.ts +50 -0
  258. package/src/session/types.ts +104 -0
  259. package/src/session/util.ts +130 -0
  260. package/src/sqlite.ts +48 -0
@@ -0,0 +1,241 @@
1
+ // Gateway connector for Kimi Code (Moonshot's TypeScript CLI, `kimi`; the successor of the archived Python Kimi CLI that
2
+ // used ~/.kimi). Checked against @moonshot-ai/kimi-code 2.5.2 (the copy in ~/.cache/brain-wire/kimi-code, dist/main.mjs)
3
+ // and https://moonshotai.github.io/kimi-code/en/configuration/config-files.html.
4
+ //
5
+ // Settings live in one TOML file, $KIMI_CODE_HOME/config.toml, else ~/.kimi-code/config.toml (main.mjs resolveKimiHome /
6
+ // resolveConfigPath; docs "Config file location"). Keys are snake_case and read into camelCase (providerEntryFromToml):
7
+ // default_model = "<alias>" the model alias used when none is given (-m)
8
+ // [providers.<id>] type ("openai" = Chat Completions, also "openai_responses", "anthropic", ...), base_url, api_key or
9
+ // api_key_env (the NAME of an exported variable), custom_headers (a string table, sent with every model
10
+ // request: main.mjs resolveOutboundHeaders), env (more literal values) (ProviderConfigSchema$1)
11
+ // [models.<alias>] provider, model (sent to the API), max_context_size (required), capabilities, display_name (what the
12
+ // UI shows: main.mjs `displayName ?? model ?? alias`) (ModelBaseSchema)
13
+ // The key: Kimi Code takes it only as api_key (in the file), api_key_env (a variable the person exports; brain cannot set
14
+ // one for a tool started from a shell), the env sub-table (in the file again) or its OAuth login. It cannot run a command
15
+ // or read a file ("The documentation does not describe supplying a key by running a command or by reading a file"; no such
16
+ // field in the schema). So the key is copied into config.toml: holdsKey (0600), and the provider table is a secret key
17
+ // (hidden, never recorded, replaced on a new sign-in).
18
+ //
19
+ // What is written: default_model = "brain-gateway", [providers.brain-gateway] (type "openai", base_url <route>/v1, api_key,
20
+ // custom_headers with x-100x-context-edit) and [models.brain-gateway] (the gateway's model id, shown as "100xprompt Pro").
21
+ // Kimi Code needs no model list from the gateway for a provider of type "openai" (only its own "kimi" provider refreshes one).
22
+ //
23
+ // The core's TOML handling knows only Codex's table, so this file has its own codec: top-level keys are edited line by
24
+ // line, and a table is one key ("providers/brain-gateway") replaced, added or removed as a whole block. Every other line,
25
+ // comments included, stays as it was; a table brain added is removed with the blank line it brought, so disconnect gives
26
+ // back the exact bytes. Before any change the codec makes a trial edit and refuses a file where that would not land
27
+ // exactly on brain's entries (an inline `providers = {...}`, dotted keys, sub-tables of brain's tables, ...).
28
+ import { join } from "node:path"
29
+ import { type Codec, type Connector, dirFromEnv, EDIT_HEADER, type Json, modelName, readKey, routeUrl } from "../common.ts"
30
+
31
+ export const kimiConfig = (home: string, env: Record<string, string | undefined>) => join(dirFromEnv(env, "KIMI_CODE_HOME", join(home, ".kimi-code")), "config.toml")
32
+
33
+ // ---- TOML: top-level keys and whole tables ------------------------------------------------------------------------------
34
+
35
+ const esc = (s: string) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
36
+ const tomlKey = (k: string) => (/^[A-Za-z0-9_-]+$/.test(k) ? k : JSON.stringify(k))
37
+ const tomlValue = (v: unknown): string =>
38
+ typeof v === "string"
39
+ ? JSON.stringify(v)
40
+ : typeof v === "number" || typeof v === "boolean"
41
+ ? String(v)
42
+ : Array.isArray(v)
43
+ ? `[${v.map(tomlValue).join(", ")}]`
44
+ : `{ ${Object.entries(v as Json).map(([k, x]) => `${tomlKey(k)} = ${tomlValue(x)}`).join(", ")} }`
45
+
46
+ const ANY_HEADER = /^[ \t]*\[/
47
+ const quiet = (l: string) => l.trim() === "" || /^\s*#/.test(l)
48
+
49
+ /** The ` # comment` after a `key = value` line (a # outside quotes), with the spaces before it. */
50
+ function lineComment(line: string): string {
51
+ let quote: string | undefined
52
+ for (let i = line.indexOf("=") + 1; i > 0 && i < line.length; i++) {
53
+ const c = line[i]!
54
+ if (quote) {
55
+ if (c === "\\" && quote === '"') i++
56
+ else if (c === quote) quote = undefined
57
+ } else if (c === '"' || c === "'") quote = c
58
+ else if (c === "#") {
59
+ let j = i
60
+ while (j > 0 && /[ \t]/.test(line[j - 1]!)) j--
61
+ return line.slice(j).trimEnd()
62
+ }
63
+ }
64
+ return ""
65
+ }
66
+
67
+ function tomlSet(text: string, key: string, value: unknown): string {
68
+ const nl = text === "" || text.endsWith("\n")
69
+ const lines = text ? text.replace(/\n$/, "").split("\n") : []
70
+ const slash = key.indexOf("/")
71
+ if (slash < 0) {
72
+ // A top-level key: its line before the first table, replaced in place (keeping a trailing comment), or added at the
73
+ // end of that part (before the comments that introduce the first table).
74
+ const first = lines.findIndex((l) => ANY_HEADER.test(l))
75
+ const preEnd = first < 0 ? lines.length : first
76
+ const re = new RegExp(`^[ \\t]*(?:${esc(key)}|"${esc(key)}"|'${esc(key)}')[ \\t]*=`)
77
+ const at = lines.slice(0, preEnd).findIndex((l) => re.test(l))
78
+ if (value === undefined) {
79
+ if (at >= 0) lines.splice(at, 1)
80
+ } else if (at >= 0) lines[at] = `${tomlKey(key)} = ${tomlValue(value)}${lineComment(lines[at]!)}`
81
+ else {
82
+ let ins = preEnd
83
+ if (first >= 0) while (ins > 0 && quiet(lines[ins - 1]!)) ins--
84
+ lines.splice(ins, 0, `${tomlKey(key)} = ${tomlValue(value)}`)
85
+ }
86
+ } else {
87
+ // A whole table [parent.name]: its header line and the lines up to the next header (the blank lines and comments
88
+ // just before that header belong to it, not to this table).
89
+ const parent = key.slice(0, slash)
90
+ const name = key.slice(slash + 1)
91
+ const head = new RegExp(`^[ \\t]*\\[[ \\t]*${esc(parent)}[ \\t]*\\.[ \\t]*(?:${esc(name)}|"${esc(name)}"|'${esc(name)}')[ \\t]*\\][ \\t]*(?:#.*)?$`)
92
+ const at = lines.findIndex((l) => head.test(l))
93
+ let end = at + 1
94
+ if (at >= 0) {
95
+ while (end < lines.length && !ANY_HEADER.test(lines[end]!)) end++
96
+ while (end > at + 1 && quiet(lines[end - 1]!)) end--
97
+ }
98
+ const block = value === undefined ? [] : [`[${tomlKey(parent)}.${tomlKey(name)}]`, ...Object.entries(value as Json).map(([k, x]) => `${tomlKey(k)} = ${tomlValue(x)}`)]
99
+ if (value === undefined) {
100
+ if (at >= 0) {
101
+ lines.splice(at, end - at)
102
+ // The blank line that was put before it when it was added.
103
+ if (at > 0 && lines[at - 1] === "") lines.splice(at - 1, 1)
104
+ }
105
+ } else if (at >= 0) lines.splice(at, end - at, ...block)
106
+ else {
107
+ if (lines.length) lines.push("")
108
+ lines.push(...block)
109
+ }
110
+ }
111
+ return lines.length ? `${lines.join("\n")}${nl ? "\n" : ""}` : ""
112
+ }
113
+
114
+ const pathGet = (o: unknown, key: string): unknown => key.split("/").reduce<unknown>((cur, p) => (cur && typeof cur === "object" && !Array.isArray(cur) ? (cur as Json)[p] : undefined), o)
115
+
116
+ const canon = (v: unknown): string =>
117
+ v instanceof Date
118
+ ? JSON.stringify(v.toISOString())
119
+ : Array.isArray(v)
120
+ ? `[${v.map(canon).join(",")}]`
121
+ : v && typeof v === "object"
122
+ ? `{${Object.keys(v).sort().map((k) => `${JSON.stringify(k)}:${canon((v as Json)[k])}`).join(",")}}`
123
+ : JSON.stringify(v)
124
+
125
+ /** `o` without `key`, and without the tables that leaves empty (to compare the rest of a file). */
126
+ function without(o: Json, key: string): Json {
127
+ const c = structuredClone(o)
128
+ const ps = key.split("/")
129
+ const parent = ps.length > 1 ? c[ps[0]!] : c
130
+ if (!parent || typeof parent !== "object") return c
131
+ delete (parent as Json)[ps[ps.length - 1]!]
132
+ if (ps.length > 1 && !Object.keys(parent).length) delete c[ps[0]!]
133
+ return c
134
+ }
135
+
136
+ /** Stand-ins for what brain writes: a trial edit with them must land exactly on those entries and change nothing else. */
137
+ const TRIAL: Record<string, unknown> = {
138
+ default_model: "brain-trial",
139
+ "providers/brain-gateway": { type: "openai", base_url: "http://127.0.0.1/v1", custom_headers: { a: "b" } },
140
+ "models/brain-gateway": { provider: "brain-gateway", model: "m", max_context_size: 1, capabilities: ["tool_use"] },
141
+ }
142
+
143
+ /**
144
+ * Whether `providers` or `models`, or brain's entry in them, is written in a form a `[providers.brain-gateway]` table
145
+ * cannot be added next to (an inline table, dotted keys, a sub-table or an array of tables). TOML forbids defining a table
146
+ * twice and Kimi Code's parser enforces it; Bun's parser does not, so this is checked line by line.
147
+ */
148
+ function unusualLayout(text: string): boolean {
149
+ let table: string | undefined
150
+ for (const l of text.split("\n")) {
151
+ const h = /^[ \t]*(\[\[?)([^\]]*)\]/.exec(l)
152
+ if (h) {
153
+ table = h[2]!.replace(/[\s"']/g, "")
154
+ if (/^(providers|models)\.brain-gateway\./.test(table) || (h[1] === "[[" && /^(providers|models)(\.|$)/.test(table))) return true
155
+ continue
156
+ }
157
+ if (table === undefined && /^[ \t]*["']?(providers|models)["']?[ \t]*[.=]/.test(l)) return true
158
+ if ((table === "providers" || table === "models") && /^[ \t]*["']?brain-gateway["']?[ \t]*[.=]/.test(l)) return true
159
+ }
160
+ return false
161
+ }
162
+
163
+ /** Kimi Code's config.toml: top-level keys ("default_model") and whole tables ("providers/brain-gateway"). */
164
+ export const kimiToml: Codec = {
165
+ check(text) {
166
+ if (!text.trim()) return undefined
167
+ let before: Json
168
+ try {
169
+ before = Bun.TOML.parse(text) as Json
170
+ } catch {
171
+ return "it is not valid TOML; left as it is"
172
+ }
173
+ try {
174
+ if (unusualLayout(text)) throw new Error("layout")
175
+ let t = text
176
+ for (const [k, v] of Object.entries(TRIAL)) t = tomlSet(t, k, v)
177
+ const after = Bun.TOML.parse(t) as Json
178
+ let rest = after
179
+ let was = before
180
+ for (const [k, v] of Object.entries(TRIAL)) {
181
+ if (canon(pathGet(after, k)) !== canon(v)) throw new Error(k)
182
+ rest = without(rest, k)
183
+ was = without(was, k)
184
+ }
185
+ if (canon(rest) !== canon(was)) throw new Error("other values moved")
186
+ // Where none of brain's entries is there yet, taking them out again must give back the very same text.
187
+ if (Object.keys(TRIAL).every((k) => pathGet(before, k) === undefined)) {
188
+ for (const k of Object.keys(TRIAL)) t = tomlSet(t, k, undefined)
189
+ if (t !== text) throw new Error("not reversible")
190
+ }
191
+ } catch {
192
+ return "brain cannot add its entries without touching other settings (an unusual layout: inline tables or dotted keys for providers or models?); left as it is"
193
+ }
194
+ return undefined
195
+ },
196
+ get(text, key) {
197
+ if (!text.trim()) return undefined
198
+ return pathGet(Bun.TOML.parse(text), key)
199
+ },
200
+ set: tomlSet,
201
+ }
202
+
203
+ export const PROVIDER = "brain-gateway"
204
+
205
+ export const connectors: Connector[] = [
206
+ {
207
+ id: "kimi-code",
208
+ label: "Kimi Code",
209
+ file: kimiConfig,
210
+ probe: [".kimi-code"],
211
+ authHeader: "authorization",
212
+ edits: (home, s, env) => [
213
+ {
214
+ path: kimiConfig(home, env),
215
+ format: "custom",
216
+ codec: kimiToml,
217
+ holdsKey: true,
218
+ secretKeys: [`providers/${PROVIDER}`],
219
+ values: {
220
+ default_model: PROVIDER,
221
+ [`providers/${PROVIDER}`]: {
222
+ type: "openai",
223
+ base_url: `${routeUrl(s)}/v1`,
224
+ api_key: readKey(home),
225
+ ...(s.contextEdit ? { custom_headers: { [EDIT_HEADER]: "on" } } : {}),
226
+ },
227
+ [`models/${PROVIDER}`]: {
228
+ provider: PROVIDER,
229
+ model: s.model,
230
+ max_context_size: 1048576,
231
+ capabilities: ["image_in", "tool_use"],
232
+ display_name: modelName(s.model),
233
+ },
234
+ },
235
+ },
236
+ ],
237
+ notes: [
238
+ "Kimi Code cannot read a key from a file or a command (only from its config.toml, or from a variable you export), so the key is kept in ~/.kimi-code/config.toml, which is now readable by you only (0600).",
239
+ ],
240
+ },
241
+ ]
@@ -0,0 +1,6 @@
1
+ // Gateway connector for Kiro CLI: none. It uses only Kiro's AWS-hosted models (sign-in required); ~/.kiro/settings/cli.json
2
+ // picks a model by name (chat.defaultModel) and has no endpoint, provider or key (https://kiro.dev/docs/reference/settings/,
3
+ // updated 2026-11-06; the BYOK request is still open: https://github.com/kirodotdev/Kiro/issues/8939).
4
+ import type { Connector } from "../common.ts"
5
+
6
+ export const connectors: Connector[] = []
@@ -0,0 +1,6 @@
1
+ // Gateway connector for Kiro IDE: none. Its model dropdown offers only Kiro's own models, served from AWS regions; there is
2
+ // no custom model, endpoint or key (https://kiro.dev/docs/chat/model-selection/, updated 2026-10-24; BYOK request open since
3
+ // 2026-05-28: https://github.com/kirodotdev/Kiro/issues/8939).
4
+ import type { Connector } from "../common.ts"
5
+
6
+ export const connectors: Connector[] = []
@@ -0,0 +1,170 @@
1
+ // Gateway connector for Letta Code (`letta`, npm @letta-ai/letta-code). Checked against its source at 0.34.51
2
+ // (github.com/letta-ai/letta-code, main at 0aed9a6, 2026-11-10; Letta Code is not installed here: wire/drivers/b.ts says
3
+ // why) and docs.letta.com/letta-code/models.
4
+ //
5
+ // Letta Code has two backends, and only one of them makes its model calls from this machine:
6
+ // - Letta Cloud / a Letta server (the default, `letta backend cloud`): the agent runs on the server, which calls the
7
+ // model. Custom endpoints there are BYOK providers stored in the Letta account through the server's /v1/providers
8
+ // API (src/backend/api/providers.ts:111-152), and they "must be reachable from the public internet"
9
+ // (docs.letta.com/letta-code/models). Nothing on disk to change: those agents are left alone.
10
+ // - Local mode (`letta backend local`, or --backend local; src/cli/subcommands/backend.ts:27-79): agents live in
11
+ // ~/.letta/lc-local-backend and Letta Code calls the model itself through pi-ai. Its built-in "OpenAI-compatible"
12
+ // provider (/connect, stored in lc-local-backend/providers/auth.json) cannot be used: it offers only the models its
13
+ // GET <base>/v1/models returns (src/backend/dev/pi-openai-compatible-provider.ts:16-22; models are kept in memory
14
+ // only and "never fabricated from name strings": pi-models-runtime.ts:209, pi-model-factory.ts), and the gateway
15
+ // answers /v1/models with 403. A provider MOD can: Letta loads every .js/.mjs/.ts file in
16
+ // ${LETTA_CONFIG_DIR:-~/.letta}/mods (or the legacy ~/.letta/extensions) as trusted local code at start and on
17
+ // /reload (src/mods/paths.ts:20-53, mod-sources.ts:45-106, mod-engine.ts:1430-1490), and a mod's
18
+ // letta.providers.register(id, {...}) declares an OpenAI-compatible provider with a fixed model list, its own
19
+ // baseUrl, an apiKey and headers (src/backend/dev/pi-provider-mod-types.ts:68-82;
20
+ // src/skills/builtin/creating-mods/references/providers.md). Static models are never re-listed
21
+ // (pi-mod-provider.ts:98-122), `connect: false` makes it usable with no /connect step
22
+ // (registered-pi-provider-runtime.ts:62-73), `authHeader` sends `Authorization: Bearer <apiKey>` and `headers` go on
23
+ // every request (pi-mod-provider.ts:41-59,124-153).
24
+ //
25
+ // So brain writes one file it owns, <mods>/brain-gateway.mjs: a mod registering the provider "brain-gateway" (shown as
26
+ // "100xprompt gateway", its model as modelName(s.model)) on OpenAI Chat Completions at <route>/v1, with the
27
+ // context-edit header when on. The mod reads the key from the brain's key file each time it is activated, so the key is
28
+ // never copied (and a new sign-in is picked up at the next start or /reload). disconnect deletes the file.
29
+ //
30
+ // Limits, said on connect: only local agents can use it; the model is chosen per agent (each local agent record keeps
31
+ // its own model, src/backend/local/local-agent-record.ts; there is no global default-model setting,
32
+ // src/settings-manager.ts:68-112), so the person picks it with /model. Letta adds its own x-letta-request-kind header
33
+ // (src/backend/dev/pi-request-headers.ts).
34
+ import { existsSync } from "node:fs"
35
+ import { isAbsolute, join } from "node:path"
36
+ import { type Codec, type Connector, EDIT_HEADER, type Env, type GatewaySettings, type Json, keyPath, modelName, routeUrl } from "../common.ts"
37
+
38
+ export const MOD_FILE = "brain-gateway.mjs"
39
+ const MARK = "// Written by `brain gateway connect --tool letta-code`"
40
+ const SETTINGS_LINE = /^const gateway = (.*)$/m
41
+
42
+ /** ${LETTA_CONFIG_DIR:-~/.letta} (src/utils/settings-path.ts:10-22): the override counts when absolute and present. */
43
+ function configDir(home: string, env: Env): string {
44
+ const o = env.LETTA_CONFIG_DIR?.trim()
45
+ return o && isAbsolute(o) && existsSync(o) ? o : join(home, ".letta")
46
+ }
47
+
48
+ /** Where Letta looks for global mods, as resolveDefaultGlobalModsDirectory does (src/mods/paths.ts:34-53). */
49
+ export function modsDir(home: string, env: Env): string {
50
+ const fromEnv = (env.LETTA_MODS_DIR ?? env.LETTA_EXTENSIONS_DIR)?.trim()
51
+ if (fromEnv && existsSync(fromEnv)) return fromEnv
52
+ const mods = join(configDir(home, env), "mods")
53
+ if (existsSync(mods)) return mods
54
+ const legacy = join(configDir(home, env), "extensions")
55
+ return existsSync(legacy) ? legacy : mods
56
+ }
57
+
58
+ /** A JS literal for `v` (JSON, with the two line separators JSON leaves raw escaped, so it is safe on one line of code). */
59
+ const literal = (v: unknown) => JSON.stringify(v).replace(/[\u2028\u2029]/g, (c) => `\\u${c.charCodeAt(0).toString(16)}`)
60
+
61
+ /** The whole mod, from its settings: the only part that varies is the `const gateway = {...}` line. */
62
+ export function renderMod(settings: Json): string {
63
+ if (!Object.keys(settings).length) return ""
64
+ return `${MARK}: it lets Letta Code's local agents use the
65
+ // 100xprompt gateway ("100xprompt gateway" in /model). \`brain gateway disconnect --tool letta-code\` removes it; a
66
+ // reconnect writes it again. The key is read from the brain's key file each time Letta Code starts or reloads mods.
67
+ import { readFileSync } from "node:fs"
68
+
69
+ const gateway = ${literal(settings)}
70
+
71
+ export default function activate(letta) {
72
+ if (!letta?.capabilities?.providers || !gateway.baseUrl || !gateway.model) return
73
+ let apiKey
74
+ try {
75
+ apiKey = readFileSync(gateway.keyFile, "utf8").trim() || undefined
76
+ } catch {}
77
+ return letta.providers.register(gateway.provider, {
78
+ name: gateway.name,
79
+ description: "The 100xprompt gateway, set up by brain",
80
+ api: "openai-completions",
81
+ baseUrl: gateway.baseUrl,
82
+ ...(apiKey ? { apiKey } : {}),
83
+ authHeader: true,
84
+ ...(gateway.headers ? { headers: gateway.headers } : {}),
85
+ connect: false,
86
+ models: [
87
+ {
88
+ id: gateway.model,
89
+ name: gateway.modelName,
90
+ reasoning: false,
91
+ input: ["text", "image"],
92
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
93
+ contextWindow: gateway.contextWindow,
94
+ maxTokens: gateway.maxTokens,
95
+ compat: { supportsDeveloperRole: false, supportsReasoningEffort: false },
96
+ },
97
+ ],
98
+ })
99
+ }
100
+ `
101
+ }
102
+
103
+ /** The settings of a mod brain wrote (undefined for any other file). */
104
+ function readSettings(text: string): Json | undefined {
105
+ if (!text.startsWith(MARK)) return undefined
106
+ const m = SETTINGS_LINE.exec(text)
107
+ if (!m) return undefined
108
+ try {
109
+ const v = JSON.parse(m[1]!)
110
+ return v && typeof v === "object" && !Array.isArray(v) ? v : undefined
111
+ } catch {
112
+ return undefined
113
+ }
114
+ }
115
+
116
+ /**
117
+ * The mod file as settings: each key is one field of its `const gateway = {...}` line, and the file is generated whole
118
+ * from them (no fields: no file). A file brain did not write is refused; a brain file that was edited by hand reads as
119
+ * changed, so disconnect leaves it (and says so).
120
+ */
121
+ export const lettaMod: Codec = {
122
+ check(text) {
123
+ if (!text.trim() || readSettings(text)) return undefined
124
+ return "a mod by that name that brain did not write is there; left as it is"
125
+ },
126
+ get(text, key) {
127
+ const s = readSettings(text)
128
+ if (!s) return undefined
129
+ return renderMod(s) === text ? s[key] : "(changed by hand)"
130
+ },
131
+ set(text, key, value) {
132
+ const s = { ...(readSettings(text) ?? {}) }
133
+ if (value === undefined) delete s[key]
134
+ else s[key] = value
135
+ return renderMod(s)
136
+ },
137
+ }
138
+
139
+ /** What 100xprompt's models take (Gemini 3.x Flash: 1M tokens in, 64k out). */
140
+ const CONTEXT_WINDOW = 1_048_576
141
+ const MAX_TOKENS = 65_536
142
+
143
+ const modValues = (home: string, s: GatewaySettings): Record<string, unknown> => ({
144
+ provider: "brain-gateway",
145
+ name: "100xprompt gateway",
146
+ baseUrl: `${routeUrl(s)}/v1`,
147
+ model: s.model,
148
+ modelName: modelName(s.model),
149
+ contextWindow: CONTEXT_WINDOW,
150
+ maxTokens: MAX_TOKENS,
151
+ keyFile: keyPath(home),
152
+ ...(s.contextEdit ? { headers: { [EDIT_HEADER]: "on" } } : {}),
153
+ })
154
+
155
+ export const connectors: Connector[] = [
156
+ {
157
+ id: "letta-code",
158
+ label: "Letta Code",
159
+ file: (home, env) => join(modsDir(home, env), MOD_FILE),
160
+ // Its local mode has been used here (the local backend's store); a mods folder counts too (the file's own folder).
161
+ probe: [".letta/lc-local-backend"],
162
+ authHeader: "authorization",
163
+ edits: (home, s, env) => [{ path: join(modsDir(home, env), MOD_FILE), format: "custom", codec: lettaMod, values: modValues(home, s) }],
164
+ notes: [
165
+ "Letta Code reaches the gateway through a provider mod brain wrote in its mods folder; the mod reads the key from the brain's key file each time Letta Code starts (or on /reload), so the key is not copied.",
166
+ "Only local agents (letta backend local, or --backend local) use it: agents on Letta Cloud or a Letta server call their model from that server and are not changed.",
167
+ "Letta Code keeps a model per agent: in each local agent, choose the 100xprompt gateway model with /model. After disconnect, an agent still set to it asks you to choose another.",
168
+ ],
169
+ },
170
+ ]
@@ -0,0 +1,91 @@
1
+ // Gateway connector for Mastra Code (the `mastracode` CLI, npm `mastracode`, source mastra-ai/mastra/mastracode). Checked against
2
+ // the source at tag mastracode@0.49.0 (the npm latest on 2026-11-10) and, for the same facts, mastracode@0.37.1 (the version
3
+ // brain's session adapter was checked against). It is not installed on this machine (wire/drivers/f.ts), so there is no
4
+ // real-tool check for it.
5
+ //
6
+ // Everything Mastra Code keeps is in one JSON file, settings.json in its app-data folder: $MASTRA_APP_DATA_DIR, else
7
+ // ~/Library/Application Support/mastracode (macOS), %APPDATA%\mastracode (Windows), $XDG_DATA_HOME/mastracode or
8
+ // ~/.local/share/mastracode (sdk/src/utils/project.ts:187-212; settings.ts:781-783 getSettingsPath).
9
+ // - `customProviders: [{name, url, apiKey?, models: [id]}]` adds an OpenAI-compatible provider (settings.ts:41-46). Its id
10
+ // is the slugged name, "100xprompt gateway" -> 100xprompt-gateway (settings.ts:785-792), its models are
11
+ // `<id>/<model>`, and a request goes to `<url>/chat/completions` through @ai-sdk/openai-compatible with
12
+ // `Authorization: Bearer <apiKey>` (sdk/src/agents/mastracode-gateway.ts:490-511; the key is the literal apiKey,
13
+ // :477-479). There is no key file, env or command form and no per-provider headers, so the key is COPIED into
14
+ // settings.json (made 0600; Mastra Code keeps a file's mode when it rewrites it, settings.ts:1519) and the
15
+ // context-edit header cannot be sent.
16
+ // - `customModelPacks: [{name, models: {build, plan, fast}, createdAt}]` plus `models.activeModelPackId: "custom:<name>"`
17
+ // make every mode's default model the pack's (settings.ts:34-38, 1393-1407 resolveModelDefaults), which is how
18
+ // Mastra Code's own /models picker saves a custom pack. That leaves the person's own modeDefaults untouched.
19
+ // The file is plain JSON (settings.ts:1199 JSON.parse) that Mastra Code rewrites whole with JSON.stringify(_, null, 2)
20
+ // (settings.ts:1537) and that always holds both lists; brain's list entries are found by name, so they survive those
21
+ // rewrites, and the text editor (pi.ts) gives the exact bytes back on disconnect. A missing settings.json is refused: Mastra
22
+ // Code writes it when its first-run setup finishes, and that setup picks a model pack of its own.
23
+ import { existsSync } from "node:fs"
24
+ import { join, resolve, sep } from "node:path"
25
+ import { type Connector, type Env, type GatewaySettings, type Json, readKey, routeUrl } from "../common.ts"
26
+ import { jsonText, PROVIDER_NAME } from "./pi.ts"
27
+
28
+ /** Mastra Code's provider id for a custom provider: its name, slugged (settings.ts getCustomProviderId). */
29
+ export const providerSlug = (name: string) =>
30
+ name
31
+ .trim()
32
+ .toLowerCase()
33
+ .replace(/[^a-z0-9]+/g, "-")
34
+ .replace(/^-+|-+$/g, "") || "provider"
35
+
36
+ /** The pack's creation time: fixed, so a reconnect with the same settings changes nothing. */
37
+ const CREATED = "2026-11-10T00:00:00.000Z"
38
+
39
+ /** An env folder counts only inside this home (a throwaway home in tests never follows the real XDG_DATA_HOME). */
40
+ const inside = (home: string, dir: string | undefined) => !!dir && dir.startsWith("/") && existsSync(dir) && resolve(dir).startsWith(resolve(home) + sep)
41
+
42
+ export function appDir(home: string, env: Env, platform: string = process.platform): string {
43
+ const own = env.MASTRA_APP_DATA_DIR
44
+ if (own && existsSync(own)) return own
45
+ if (platform === "darwin") return join(home, "Library", "Application Support", "mastracode")
46
+ if (platform === "win32") return join(inside(home, env.APPDATA) ? env.APPDATA! : join(home, "AppData", "Roaming"), "mastracode")
47
+ return join(inside(home, env.XDG_DATA_HOME) ? env.XDG_DATA_HOME! : join(home, ".local", "share"), "mastracode")
48
+ }
49
+
50
+ const PROVIDERS_KEY = `customProviders[name=${PROVIDER_NAME}]`
51
+ const PACK_KEY = `customModelPacks[name=${PROVIDER_NAME}]`
52
+
53
+ export function mastraValues(home: string, s: GatewaySettings): Json {
54
+ const model = `${providerSlug(PROVIDER_NAME)}/${s.model}`
55
+ return {
56
+ // Key order as Mastra Code writes a provider back (parseCustomProviders), so its rewrites compare equal.
57
+ [PROVIDERS_KEY]: { name: PROVIDER_NAME, url: `${routeUrl(s)}/v1`, apiKey: readKey(home), models: [s.model] },
58
+ [PACK_KEY]: { name: PROVIDER_NAME, models: { build: model, plan: model, fast: model }, createdAt: CREATED },
59
+ "models/activeModelPackId": `custom:${PROVIDER_NAME}`,
60
+ }
61
+ }
62
+
63
+ const mastracode: Connector = {
64
+ id: "mastracode",
65
+ label: "Mastra Code",
66
+ file: (home, env) => join(appDir(home, env), "settings.json"),
67
+ // Its app-data folder (the main file's folder, also where mastra.db lives) is what the first run creates.
68
+ probe: [],
69
+ authHeader: "authorization",
70
+ edits: (home, s, env) => [
71
+ {
72
+ path: join(appDir(home, env), "settings.json"),
73
+ format: "custom",
74
+ codec: jsonText({
75
+ jsonc: false,
76
+ mustExist: "Mastra Code has not saved its settings yet; start mastracode once and finish its setup, then connect",
77
+ shape: { customProviders: "array", customModelPacks: "array", models: "object" },
78
+ }),
79
+ holdsKey: true,
80
+ secretKeys: [PROVIDERS_KEY],
81
+ values: mastraValues(home, s),
82
+ },
83
+ ],
84
+ notes: [
85
+ `Mastra Code now uses the "${PROVIDER_NAME}" custom provider for its build, plan and fast modes (the "${PROVIDER_NAME}" model pack). It can only take the key in its settings.json, so the file is now readable by you only (0600).`,
86
+ "Mastra Code cannot send extra headers to a custom provider: context editing applies only when the gateway turns it on for everyone.",
87
+ "Threads you already have keep their own model; observational memory and subagent models keep theirs (/om, /subagents). Picking another pack in /models switches back without disconnecting.",
88
+ ],
89
+ }
90
+
91
+ export const connectors: Connector[] = [mastracode]