@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,153 @@
1
+ /**
2
+ * Project trust store.
3
+ *
4
+ * Agent tools can read, write, delete and execute inside the workspace, so a
5
+ * folder is only enabled after the user confirms it once. The list lives in
6
+ * ~/.fastcode/trusted.json and is compared by real path.
7
+ *
8
+ * FASTCODE_TRUST_ALL=1 marks every directory trusted (scripts, CI).
9
+ */
10
+ import { existsSync, readFileSync, realpathSync, statSync, writeFileSync } from "node:fs";
11
+ import { resolve } from "node:path";
12
+ import { ensureDir, getPaths } from "./paths.js";
13
+ const TRUTHY = ["1", "true", "yes", "on"];
14
+ let cache;
15
+ export function normalizeProjectPath(dir) {
16
+ const absolute = resolve(dir);
17
+ try {
18
+ return realpathSync(absolute);
19
+ }
20
+ catch {
21
+ return absolute;
22
+ }
23
+ }
24
+ function emptyStore() {
25
+ return { version: 1, tools: {}, paths: {} };
26
+ }
27
+ function normalizePaths(raw) {
28
+ const paths = {};
29
+ if (!raw || typeof raw !== "object")
30
+ return paths;
31
+ for (const [key, value] of Object.entries(raw)) {
32
+ if (!key || !value || typeof value !== "object")
33
+ continue;
34
+ paths[key] = {
35
+ grantedAt: typeof value.grantedAt === "string" ? value.grantedAt : "",
36
+ lastUsed: typeof value.lastUsed === "string" ? value.lastUsed : "",
37
+ };
38
+ }
39
+ return paths;
40
+ }
41
+ function normalizeTools(raw) {
42
+ const tools = {};
43
+ if (!raw || typeof raw !== "object")
44
+ return tools;
45
+ for (const [key, value] of Object.entries(raw)) {
46
+ if (!key || !value || typeof value !== "object" || typeof value.catalogHash !== "string")
47
+ continue;
48
+ tools[key] = {
49
+ grantedAt: typeof value.grantedAt === "string" ? value.grantedAt : "",
50
+ lastUsed: typeof value.lastUsed === "string" ? value.lastUsed : "",
51
+ catalogHash: value.catalogHash,
52
+ };
53
+ }
54
+ return tools;
55
+ }
56
+ function migrateLegacy(raw) {
57
+ if (!raw || typeof raw !== "object" || !Array.isArray(raw.trusted))
58
+ return undefined;
59
+ const store = emptyStore();
60
+ for (const entry of raw.trusted) {
61
+ if (!entry || typeof entry.path !== "string" || !entry.path)
62
+ continue;
63
+ const at = typeof entry.addedAt === "string" ? entry.addedAt : new Date().toISOString();
64
+ store.paths[entry.path] = { grantedAt: at, lastUsed: at };
65
+ }
66
+ return store;
67
+ }
68
+ function readStore(file) {
69
+ if (!existsSync(file))
70
+ return emptyStore();
71
+ try {
72
+ const raw = JSON.parse(readFileSync(file, "utf8"));
73
+ if (raw && raw.version === 1) {
74
+ return {
75
+ version: 1,
76
+ tools: normalizeTools(raw.tools),
77
+ paths: normalizePaths(raw.paths),
78
+ };
79
+ }
80
+ const migrated = migrateLegacy(raw);
81
+ if (!migrated)
82
+ return emptyStore();
83
+ try {
84
+ writeFileSync(file, JSON.stringify(migrated, null, 2), "utf8");
85
+ }
86
+ catch {
87
+ // A read-only home still gets the migrated view in memory.
88
+ }
89
+ return migrated;
90
+ }
91
+ catch {
92
+ return emptyStore();
93
+ }
94
+ }
95
+ export function loadTrust() {
96
+ const { trust } = getPaths();
97
+ let key = "missing";
98
+ try {
99
+ const info = statSync(trust);
100
+ key = `${info.mtimeMs}:${info.size}`;
101
+ }
102
+ catch {
103
+ // Missing file: cache the empty store until it appears.
104
+ }
105
+ if (cache && cache.file === trust && cache.key === key)
106
+ return cache.state;
107
+ const state = readStore(trust);
108
+ cache = { file: trust, key, state };
109
+ return state;
110
+ }
111
+ function saveTrust(state) {
112
+ const paths = getPaths();
113
+ ensureDir(paths.root);
114
+ writeFileSync(paths.trust, JSON.stringify(state, null, 2), "utf8");
115
+ try {
116
+ const info = statSync(paths.trust);
117
+ cache = { file: paths.trust, key: `${info.mtimeMs}:${info.size}`, state };
118
+ }
119
+ catch {
120
+ cache = undefined;
121
+ }
122
+ }
123
+ export function trustAllEnabled() {
124
+ return TRUTHY.includes((process.env["FASTCODE_TRUST_ALL"] ?? "").trim().toLowerCase());
125
+ }
126
+ export function isTrusted(dir) {
127
+ if (trustAllEnabled())
128
+ return true;
129
+ const target = normalizeProjectPath(dir);
130
+ return Boolean(loadTrust().paths[target]);
131
+ }
132
+ export function trustProject(dir) {
133
+ const target = normalizeProjectPath(dir);
134
+ const state = loadTrust();
135
+ if (!state.paths[target]) {
136
+ const at = new Date().toISOString();
137
+ state.paths[target] = { grantedAt: at, lastUsed: at };
138
+ saveTrust(state);
139
+ }
140
+ return target;
141
+ }
142
+ export function untrustProject(dir) {
143
+ const target = normalizeProjectPath(dir);
144
+ const state = loadTrust();
145
+ if (!state.paths[target])
146
+ return false;
147
+ delete state.paths[target];
148
+ saveTrust(state);
149
+ return true;
150
+ }
151
+ export function listTrusted() {
152
+ return Object.entries(loadTrust().paths).map(([path, entry]) => ({ path, addedAt: entry.grantedAt }));
153
+ }
@@ -0,0 +1 @@
1
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,31 @@
1
+ /**
2
+ * FastCode entrypoint.
3
+ *
4
+ * Parses CLI args, then either runs a one-shot subcommand or boots the
5
+ * Ink-based TUI. We keep this file tiny so the slow imports (Ink, React)
6
+ * are not loaded for `fastcode skills list`.
7
+ */
8
+ import { parseCli } from "./cli.js";
9
+ async function main() {
10
+ const cli = await parseCli();
11
+ if (cli.done)
12
+ return;
13
+ if (!process.stdout.isTTY) {
14
+ console.error("FastCode TUI requires a TTY. Pipe-mode prompts aren't supported yet — try `fastcode \"<prompt>\"` from a real terminal.");
15
+ process.exit(1);
16
+ }
17
+ // Lazy-load Ink so subcommands don't pay the React boot cost.
18
+ const [{ render }, React, { App }] = await Promise.all([
19
+ import("ink"),
20
+ import("react"),
21
+ import("./app.js"),
22
+ ]);
23
+ // React.createElement avoids needing a separate .tsx loader for this file.
24
+ // exitOnCtrlC: false lets the app own Ctrl+C so the documented
25
+ // "press Ctrl+C twice to quit" behavior actually works.
26
+ render(React.createElement(App, { initialPrompt: cli.prompt, resume: cli.resume }), { exitOnCtrlC: false });
27
+ }
28
+ main().catch((err) => {
29
+ console.error(err);
30
+ process.exit(1);
31
+ });
@@ -0,0 +1,66 @@
1
+ export declare function collectDiagnostics(): {
2
+ version: string;
3
+ node: string;
4
+ nodeRequired: string;
5
+ nodeOk: boolean;
6
+ platform: string;
7
+ platformLabel: string;
8
+ termux: boolean;
9
+ termuxVersion: string;
10
+ stableUi: boolean;
11
+ tty: boolean;
12
+ configRoot: any;
13
+ configFile: any;
14
+ authFile: any;
15
+ authMode: string;
16
+ tmpdir: string;
17
+ memTotalMb: number;
18
+ memFreeMb: number;
19
+ providerId: any;
20
+ model: any;
21
+ keyPresent: boolean;
22
+ keyStatus: "ok" | "missing" | "unbound";
23
+ keyEnv: string;
24
+ skillsInstalled: number;
25
+ pluginsInstalled: number;
26
+ enabledSkills: any;
27
+ enabledPlugins: any;
28
+ sessionLogEnabled: boolean;
29
+ sessionCacheMb: number;
30
+ sessionFsync: "auto" | "off" | "on";
31
+ unclosedSessions: number;
32
+ sessionsDir: any;
33
+ sessionsCount: number;
34
+ sessionsBytes: number;
35
+ trustFile: any;
36
+ trusted: boolean;
37
+ shellDenyPatterns: number;
38
+ allowDangerous: boolean;
39
+ toolOutputLimit: number;
40
+ tokenBudget: number;
41
+ costBudgetUsd: number;
42
+ compactionEnabled: boolean;
43
+ compactionRatio: number;
44
+ compactionHeapMb: number;
45
+ compactionHeapUsedMb: number;
46
+ compactionForced: boolean;
47
+ serverCompactionAware: boolean;
48
+ shellActive: number;
49
+ shellPool: number;
50
+ shellSpawns: number;
51
+ shellMaxSpawn: number;
52
+ sseIdleMs: number;
53
+ toolProtocol: import("../agent/protocol-mode.js").ToolProtocol;
54
+ toolProtocolMode: string;
55
+ toolCapability: any;
56
+ loopExempt: string[];
57
+ modelMetaFile: string;
58
+ modelMeta: import("./model-meta.js").ModelMeta;
59
+ providers: {
60
+ id: string;
61
+ label: string;
62
+ env: string[];
63
+ keyPresent: boolean;
64
+ }[];
65
+ };
66
+ export declare function formatDiagnostics(info: any): string;
@@ -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" };
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,23 @@
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 declare function fetchJson(url: string, init?: any): Promise<any>;
9
+ export declare class NetworkStallError extends Error {
10
+ readonly code = "NETWORK_STALL";
11
+ readonly bytesReceived: number;
12
+ constructor(message: string, bytesReceived?: number);
13
+ }
14
+ export interface StreamSseOptions {
15
+ idleTimeoutMs?: number;
16
+ totalTimeoutMs?: number;
17
+ }
18
+ /** Async iterator over SSE events from a streaming HTTP response. */
19
+ export declare function streamSSE(res: any, options?: StreamSseOptions): AsyncGenerator<{
20
+ event: any;
21
+ data: string;
22
+ }, void, unknown>;
23
+ export declare function probeConnectivity(url: string, timeoutMs?: number): Promise<boolean>;
@@ -0,0 +1,128 @@
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, init = {}) {
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
+ code = "NETWORK_STALL";
21
+ bytesReceived;
22
+ constructor(message, bytesReceived = 0) {
23
+ super(message);
24
+ this.name = "NetworkStallError";
25
+ this.bytesReceived = bytesReceived;
26
+ }
27
+ }
28
+ /** Async iterator over SSE events from a streaming HTTP response. */
29
+ export async function* streamSSE(res, options = {}) {
30
+ if (!res.ok) {
31
+ const body = await res.text().catch(() => "");
32
+ throw new Error(`${res.status} ${res.statusText}: ${body.slice(0, 500)}`);
33
+ }
34
+ if (!res.body)
35
+ throw new Error("Empty response body");
36
+ const reader = res.body.getReader();
37
+ const decoder = new TextDecoder("utf-8");
38
+ const envIdle = Number(process.env["FASTCODE_SSE_IDLE_MS"]);
39
+ const idleMs = positive(options.idleTimeoutMs, positive(envIdle, 25_000));
40
+ const totalMs = positive(options.totalTimeoutMs, 0);
41
+ const startedAt = Date.now();
42
+ let bytesReceived = 0;
43
+ let buffer = "";
44
+ while (true) {
45
+ const remaining = totalMs > 0 ? totalMs - (Date.now() - startedAt) : Number.POSITIVE_INFINITY;
46
+ if (remaining <= 0)
47
+ throw new NetworkStallError(`SSE total timeout after ${totalMs}ms`, bytesReceived);
48
+ const boundedByTotal = remaining <= idleMs;
49
+ const waitMs = boundedByTotal ? Math.max(1, remaining) : idleMs;
50
+ let timer;
51
+ const timeout = new Promise((_resolve, reject) => {
52
+ timer = setTimeout(() => {
53
+ reject(new NetworkStallError(boundedByTotal
54
+ ? `SSE total timeout after ${totalMs}ms`
55
+ : `SSE idle timeout after ${idleMs}ms (no data received; set FASTCODE_SSE_IDLE_MS to wait longer)`, bytesReceived));
56
+ }, waitMs);
57
+ });
58
+ let chunk;
59
+ try {
60
+ chunk = await Promise.race([reader.read(), timeout]);
61
+ }
62
+ catch (err) {
63
+ await reader.cancel().catch(() => undefined);
64
+ throw err;
65
+ }
66
+ finally {
67
+ if (timer)
68
+ clearTimeout(timer);
69
+ }
70
+ const { value, done } = chunk;
71
+ if (done)
72
+ break;
73
+ bytesReceived += value?.byteLength ?? 0;
74
+ // Normalize CR/LF so both "\n\n" and "\r\n\r\n" frame separators
75
+ // are detected, including a CR/LF split across two chunks.
76
+ buffer = (buffer + decoder.decode(value, { stream: true })).replace(/\r\n?/g, "\n");
77
+ let sep = buffer.indexOf("\n\n");
78
+ while (sep !== -1) {
79
+ const chunkText = buffer.slice(0, sep);
80
+ buffer = buffer.slice(sep + 2);
81
+ const ev = parseSseChunk(chunkText);
82
+ if (ev)
83
+ yield ev;
84
+ sep = buffer.indexOf("\n\n");
85
+ }
86
+ }
87
+ // Flush trailing event without final \n\n (some servers do this).
88
+ const trailing = buffer.trim();
89
+ if (trailing) {
90
+ const ev = parseSseChunk(trailing);
91
+ if (ev)
92
+ yield ev;
93
+ }
94
+ }
95
+ export async function probeConnectivity(url, timeoutMs = 3000) {
96
+ const controller = new AbortController();
97
+ const timer = setTimeout(() => controller.abort(), positive(timeoutMs, 3000));
98
+ try {
99
+ const res = await fetch(url, { signal: controller.signal });
100
+ await res.body?.cancel().catch(() => undefined);
101
+ return res.ok;
102
+ }
103
+ catch {
104
+ return false;
105
+ }
106
+ finally {
107
+ clearTimeout(timer);
108
+ }
109
+ }
110
+ function positive(value, fallback) {
111
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : fallback;
112
+ }
113
+ function parseSseChunk(chunk) {
114
+ let event;
115
+ const dataLines = [];
116
+ for (const lineRaw of chunk.split("\n")) {
117
+ const line = lineRaw.replace(/\r$/, "");
118
+ if (!line || line.startsWith(":"))
119
+ continue;
120
+ if (line.startsWith("event:"))
121
+ event = line.slice(6).trim();
122
+ else if (line.startsWith("data:"))
123
+ dataLines.push(line.slice(5).trimStart());
124
+ }
125
+ if (dataLines.length === 0)
126
+ return undefined;
127
+ return { event, data: dataLines.join("\n") };
128
+ }
@@ -0,0 +1 @@
1
+ export declare function renderMarkdown(md: any): any;
@@ -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
+ }));
19
+ configured = true;
20
+ }
21
+ export function renderMarkdown(md) {
22
+ configure();
23
+ try {
24
+ const out = marked.parse(md);
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,19 @@
1
+ export interface ModelMeta {
2
+ contextWindow: number;
3
+ supportsTools: boolean;
4
+ charsPerToken: number;
5
+ maxOutputTokens?: number;
6
+ }
7
+ export interface EstTokenOptions {
8
+ providerId?: string;
9
+ model?: string;
10
+ charsPerToken?: number;
11
+ }
12
+ export declare const UNKNOWN_MODEL_META: ModelMeta;
13
+ export declare function modelMetaOverridePath(): string;
14
+ export declare function clearModelMetaCache(): void;
15
+ export declare function getModelMeta(providerId?: string, model?: string): ModelMeta;
16
+ export declare function estTokens(text: string, options?: number | EstTokenOptions): number;
17
+ export declare function estMessagesTokens(messages: Array<{
18
+ content: string;
19
+ }>, options?: EstTokenOptions): number;