@datename/fastcode-termux 0.1.6-beta → 0.1.6

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 (246) hide show
  1. package/README.md +6 -24
  2. package/dist/agent/guards.d.ts +0 -8
  3. package/dist/agent/guards.js +0 -195
  4. package/dist/agent/protocol.js +1 -4
  5. package/dist/agent/tools.d.ts +1 -3
  6. package/dist/agent/tools.js +377 -2
  7. package/dist/app.js +103 -335
  8. package/dist/cli.js +311 -362
  9. package/dist/commands/index.js +0 -22
  10. package/dist/commands/login.d.ts +2 -4
  11. package/dist/commands/login.js +10 -12
  12. package/dist/commands/logout.js +16 -8
  13. package/dist/commands/model.d.ts +0 -13
  14. package/dist/commands/model.js +22 -42
  15. package/dist/commands/provider.d.ts +0 -13
  16. package/dist/commands/provider.js +9 -20
  17. package/dist/commands/whoami.js +5 -6
  18. package/dist/config/store.d.ts +12 -32
  19. package/dist/config/store.js +65 -117
  20. package/dist/config/trust.d.ts +8 -21
  21. package/dist/config/trust.js +13 -99
  22. package/dist/lib/diagnostics.d.ts +4 -29
  23. package/dist/lib/diagnostics.js +4 -60
  24. package/dist/lib/http.d.ts +1 -11
  25. package/dist/lib/http.js +4 -62
  26. package/dist/providers/anthropic.d.ts +0 -1
  27. package/dist/providers/anthropic.js +0 -12
  28. package/dist/providers/custom.d.ts +0 -9
  29. package/dist/providers/deepseek.d.ts +0 -10
  30. package/dist/providers/deepseek.js +1 -2
  31. package/dist/providers/google.d.ts +0 -9
  32. package/dist/providers/groq.d.ts +0 -10
  33. package/dist/providers/groq.js +1 -2
  34. package/dist/providers/index.d.ts +201 -10
  35. package/dist/providers/index.js +0 -30
  36. package/dist/providers/mistral.d.ts +0 -10
  37. package/dist/providers/mistral.js +1 -2
  38. package/dist/providers/moonshot.d.ts +0 -10
  39. package/dist/providers/moonshot.js +1 -2
  40. package/dist/providers/ollama.d.ts +0 -9
  41. package/dist/providers/openai.d.ts +0 -10
  42. package/dist/providers/openai.js +1 -2
  43. package/dist/providers/openaiCompatible.d.ts +0 -9
  44. package/dist/providers/openaiCompatible.js +10 -58
  45. package/dist/providers/openrouter.d.ts +0 -10
  46. package/dist/providers/openrouter.js +1 -2
  47. package/dist/providers/qwen.d.ts +0 -10
  48. package/dist/providers/qwen.js +1 -2
  49. package/dist/providers/xai.d.ts +0 -10
  50. package/dist/providers/xai.js +1 -2
  51. package/dist/providers/zhipu.d.ts +0 -10
  52. package/dist/providers/zhipu.js +1 -2
  53. package/dist/session/log.d.ts +12 -35
  54. package/dist/session/log.js +10 -206
  55. package/dist/types.d.ts +1 -60
  56. package/dist/ui/Message.d.ts +7 -1
  57. package/dist/ui/Message.js +2 -13
  58. package/dist/ui/Prompt.d.ts +2 -1
  59. package/dist/ui/Prompt.js +2 -2
  60. package/dist/ui/Spinner.js +5 -11
  61. package/dist/ui/StatusBar.d.ts +1 -2
  62. package/dist/ui/StatusBar.js +5 -6
  63. package/package.json +1 -1
  64. package/src/agent/guards.ts +0 -196
  65. package/src/agent/protocol.ts +1 -4
  66. package/src/agent/tools.ts +377 -3
  67. package/src/app.ts +103 -335
  68. package/src/cli.ts +3 -30
  69. package/src/commands/index.ts +0 -22
  70. package/src/commands/login.ts +10 -12
  71. package/src/commands/logout.ts +16 -8
  72. package/src/commands/model.ts +22 -42
  73. package/src/commands/provider.ts +9 -20
  74. package/src/commands/whoami.ts +5 -6
  75. package/src/config/store.ts +69 -122
  76. package/src/config/trust.ts +21 -137
  77. package/src/lib/diagnostics.ts +4 -60
  78. package/src/lib/http.ts +4 -66
  79. package/src/providers/anthropic.ts +1 -13
  80. package/src/providers/deepseek.ts +1 -2
  81. package/src/providers/groq.ts +1 -2
  82. package/src/providers/index.ts +1 -32
  83. package/src/providers/mistral.ts +1 -2
  84. package/src/providers/moonshot.ts +1 -2
  85. package/src/providers/openai.ts +1 -2
  86. package/src/providers/openaiCompatible.ts +10 -58
  87. package/src/providers/openrouter.ts +1 -2
  88. package/src/providers/qwen.ts +1 -2
  89. package/src/providers/xai.ts +1 -2
  90. package/src/providers/zhipu.ts +1 -2
  91. package/src/session/log.ts +28 -267
  92. package/src/types.ts +1 -63
  93. package/src/ui/Message.ts +2 -13
  94. package/src/ui/Prompt.ts +2 -2
  95. package/src/ui/Spinner.ts +5 -11
  96. package/src/ui/StatusBar.ts +5 -6
  97. package/dist/agent/approval.d.ts +0 -50
  98. package/dist/agent/approval.js +0 -7
  99. package/dist/agent/compaction.d.ts +0 -39
  100. package/dist/agent/compaction.js +0 -227
  101. package/dist/agent/context.d.ts +0 -29
  102. package/dist/agent/context.js +0 -62
  103. package/dist/agent/ignore.d.ts +0 -11
  104. package/dist/agent/ignore.js +0 -129
  105. package/dist/agent/process-tree.d.ts +0 -2
  106. package/dist/agent/process-tree.js +0 -31
  107. package/dist/agent/protocol-mode.d.ts +0 -6
  108. package/dist/agent/protocol-mode.js +0 -51
  109. package/dist/agent/registry.d.ts +0 -17
  110. package/dist/agent/registry.js +0 -90
  111. package/dist/agent/runner.d.ts +0 -106
  112. package/dist/agent/runner.js +0 -352
  113. package/dist/agent/shell-session.d.ts +0 -37
  114. package/dist/agent/shell-session.js +0 -400
  115. package/dist/agent/stream.d.ts +0 -29
  116. package/dist/agent/stream.js +0 -103
  117. package/dist/agent/tool-schemas.d.ts +0 -2
  118. package/dist/agent/tool-schemas.js +0 -63
  119. package/dist/agent/tools/append_file.d.ts +0 -2
  120. package/dist/agent/tools/append_file.js +0 -26
  121. package/dist/agent/tools/apply_patch.d.ts +0 -10
  122. package/dist/agent/tools/apply_patch.js +0 -52
  123. package/dist/agent/tools/copy_file.d.ts +0 -2
  124. package/dist/agent/tools/copy_file.js +0 -30
  125. package/dist/agent/tools/delete_file.d.ts +0 -2
  126. package/dist/agent/tools/delete_file.js +0 -24
  127. package/dist/agent/tools/grep.d.ts +0 -2
  128. package/dist/agent/tools/grep.js +0 -23
  129. package/dist/agent/tools/index.d.ts +0 -2
  130. package/dist/agent/tools/index.js +0 -32
  131. package/dist/agent/tools/list_files.d.ts +0 -2
  132. package/dist/agent/tools/list_files.js +0 -17
  133. package/dist/agent/tools/make_dir.d.ts +0 -2
  134. package/dist/agent/tools/make_dir.js +0 -24
  135. package/dist/agent/tools/move_path.d.ts +0 -2
  136. package/dist/agent/tools/move_path.js +0 -47
  137. package/dist/agent/tools/paths.d.ts +0 -7
  138. package/dist/agent/tools/paths.js +0 -84
  139. package/dist/agent/tools/pwd.d.ts +0 -2
  140. package/dist/agent/tools/pwd.js +0 -10
  141. package/dist/agent/tools/read_file.d.ts +0 -2
  142. package/dist/agent/tools/read_file.js +0 -21
  143. package/dist/agent/tools/read_many.d.ts +0 -2
  144. package/dist/agent/tools/read_many.js +0 -25
  145. package/dist/agent/tools/readers.d.ts +0 -6
  146. package/dist/agent/tools/readers.js +0 -216
  147. package/dist/agent/tools/replace_in_file.d.ts +0 -2
  148. package/dist/agent/tools/replace_in_file.js +0 -30
  149. package/dist/agent/tools/shell.d.ts +0 -2
  150. package/dist/agent/tools/shell.js +0 -48
  151. package/dist/agent/tools/stat_path.d.ts +0 -2
  152. package/dist/agent/tools/stat_path.js +0 -31
  153. package/dist/agent/tools/types.d.ts +0 -39
  154. package/dist/agent/tools/types.js +0 -1
  155. package/dist/agent/tools/validate.d.ts +0 -6
  156. package/dist/agent/tools/validate.js +0 -29
  157. package/dist/agent/tools/write_file.d.ts +0 -2
  158. package/dist/agent/tools/write_file.js +0 -26
  159. package/dist/commands/attach.d.ts +0 -9
  160. package/dist/commands/attach.js +0 -15
  161. package/dist/commands/bg.d.ts +0 -7
  162. package/dist/commands/bg.js +0 -9
  163. package/dist/commands/cancel.d.ts +0 -10
  164. package/dist/commands/cancel.js +0 -10
  165. package/dist/commands/compact.d.ts +0 -10
  166. package/dist/commands/compact.js +0 -22
  167. package/dist/commands/context.d.ts +0 -7
  168. package/dist/commands/context.js +0 -24
  169. package/dist/commands/edit.d.ts +0 -10
  170. package/dist/commands/edit.js +0 -36
  171. package/dist/commands/effort.d.ts +0 -25
  172. package/dist/commands/effort.js +0 -48
  173. package/dist/commands/expand.d.ts +0 -10
  174. package/dist/commands/expand.js +0 -8
  175. package/dist/commands/jobs.d.ts +0 -7
  176. package/dist/commands/jobs.js +0 -16
  177. package/dist/commands/kill.d.ts +0 -7
  178. package/dist/commands/kill.js +0 -11
  179. package/dist/commands/steer.d.ts +0 -11
  180. package/dist/commands/steer.js +0 -13
  181. package/dist/lib/model-meta.d.ts +0 -19
  182. package/dist/lib/model-meta.js +0 -113
  183. package/dist/lib/native-tools.d.ts +0 -12
  184. package/dist/lib/native-tools.js +0 -21
  185. package/dist/lib/net.d.ts +0 -33
  186. package/dist/lib/net.js +0 -168
  187. package/dist/patch/apply.d.ts +0 -15
  188. package/dist/patch/apply.js +0 -172
  189. package/dist/patch/parse.d.ts +0 -29
  190. package/dist/patch/parse.js +0 -112
  191. package/dist/patch/plan.d.ts +0 -49
  192. package/dist/patch/plan.js +0 -273
  193. package/dist/session/projection.d.ts +0 -43
  194. package/dist/session/projection.js +0 -257
  195. package/dist/ui/Picker.d.ts +0 -8
  196. package/dist/ui/Picker.js +0 -24
  197. package/src/agent/approval.ts +0 -47
  198. package/src/agent/compaction.ts +0 -276
  199. package/src/agent/context.ts +0 -97
  200. package/src/agent/ignore.ts +0 -147
  201. package/src/agent/process-tree.ts +0 -32
  202. package/src/agent/protocol-mode.ts +0 -59
  203. package/src/agent/registry.ts +0 -98
  204. package/src/agent/runner.ts +0 -443
  205. package/src/agent/shell-session.ts +0 -449
  206. package/src/agent/stream.ts +0 -143
  207. package/src/agent/tool-schemas.ts +0 -67
  208. package/src/agent/tools/append_file.ts +0 -28
  209. package/src/agent/tools/apply_patch.ts +0 -54
  210. package/src/agent/tools/copy_file.ts +0 -32
  211. package/src/agent/tools/delete_file.ts +0 -26
  212. package/src/agent/tools/grep.ts +0 -25
  213. package/src/agent/tools/index.ts +0 -34
  214. package/src/agent/tools/list_files.ts +0 -19
  215. package/src/agent/tools/make_dir.ts +0 -26
  216. package/src/agent/tools/move_path.ts +0 -50
  217. package/src/agent/tools/paths.ts +0 -92
  218. package/src/agent/tools/pwd.ts +0 -12
  219. package/src/agent/tools/read_file.ts +0 -23
  220. package/src/agent/tools/read_many.ts +0 -27
  221. package/src/agent/tools/readers.ts +0 -228
  222. package/src/agent/tools/replace_in_file.ts +0 -32
  223. package/src/agent/tools/shell.ts +0 -53
  224. package/src/agent/tools/stat_path.ts +0 -33
  225. package/src/agent/tools/types.ts +0 -42
  226. package/src/agent/tools/validate.ts +0 -35
  227. package/src/agent/tools/write_file.ts +0 -28
  228. package/src/commands/attach.ts +0 -16
  229. package/src/commands/bg.ts +0 -9
  230. package/src/commands/cancel.ts +0 -10
  231. package/src/commands/compact.ts +0 -23
  232. package/src/commands/context.ts +0 -25
  233. package/src/commands/edit.ts +0 -37
  234. package/src/commands/effort.ts +0 -49
  235. package/src/commands/expand.ts +0 -8
  236. package/src/commands/jobs.ts +0 -17
  237. package/src/commands/kill.ts +0 -12
  238. package/src/commands/steer.ts +0 -13
  239. package/src/lib/model-meta.ts +0 -138
  240. package/src/lib/native-tools.ts +0 -32
  241. package/src/lib/net.ts +0 -204
  242. package/src/patch/apply.ts +0 -194
  243. package/src/patch/parse.ts +0 -131
  244. package/src/patch/plan.ts +0 -317
  245. package/src/session/projection.ts +0 -308
  246. package/src/ui/Picker.ts +0 -24
