@datename/fastcode-termux 0.1.6 → 0.1.8-beta.0

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 (303) hide show
  1. package/LICENSE +4 -0
  2. package/README.md +67 -16
  3. package/dist/agent/approval-json.d.ts +43 -0
  4. package/dist/agent/approval-json.js +69 -0
  5. package/dist/agent/approval-native.d.ts +3 -0
  6. package/dist/agent/approval-native.js +165 -0
  7. package/dist/agent/approval-store.d.ts +16 -0
  8. package/dist/agent/approval-store.js +21 -0
  9. package/dist/agent/approval.d.ts +83 -0
  10. package/dist/agent/approval.js +42 -0
  11. package/dist/agent/compaction.d.ts +41 -0
  12. package/dist/agent/compaction.js +270 -0
  13. package/dist/agent/context.d.ts +29 -0
  14. package/dist/agent/context.js +62 -0
  15. package/dist/agent/guards.d.ts +24 -16
  16. package/dist/agent/guards.js +1167 -18
  17. package/dist/agent/ignore.d.ts +11 -0
  18. package/dist/agent/ignore.js +129 -0
  19. package/dist/agent/policy/defaults.d.ts +16 -0
  20. package/dist/agent/policy/defaults.js +17 -0
  21. package/dist/agent/policy/egress.d.ts +32 -0
  22. package/dist/agent/policy/egress.js +280 -0
  23. package/dist/agent/process-tree.d.ts +22 -0
  24. package/dist/agent/process-tree.js +102 -0
  25. package/dist/agent/protocol-mode.d.ts +6 -0
  26. package/dist/agent/protocol-mode.js +51 -0
  27. package/dist/agent/protocol.d.ts +20 -1
  28. package/dist/agent/protocol.js +169 -39
  29. package/dist/agent/registry.d.ts +19 -0
  30. package/dist/agent/registry.js +247 -0
  31. package/dist/agent/runner.d.ts +127 -0
  32. package/dist/agent/runner.js +391 -0
  33. package/dist/agent/shell-ast.d.ts +11 -0
  34. package/dist/agent/shell-ast.js +204 -0
  35. package/dist/agent/shell-session.d.ts +51 -0
  36. package/dist/agent/shell-session.js +611 -0
  37. package/dist/agent/stream.d.ts +29 -0
  38. package/dist/agent/stream.js +103 -0
  39. package/dist/agent/tool-schemas.d.ts +2 -0
  40. package/dist/agent/tool-schemas.js +82 -0
  41. package/dist/agent/tools/append_file.d.ts +2 -0
  42. package/dist/agent/tools/append_file.js +28 -0
  43. package/dist/agent/tools/apply_patch.d.ts +10 -0
  44. package/dist/agent/tools/apply_patch.js +92 -0
  45. package/dist/agent/tools/copy_file.d.ts +2 -0
  46. package/dist/agent/tools/copy_file.js +53 -0
  47. package/dist/agent/tools/delete_file.d.ts +2 -0
  48. package/dist/agent/tools/delete_file.js +43 -0
  49. package/dist/agent/tools/grep.d.ts +2 -0
  50. package/dist/agent/tools/grep.js +54 -0
  51. package/dist/agent/tools/index.d.ts +2 -0
  52. package/dist/agent/tools/index.js +32 -0
  53. package/dist/agent/tools/list_files.d.ts +2 -0
  54. package/dist/agent/tools/list_files.js +17 -0
  55. package/dist/agent/tools/make_dir.d.ts +2 -0
  56. package/dist/agent/tools/make_dir.js +26 -0
  57. package/dist/agent/tools/move_path.d.ts +9 -0
  58. package/dist/agent/tools/move_path.js +73 -0
  59. package/dist/agent/tools/paths.d.ts +7 -0
  60. package/dist/agent/tools/paths.js +84 -0
  61. package/dist/agent/tools/pwd.d.ts +2 -0
  62. package/dist/agent/tools/pwd.js +10 -0
  63. package/dist/agent/tools/read_file.d.ts +2 -0
  64. package/dist/agent/tools/read_file.js +81 -0
  65. package/dist/agent/tools/read_many.d.ts +2 -0
  66. package/dist/agent/tools/read_many.js +43 -0
  67. package/dist/agent/tools/readers.d.ts +58 -0
  68. package/dist/agent/tools/readers.js +426 -0
  69. package/dist/agent/tools/replace_in_file.d.ts +2 -0
  70. package/dist/agent/tools/replace_in_file.js +63 -0
  71. package/dist/agent/tools/shell.d.ts +2 -0
  72. package/dist/agent/tools/shell.js +48 -0
  73. package/dist/agent/tools/stat_path.d.ts +2 -0
  74. package/dist/agent/tools/stat_path.js +31 -0
  75. package/dist/agent/tools/types.d.ts +65 -0
  76. package/dist/agent/tools/types.js +1 -0
  77. package/dist/agent/tools/validate.d.ts +19 -0
  78. package/dist/agent/tools/validate.js +92 -0
  79. package/dist/agent/tools/write_file.d.ts +2 -0
  80. package/dist/agent/tools/write_file.js +28 -0
  81. package/dist/agent/tools.d.ts +3 -1
  82. package/dist/agent/tools.js +2 -377
  83. package/dist/app.d.ts +2 -1
  84. package/dist/app.js +846 -173
  85. package/dist/cli-main.js +363 -0
  86. package/dist/cli.js +444 -333
  87. package/dist/commands/about.js +1 -1
  88. package/dist/commands/attach.d.ts +9 -0
  89. package/dist/commands/attach.js +15 -0
  90. package/dist/commands/bg.d.ts +7 -0
  91. package/dist/commands/bg.js +9 -0
  92. package/dist/commands/cancel.d.ts +10 -0
  93. package/dist/commands/cancel.js +10 -0
  94. package/dist/commands/clear.d.ts +1 -0
  95. package/dist/commands/clear.js +1 -1
  96. package/dist/commands/compact.d.ts +22 -0
  97. package/dist/commands/compact.js +34 -0
  98. package/dist/commands/context.d.ts +7 -0
  99. package/dist/commands/context.js +25 -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/expand.d.ts +10 -0
  105. package/dist/commands/expand.js +8 -0
  106. package/dist/commands/index.js +24 -0
  107. package/dist/commands/jobs.d.ts +7 -0
  108. package/dist/commands/jobs.js +17 -0
  109. package/dist/commands/kill.d.ts +7 -0
  110. package/dist/commands/kill.js +13 -0
  111. package/dist/commands/login.d.ts +6 -4
  112. package/dist/commands/login.js +24 -22
  113. package/dist/commands/logout.js +8 -16
  114. package/dist/commands/model.d.ts +1 -0
  115. package/dist/commands/model.js +10 -22
  116. package/dist/commands/provider.d.ts +1 -2
  117. package/dist/commands/provider.js +4 -11
  118. package/dist/commands/skills.js +34 -4
  119. package/dist/commands/steer.d.ts +11 -0
  120. package/dist/commands/steer.js +13 -0
  121. package/dist/commands/threads.d.ts +9 -0
  122. package/dist/commands/threads.js +26 -0
  123. package/dist/commands/whoami.js +6 -5
  124. package/dist/config/store.d.ts +53 -12
  125. package/dist/config/store.js +163 -73
  126. package/dist/config/trust.d.ts +30 -8
  127. package/dist/config/trust.js +139 -14
  128. package/dist/headless.d.ts +7 -0
  129. package/dist/headless.js +213 -0
  130. package/dist/index.js +19 -3
  131. package/dist/lib/debug.d.ts +2 -0
  132. package/dist/lib/debug.js +22 -0
  133. package/dist/lib/diagnostics.d.ts +48 -4
  134. package/dist/lib/diagnostics.js +90 -8
  135. package/dist/lib/highlight-subset.d.ts +21 -0
  136. package/dist/lib/highlight-subset.js +88 -0
  137. package/dist/lib/http.d.ts +11 -1
  138. package/dist/lib/http.js +62 -4
  139. package/dist/lib/lifecycle.d.ts +5 -0
  140. package/dist/lib/lifecycle.js +62 -0
  141. package/dist/lib/markdown.js +22 -4
  142. package/dist/lib/model-meta.d.ts +19 -0
  143. package/dist/lib/model-meta.js +113 -0
  144. package/dist/lib/native-tools.d.ts +12 -0
  145. package/dist/lib/native-tools.js +21 -0
  146. package/dist/lib/net.d.ts +33 -0
  147. package/dist/lib/net.js +168 -0
  148. package/dist/patch/apply.d.ts +15 -0
  149. package/dist/patch/apply.js +172 -0
  150. package/dist/patch/parse.d.ts +29 -0
  151. package/dist/patch/parse.js +112 -0
  152. package/dist/patch/plan.d.ts +49 -0
  153. package/dist/patch/plan.js +273 -0
  154. package/dist/providers/anthropic.d.ts +1 -0
  155. package/dist/providers/anthropic.js +12 -0
  156. package/dist/providers/custom.d.ts +9 -0
  157. package/dist/providers/deepseek.d.ts +10 -0
  158. package/dist/providers/deepseek.js +3 -2
  159. package/dist/providers/google.d.ts +9 -0
  160. package/dist/providers/groq.d.ts +10 -0
  161. package/dist/providers/groq.js +2 -1
  162. package/dist/providers/index.d.ts +10 -201
  163. package/dist/providers/index.js +30 -0
  164. package/dist/providers/mistral.d.ts +10 -0
  165. package/dist/providers/mistral.js +2 -1
  166. package/dist/providers/moonshot.d.ts +10 -0
  167. package/dist/providers/moonshot.js +2 -1
  168. package/dist/providers/ollama.d.ts +9 -0
  169. package/dist/providers/openai.d.ts +10 -0
  170. package/dist/providers/openai.js +2 -1
  171. package/dist/providers/openaiCompatible.d.ts +33 -2
  172. package/dist/providers/openaiCompatible.js +78 -16
  173. package/dist/providers/openrouter.d.ts +10 -0
  174. package/dist/providers/openrouter.js +2 -1
  175. package/dist/providers/qwen.d.ts +10 -0
  176. package/dist/providers/qwen.js +2 -1
  177. package/dist/providers/spec.d.ts +12 -0
  178. package/dist/providers/spec.js +29 -0
  179. package/dist/providers/xai.d.ts +10 -0
  180. package/dist/providers/xai.js +2 -1
  181. package/dist/providers/zhipu.d.ts +10 -0
  182. package/dist/providers/zhipu.js +2 -1
  183. package/dist/session/log.d.ts +49 -14
  184. package/dist/session/log.js +236 -11
  185. package/dist/session/projection.d.ts +76 -0
  186. package/dist/session/projection.js +526 -0
  187. package/dist/skills/manager.d.ts +15 -0
  188. package/dist/skills/manager.js +135 -18
  189. package/dist/types.d.ts +75 -1
  190. package/dist/ui/Message.d.ts +1 -7
  191. package/dist/ui/Message.js +13 -2
  192. package/dist/ui/Picker.d.ts +8 -0
  193. package/dist/ui/Picker.js +24 -0
  194. package/dist/ui/Prompt.d.ts +1 -2
  195. package/dist/ui/Prompt.js +2 -2
  196. package/dist/ui/Spinner.js +11 -5
  197. package/dist/ui/StatusBar.d.ts +2 -1
  198. package/dist/ui/StatusBar.js +6 -5
  199. package/package.json +3 -2
  200. package/src/agent/approval-json.ts +103 -0
  201. package/src/agent/approval-native.ts +168 -0
  202. package/src/agent/approval-store.ts +38 -0
  203. package/src/agent/approval.ts +94 -0
  204. package/src/agent/compaction.ts +321 -0
  205. package/src/agent/context.ts +97 -0
  206. package/src/agent/guards.ts +1239 -19
  207. package/src/agent/ignore.ts +147 -0
  208. package/src/agent/policy/defaults.ts +34 -0
  209. package/src/agent/policy/egress.ts +335 -0
  210. package/src/agent/process-tree.ts +114 -0
  211. package/src/agent/protocol-mode.ts +59 -0
  212. package/src/agent/protocol.ts +191 -40
  213. package/src/agent/registry.ts +270 -0
  214. package/src/agent/runner.ts +486 -0
  215. package/src/agent/shell-ast.ts +227 -0
  216. package/src/agent/shell-session.ts +673 -0
  217. package/src/agent/stream.ts +143 -0
  218. package/src/agent/tool-schemas.ts +86 -0
  219. package/src/agent/tools/append_file.ts +30 -0
  220. package/src/agent/tools/apply_patch.ts +95 -0
  221. package/src/agent/tools/copy_file.ts +56 -0
  222. package/src/agent/tools/delete_file.ts +45 -0
  223. package/src/agent/tools/grep.ts +57 -0
  224. package/src/agent/tools/index.ts +34 -0
  225. package/src/agent/tools/list_files.ts +19 -0
  226. package/src/agent/tools/make_dir.ts +28 -0
  227. package/src/agent/tools/move_path.ts +83 -0
  228. package/src/agent/tools/paths.ts +92 -0
  229. package/src/agent/tools/pwd.ts +12 -0
  230. package/src/agent/tools/read_file.ts +84 -0
  231. package/src/agent/tools/read_many.ts +45 -0
  232. package/src/agent/tools/readers.ts +491 -0
  233. package/src/agent/tools/replace_in_file.ts +66 -0
  234. package/src/agent/tools/shell.ts +53 -0
  235. package/src/agent/tools/stat_path.ts +33 -0
  236. package/src/agent/tools/types.ts +84 -0
  237. package/src/agent/tools/validate.ts +104 -0
  238. package/src/agent/tools/write_file.ts +30 -0
  239. package/src/agent/tools.ts +3 -377
  240. package/src/app.ts +853 -170
  241. package/src/{cli.ts → cli-main.ts} +64 -9
  242. package/src/commands/about.ts +1 -1
  243. package/src/commands/attach.ts +16 -0
  244. package/src/commands/bg.ts +9 -0
  245. package/src/commands/cancel.ts +10 -0
  246. package/src/commands/clear.ts +1 -1
  247. package/src/commands/compact.ts +35 -0
  248. package/src/commands/context.ts +26 -0
  249. package/src/commands/edit.ts +37 -0
  250. package/src/commands/effort.ts +49 -0
  251. package/src/commands/expand.ts +8 -0
  252. package/src/commands/index.ts +24 -0
  253. package/src/commands/jobs.ts +18 -0
  254. package/src/commands/kill.ts +14 -0
  255. package/src/commands/login.ts +24 -22
  256. package/src/commands/logout.ts +8 -16
  257. package/src/commands/model.ts +10 -22
  258. package/src/commands/provider.ts +4 -11
  259. package/src/commands/skills.ts +34 -4
  260. package/src/commands/steer.ts +13 -0
  261. package/src/commands/threads.ts +28 -0
  262. package/src/commands/whoami.ts +6 -5
  263. package/src/config/store.ts +168 -77
  264. package/src/config/trust.ts +181 -22
  265. package/src/headless.ts +225 -0
  266. package/src/index.ts +19 -3
  267. package/src/lib/debug.ts +24 -0
  268. package/src/lib/diagnostics.ts +90 -8
  269. package/src/lib/highlight-subset.ts +93 -0
  270. package/src/lib/http.ts +66 -4
  271. package/src/lib/lifecycle.ts +69 -0
  272. package/src/lib/markdown.ts +22 -4
  273. package/src/lib/model-meta.ts +138 -0
  274. package/src/lib/native-tools.ts +32 -0
  275. package/src/lib/net.ts +204 -0
  276. package/src/patch/apply.ts +194 -0
  277. package/src/patch/parse.ts +131 -0
  278. package/src/patch/plan.ts +317 -0
  279. package/src/providers/anthropic.ts +13 -1
  280. package/src/providers/deepseek.ts +3 -2
  281. package/src/providers/groq.ts +2 -1
  282. package/src/providers/index.ts +32 -1
  283. package/src/providers/mistral.ts +2 -1
  284. package/src/providers/moonshot.ts +2 -1
  285. package/src/providers/openai.ts +2 -1
  286. package/src/providers/openaiCompatible.ts +104 -19
  287. package/src/providers/openrouter.ts +2 -1
  288. package/src/providers/qwen.ts +2 -1
  289. package/src/providers/spec.ts +42 -0
  290. package/src/providers/xai.ts +2 -1
  291. package/src/providers/zhipu.ts +2 -1
  292. package/src/session/log.ts +311 -29
  293. package/src/session/projection.ts +621 -0
  294. package/src/skills/manager.ts +140 -18
  295. package/src/types/highlight-shims.d.ts +14 -0
  296. package/src/types.ts +79 -2
  297. package/src/ui/Message.ts +13 -2
  298. package/src/ui/Picker.ts +24 -0
  299. package/src/ui/Prompt.ts +2 -2
  300. package/src/ui/Spinner.ts +11 -5
  301. package/src/ui/StatusBar.ts +6 -5
  302. package/tsconfig.json +1 -0
  303. /package/dist/{cli.d.ts → cli-main.d.ts} +0 -0
