@datename/fastcode-termux 0.0.0-stage → 0.1.6-beta

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 (347) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +418 -2
  3. package/bin/fastcode.mjs +59 -0
  4. package/dist/agent/approval.d.ts +50 -0
  5. package/dist/agent/approval.js +7 -0
  6. package/dist/agent/compaction.d.ts +39 -0
  7. package/dist/agent/compaction.js +227 -0
  8. package/dist/agent/context.d.ts +29 -0
  9. package/dist/agent/context.js +62 -0
  10. package/dist/agent/guards.d.ts +43 -0
  11. package/dist/agent/guards.js +269 -0
  12. package/dist/agent/ignore.d.ts +11 -0
  13. package/dist/agent/ignore.js +129 -0
  14. package/dist/agent/limits.d.ts +24 -0
  15. package/dist/agent/limits.js +71 -0
  16. package/dist/agent/process-tree.d.ts +2 -0
  17. package/dist/agent/process-tree.js +31 -0
  18. package/dist/agent/protocol-mode.d.ts +6 -0
  19. package/dist/agent/protocol-mode.js +51 -0
  20. package/dist/agent/protocol.d.ts +4 -0
  21. package/dist/agent/protocol.js +129 -0
  22. package/dist/agent/registry.d.ts +17 -0
  23. package/dist/agent/registry.js +90 -0
  24. package/dist/agent/runner.d.ts +106 -0
  25. package/dist/agent/runner.js +352 -0
  26. package/dist/agent/shell-session.d.ts +37 -0
  27. package/dist/agent/shell-session.js +400 -0
  28. package/dist/agent/stream.d.ts +29 -0
  29. package/dist/agent/stream.js +103 -0
  30. package/dist/agent/tool-schemas.d.ts +2 -0
  31. package/dist/agent/tool-schemas.js +63 -0
  32. package/dist/agent/tools/append_file.d.ts +2 -0
  33. package/dist/agent/tools/append_file.js +26 -0
  34. package/dist/agent/tools/apply_patch.d.ts +10 -0
  35. package/dist/agent/tools/apply_patch.js +52 -0
  36. package/dist/agent/tools/copy_file.d.ts +2 -0
  37. package/dist/agent/tools/copy_file.js +30 -0
  38. package/dist/agent/tools/delete_file.d.ts +2 -0
  39. package/dist/agent/tools/delete_file.js +24 -0
  40. package/dist/agent/tools/grep.d.ts +2 -0
  41. package/dist/agent/tools/grep.js +23 -0
  42. package/dist/agent/tools/index.d.ts +2 -0
  43. package/dist/agent/tools/index.js +32 -0
  44. package/dist/agent/tools/list_files.d.ts +2 -0
  45. package/dist/agent/tools/list_files.js +17 -0
  46. package/dist/agent/tools/make_dir.d.ts +2 -0
  47. package/dist/agent/tools/make_dir.js +24 -0
  48. package/dist/agent/tools/move_path.d.ts +2 -0
  49. package/dist/agent/tools/move_path.js +47 -0
  50. package/dist/agent/tools/paths.d.ts +7 -0
  51. package/dist/agent/tools/paths.js +84 -0
  52. package/dist/agent/tools/pwd.d.ts +2 -0
  53. package/dist/agent/tools/pwd.js +10 -0
  54. package/dist/agent/tools/read_file.d.ts +2 -0
  55. package/dist/agent/tools/read_file.js +21 -0
  56. package/dist/agent/tools/read_many.d.ts +2 -0
  57. package/dist/agent/tools/read_many.js +25 -0
  58. package/dist/agent/tools/readers.d.ts +6 -0
  59. package/dist/agent/tools/readers.js +216 -0
  60. package/dist/agent/tools/replace_in_file.d.ts +2 -0
  61. package/dist/agent/tools/replace_in_file.js +30 -0
  62. package/dist/agent/tools/shell.d.ts +2 -0
  63. package/dist/agent/tools/shell.js +48 -0
  64. package/dist/agent/tools/stat_path.d.ts +2 -0
  65. package/dist/agent/tools/stat_path.js +31 -0
  66. package/dist/agent/tools/types.d.ts +39 -0
  67. package/dist/agent/tools/types.js +1 -0
  68. package/dist/agent/tools/validate.d.ts +6 -0
  69. package/dist/agent/tools/validate.js +29 -0
  70. package/dist/agent/tools/write_file.d.ts +2 -0
  71. package/dist/agent/tools/write_file.js +26 -0
  72. package/dist/agent/tools.d.ts +3 -0
  73. package/dist/agent/tools.js +2 -0
  74. package/dist/app.d.ts +4 -0
  75. package/dist/app.js +708 -0
  76. package/dist/cli.d.ts +1 -0
  77. package/dist/cli.js +569 -0
  78. package/dist/commands/about.d.ts +8 -0
  79. package/dist/commands/about.js +16 -0
  80. package/dist/commands/agent.d.ts +11 -0
  81. package/dist/commands/agent.js +18 -0
  82. package/dist/commands/attach.d.ts +9 -0
  83. package/dist/commands/attach.js +15 -0
  84. package/dist/commands/bg.d.ts +7 -0
  85. package/dist/commands/bg.js +9 -0
  86. package/dist/commands/cancel.d.ts +10 -0
  87. package/dist/commands/cancel.js +10 -0
  88. package/dist/commands/clear.d.ts +11 -0
  89. package/dist/commands/clear.js +9 -0
  90. package/dist/commands/compact.d.ts +10 -0
  91. package/dist/commands/compact.js +22 -0
  92. package/dist/commands/config.d.ts +7 -0
  93. package/dist/commands/config.js +24 -0
  94. package/dist/commands/context.d.ts +7 -0
  95. package/dist/commands/context.js +24 -0
  96. package/dist/commands/cost.d.ts +9 -0
  97. package/dist/commands/cost.js +13 -0
  98. package/dist/commands/cwd.d.ts +7 -0
  99. package/dist/commands/cwd.js +7 -0
  100. package/dist/commands/edit.d.ts +10 -0
  101. package/dist/commands/edit.js +36 -0
  102. package/dist/commands/effort.d.ts +25 -0
  103. package/dist/commands/effort.js +48 -0
  104. package/dist/commands/exit.d.ts +9 -0
  105. package/dist/commands/exit.js +8 -0
  106. package/dist/commands/expand.d.ts +10 -0
  107. package/dist/commands/expand.js +8 -0
  108. package/dist/commands/help.d.ts +6 -0
  109. package/dist/commands/help.js +18 -0
  110. package/dist/commands/index.d.ts +9 -0
  111. package/dist/commands/index.js +80 -0
  112. package/dist/commands/jobs.d.ts +7 -0
  113. package/dist/commands/jobs.js +16 -0
  114. package/dist/commands/kill.d.ts +7 -0
  115. package/dist/commands/kill.js +11 -0
  116. package/dist/commands/login.d.ts +18 -0
  117. package/dist/commands/login.js +63 -0
  118. package/dist/commands/logout.d.ts +9 -0
  119. package/dist/commands/logout.js +45 -0
  120. package/dist/commands/model.d.ts +24 -0
  121. package/dist/commands/model.js +50 -0
  122. package/dist/commands/plugin.d.ts +11 -0
  123. package/dist/commands/plugin.js +74 -0
  124. package/dist/commands/provider.d.ts +24 -0
  125. package/dist/commands/provider.js +39 -0
  126. package/dist/commands/reset.d.ts +9 -0
  127. package/dist/commands/reset.js +13 -0
  128. package/dist/commands/skills.d.ts +11 -0
  129. package/dist/commands/skills.js +106 -0
  130. package/dist/commands/steer.d.ts +11 -0
  131. package/dist/commands/steer.js +13 -0
  132. package/dist/commands/system.d.ts +11 -0
  133. package/dist/commands/system.js +20 -0
  134. package/dist/commands/theme.d.ts +11 -0
  135. package/dist/commands/theme.js +22 -0
  136. package/dist/commands/tools.d.ts +7 -0
  137. package/dist/commands/tools.js +29 -0
  138. package/dist/commands/whoami.d.ts +9 -0
  139. package/dist/commands/whoami.js +52 -0
  140. package/dist/config/paths.d.ts +4 -0
  141. package/dist/config/paths.js +59 -0
  142. package/dist/config/store.d.ts +89 -0
  143. package/dist/config/store.js +260 -0
  144. package/dist/config/themes.d.ts +73 -0
  145. package/dist/config/themes.js +77 -0
  146. package/dist/config/trust.d.ts +22 -0
  147. package/dist/config/trust.js +153 -0
  148. package/dist/index.d.ts +1 -0
  149. package/dist/index.js +31 -0
  150. package/dist/lib/diagnostics.d.ts +66 -0
  151. package/dist/lib/diagnostics.js +195 -0
  152. package/dist/lib/http.d.ts +23 -0
  153. package/dist/lib/http.js +128 -0
  154. package/dist/lib/markdown.d.ts +1 -0
  155. package/dist/lib/markdown.js +32 -0
  156. package/dist/lib/model-meta.d.ts +19 -0
  157. package/dist/lib/model-meta.js +113 -0
  158. package/dist/lib/native-tools.d.ts +12 -0
  159. package/dist/lib/native-tools.js +21 -0
  160. package/dist/lib/net.d.ts +33 -0
  161. package/dist/lib/net.js +168 -0
  162. package/dist/lib/pkg.d.ts +5 -0
  163. package/dist/lib/pkg.js +27 -0
  164. package/dist/lib/platform.d.ts +7 -0
  165. package/dist/lib/platform.js +57 -0
  166. package/dist/lib/startup.d.ts +6 -0
  167. package/dist/lib/startup.js +25 -0
  168. package/dist/lib/uiMode.d.ts +2 -0
  169. package/dist/lib/uiMode.js +7 -0
  170. package/dist/patch/apply.d.ts +15 -0
  171. package/dist/patch/apply.js +172 -0
  172. package/dist/patch/parse.d.ts +29 -0
  173. package/dist/patch/parse.js +112 -0
  174. package/dist/patch/plan.d.ts +49 -0
  175. package/dist/patch/plan.js +273 -0
  176. package/dist/plugins/manager.d.ts +23 -0
  177. package/dist/plugins/manager.js +101 -0
  178. package/dist/providers/anthropic.d.ts +22 -0
  179. package/dist/providers/anthropic.js +119 -0
  180. package/dist/providers/custom.d.ts +33 -0
  181. package/dist/providers/custom.js +25 -0
  182. package/dist/providers/deepseek.d.ts +34 -0
  183. package/dist/providers/deepseek.js +16 -0
  184. package/dist/providers/google.d.ts +33 -0
  185. package/dist/providers/google.js +19 -0
  186. package/dist/providers/groq.d.ts +34 -0
  187. package/dist/providers/groq.js +21 -0
  188. package/dist/providers/index.d.ts +10 -0
  189. package/dist/providers/index.js +67 -0
  190. package/dist/providers/mistral.d.ts +34 -0
  191. package/dist/providers/mistral.js +20 -0
  192. package/dist/providers/moonshot.d.ts +34 -0
  193. package/dist/providers/moonshot.js +23 -0
  194. package/dist/providers/ollama.d.ts +33 -0
  195. package/dist/providers/ollama.js +22 -0
  196. package/dist/providers/openai.d.ts +34 -0
  197. package/dist/providers/openai.js +26 -0
  198. package/dist/providers/openaiCompatible.d.ts +26 -0
  199. package/dist/providers/openaiCompatible.js +158 -0
  200. package/dist/providers/openrouter.d.ts +34 -0
  201. package/dist/providers/openrouter.js +27 -0
  202. package/dist/providers/qwen.d.ts +34 -0
  203. package/dist/providers/qwen.js +24 -0
  204. package/dist/providers/xai.d.ts +34 -0
  205. package/dist/providers/xai.js +19 -0
  206. package/dist/providers/zhipu.d.ts +34 -0
  207. package/dist/providers/zhipu.js +24 -0
  208. package/dist/session/log.d.ts +52 -0
  209. package/dist/session/log.js +331 -0
  210. package/dist/session/projection.d.ts +43 -0
  211. package/dist/session/projection.js +257 -0
  212. package/dist/skills/manager.d.ts +18 -0
  213. package/dist/skills/manager.js +201 -0
  214. package/dist/skills/parser.d.ts +18 -0
  215. package/dist/skills/parser.js +65 -0
  216. package/dist/types.d.ts +205 -0
  217. package/dist/types.js +1 -0
  218. package/dist/ui/Banner.d.ts +5 -0
  219. package/dist/ui/Banner.js +65 -0
  220. package/dist/ui/Message.d.ts +1 -0
  221. package/dist/ui/Message.js +24 -0
  222. package/dist/ui/Picker.d.ts +8 -0
  223. package/dist/ui/Picker.js +24 -0
  224. package/dist/ui/Prompt.d.ts +9 -0
  225. package/dist/ui/Prompt.js +38 -0
  226. package/dist/ui/Spinner.d.ts +5 -0
  227. package/dist/ui/Spinner.js +39 -0
  228. package/dist/ui/StatusBar.d.ts +11 -0
  229. package/dist/ui/StatusBar.js +20 -0
  230. package/dist/ui/TrustGate.d.ts +5 -0
  231. package/dist/ui/TrustGate.js +26 -0
  232. package/package.json +96 -4
  233. package/src/agent/approval.ts +47 -0
  234. package/src/agent/compaction.ts +276 -0
  235. package/src/agent/context.ts +97 -0
  236. package/src/agent/guards.ts +270 -0
  237. package/src/agent/ignore.ts +147 -0
  238. package/src/agent/limits.ts +71 -0
  239. package/src/agent/process-tree.ts +32 -0
  240. package/src/agent/protocol-mode.ts +59 -0
  241. package/src/agent/protocol.ts +129 -0
  242. package/src/agent/registry.ts +98 -0
  243. package/src/agent/runner.ts +443 -0
  244. package/src/agent/shell-session.ts +449 -0
  245. package/src/agent/stream.ts +143 -0
  246. package/src/agent/tool-schemas.ts +67 -0
  247. package/src/agent/tools/append_file.ts +28 -0
  248. package/src/agent/tools/apply_patch.ts +54 -0
  249. package/src/agent/tools/copy_file.ts +32 -0
  250. package/src/agent/tools/delete_file.ts +26 -0
  251. package/src/agent/tools/grep.ts +25 -0
  252. package/src/agent/tools/index.ts +34 -0
  253. package/src/agent/tools/list_files.ts +19 -0
  254. package/src/agent/tools/make_dir.ts +26 -0
  255. package/src/agent/tools/move_path.ts +50 -0
  256. package/src/agent/tools/paths.ts +92 -0
  257. package/src/agent/tools/pwd.ts +12 -0
  258. package/src/agent/tools/read_file.ts +23 -0
  259. package/src/agent/tools/read_many.ts +27 -0
  260. package/src/agent/tools/readers.ts +228 -0
  261. package/src/agent/tools/replace_in_file.ts +32 -0
  262. package/src/agent/tools/shell.ts +53 -0
  263. package/src/agent/tools/stat_path.ts +33 -0
  264. package/src/agent/tools/types.ts +42 -0
  265. package/src/agent/tools/validate.ts +35 -0
  266. package/src/agent/tools/write_file.ts +28 -0
  267. package/src/agent/tools.ts +3 -0
  268. package/src/app.ts +708 -0
  269. package/src/cli.ts +335 -0
  270. package/src/commands/about.ts +16 -0
  271. package/src/commands/agent.ts +18 -0
  272. package/src/commands/attach.ts +16 -0
  273. package/src/commands/bg.ts +9 -0
  274. package/src/commands/cancel.ts +10 -0
  275. package/src/commands/clear.ts +9 -0
  276. package/src/commands/compact.ts +23 -0
  277. package/src/commands/config.ts +24 -0
  278. package/src/commands/context.ts +25 -0
  279. package/src/commands/cost.ts +13 -0
  280. package/src/commands/cwd.ts +7 -0
  281. package/src/commands/edit.ts +37 -0
  282. package/src/commands/effort.ts +49 -0
  283. package/src/commands/exit.ts +8 -0
  284. package/src/commands/expand.ts +8 -0
  285. package/src/commands/help.ts +18 -0
  286. package/src/commands/index.ts +80 -0
  287. package/src/commands/jobs.ts +17 -0
  288. package/src/commands/kill.ts +12 -0
  289. package/src/commands/login.ts +63 -0
  290. package/src/commands/logout.ts +45 -0
  291. package/src/commands/model.ts +50 -0
  292. package/src/commands/plugin.ts +74 -0
  293. package/src/commands/provider.ts +39 -0
  294. package/src/commands/reset.ts +13 -0
  295. package/src/commands/skills.ts +106 -0
  296. package/src/commands/steer.ts +13 -0
  297. package/src/commands/system.ts +20 -0
  298. package/src/commands/theme.ts +22 -0
  299. package/src/commands/tools.ts +29 -0
  300. package/src/commands/whoami.ts +52 -0
  301. package/src/config/paths.ts +59 -0
  302. package/src/config/store.ts +293 -0
  303. package/src/config/themes.ts +77 -0
  304. package/src/config/trust.ts +183 -0
  305. package/src/index.ts +31 -0
  306. package/src/lib/diagnostics.ts +195 -0
  307. package/src/lib/http.ts +132 -0
  308. package/src/lib/markdown.ts +32 -0
  309. package/src/lib/model-meta.ts +138 -0
  310. package/src/lib/native-tools.ts +32 -0
  311. package/src/lib/net.ts +204 -0
  312. package/src/lib/pkg.ts +35 -0
  313. package/src/lib/platform.ts +57 -0
  314. package/src/lib/startup.ts +25 -0
  315. package/src/lib/uiMode.ts +7 -0
  316. package/src/patch/apply.ts +194 -0
  317. package/src/patch/parse.ts +131 -0
  318. package/src/patch/plan.ts +317 -0
  319. package/src/plugins/manager.ts +101 -0
  320. package/src/providers/anthropic.ts +119 -0
  321. package/src/providers/custom.ts +25 -0
  322. package/src/providers/deepseek.ts +16 -0
  323. package/src/providers/google.ts +19 -0
  324. package/src/providers/groq.ts +21 -0
  325. package/src/providers/index.ts +68 -0
  326. package/src/providers/mistral.ts +20 -0
  327. package/src/providers/moonshot.ts +23 -0
  328. package/src/providers/ollama.ts +22 -0
  329. package/src/providers/openai.ts +26 -0
  330. package/src/providers/openaiCompatible.ts +158 -0
  331. package/src/providers/openrouter.ts +27 -0
  332. package/src/providers/qwen.ts +24 -0
  333. package/src/providers/xai.ts +19 -0
  334. package/src/providers/zhipu.ts +24 -0
  335. package/src/session/log.ts +374 -0
  336. package/src/session/projection.ts +308 -0
  337. package/src/skills/manager.ts +201 -0
  338. package/src/skills/parser.ts +65 -0
  339. package/src/types.ts +205 -0
  340. package/src/ui/Banner.ts +68 -0
  341. package/src/ui/Message.ts +24 -0
  342. package/src/ui/Picker.ts +24 -0
  343. package/src/ui/Prompt.ts +38 -0
  344. package/src/ui/Spinner.ts +39 -0
  345. package/src/ui/StatusBar.ts +20 -0
  346. package/src/ui/TrustGate.ts +26 -0
  347. package/tsconfig.json +22 -0