@@ -5,12 +5,14 @@
5
5
  * edit it from `nano ~/.fastcode/config.json` if needed. Atomic writes prevent
6
6
  * partial files on Ctrl+C.
7
7
  *
8
- * API keys are scoped to a provider, not to a model. auth.json stores one
9
- * credential per provider id, so switching between models of the same provider
10
- * reuses the same key. The old "provider::model" (v2) and provider-only (v1)
11
- * formats are read and migrated to the provider-scoped v3 layout on first load.
8
+ * API keys are NOT a single global string: each one is scoped to a concrete
9
+ * "provider + model" pair. auth.json stores one credential per
10
+ * `<provider>::<model>` scope, and a stored key is only used when the current
11
+ * provider AND model match that scope. The old provider-only format is read as
12
+ * "legacy": it is shown back to the user but never used silently, because a
13
+ * provider-only key does not tell us which model it was meant for.
12
14
  */
13
- import { readFileSync, writeFileSync, existsSync, renameSync, copyFileSync } from "node:fs";
15
+ import { readFileSync, writeFileSync, existsSync, renameSync } from "node:fs";
14
16
  import { join, dirname } from "node:path";
15
17
  import { getPaths, lockdown, ensureDir } from "./paths.js";
16
18
  import { DEFAULT_THEME } from "./themes.js";