@@ -1,9 +1,31 @@
1
- export declare function normalizeProjectPath(dir: any): string;
2
- export declare function loadTrust(): {
3
- trusted: any;
4
- };
1
+ export interface TrustPathEntry {
2
+ grantedAt: string;
3
+ lastUsed: string;
4
+ }
5
+ export interface TrustToolEntry extends TrustPathEntry {
6
+ catalogHash: string;
7
+ }
8
+ export interface TrustStore {
9
+ version: 1;
10
+ tools: Record<string, TrustToolEntry>;
11
+ paths: Record<string, TrustPathEntry>;
12
+ }
13
+ export declare function normalizeProjectPath(dir: string): string;
14
+ export declare function loadTrust(): TrustStore;
5
15
  export declare function trustAllEnabled(): boolean;
6
- export declare function isTrusted(dir: any): any;
7
- export declare function trustProject(dir: any): string;
8
- export declare function untrustProject(dir: any): boolean;
9
- export declare function listTrusted(): any;
16
+ export declare function isTrusted(dir: string): boolean;
17
+ export declare function trustProject(dir: string): string;
18
+ export declare function untrustProject(dir: string): boolean;
19
+ export declare function listTrusted(): Array<{
20
+ path: string;
21
+ addedAt: string;
22
+ }>;
23
+ /** Check a tool/fingerprint approval bound to the current tool catalog. */
24
+ export declare function isToolApproved(key: string, catalogHash: string): boolean;
25
+ export declare function rememberToolApproval(key: string, catalogHash: string): void;
26
+ export declare function forgetToolApproval(key: string): boolean;
27
+ export declare function listToolApprovals(): Array<{
28
+ key: string;
29
+ grantedAt: string;
30
+ catalogHash: string;
31
+ }>;
@@ -7,10 +7,11 @@
7
7
  *