@@ -0,0 +1,195 @@
1
+ /**
2
+ * Environment diagnostics for mobile / Termux troubleshooting.
3
+ *
4
+ * `fastcode doctor` prints the values below so a broken mobile install can be
5
+ * diagnosed without opening the TUI: Node version, Termux detection, whether
6
+ * stdout is a real terminal, the config root, and which provider keys exist.
7
+ */
8
+ import { existsSync, statSync } from "node:fs";
9
+ import { totalmem, freemem, tmpdir as osTmpdir } from "node:os";
10
+ import { detectPlatform, platformLabel } from "./platform.js";
11
+ import { readPackageMeta } from "./pkg.js";
12
+ import { shouldUseStableTerminalUi } from "./uiMode.js";
13
+ import { getPaths } from "../config/paths.js";
14
+ import { loadConfig, resolveApiKey, resolveProviderKey } from "../config/store.js";
15
+ import { PROVIDERS } from "../providers/index.js";
16
+ import { listInstalledSkills } from "../skills/manager.js";
17
+ import { listInstalledPlugins } from "../plugins/manager.js";
18
+ import { findUnclosedSessions, fsyncMode, loggingEnabled, listSessions, sessionCacheLimitBytes } from "../session/log.js";
19
+ import { isTrusted } from "../config/trust.js";
20
+ import { DEFAULT_DENY_PATTERNS, loopExemptTools } from "../agent/guards.js";
21
+ import { toolOutputLimit } from "../agent/limits.js";
22
+ import { compactionConfigFromEnv, heapExceeded } from "../agent/compaction.js";
23
+ import { shellPoolStats } from "../agent/shell-session.js";
24
+ import { resolveToolProtocol, toolCapabilityKey } from "../agent/protocol-mode.js";
25
+ import { getModelMeta, modelMetaOverridePath } from "./model-meta.js";
26
+ const NODE_REQUIRED = "18.17";
27
+ function readVersion() {
28
+ return readPackageMeta().version;
29
+ }
30
+ function nodeIsSupported() {
31
+ const [major = 0, minor = 0] = process.versions.node.split(".").map(Number);
32
+ const [reqMajor = 0, reqMinor = 0] = NODE_REQUIRED.split(".").map(Number);
33
+ return major > reqMajor || (major === reqMajor && minor >= reqMinor);
34
+ }
35
+ export function collectDiagnostics() {
36
+ const config = loadConfig();
37
+ let paths;
38
+ try {
39
+ paths = getPaths();
40
+ }
41
+ catch {
42
+ paths = undefined;
43
+ }
44
+ const provider = PROVIDERS.find((p) => p.id === config.providerId);
45
+ const keyLookup = provider && provider.authEnv.length > 0
46
+ ? resolveApiKey(provider.id, config.model, provider.authEnv)
47
+ : { status: "ok" as const };
48
+ const keyPresent = Boolean(provider) && (provider.authEnv.length === 0 || keyLookup.status === "ok");
49
+ let skillsInstalled = 0;
50
+ let pluginsInstalled = 0;
51
+ try {
52
+ skillsInstalled = listInstalledSkills().length;
53
+ }
54
+ catch {
55
+ // A broken skills dir must not break the whole report.
56
+ }
57
+ try {
58
+ pluginsInstalled = listInstalledPlugins().length;
59
+ }
60
+ catch {
61
+ // Same for plugins.
62
+ }
63
+ const platform = detectPlatform();
64
+ const compaction = compactionConfigFromEnv();
65
+ const shells = shellPoolStats();
66
+ let authMode = "missing";
67
+ try {
68
+ if (paths?.auth && existsSync(paths.auth))
69
+ authMode = (statSync(paths.auth).mode & 0o777).toString(8).padStart(4, "0");
70
+ }
71
+ catch {
72
+ authMode = "unreadable";
73
+ }
74
+ return {
75
+ version: readVersion(),
76
+ node: process.version,
77
+ nodeRequired: NODE_REQUIRED,
78
+ nodeOk: nodeIsSupported(),
79
+ platform,
80
+ platformLabel: platformLabel(),
81
+ termux: platform === "termux",
82
+ termuxVersion: process.env["TERMUX_VERSION"] ?? null,
83
+ stableUi: shouldUseStableTerminalUi(),
84
+ tty: Boolean(process.stdout.isTTY),
85
+ configRoot: paths?.root,
86
+ configFile: paths?.config,
87
+ authFile: paths?.auth,
88
+ authMode,
89
+ tmpdir: process.env["TMPDIR"] || osTmpdir(),
90
+ memTotalMb: totalmem() / 1024 / 1024,
91
+ memFreeMb: freemem() / 1024 / 1024,
92
+ providerId: config.providerId,
93
+ model: config.model,
94
+ keyPresent,
95
+ keyStatus: provider ? keyLookup.status : "missing",
96
+ keyEnv: provider?.authEnv?.[0],
97
+ skillsInstalled,
98
+ pluginsInstalled,
99
+ enabledSkills: (config.enabledSkills ?? []).length,
100
+ enabledPlugins: (config.enabledPlugins ?? []).length,
101
+ sessionLogEnabled: loggingEnabled(),
102
+ sessionCacheMb: Math.round(sessionCacheLimitBytes() / (1024 * 1024)),
103
+ sessionFsync: fsyncMode(),
104
+ unclosedSessions: findUnclosedSessions().length,
105
+ sessionsDir: paths?.sessions,
106
+ sessionsCount: listSessions().length,
107
+ sessionsBytes: listSessions().reduce((sum, entry) => sum + entry.bytes, 0),
108
+ trustFile: paths?.trust,
109
+ trusted: isTrusted(process.cwd()),
110
+ shellDenyPatterns: DEFAULT_DENY_PATTERNS.length,
111
+ allowDangerous: ["1", "true", "yes", "on"].includes((process.env["FASTCODE_ALLOW_DANGEROUS"] ?? "").trim().toLowerCase()),
112
+ toolOutputLimit: toolOutputLimit(),
113
+ tokenBudget: positiveEnv("FASTCODE_TOKEN_BUDGET"),
114
+ costBudgetUsd: positiveEnv("FASTCODE_COST_BUDGET_USD"),
115
+ compactionEnabled: compaction.enabled,
116
+ compactionRatio: compaction.localThresholdRatio,
117
+ compactionHeapMb: compaction.heapLimitMb,
118
+ compactionHeapUsedMb: process.memoryUsage().heapUsed / 1024 / 1024,
119
+ compactionForced: heapExceeded(compaction),
120
+ serverCompactionAware: compaction.serverCompactionAware,
121
+ shellActive: shells.active,
122
+ shellPool: shells.pool,
123
+ shellSpawns: shells.spawns,
124
+ shellMaxSpawn: shells.maxSpawnPerTurn,
125
+ sseIdleMs: positiveEnv("FASTCODE_SSE_IDLE_MS") || 25_000,
126
+ toolProtocol: resolveToolProtocol(config, config.providerId, config.model),
127
+ toolProtocolMode: (process.env["FASTCODE_TOOL_PROTOCOL"] ?? "legacy").trim().toLowerCase(),
128
+ toolCapability: config.capabilities?.toolCalling?.[toolCapabilityKey(config.providerId, config.model)] ?? "unknown",
129
+ loopExempt: Array.from(loopExemptTools()),
130
+ modelMetaFile: modelMetaOverridePath(),
131
+ modelMeta: getModelMeta(config.providerId, config.model),
132
+ providers: PROVIDERS.map((p) => ({
133
+ id: p.id,
134
+ label: p.label,
135
+ env: p.authEnv,
136
+ keyPresent: p.authEnv.length === 0 || Boolean(resolveProviderKey(p.id, p.authEnv).key),
137
+ })),
138
+ };
139
+ }
140
+ function positiveEnv(name) {
141
+ const value = Number(process.env[name]);
142
+ return Number.isFinite(value) && value > 0 ? value : 0;
143
+ }
144
+ export function formatDiagnostics(info) {
145
+ const lines = [];
146
+ lines.push(`FastCode ${info.version} — environment check`);
147
+ lines.push("");
148
+ lines.push(` node ${info.node} (needs >= ${info.nodeRequired}) [${info.nodeOk ? "ok" : "TOO OLD"}]`);
149
+ lines.push(` platform ${info.platformLabel}${info.termux ? " (Termux)" : ""}`);
150
+ lines.push(` terminal ${info.tty ? "interactive TTY" : "NOT a TTY — the TUI needs a real terminal"}`);
151
+ lines.push(` stable UI ${info.stableUi ? "on (anti-flicker)" : "off"}`);
152
+ lines.push(" config root " + (info.configRoot ?? "(unavailable)"));
153
+ lines.push(" config file " + (info.configFile ?? "(unavailable)"));
154
+ lines.push(" keys file " + (info.authFile ?? "(unavailable)") + " mode " + info.authMode + (info.authMode !== "missing" && info.authMode !== "0600" ? " [WARN: expected 0600]" : ""));
155
+ lines.push(" tmpdir " + info.tmpdir + (info.termux && !String(info.tmpdir).startsWith("/data/data/com.termux") ? " [WARN: use $PREFIX/tmp]" : ""));
156
+ lines.push(" memory " + info.memFreeMb.toFixed(0) + "MB free / " + info.memTotalMb.toFixed(0) + "MB total");
157
+ lines.push(" trusted cwd " + (info.trusted ? "yes" : "NO - agent tools ask first or stay off") + " (" + (info.trustFile ?? "?") + ")");
158
+ lines.push(" session log " + (info.sessionLogEnabled ? "on" : "off") + " cache cap " + info.sessionCacheMb + "MB, " + info.sessionsCount + " file(s), " + (info.sessionsBytes / 1024 / 1024).toFixed(2) + "MB used");
159
+ lines.push(" session fsync " + info.sessionFsync + " unclosed " + info.unclosedSessions + " " + (info.sessionsDir ?? "(unavailable)"));
160
+ lines.push(" shell guard " + info.shellDenyPatterns + " deny patterns" + (info.allowDangerous ? " [FASTCODE_ALLOW_DANGEROUS is ON]" : ""));
161
+ lines.push(" tool output " + info.toolOutputLimit + " chars max (streaming cap)");
162
+ lines.push(` budgets ${info.tokenBudget > 0 ? "tokens ≤ " + info.tokenBudget : "token budget off"} · ${info.costBudgetUsd > 0 ? "$" + info.costBudgetUsd : "cost budget off"}`);
163
+ lines.push(` compaction ${info.compactionEnabled ? "on" : "off"} · ${Math.round(info.compactionRatio * 100)}% · heap ${info.compactionHeapUsedMb.toFixed(1)}/${info.compactionHeapMb}MB${info.compactionForced ? " [FORCED]" : ""}${info.serverCompactionAware ? " · server-aware" : ""}`);
164
+ lines.push(` shell pool ${info.shellActive} active · ${info.shellPool} resident · ${info.shellSpawns} spawns · cap ${info.shellMaxSpawn}/turn`);
165
+ lines.push(` loop exempt ${info.loopExempt.length > 0 ? info.loopExempt.join(", ") : "(none)"}`);
166
+ lines.push(` model meta ctx ${info.modelMeta.contextWindow} · ~${info.modelMeta.charsPerToken} chars/token · ${info.modelMeta.supportsTools ? "tools" : "no tools"} · ${info.modelMetaFile}`);
167
+ lines.push(` sse timeout ${info.sseIdleMs}ms idle`);
168
+ lines.push(` tool protocol ${info.toolProtocol} (${info.toolProtocolMode}) · cached ${info.toolCapability}`);
169
+ lines.push(` provider ${info.providerId} · model ${info.model}`);
170
+ let apiKeyStatus;
171
+ if (info.keyPresent)
172
+ apiKeyStatus = "present";
173
+ else if (info.keyStatus === "unbound")
174
+ apiKeyStatus = `UNBOUND — a stored ${info.providerId} key isn't tied to a model; re-bind with /login ${info.providerId} ${info.model} <key>`;
175
+ else
176
+ apiKeyStatus = `MISSING — set ${info.keyEnv ?? "the provider env var"} or /login ${info.providerId} ${info.model} <key>`;
177
+ lines.push(` api key ${apiKeyStatus}`);
178
+ lines.push(` skills ${info.skillsInstalled} installed, ${info.enabledSkills} enabled`);
179
+ lines.push(` plugins ${info.pluginsInstalled} installed, ${info.enabledPlugins} enabled`);
180
+ lines.push("");
181
+ lines.push(" provider keys:");
182
+ for (const p of info.providers) {
183
+ lines.push(` ${p.keyPresent ? "●" : "○"} ${p.id.padEnd(12)} ${p.env.join(" / ") || "(no key needed)"}`);
184
+ }
185
+ if (info.termux) {
186
+ lines.push("");
187
+ lines.push(" Termux tips:");
188
+ lines.push(" - pkg install nodejs git");
189
+ lines.push(" - keep the session alive with: termux-wake-lock");
190
+ lines.push(" - add an extra-keys row for Esc/Ctrl in ~/.termux/termux.properties");
191
+ lines.push(" - long tasks: export TMPDIR=$PREFIX/tmp and use termux-wake-lock");
192
+ lines.push(` - Android 12+ caps phantom processes (~32); background shells are pooled (${info.shellPool} resident, cap ${info.shellMaxSpawn}/turn)`);
193
+ }
194
+ return lines.join("\n");
195
+ }
@@ -0,0 +1,132 @@
1
+ /**
2
+ * Tiny fetch helpers. We standardize on globalThis.fetch (Node 18+).
3
+ *
4
+ * The `streamSSE` helper parses Server-Sent Events for any provider that uses
5
+ * the OpenAI-style `data: {...}\n\n` chunk format. Non-SSE providers (e.g.
6
+ * Anthropic's older streaming) handle their own parsing.
7
+ */
8
+ export async function fetchJson(url: string, init: any = {}): Promise<any> {
9
+ const res = await fetch(url, {
10
+ ...init,
11
+ headers: { "content-type": "application/json", accept: "application/json", ...(init.headers ?? {}) },
12
+ });
13
+ if (!res.ok) {
14
+ const body = await res.text().catch(() => "");
15
+ throw new Error(`${res.status} ${res.statusText}: ${body.slice(0, 500)}`);
16
+ }
17
+ return (await res.json());
18
+ }
19
+ export class NetworkStallError extends Error {
20
+ readonly code = "NETWORK_STALL";
21
+ readonly bytesReceived: number;
22
+ constructor(message: string, bytesReceived = 0) {
23
+ super(message);
24
+ this.name = "NetworkStallError";
25
+ this.bytesReceived = bytesReceived;
26
+ }
27
+ }
28
+ export interface StreamSseOptions {
29
+ idleTimeoutMs?: number;
30
+ totalTimeoutMs?: number;
31
+ }
32
+ /** Async iterator over SSE events from a streaming HTTP response. */
33
+ export async function* streamSSE(res, options: StreamSseOptions = {}) {
34
+ if (!res.ok) {
35
+ const body = await res.text().catch(() => "");
36
+ throw new Error(`${res.status} ${res.statusText}: ${body.slice(0, 500)}`);
37
+ }
38
+ if (!res.body)
39
+ throw new Error("Empty response body");
40
+ const reader = res.body.getReader();
41
+ const decoder = new TextDecoder("utf-8");
42
+ const envIdle = Number(process.env["FASTCODE_SSE_IDLE_MS"]);
43
+ const idleMs = positive(options.idleTimeoutMs, positive(envIdle, 25_000));
44
+ const totalMs = positive(options.totalTimeoutMs, 0);
45
+ const startedAt = Date.now();
46
+ let bytesReceived = 0;
47
+ let buffer = "";
48
+ while (true) {
49
+ const remaining = totalMs > 0 ? totalMs - (Date.now() - startedAt) : Number.POSITIVE_INFINITY;
50
+ if (remaining <= 0)
51
+ throw new NetworkStallError(`SSE total timeout after ${totalMs}ms`, bytesReceived);
52
+ const boundedByTotal = remaining <= idleMs;
53
+ const waitMs = boundedByTotal ? Math.max(1, remaining) : idleMs;
54
+ let timer: ReturnType<typeof setTimeout> | undefined;
55
+ const timeout = new Promise<never>((_resolve, reject) => {
56
+ timer = setTimeout(() => {
57
+ reject(new NetworkStallError(boundedByTotal
58
+ ? `SSE total timeout after ${totalMs}ms`
59
+ : `SSE idle timeout after ${idleMs}ms (no data received; set FASTCODE_SSE_IDLE_MS to wait longer)`, bytesReceived));
60
+ }, waitMs);
61
+ });
62
+ let chunk;
63
+ try {
64
+ chunk = await Promise.race([reader.read(), timeout]);
65
+ }
66
+ catch (err) {
67
+ await reader.cancel().catch(() => undefined);
68
+ throw err;
69
+ }
70
+ finally {
71
+ if (timer)
72
+ clearTimeout(timer);
73
+ }
74
+ const { value, done } = chunk;
75
+ if (done)
76
+ break;
77
+ bytesReceived += value?.byteLength ?? 0;
78
+ // Normalize CR/LF so both "\n\n" and "\r\n\r\n" frame separators
79
+ // are detected, including a CR/LF split across two chunks.
80
+ buffer = (buffer + decoder.decode(value, { stream: true })).replace(/\r\n?/g, "\n");
81
+ let sep = buffer.indexOf("\n\n");
82
+ while (sep !== -1) {
83
+ const chunkText = buffer.slice(0, sep);
84
+ buffer = buffer.slice(sep + 2);
85
+ const ev = parseSseChunk(chunkText);
86
+ if (ev)
87
+ yield ev;
88
+ sep = buffer.indexOf("\n\n");
89
+ }
90
+ }
91
+ // Flush trailing event without final \n\n (some servers do this).
92
+ const trailing = buffer.trim();
93
+ if (trailing) {
94
+ const ev = parseSseChunk(trailing);
95
+ if (ev)
96
+ yield ev;
97
+ }
98
+ }
99
+ export async function probeConnectivity(url: string, timeoutMs = 3000): Promise<boolean> {
100
+ const controller = new AbortController();
101
+ const timer = setTimeout(() => controller.abort(), positive(timeoutMs, 3000));
102
+ try {
103
+ const res = await fetch(url, { signal: controller.signal });
104
+ await res.body?.cancel().catch(() => undefined);
105
+ return res.ok;
106
+ }
107
+ catch {
108
+ return false;
109
+ }
110
+ finally {
111
+ clearTimeout(timer);
112
+ }
113
+ }
114
+ function positive(value, fallback) {
115
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : fallback;
116
+ }
117
+ function parseSseChunk(chunk) {
118
+ let event;
119
+ const dataLines = [];
120
+ for (const lineRaw of chunk.split("\n")) {
121
+ const line = lineRaw.replace(/\r$/, "");
122
+ if (!line || line.startsWith(":"))
123
+ continue;
124
+ if (line.startsWith("event:"))
125
+ event = line.slice(6).trim();
126
+ else if (line.startsWith("data:"))
127
+ dataLines.push(line.slice(5).trimStart());
128
+ }
129
+ if (dataLines.length === 0)
130
+ return undefined;
131
+ return { event, data: dataLines.join("\n") };
132
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Markdown → ANSI rendering for terminal output.
3
+ *
4
+ * We use marked + marked-terminal because it gives us proper code-fence
5
+ * syntax highlighting (via cli-highlight) without us hand-rolling a
6
+ * markdown parser. The result is a fully-formatted ANSI string we can
7
+ * print directly inside an Ink <Text>.
8
+ */
9
+ import { marked } from "marked";
10
+ import { markedTerminal } from "marked-terminal";
11
+ let configured = false;
12
+ function configure() {
13
+ if (configured)
14
+ return;
15
+ marked.use(markedTerminal({
16
+ reflowText: false,
17
+ tab: 2,
18
+ }) as any);
19
+ configured = true;
20
+ }
21
+ export function renderMarkdown(md) {
22
+ configure();
23
+ try {
24
+ const out = marked.parse(md) as string;
25
+ // marked-terminal sometimes appends a stray trailing newline; trim only
26
+ // trailing whitespace so we don't lose meaningful blank lines.
27
+ return out.replace(/\s+$/, "");
28
+ }
29
+ catch {
30
+ return md;
31
+ }
32
+ }
@@ -0,0 +1,138 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { join } from "node:path";
4
+
5
+ export interface ModelMeta {
6
+ contextWindow: number;
7
+ supportsTools: boolean;
8
+ charsPerToken: number;
9
+ maxOutputTokens?: number;
10
+ }
11
+
12
+ export interface EstTokenOptions {
13
+ providerId?: string;
14
+ model?: string;
15
+ charsPerToken?: number;
16
+ }
17
+
18
+ export const UNKNOWN_MODEL_META: ModelMeta = {
19
+ contextWindow: 32_768,
20
+ supportsTools: false,
21
+ charsPerToken: 2.5,
22
+ };
23
+
24
+ const PROVIDER_META: Record<string, ModelMeta> = {
25
+ anthropic: { contextWindow: 1_000_000, supportsTools: true, charsPerToken: 3.8, maxOutputTokens: 64_000 },
26
+ openai: { contextWindow: 400_000, supportsTools: true, charsPerToken: 3.8, maxOutputTokens: 32_000 },
27
+ google: { contextWindow: 1_048_576, supportsTools: true, charsPerToken: 3.6, maxOutputTokens: 65_536 },
28
+ deepseek: { contextWindow: 128_000, supportsTools: true, charsPerToken: 1.7, maxOutputTokens: 8_192 },
29
+ openrouter: { contextWindow: 128_000, supportsTools: true, charsPerToken: 2.5, maxOutputTokens: 16_384 },
30
+ groq: { contextWindow: 128_000, supportsTools: true, charsPerToken: 3.8, maxOutputTokens: 8_192 },
31
+ mistral: { contextWindow: 128_000, supportsTools: true, charsPerToken: 3.8, maxOutputTokens: 8_192 },
32
+ xai: { contextWindow: 256_000, supportsTools: true, charsPerToken: 3.8, maxOutputTokens: 16_384 },
33
+ moonshot: { contextWindow: 256_000, supportsTools: true, charsPerToken: 2.0, maxOutputTokens: 16_384 },
34
+ zhipu: { contextWindow: 128_000, supportsTools: true, charsPerToken: 1.7, maxOutputTokens: 8_192 },
35
+ qwen: { contextWindow: 128_000, supportsTools: true, charsPerToken: 1.7, maxOutputTokens: 8_192 },
36
+ ollama: { contextWindow: 32_768, supportsTools: true, charsPerToken: 2.5, maxOutputTokens: 4_096 },
37
+ custom: { contextWindow: 32_768, supportsTools: false, charsPerToken: 2.5 },
38
+ };
39
+
40
+ const MODEL_OVERRIDES: Record<string, Partial<ModelMeta>> = {
41
+ "claude-haiku-5.5": { contextWindow: 200_000 },
42
+ "claude-haiku-4.5": { contextWindow: 200_000 },
43
+ "claude-sonnet-5": { contextWindow: 1_000_000 },
44
+ "claude-opus-5": { contextWindow: 1_000_000 },
45
+ "claude-sonnet-4.6": { contextWindow: 1_000_000 },
46
+ "claude-opus-4.6": { contextWindow: 1_000_000 },
47
+ "gpt-4o": { contextWindow: 128_000 },
48
+ "gpt-4o-mini": { contextWindow: 128_000 },
49
+ "gpt-4.1": { contextWindow: 1_000_000 },
50
+ o3: { contextWindow: 200_000 },
51
+ "o4-mini": { contextWindow: 200_000 },
52
+ "gemini-3.5-flash-lite": { contextWindow: 1_048_576 },
53
+ "gemini-3.1-flash-lite": { contextWindow: 1_048_576 },
54
+ "deepseek-v4-pro": { contextWindow: 128_000, charsPerToken: 1.7 },
55
+ "deepseek-flash": { contextWindow: 128_000, charsPerToken: 1.7 },
56
+ "glm-5.3": { contextWindow: 128_000, charsPerToken: 1.7 },
57
+ "glm-5.2": { contextWindow: 128_000, charsPerToken: 1.7 },
58
+ "qwen3.8-max": { contextWindow: 128_000, charsPerToken: 1.7 },
59
+ "qwen3.8-flash": { contextWindow: 128_000, charsPerToken: 1.7 },
60
+ "kimi-k3": { contextWindow: 256_000, charsPerToken: 2.0 },
61
+ "grok-4.7": { contextWindow: 256_000, charsPerToken: 3.8 },
62
+ "mistral-large-latest": { contextWindow: 128_000, charsPerToken: 3.8 },
63
+ };
64
+
65
+ let overrideCache: Record<string, Partial<ModelMeta>> | undefined;
66
+
67
+ export function modelMetaOverridePath(): string {
68
+ const root = process.env["FASTCODE_HOME"] ?? join(homedir(), ".fastcode");
69
+ return join(root, "model-meta.json");
70
+ }
71
+
72
+ export function clearModelMetaCache(): void {
73
+ overrideCache = undefined;
74
+ }
75
+
76
+ function loadOverrides(): Record<string, Partial<ModelMeta>> {
77
+ if (overrideCache)
78
+ return overrideCache;
79
+ const file = modelMetaOverridePath();
80
+ if (!existsSync(file)) {
81
+ overrideCache = {};
82
+ return overrideCache;
83
+ }
84
+ try {
85
+ const raw = JSON.parse(readFileSync(file, "utf8"));
86
+ overrideCache = raw && typeof raw === "object" && !Array.isArray(raw) ? raw : {};
87
+ }
88
+ catch {
89
+ overrideCache = {};
90
+ }
91
+ return overrideCache;
92
+ }
93
+
94
+ function usable(meta: Partial<ModelMeta> | undefined): meta is Partial<ModelMeta> {
95
+ return Boolean(meta) && typeof meta === "object";
96
+ }
97
+
98
+ function apply(base: ModelMeta, ...layers: Array<Partial<ModelMeta> | undefined>): ModelMeta {
99
+ const next = { ...base };
100
+ for (const layer of layers) {
101
+ if (!usable(layer))
102
+ continue;
103
+ if (typeof layer.contextWindow === "number" && layer.contextWindow > 0)
104
+ next.contextWindow = Math.floor(layer.contextWindow);
105
+ if (typeof layer.charsPerToken === "number" && layer.charsPerToken > 0)
106
+ next.charsPerToken = layer.charsPerToken;
107
+ if (typeof layer.supportsTools === "boolean")
108
+ next.supportsTools = layer.supportsTools;
109
+ if (typeof layer.maxOutputTokens === "number" && layer.maxOutputTokens > 0)
110
+ next.maxOutputTokens = Math.floor(layer.maxOutputTokens);
111
+ }
112
+ return next;
113
+ }
114
+
115
+ export function getModelMeta(providerId?: string, model?: string): ModelMeta {
116
+ const provider = providerId && PROVIDER_META[providerId] ? PROVIDER_META[providerId] : UNKNOWN_MODEL_META;
117
+ const overrides = loadOverrides();
118
+ const keys = [
119
+ providerId && model ? `${providerId}/${model}` : undefined,
120
+ providerId && model ? `${providerId}::${model}` : undefined,
121
+ model,
122
+ ].filter((key): key is string => Boolean(key));
123
+ const layers = keys.map((key) => overrides[key]);
124
+ return apply(provider, model ? MODEL_OVERRIDES[model] : undefined, ...layers);
125
+ }
126
+
127
+ export function estTokens(text: string, options: number | EstTokenOptions = {}): number {
128
+ if (!text)
129
+ return 0;
130
+ const ratio = typeof options === "number"
131
+ ? options
132
+ : options.charsPerToken ?? getModelMeta(options.providerId, options.model).charsPerToken;
133
+ return Math.ceil(text.length / Math.max(0.1, ratio));
134
+ }
135
+
136
+ export function estMessagesTokens(messages: Array<{ content: string }>, options: EstTokenOptions = {}): number {
137
+ return messages.reduce((sum, message) => sum + estTokens(message.content ?? "", options), 0);
138
+ }
@@ -0,0 +1,32 @@
1
+ import type { NativeToolCall } from "../types.js";
2
+
3
+ export type ToolCallAccumulator = Map<number, NativeToolCall>;
4
+
5
+ export interface ToolCallDelta {
6
+ index?: number;
7
+ id?: string;
8
+ function?: { name?: string; arguments?: string };
9
+ }
10
+
11
+ export function mergeToolCallDelta(accumulator: ToolCallAccumulator, delta: ToolCallDelta): void {
12
+ const index = typeof delta.index === "number" && Number.isFinite(delta.index) ? delta.index : accumulator.size;
13
+ const entry = accumulator.get(index) ?? { id: "", name: "", arguments: "" };
14
+ if (typeof delta.id === "string" && delta.id)
15
+ entry.id = delta.id;
16
+ if (typeof delta.function?.name === "string" && delta.function.name)
17
+ entry.name = delta.function.name;
18
+ if (typeof delta.function?.arguments === "string")
19
+ entry.arguments += delta.function.arguments;
20
+ accumulator.set(index, entry);
21
+ }
22
+
23
+ export function finalizeToolCalls(accumulator: ToolCallAccumulator): NativeToolCall[] {
24
+ return Array.from(accumulator.entries())
25
+ .sort((a, b) => a[0] - b[0])
26
+ .filter(([, call]) => Boolean(call.name))
27
+ .map(([index, call]) => ({
28
+ id: call.id || `call_${index}`,
29
+ name: call.name,
30
+ arguments: call.arguments || "{}",
31
+ }));
32
+ }