@@ -27,26 +29,7 @@ export const DEFAULT_CONFIG = {
27
29
  enabledPlugins: [],
28
30
  skillsRegistry: "https://skillshub.wtf",
29
31
  lifetimeCostUsd: 0,
30
- /**
31
- * Extra directories the file tools may read and write outside the project
32
- * root. Relative entries are resolved against the project root; `~/` is
33
- * expanded against the user's home directory.
34
- */
35
- sharedDirs: [],
36
- /** Maximum agent tool turns before the loop asks the user to continue. */
37
- maxAgentTurns: 24,
38
- /** Abort a stream that receives no data for this long (ms). */
39
- requestIdleTimeoutMs: 60_000,
40
- /** Approximate character budget before history compaction kicks in. */
41
- contextBudgetChars: 400_000,
42
- /** Thinking depth: auto = send nothing, provider default. */
43
- reasoningEffort: "auto",
44
- /** Cached per-provider/model capabilities (native vs pseudo tool calling). */
45
- capabilities: {
46
- toolCalling: {},
47
- },
48
32
  };
49
- export const AUTH_VERSION = 3;
50
33
  export function loadConfig() {
51
34
  const { config } = getPaths();
52
35
  if (!existsSync(config))
@@ -63,97 +46,58 @@ export function saveConfig(cfg) {
63
46
  const { config } = getPaths();
64
47
  atomicWrite(config, JSON.stringify(cfg, null, 2));
65
48
  }
66
- /** Composite id used by the legacy v2 format; kept for API compatibility. */
49
+ /** Composite scope id for a provider + model credential. */
67
50
  export function scopeId(providerId, model) {
68
51
  return `${providerId}::${model}`;
69
52
  }
70
- /** Pick the most recently saved credential when several legacy scopes map to one provider. */
71
- function keepNewest(keys, credential) {
72
- const existing = keys[credential.provider];
73
- if (!existing) {
74
- keys[credential.provider] = credential;
75
- return;
76
- }
77
- const previous = Date.parse(existing.addedAt || "") || 0;
78
- const next = Date.parse(credential.addedAt || "") || 0;
79
- if (next > previous)
80
- keys[credential.provider] = credential;
81
- }
82
- let migrationChecked = false;
83
53
  export function loadAuth() {
84
54
  const { auth } = getPaths();
85
- const empty = { version: AUTH_VERSION, keys: {}, legacy: {} };
55
+ const empty = { version: 2, keys: {}, legacy: {} };
86
56
  if (!existsSync(auth))
87
57
  return empty;
88
- let raw;
89
58
  try {
90
- raw = JSON.parse(readFileSync(auth, "utf8"));
91
- }
92
- catch {
93
- return empty;
94
- }
95
- if (!raw || typeof raw !== "object")
96
- return empty;
97
- const keys = {};
98
- const legacy = {};
99
- let migrated = false;
100
- const source = raw.keys && typeof raw.keys === "object" ? raw.keys : {};
101
- for (const [id, value] of Object.entries(source)) {
102
- if (value && typeof value === "object" && typeof value.key === "string" && value.key.trim()) {
103
- const [idProvider, idModel] = id.split("::");
104
- const provider = typeof value.provider === "string" && value.provider ? value.provider : idProvider || id;
105
- const model = typeof value.model === "string" ? value.model : idModel || "";
106
- if (id.includes("::") || raw.version !== AUTH_VERSION)
107
- migrated = true;
108
- keepNewest(keys, {
109
- provider,
110
- model,
111
- key: value.key,
112
- addedAt: typeof value.addedAt === "string" ? value.addedAt : "",
113
- });
114
- }
115
- else if (typeof value === "string" && value.trim()) {
116
- // v1: keys were plain provider -> key strings.
117
- keepNewest(keys, { provider: id, key: value, addedAt: "" });
118
- migrated = true;
59
+ const raw = JSON.parse(readFileSync(auth, "utf8"));
60
+ const keys = {};
61
+ const legacy = {};
62
+ const source = raw && typeof raw === "object" ? (raw.keys ?? {}) : {};
63
+ for (const [id, value] of Object.entries(source)) {
64
+ if (value && typeof value === "object" && typeof value.key === "string") {
65
+ const [idProvider, idModel] = id.split("::");
66
+ const provider = value.provider || idProvider || id;
67
+ const model = value.model || idModel || "";
68
+ keys[scopeId(provider, model)] = {
69
+ provider,
70
+ model,
71
+ key: value.key,
72
+ addedAt: value.addedAt ?? "",
73
+ };
74
+ }
75
+ else if (typeof value === "string" && value.trim()) {
76
+ legacy[id] = value;
77
+ }
119
78
  }
120
- }
121
- if (raw.legacy && typeof raw.legacy === "object") {
122
- for (const [provider, value] of Object.entries(raw.legacy)) {
123
- if (typeof value === "string" && value.trim()) {
124
- // Provider-only keys are exactly what v3 wants: migrate instead
125
- // of leaving them unusable.
126
- keepNewest(keys, { provider, key: value, addedAt: "" });
127
- migrated = true;
79
+ if (raw && typeof raw === "object" && raw.legacy && typeof raw.legacy === "object") {
80
+ for (const [id, value] of Object.entries(raw.legacy)) {
81
+ if (typeof value === "string" && value.trim())
82
+ legacy[id] = value;
128
83
  }
129
84
  }
85
+ return { version: 2, keys, legacy };
130
86
  }
131
- if (migrated && !migrationChecked) {
132
- try {
133
- // Keep the pre-migration file so a user with several old model
134
- // keys can recover a credential we had to collapse.
135
- const backup = auth + ".v2.bak";
136
- if (!existsSync(backup))
137
- copyFileSync(auth, backup);
138
- writeAuth({ version: AUTH_VERSION, keys, legacy });
139
- }
140
- catch {
141
- // Read-only home (or locked file): keep the migrated view in memory.
142
- }
87
+ catch {
88
+ return empty;
143
89
  }
144
- migrationChecked = true;
145
- return { version: AUTH_VERSION, keys, legacy };
146
90
  }
147
- function writeAuth(store) {
91
+ export function saveAuth(store) {
148
92
  const { auth } = getPaths();
149
- atomicWrite(auth, JSON.stringify({ version: AUTH_VERSION, keys: store.keys }, null, 2));
93
+ const payload = { version: 2, keys: store.keys };
94
+ if (Object.keys(store.legacy).length > 0)
95
+ payload.legacy = store.legacy;
96
+ atomicWrite(auth, JSON.stringify(payload, null, 2));
150
97
  lockdown(auth);
151
98
  }
152
- export function saveAuth(store) {
153
- writeAuth(store);
154
- }
155
- export function getCredential(providerId, _model) {
156
- return loadAuth().keys[providerId];
99
+ export function getCredential(providerId, model) {
100
+ return loadAuth().keys[scopeId(providerId, model)];
157
101
  }
158
102
  export function listCredentials() {
159
103
  return Object.values(loadAuth().keys);
@@ -164,7 +108,7 @@ export function listLegacy() {
164
108
  export function getLegacy(providerId) {
165
109
  return loadAuth().legacy[providerId];
166
110
  }
167
- /** Save/replace the provider-wide credential (drops any legacy key). */
111
+ /** Save/replace the credential for one provider+model scope (drops the legacy key). */
168
112
  export function setCredential(providerId, model, key) {
169
113
  const store = loadAuth();
170
114
  const credential = {
@@ -173,26 +117,29 @@ export function setCredential(providerId, model, key) {
173
117
  key: key.trim(),
174
118
  addedAt: new Date().toISOString(),
175
119
  };
176
- store.keys[providerId] = credential;
120
+ store.keys[scopeId(providerId, model)] = credential;
177
121
  delete store.legacy[providerId];
178
122
  saveAuth(store);
179
123
  return credential;
180
124
  }
181
- export function removeCredential(providerId, _model) {
125
+ export function removeCredential(providerId, model) {
182
126
  const store = loadAuth();
183
- if (!store.keys[providerId])
127
+ const id = scopeId(providerId, model);
128
+ if (!store.keys[id])
184
129
  return false;
185
- delete store.keys[providerId];
130
+ delete store.keys[id];
186
131
  saveAuth(store);
187
132
  return true;
188
133
  }
189
- /** Remove the provider-wide credential plus any legacy leftovers. */
134
+ /** Remove every credential for a provider (all models) plus its legacy key. */
190
135
  export function removeCredentialsForProvider(providerId) {
191
136
  const store = loadAuth();
192
137
  let removed = 0;
193
- if (store.keys[providerId]) {
194
- delete store.keys[providerId];
195
- removed += 1;
138
+ for (const id of Object.keys(store.keys)) {
139
+ if (store.keys[id].provider === providerId) {
140
+ delete store.keys[id];
141
+ removed += 1;
142
+ }
196
143
  }
197
144
  if (store.legacy[providerId]) {
198
145
  delete store.legacy[providerId];
@@ -212,16 +159,16 @@ export function maskKey(key) {
212
159
  return value.slice(0, 3) + "****" + value.slice(-4);
213
160
  }
214
161
  /**
215
- * Resolve the key to use for a provider:
216
- * 1. the provider-wide credential in auth.json,
162
+ * Resolve the key to use for a specific provider+model:
163
+ * 1. the scoped credential in auth.json (exact provider+model match), then
217
164
  * 2. the provider's environment variable (provider-scoped, any model), and
218
- * 3. a legacy provider-only key that somehow escaped migration.
165
+ * 3. report an "unbound" legacy key instead of using it silently.
219
166
  */
220
- export function resolveApiKey(providerId, _model, envVars) {
167
+ export function resolveApiKey(providerId, model, envVars) {
221
168
  const store = loadAuth();
222
- const credential = store.keys[providerId];
223
- if (credential && credential.key)
224
- return { status: "ok", key: credential.key, source: "file" };
169
+ const scoped = store.keys[scopeId(providerId, model)];
170
+ if (scoped && scoped.key)
171
+ return { status: "ok", key: scoped.key, source: "file" };
225
172
  for (const envVar of envVars ?? []) {
226
173
  const value = process.env[envVar];
227
174
  if (value && value.trim())
@@ -234,9 +181,10 @@ export function resolveApiKey(providerId, _model, envVars) {
234
181
  /** Best-effort key for a provider regardless of model (used to list models). */
235
182
  export function resolveProviderKey(providerId, envVars) {
236
183
  const store = loadAuth();
237
- const credential = store.keys[providerId];
238
- if (credential && credential.key)
239
- return { key: credential.key, source: "file" };
184
+ for (const credential of Object.values(store.keys)) {
185
+ if (credential.provider === providerId && credential.key)
186
+ return { key: credential.key, source: "file" };
187
+ }
240
188
  for (const envVar of envVars ?? []) {
241
189
  const value = process.env[envVar];
242
190
  if (value && value.trim())
@@ -1,22 +1,9 @@
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;
1
+ export declare function normalizeProjectPath(dir: any): string;
2
+ export declare function loadTrust(): {
3
+ trusted: any;
4
+ };
15
5
  export declare function trustAllEnabled(): boolean;
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
- }>;
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;
@@ -7,11 +7,10 @@
7
7
  *
8
8
  * FASTCODE_TRUST_ALL=1 marks every directory trusted (scripts, CI).
9
9
  */
10
- import { existsSync, readFileSync, realpathSync, statSync, writeFileSync } from "node:fs";
10
+ import { existsSync, readFileSync, realpathSync, 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;
15
14
  export function normalizeProjectPath(dir) {
16
15
  const absolute = resolve(dir);
17
16
  try {
@@ -21,104 +20,22 @@ export function normalizeProjectPath(dir) {
21
20
  return absolute;
22
21
  }
23
22
  }
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
23
  export function loadTrust() {
96
24
  const { trust } = getPaths();
97
- let key = "missing";
25
+ if (!existsSync(trust))
26
+ return { trusted: [] };
98
27
  try {
99
- const info = statSync(trust);
100
- key = `${info.mtimeMs}:${info.size}`;
28
+ const raw = JSON.parse(readFileSync(trust, "utf8"));
29
+ return { trusted: Array.isArray(raw.trusted) ? raw.trusted.filter((entry) => entry && typeof entry.path === "string") : [] };
101
30
  }
102
31
  catch {
103
- // Missing file: cache the empty store until it appears.
32
+ return { trusted: [] };
104
33
  }
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
34
  }
111
35
  function saveTrust(state) {
112
36
  const paths = getPaths();
113
37
  ensureDir(paths.root);
114
38
  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
39
  }
123
40
  export function trustAllEnabled() {
124
41
  return TRUTHY.includes((process.env["FASTCODE_TRUST_ALL"] ?? "").trim().toLowerCase());
@@ -127,14 +44,13 @@ export function isTrusted(dir) {
127
44
  if (trustAllEnabled())
128
45
  return true;
129
46
  const target = normalizeProjectPath(dir);
130
- return Boolean(loadTrust().paths[target]);
47
+ return loadTrust().trusted.some((entry) => entry.path === target);
131
48
  }
132
49
  export function trustProject(dir) {
133
50
  const target = normalizeProjectPath(dir);
134
51
  const state = loadTrust();
135
- if (!state.paths[target]) {
136
- const at = new Date().toISOString();
137
- state.paths[target] = { grantedAt: at, lastUsed: at };
52
+ if (!state.trusted.some((entry) => entry.path === target)) {
53
+ state.trusted.push({ path: target, addedAt: new Date().toISOString() });
138
54
  saveTrust(state);
139
55
  }
140
56
  return target;
@@ -142,12 +58,10 @@ export function trustProject(dir) {
142
58
  export function untrustProject(dir) {
143
59
  const target = normalizeProjectPath(dir);
144
60
  const state = loadTrust();
145
- if (!state.paths[target])
146
- return false;
147
- delete state.paths[target];
148
- saveTrust(state);
149
- return true;
61
+ const next = state.trusted.filter((entry) => entry.path !== target);
62
+ saveTrust({ trusted: next });
63
+ return state.trusted.length !== next.length;
150
64
  }
151
65
  export function listTrusted() {
152
- return Object.entries(loadTrust().paths).map(([path, entry]) => ({ path, addedAt: entry.grantedAt }));
66
+ return loadTrust().trusted;
153
67
  }
@@ -12,54 +12,29 @@ 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;
19
15
  providerId: any;
20
16
  model: any;
21
17
  keyPresent: boolean;
22
- keyStatus: "ok" | "missing" | "unbound";
23
- keyEnv: string;
18
+ keyStatus: "ok" | "unbound" | "missing";
19
+ keyEnv: any;
24
20
  skillsInstalled: number;
25
21
  pluginsInstalled: number;
26
22
  enabledSkills: any;
27
23
  enabledPlugins: any;
28
24
  sessionLogEnabled: boolean;
29
25
  sessionCacheMb: number;
30
- sessionFsync: "auto" | "off" | "on";
31
- unclosedSessions: number;
32
26
  sessionsDir: any;
33
27
  sessionsCount: number;
34
28
  sessionsBytes: number;
35
29
  trustFile: any;
36
- trusted: boolean;
30
+ trusted: any;
37
31
  shellDenyPatterns: number;
38
32
  allowDangerous: boolean;
39
33
  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
34
  providers: {
60
35
  id: string;
61
36
  label: string;
62
- env: string[];
37
+ env: any[] | string[];
63
38
  keyPresent: boolean;
64
39
  }[];
65
40
  };
@@ -5,8 +5,6 @@
5
5
  * diagnosed without opening the TUI: Node version, Termux detection, whether
6
6
  * stdout is a real terminal, the config root, and which provider keys exist.
7
7
  */
8
- import { existsSync, statSync } from "node:fs";
9
- import { totalmem, freemem, tmpdir as osTmpdir } from "node:os";
10
8
  import { detectPlatform, platformLabel } from "./platform.js";
11
9
  import { readPackageMeta } from "./pkg.js";
12
10
  import { shouldUseStableTerminalUi } from "./uiMode.js";
@@ -15,14 +13,10 @@ import { loadConfig, resolveApiKey, resolveProviderKey } from "../config/store.j
15
13
  import { PROVIDERS } from "../providers/index.js";
16
14
  import { listInstalledSkills } from "../skills/manager.js";
17
15
  import { listInstalledPlugins } from "../plugins/manager.js";
18
- import { findUnclosedSessions, fsyncMode, loggingEnabled, listSessions, sessionCacheLimitBytes } from "../session/log.js";
16
+ import { loggingEnabled, listSessions, sessionCacheLimitBytes } from "../session/log.js";
19
17
  import { isTrusted } from "../config/trust.js";
20
- import { DEFAULT_DENY_PATTERNS, loopExemptTools } from "../agent/guards.js";
18
+ import { DEFAULT_DENY_PATTERNS } from "../agent/guards.js";
21
19
  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
20
  const NODE_REQUIRED = "18.17";
27
21
  function readVersion() {
28
22
  return readPackageMeta().version;
@@ -61,16 +55,6 @@ export function collectDiagnostics() {
61
55
  // Same for plugins.
62
56
  }
63
57
  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
58
  return {
75
59
  version: readVersion(),
76
60
  node: process.version,
@@ -85,10 +69,6 @@ export function collectDiagnostics() {
85
69
  configRoot: paths?.root,
86
70
  configFile: paths?.config,
87
71
  authFile: paths?.auth,
88
- authMode,
89
- tmpdir: process.env["TMPDIR"] || osTmpdir(),
90
- memTotalMb: totalmem() / 1024 / 1024,
91
- memFreeMb: freemem() / 1024 / 1024,
92
72
  providerId: config.providerId,
93
73
  model: config.model,
94
74
  keyPresent,
@@ -100,8 +80,6 @@ export function collectDiagnostics() {
100
80
  enabledPlugins: (config.enabledPlugins ?? []).length,
101
81
  sessionLogEnabled: loggingEnabled(),
102
82
  sessionCacheMb: Math.round(sessionCacheLimitBytes() / (1024 * 1024)),
103
- sessionFsync: fsyncMode(),
104
- unclosedSessions: findUnclosedSessions().length,
105
83
  sessionsDir: paths?.sessions,
106
84
  sessionsCount: listSessions().length,
107
85
  sessionsBytes: listSessions().reduce((sum, entry) => sum + entry.bytes, 0),
@@ -110,25 +88,6 @@ export function collectDiagnostics() {
110
88
  shellDenyPatterns: DEFAULT_DENY_PATTERNS.length,
111
89
  allowDangerous: ["1", "true", "yes", "on"].includes((process.env["FASTCODE_ALLOW_DANGEROUS"] ?? "").trim().toLowerCase()),
112
90
  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
91
  providers: PROVIDERS.map((p) => ({
133
92
  id: p.id,
134
93
  label: p.label,
@@ -137,10 +96,6 @@ export function collectDiagnostics() {
137
96
  })),
138
97
  };
139
98
  }
140
- function positiveEnv(name) {
141
- const value = Number(process.env[name]);
142
- return Number.isFinite(value) && value > 0 ? value : 0;
143
- }
144
99
  export function formatDiagnostics(info) {
145
100
  const lines = [];
146
101
  lines.push(`FastCode ${info.version} — environment check`);
@@ -151,21 +106,12 @@ export function formatDiagnostics(info) {
151
106
  lines.push(` stable UI ${info.stableUi ? "on (anti-flicker)" : "off"}`);
152
107
  lines.push(" config root " + (info.configRoot ?? "(unavailable)"));
153
108
  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");
109
+ lines.push(" keys file " + (info.authFile ?? "(unavailable)") + " (separate, chmod 0600)");
157
110
  lines.push(" trusted cwd " + (info.trusted ? "yes" : "NO - agent tools ask first or stay off") + " (" + (info.trustFile ?? "?") + ")");
158
111
  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)"));
112
+ lines.push(" session dir " + (info.sessionsDir ?? "(unavailable)"));
160
113
  lines.push(" shell guard " + info.shellDenyPatterns + " deny patterns" + (info.allowDangerous ? " [FASTCODE_ALLOW_DANGEROUS is ON]" : ""));
161
114
  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
115
  lines.push(` provider ${info.providerId} · model ${info.model}`);
170
116
  let apiKeyStatus;
171
117
  if (info.keyPresent)
@@ -188,8 +134,6 @@ export function formatDiagnostics(info) {
188
134
  lines.push(" - pkg install nodejs git");
189
135
  lines.push(" - keep the session alive with: termux-wake-lock");
190
136
  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
137
  }
194
138
  return lines.join("\n");
195
139
  }
@@ -6,18 +6,8 @@
6
6
  * Anthropic's older streaming) handle their own parsing.
7
7
  */
8
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
9
  /** Async iterator over SSE events from a streaming HTTP response. */
19
- export declare function streamSSE(res: any, options?: StreamSseOptions): AsyncGenerator<{
10
+ export declare function streamSSE(res: any): AsyncGenerator<{
20
11
  event: any;
21
12
  data: string;
22
13
  }, void, unknown>;
23
- export declare function probeConnectivity(url: string, timeoutMs?: number): Promise<boolean>;