8
8
  * FASTCODE_TRUST_ALL=1 marks every directory trusted (scripts, CI).
9
9
  */
10
- import { existsSync, readFileSync, realpathSync, writeFileSync } from "node:fs";
10
+ import { existsSync, readFileSync, realpathSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
11
11
  import { resolve } from "node:path";
12
12
  import { ensureDir, getPaths } from "./paths.js";
13
13
  const TRUTHY = ["1", "true", "yes", "on"];
14
+ let cache;
14
15
  export function normalizeProjectPath(dir) {
15
16
  const absolute = resolve(dir);
16
17
  try {
@@ -20,22 +21,117 @@ export function normalizeProjectPath(dir) {
20
21
  return absolute;
21
22
  }
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
+ }
23
95
  export function loadTrust() {
24
96
  const { trust } = getPaths();
25
- if (!existsSync(trust))
26
- return { trusted: [] };
97
+ let key = "missing";
27
98
  try {
28
- const raw = JSON.parse(readFileSync(trust, "utf8"));
29
- return { trusted: Array.isArray(raw.trusted) ? raw.trusted.filter((entry) => entry && typeof entry.path === "string") : [] };
99
+ const info = statSync(trust);
100
+ key = `${info.mtimeMs}:${info.size}`;
30
101
  }
31
102
  catch {
32
- return { trusted: [] };
103
+ // Missing file: cache the empty store until it appears.
33
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;
34
110
  }
35
111
  function saveTrust(state) {
36
112
  const paths = getPaths();
37
113
  ensureDir(paths.root);
38
- writeFileSync(paths.trust, JSON.stringify(state, null, 2), "utf8");
114
+ const tmp = paths.trust + "." + process.pid + ".tmp";
115
+ try {
116
+ writeFileSync(tmp, JSON.stringify(state, null, 2), "utf8");
117
+ renameSync(tmp, paths.trust);
118
+ }
119
+ catch (err) {
120
+ try {
121
+ rmSync(tmp, { force: true });
122
+ }
123
+ catch {
124
+ // best effort
125
+ }
126
+ throw err;
127
+ }
128
+ try {
129
+ const info = statSync(paths.trust);
130
+ cache = { file: paths.trust, key: `${info.mtimeMs}:${info.size}`, state };
131
+ }
132
+ catch {
133
+ cache = undefined;
134
+ }
39
135
  }
40
136
  export function trustAllEnabled() {
41
137
  return TRUTHY.includes((process.env["FASTCODE_TRUST_ALL"] ?? "").trim().toLowerCase());
@@ -44,13 +140,14 @@ export function isTrusted(dir) {
44
140
  if (trustAllEnabled())
45
141
  return true;
46
142
  const target = normalizeProjectPath(dir);
47
- return loadTrust().trusted.some((entry) => entry.path === target);
143
+ return Boolean(loadTrust().paths[target]);
48
144
  }
49
145
  export function trustProject(dir) {
50
146
  const target = normalizeProjectPath(dir);
51
147
  const state = loadTrust();
52
- if (!state.trusted.some((entry) => entry.path === target)) {
53
- state.trusted.push({ path: target, addedAt: new Date().toISOString() });
148
+ if (!state.paths[target]) {
149
+ const at = new Date().toISOString();
150
+ state.paths[target] = { grantedAt: at, lastUsed: at };
54
151
  saveTrust(state);
55
152
  }
56
153
  return target;
@@ -58,10 +155,38 @@ export function trustProject(dir) {
58
155
  export function untrustProject(dir) {
59
156
  const target = normalizeProjectPath(dir);
60
157
  const state = loadTrust();
61
- const next = state.trusted.filter((entry) => entry.path !== target);
62
- saveTrust({ trusted: next });
63
- return state.trusted.length !== next.length;
158
+ if (!state.paths[target])
159
+ return false;
160
+ delete state.paths[target];
161
+ saveTrust(state);
162
+ return true;
64
163
  }
65
164
  export function listTrusted() {
66
- return loadTrust().trusted;
165
+ return Object.entries(loadTrust().paths).map(([path, entry]) => ({ path, addedAt: entry.grantedAt }));
166
+ }
167
+ /** Check a tool/fingerprint approval bound to the current tool catalog. */
168
+ export function isToolApproved(key, catalogHash) {
169
+ const entry = loadTrust().tools[key];
170
+ return Boolean(entry && entry.catalogHash === catalogHash);
171
+ }
172
+ export function rememberToolApproval(key, catalogHash) {
173
+ const state = loadTrust();
174
+ const at = new Date().toISOString();
175
+ state.tools[key] = { grantedAt: at, lastUsed: at, catalogHash };
176
+ saveTrust(state);
177
+ }
178
+ export function forgetToolApproval(key) {
179
+ const state = loadTrust();
180
+ if (!state.tools[key])
181
+ return false;
182
+ delete state.tools[key];
183
+ saveTrust(state);
184
+ return true;
185
+ }
186
+ export function listToolApprovals() {
187
+ return Object.entries(loadTrust().tools).map(([key, entry]) => ({
188
+ key,
189
+ grantedAt: entry.grantedAt,
190
+ catalogHash: entry.catalogHash,
191
+ }));
67
192
  }
@@ -0,0 +1,7 @@
1
+ interface HeadlessOptions {
2
+ prompt: string;
3
+ ui: "json" | "plain";
4
+ resume?: string;
5
+ }
6
+ export declare function runHeadless(options: HeadlessOptions): Promise<void>;
7
+ export {};
@@ -0,0 +1,213 @@
1
+ /**
2
+ * Non-Ink headless runner for `--ui json` / non-TTY `--ui plain`.
3
+ *
4
+ * stdout is reserved for the protocol/answer; diagnostics go to stderr.
5
+ */
6
+ import readline from "node:readline";
7
+ import { AGENT_SYSTEM_PROMPT } from "./agent/protocol.js";
8
+ import { compactionConfigFromEnv } from "./agent/compaction.js";
9
+ import { resolveToolProtocol } from "./agent/protocol-mode.js";
10
+ import { runAgent } from "./agent/runner.js";
11
+ import { setShellPoolSize } from "./agent/shell-session.js";
12
+ import { toolSchemas } from "./agent/tool-schemas.js";
13
+ import { encodeGateAsk, parseGateDecision } from "./agent/approval-json.js";
14
+ import { decisionForOption, parseApprovalChoice } from "./agent/approval.js";
15
+ import { buildPluginsPrompt } from "./plugins/manager.js";
16
+ import { buildSkillsPrompt } from "./skills/manager.js";
17
+ import { loadConfig, resolveApiKey } from "./config/store.js";
18
+ import { getProviderOrThrow } from "./providers/index.js";
19
+ import { resumeSession } from "./session/projection.js";
20
+ let eventSeq = 0;
21
+ function writeEvent(value) {
22
+ eventSeq += 1;
23
+ process.stdout.write(JSON.stringify({ v: 1, seq: eventSeq, ts: new Date().toISOString(), ...value }) + "\n");
24
+ }
25
+ function optionalBudget(name) {
26
+ const value = Number(process.env[name]);
27
+ return Number.isFinite(value) && value > 0 ? value : undefined;
28
+ }
29
+ export async function runHeadless(options) {
30
+ const config = loadConfig();
31
+ setShellPoolSize(config.shellPoolSize);
32
+ const provider = getProviderOrThrow(config.providerId);
33
+ let apiKey;
34
+ if (provider.authEnv.length > 0) {
35
+ const lookup = resolveApiKey(provider.id, config.model, provider.authEnv);
36
+ if (lookup.status !== "ok")
37
+ throw new Error(`No usable API key for ${provider.id}.`);
38
+ apiKey = lookup.key;
39
+ }
40
+ const history = options.resume ? resumeSession(options.resume, process.cwd()).messages : [];
41
+ const system = [
42
+ config.systemPrompt,
43
+ buildSkillsPrompt(config.enabledSkills),
44
+ buildPluginsPrompt(config.enabledPlugins),
45
+ config.agentMode ? AGENT_SYSTEM_PROMPT : "",
46
+ ].filter((part) => part.trim()).join("\n\n");
47
+ const protocol = config.agentMode ? resolveToolProtocol(config, config.providerId, config.model) : "legacy";
48
+ const rl = readline.createInterface({ input: process.stdin, crlfDelay: Infinity });
49
+ const queued = [];
50
+ const waiters = [];
51
+ let eof = false;
52
+ rl.on("line", (line) => {
53
+ const waiter = waiters.shift();
54
+ if (waiter)
55
+ waiter(line);
56
+ else
57
+ queued.push(line);
58
+ });
59
+ rl.on("close", () => {
60
+ eof = true;
61
+ while (waiters.length > 0)
62
+ waiters.shift()(undefined);
63
+ });
64
+ const nextLine = (timeoutMs) => {
65
+ if (queued.length > 0)
66
+ return Promise.resolve(queued.shift());
67
+ if (eof)
68
+ return Promise.resolve(undefined);
69
+ return new Promise((resolve) => {
70
+ const waiter = (line) => {
71
+ clearTimeout(timer);
72
+ resolve(line);
73
+ };
74
+ const timer = setTimeout(() => {
75
+ const index = waiters.indexOf(waiter);
76
+ if (index >= 0)
77
+ waiters.splice(index, 1);
78
+ resolve(undefined);
79
+ }, Math.max(1000, timeoutMs));
80
+ timer.unref?.();
81
+ waiters.push(waiter);
82
+ });
83
+ };
84
+ const requestApproval = async (request) => {
85
+ if (options.ui === "json") {
86
+ writeEvent(encodeGateAsk(request));
87
+ const line = await nextLine(request.timeoutMs ?? 60_000);
88
+ if (line === undefined)
89
+ return { kind: "deny", reason: "approval timeout/EOF" };
90
+ const parsed = parseGateDecision(request, line);
91
+ if (parsed.error) {
92
+ writeEvent({ event: "gate:error", version: 1, id: request.id, error: parsed.error });
93
+ return parsed.decision;
94
+ }
95
+ return parsed.decision;
96
+ }
97
+ process.stderr.write(`${request.prompt ?? "Approval required"}\n`);
98
+ request.options.forEach((option, index) => process.stderr.write(`${index + 1}. ${option.label}\n`));
99
+ process.stderr.write(`Choose 1-${request.options.length}: `);
100
+ const line = await nextLine(request.timeoutMs ?? 60_000);
101
+ const option = line === undefined ? undefined : parseApprovalChoice(request, line);
102
+ if (!option)
103
+ return { kind: "deny", reason: "approval cancelled/invalid/timeout" };
104
+ if (option.kind === "feedback") {
105
+ process.stderr.write(`${option.prompt || "Why should this be denied?"}\n> `);
106
+ const feedbackLine = await nextLine(request.timeoutMs ?? 60_000);
107
+ const feedback = feedbackLine?.trim();
108
+ return feedback
109
+ ? { kind: "feedback", text: feedback }
110
+ : { kind: "deny", reason: "feedback cancelled/empty (fail closed)" };
111
+ }
112
+ return decisionForOption(option);
113
+ };
114
+ const abort = new AbortController();
115
+ const onSigint = () => abort.abort();
116
+ const onTerminate = () => abort.abort();
117
+ process.once("SIGINT", onSigint);
118
+ process.once("SIGTERM", onTerminate);
119
+ process.once("SIGHUP", onTerminate);
120
+ try {
121
+ for await (const event of runAgent({
122
+ provider,
123
+ apiKey,
124
+ model: config.model,
125
+ messages: history,
126
+ prompt: options.prompt,
127
+ system,
128
+ maxTurns: config.agentMode ? Math.max(1, Math.floor(Number(config.maxAgentTurns) || 24)) : 1,
129
+ allowTools: config.agentMode,
130
+ tokenBudget: optionalBudget("FASTCODE_TOKEN_BUDGET"),
131
+ costBudgetUsd: optionalBudget("FASTCODE_COST_BUDGET_USD"),
132
+ signal: abort.signal,
133
+ cwd: process.cwd(),
134
+ idleTimeoutMs: config.requestIdleTimeoutMs,
135
+ compaction: compactionConfigFromEnv(),
136
+ reasoningEffort: config.reasoningEffort,
137
+ protocol,
138
+ tools: protocol === "native" ? toolSchemas() : undefined,
139
+ requestApproval,
140
+ })) {
141
+ if (options.ui === "json") {
142
+ if (event.type === "assistant_text")
143
+ writeEvent({ event: "assistant", text: event.text });
144
+ else if (event.type === "tool_call")
145
+ writeEvent({
146
+ event: "tool_call",
147
+ id: event.id,
148
+ tool: event.call.tool,
149
+ args: event.call,
150
+ ...(event.native ? { native: true } : {}),
151
+ });
152
+ else if (event.type === "egress_denied")
153
+ writeEvent({
154
+ event: "egress_denied",
155
+ outlet: event.outlet,
156
+ ...(event.target ? { target: event.target } : {}),
157
+ rule: event.rule,
158
+ mode: event.mode,
159
+ would_block: event.wouldBlock,
160
+ code: event.code,
161
+ reason: event.reason,
162
+ });
163
+ else if (event.type === "tool_result")
164
+ writeEvent({
165
+ event: "tool_result",
166
+ id: event.id,
167
+ tool: event.call.tool,
168
+ ok: event.ok,
169
+ output: event.output,
170
+ ...(event.sideEffects ? { side_effects: event.sideEffects } : {}),
171
+ ...(event.code ? { code: event.code } : {}),
172
+ ...(event.hint ? { hint: event.hint } : {}),
173
+ ...(event.truncated !== undefined ? { truncated: event.truncated } : {}),
174
+ ...(event.totalLines !== undefined ? { total_lines: event.totalLines } : {}),
175
+ ...(event.returnedLines !== undefined ? { returned_lines: event.returnedLines } : {}),
176
+ ...(event.totalBytes !== undefined ? { total_bytes: event.totalBytes } : {}),
177
+ ...(event.returnedBytes !== undefined ? { returned_bytes: event.returnedBytes } : {}),
178
+ ...(event.filesRead !== undefined ? { files_read: event.filesRead } : {}),
179
+ ...(event.filesSkipped !== undefined ? { files_skipped: event.filesSkipped } : {}),
180
+ ...(event.matches !== undefined ? { matches: event.matches } : {}),
181
+ });
182
+ else if (event.type === "usage")
183
+ writeEvent({
184
+ event: "usage",
185
+ input_tokens: event.usage.inputTokens ?? 0,
186
+ output_tokens: event.usage.outputTokens ?? 0,
187
+ cost_usd: event.usage.costUsd ?? 0,
188
+ });
189
+ else if (event.type === "notice")
190
+ writeEvent({ event: "notice", text: event.text });
191
+ else if (event.type === "error")
192
+ writeEvent({ event: "error", kind: event.kind, code: event.kind, message: event.message });
193
+ else if (event.type === "done")
194
+ writeEvent({ event: "done", stopReason: event.stopReason, finalText: event.finalText });
195
+ }
196
+ else if (event.type === "assistant_text") {
197
+ process.stdout.write(event.text);
198
+ }
199
+ else if (event.type === "notice") {
200
+ process.stderr.write(`[fastcode] ${event.text}\n`);
201
+ }
202
+ else if (event.type === "done" && event.finalText) {
203
+ process.stdout.write(event.finalText + "\n");
204
+ }
205
+ }
206
+ }
207
+ finally {
208
+ process.removeListener("SIGINT", onSigint);
209
+ process.removeListener("SIGTERM", onTerminate);
210
+ process.removeListener("SIGHUP", onTerminate);
211
+ rl.close();
212
+ }
213
+ }
package/dist/index.js CHANGED
@@ -5,13 +5,29 @@
5
5
  * Ink-based TUI. We keep this file tiny so the slow imports (Ink, React)
6
6
  * are not loaded for `fastcode skills list`.
7
7
  */
8
- import { parseCli } from "./cli.js";
8
+ import { parseCli } from "./cli-main.js";
9
+ import { installProcessGuards } from "./lib/lifecycle.js";
9
10
  async function main() {
11
+ installProcessGuards();
10
12
  const cli = await parseCli();
11
13
  if (cli.done)
12
14
  return;
15
+ if (cli.ui === "json") {
16
+ if (!cli.prompt) {
17
+ console.error("--ui json requires a prompt: fastcode --ui json \"<prompt>\"");
18
+ process.exit(1);
19
+ }
20
+ const { runHeadless } = await import("./headless.js");
21
+ await runHeadless({ prompt: cli.prompt, ui: "json", resume: cli.resume });
22
+ return;
23
+ }
13
24
  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.");
25
+ if (cli.ui === "plain" && cli.prompt) {
26
+ const { runHeadless } = await import("./headless.js");
27
+ await runHeadless({ prompt: cli.prompt, ui: "plain", resume: cli.resume });
28
+ return;
29
+ }
30
+ console.error("FastCode TUI requires a TTY. Use `fastcode --ui plain|json \"<prompt>\"` for pipe mode.");
15
31
  process.exit(1);
16
32
  }
17
33
  // Lazy-load Ink so subcommands don't pay the React boot cost.
@@ -23,7 +39,7 @@ async function main() {
23
39
  // React.createElement avoids needing a separate .tsx loader for this file.
24
40
  // exitOnCtrlC: false lets the app own Ctrl+C so the documented
25
41
  // "press Ctrl+C twice to quit" behavior actually works.
26
- render(React.createElement(App, { initialPrompt: cli.prompt, resume: cli.resume }), { exitOnCtrlC: false });
42
+ render(React.createElement(App, { initialPrompt: cli.prompt, resume: cli.resume, ui: cli.ui ?? "tty" }), { exitOnCtrlC: false });
27
43
  }
28
44
  main().catch((err) => {
29
45
  console.error(err);
@@ -0,0 +1,2 @@
1
+ export declare function debugEnabled(): boolean;
2
+ export declare function debugLog(scope: string, message: string): void;
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Minimal debug logging.
3
+ *
4
+ * There is no logging framework in this project: normal output goes through
5
+ * Ink and lifecycle warnings go straight to stderr. `FASTCODE_DEBUG=1` opts
6
+ * into low-volume stderr diagnostics (for example timeout clamping or config
7
+ * cache invalidation) without changing the default experience.
8
+ */
9
+ const TRUTHY = new Set(["1", "true", "yes", "on"]);
10
+ export function debugEnabled() {
11
+ return TRUTHY.has((process.env["FASTCODE_DEBUG"] ?? "").trim().toLowerCase());
12
+ }
13
+ export function debugLog(scope, message) {
14
+ if (!debugEnabled())
15
+ return;
16
+ try {
17
+ process.stderr.write(`[fastcode:${scope}] ${message}\n`);
18
+ }
19
+ catch {
20
+ // Debug logging must never break the operation it observes.
21
+ }
22
+ }
@@ -12,29 +12,73 @@ export declare function collectDiagnostics(): {
12
12
  configRoot: any;
13
13
  configFile: any;
14
14
  authFile: any;
15
+ authMode: string;
16
+ tmpdir: string;
17
+ memTotalMb: number;
18
+ memFreeMb: number;
15
19
  providerId: any;
16
20
  model: any;
17
21
  keyPresent: boolean;
18
- keyStatus: "ok" | "unbound" | "missing";
19
- keyEnv: any;
22
+ keyStatus: "ok" | "missing" | "unbound";
23
+ keyEnv: string;
20
24
  skillsInstalled: number;
25
+ skillsRegistry: string;
26
+ debug: boolean;
27
+ configCache: boolean;
21
28
  pluginsInstalled: number;
22
29
  enabledSkills: any;
23
30
  enabledPlugins: any;
24
31
  sessionLogEnabled: boolean;
25
32
  sessionCacheMb: number;
33
+ sessionFsync: "auto" | "off" | "on";
34
+ unclosedSessions: number;
26
35
  sessionsDir: any;
27
36
  sessionsCount: number;
28
37
  sessionsBytes: number;
29
38
  trustFile: any;
30
- trusted: any;
39
+ trusted: boolean;
31
40
  shellDenyPatterns: number;
32
41
  allowDangerous: boolean;
33
42
  toolOutputLimit: number;
43
+ readMaxBytes: number;
44
+ readScanMaxBytes: number;
45
+ readManyMaxBytes: number;
46
+ grepMaxBytes: number;
47
+ egress: {
48
+ enabled: boolean;
49
+ mode: import("../agent/policy/defaults.js").EgressMode;
50
+ maxScanBytes: number;
51
+ denyPaths: number;
52
+ denyGlobs: number;
53
+ denyContentMarkers: number;
54
+ scanToolOutput: boolean;
55
+ };
56
+ tokenBudget: number;
57
+ costBudgetUsd: number;
58
+ compactionEnabled: boolean;
59
+ compactionRatio: number;
60
+ compactionKeepTurns: number;
61
+ compactionSummaryTokens: number;
62
+ compactionHeapMb: number;
63
+ compactionHeapUsedMb: number;
64
+ compactionForced: boolean;
65
+ serverCompactionAware: boolean;
66
+ shellActive: number;
67
+ shellPool: number;
68
+ shellPoolMax: number;
69
+ shellSpawns: number;
70
+ shellMaxSpawn: number;
71
+ sseIdleMs: number;
72
+ toolProtocol: import("../agent/protocol-mode.js").ToolProtocol;
73
+ toolProtocolMode: string;
74
+ toolCapability: any;
75
+ loopExempt: string[];
76
+ modelMetaFile: string;
77
+ modelMeta: import("./model-meta.js").ModelMeta;
34
78
  providers: {
35
79
  id: string;
36
80
  label: string;
37
- env: any[] | string[];
81
+ env: string[];
38
82
  keyPresent: boolean;
39
83
  }[];
40
84
  };