@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.
- package/README.md +6 -24
- package/dist/agent/guards.d.ts +0 -8
- package/dist/agent/guards.js +0 -195
- package/dist/agent/protocol.js +1 -4
- package/dist/agent/tools.d.ts +1 -3
- package/dist/agent/tools.js +377 -2
- package/dist/app.js +103 -335
- package/dist/cli.js +311 -362
- package/dist/commands/index.js +0 -22
- package/dist/commands/login.d.ts +2 -4
- package/dist/commands/login.js +10 -12
- package/dist/commands/logout.js +16 -8
- package/dist/commands/model.d.ts +0 -13
- package/dist/commands/model.js +22 -42
- package/dist/commands/provider.d.ts +0 -13
- package/dist/commands/provider.js +9 -20
- package/dist/commands/whoami.js +5 -6
- package/dist/config/store.d.ts +12 -32
- package/dist/config/store.js +65 -117
- package/dist/config/trust.d.ts +8 -21
- package/dist/config/trust.js +13 -99
- package/dist/lib/diagnostics.d.ts +4 -29
- package/dist/lib/diagnostics.js +4 -60
- package/dist/lib/http.d.ts +1 -11
- package/dist/lib/http.js +4 -62
- package/dist/providers/anthropic.d.ts +0 -1
- package/dist/providers/anthropic.js +0 -12
- package/dist/providers/custom.d.ts +0 -9
- package/dist/providers/deepseek.d.ts +0 -10
- package/dist/providers/deepseek.js +1 -2
- package/dist/providers/google.d.ts +0 -9
- package/dist/providers/groq.d.ts +0 -10
- package/dist/providers/groq.js +1 -2
- package/dist/providers/index.d.ts +201 -10
- package/dist/providers/index.js +0 -30
- package/dist/providers/mistral.d.ts +0 -10
- package/dist/providers/mistral.js +1 -2
- package/dist/providers/moonshot.d.ts +0 -10
- package/dist/providers/moonshot.js +1 -2
- package/dist/providers/ollama.d.ts +0 -9
- package/dist/providers/openai.d.ts +0 -10
- package/dist/providers/openai.js +1 -2
- package/dist/providers/openaiCompatible.d.ts +0 -9
- package/dist/providers/openaiCompatible.js +10 -58
- package/dist/providers/openrouter.d.ts +0 -10
- package/dist/providers/openrouter.js +1 -2
- package/dist/providers/qwen.d.ts +0 -10
- package/dist/providers/qwen.js +1 -2
- package/dist/providers/xai.d.ts +0 -10
- package/dist/providers/xai.js +1 -2
- package/dist/providers/zhipu.d.ts +0 -10
- package/dist/providers/zhipu.js +1 -2
- package/dist/session/log.d.ts +12 -35
- package/dist/session/log.js +10 -206
- package/dist/types.d.ts +1 -60
- package/dist/ui/Message.d.ts +7 -1
- package/dist/ui/Message.js +2 -13
- package/dist/ui/Prompt.d.ts +2 -1
- package/dist/ui/Prompt.js +2 -2
- package/dist/ui/Spinner.js +5 -11
- package/dist/ui/StatusBar.d.ts +1 -2
- package/dist/ui/StatusBar.js +5 -6
- package/package.json +1 -1
- package/src/agent/guards.ts +0 -196
- package/src/agent/protocol.ts +1 -4
- package/src/agent/tools.ts +377 -3
- package/src/app.ts +103 -335
- package/src/cli.ts +3 -30
- package/src/commands/index.ts +0 -22
- package/src/commands/login.ts +10 -12
- package/src/commands/logout.ts +16 -8
- package/src/commands/model.ts +22 -42
- package/src/commands/provider.ts +9 -20
- package/src/commands/whoami.ts +5 -6
- package/src/config/store.ts +69 -122
- package/src/config/trust.ts +21 -137
- package/src/lib/diagnostics.ts +4 -60
- package/src/lib/http.ts +4 -66
- package/src/providers/anthropic.ts +1 -13
- package/src/providers/deepseek.ts +1 -2
- package/src/providers/groq.ts +1 -2
- package/src/providers/index.ts +1 -32
- package/src/providers/mistral.ts +1 -2
- package/src/providers/moonshot.ts +1 -2
- package/src/providers/openai.ts +1 -2
- package/src/providers/openaiCompatible.ts +10 -58
- package/src/providers/openrouter.ts +1 -2
- package/src/providers/qwen.ts +1 -2
- package/src/providers/xai.ts +1 -2
- package/src/providers/zhipu.ts +1 -2
- package/src/session/log.ts +28 -267
- package/src/types.ts +1 -63
- package/src/ui/Message.ts +2 -13
- package/src/ui/Prompt.ts +2 -2
- package/src/ui/Spinner.ts +5 -11
- package/src/ui/StatusBar.ts +5 -6
- package/dist/agent/approval.d.ts +0 -50
- package/dist/agent/approval.js +0 -7
- package/dist/agent/compaction.d.ts +0 -39
- package/dist/agent/compaction.js +0 -227
- package/dist/agent/context.d.ts +0 -29
- package/dist/agent/context.js +0 -62
- package/dist/agent/ignore.d.ts +0 -11
- package/dist/agent/ignore.js +0 -129
- package/dist/agent/process-tree.d.ts +0 -2
- package/dist/agent/process-tree.js +0 -31
- package/dist/agent/protocol-mode.d.ts +0 -6
- package/dist/agent/protocol-mode.js +0 -51
- package/dist/agent/registry.d.ts +0 -17
- package/dist/agent/registry.js +0 -90
- package/dist/agent/runner.d.ts +0 -106
- package/dist/agent/runner.js +0 -352
- package/dist/agent/shell-session.d.ts +0 -37
- package/dist/agent/shell-session.js +0 -400
- package/dist/agent/stream.d.ts +0 -29
- package/dist/agent/stream.js +0 -103
- package/dist/agent/tool-schemas.d.ts +0 -2
- package/dist/agent/tool-schemas.js +0 -63
- package/dist/agent/tools/append_file.d.ts +0 -2
- package/dist/agent/tools/append_file.js +0 -26
- package/dist/agent/tools/apply_patch.d.ts +0 -10
- package/dist/agent/tools/apply_patch.js +0 -52
- package/dist/agent/tools/copy_file.d.ts +0 -2
- package/dist/agent/tools/copy_file.js +0 -30
- package/dist/agent/tools/delete_file.d.ts +0 -2
- package/dist/agent/tools/delete_file.js +0 -24
- package/dist/agent/tools/grep.d.ts +0 -2
- package/dist/agent/tools/grep.js +0 -23
- package/dist/agent/tools/index.d.ts +0 -2
- package/dist/agent/tools/index.js +0 -32
- package/dist/agent/tools/list_files.d.ts +0 -2
- package/dist/agent/tools/list_files.js +0 -17
- package/dist/agent/tools/make_dir.d.ts +0 -2
- package/dist/agent/tools/make_dir.js +0 -24
- package/dist/agent/tools/move_path.d.ts +0 -2
- package/dist/agent/tools/move_path.js +0 -47
- package/dist/agent/tools/paths.d.ts +0 -7
- package/dist/agent/tools/paths.js +0 -84
- package/dist/agent/tools/pwd.d.ts +0 -2
- package/dist/agent/tools/pwd.js +0 -10
- package/dist/agent/tools/read_file.d.ts +0 -2
- package/dist/agent/tools/read_file.js +0 -21
- package/dist/agent/tools/read_many.d.ts +0 -2
- package/dist/agent/tools/read_many.js +0 -25
- package/dist/agent/tools/readers.d.ts +0 -6
- package/dist/agent/tools/readers.js +0 -216
- package/dist/agent/tools/replace_in_file.d.ts +0 -2
- package/dist/agent/tools/replace_in_file.js +0 -30
- package/dist/agent/tools/shell.d.ts +0 -2
- package/dist/agent/tools/shell.js +0 -48
- package/dist/agent/tools/stat_path.d.ts +0 -2
- package/dist/agent/tools/stat_path.js +0 -31
- package/dist/agent/tools/types.d.ts +0 -39
- package/dist/agent/tools/types.js +0 -1
- package/dist/agent/tools/validate.d.ts +0 -6
- package/dist/agent/tools/validate.js +0 -29
- package/dist/agent/tools/write_file.d.ts +0 -2
- package/dist/agent/tools/write_file.js +0 -26
- package/dist/commands/attach.d.ts +0 -9
- package/dist/commands/attach.js +0 -15
- package/dist/commands/bg.d.ts +0 -7
- package/dist/commands/bg.js +0 -9
- package/dist/commands/cancel.d.ts +0 -10
- package/dist/commands/cancel.js +0 -10
- package/dist/commands/compact.d.ts +0 -10
- package/dist/commands/compact.js +0 -22
- package/dist/commands/context.d.ts +0 -7
- package/dist/commands/context.js +0 -24
- package/dist/commands/edit.d.ts +0 -10
- package/dist/commands/edit.js +0 -36
- package/dist/commands/effort.d.ts +0 -25
- package/dist/commands/effort.js +0 -48
- package/dist/commands/expand.d.ts +0 -10
- package/dist/commands/expand.js +0 -8
- package/dist/commands/jobs.d.ts +0 -7
- package/dist/commands/jobs.js +0 -16
- package/dist/commands/kill.d.ts +0 -7
- package/dist/commands/kill.js +0 -11
- package/dist/commands/steer.d.ts +0 -11
- package/dist/commands/steer.js +0 -13
- package/dist/lib/model-meta.d.ts +0 -19
- package/dist/lib/model-meta.js +0 -113
- package/dist/lib/native-tools.d.ts +0 -12
- package/dist/lib/native-tools.js +0 -21
- package/dist/lib/net.d.ts +0 -33
- package/dist/lib/net.js +0 -168
- package/dist/patch/apply.d.ts +0 -15
- package/dist/patch/apply.js +0 -172
- package/dist/patch/parse.d.ts +0 -29
- package/dist/patch/parse.js +0 -112
- package/dist/patch/plan.d.ts +0 -49
- package/dist/patch/plan.js +0 -273
- package/dist/session/projection.d.ts +0 -43
- package/dist/session/projection.js +0 -257
- package/dist/ui/Picker.d.ts +0 -8
- package/dist/ui/Picker.js +0 -24
- package/src/agent/approval.ts +0 -47
- package/src/agent/compaction.ts +0 -276
- package/src/agent/context.ts +0 -97
- package/src/agent/ignore.ts +0 -147
- package/src/agent/process-tree.ts +0 -32
- package/src/agent/protocol-mode.ts +0 -59
- package/src/agent/registry.ts +0 -98
- package/src/agent/runner.ts +0 -443
- package/src/agent/shell-session.ts +0 -449
- package/src/agent/stream.ts +0 -143
- package/src/agent/tool-schemas.ts +0 -67
- package/src/agent/tools/append_file.ts +0 -28
- package/src/agent/tools/apply_patch.ts +0 -54
- package/src/agent/tools/copy_file.ts +0 -32
- package/src/agent/tools/delete_file.ts +0 -26
- package/src/agent/tools/grep.ts +0 -25
- package/src/agent/tools/index.ts +0 -34
- package/src/agent/tools/list_files.ts +0 -19
- package/src/agent/tools/make_dir.ts +0 -26
- package/src/agent/tools/move_path.ts +0 -50
- package/src/agent/tools/paths.ts +0 -92
- package/src/agent/tools/pwd.ts +0 -12
- package/src/agent/tools/read_file.ts +0 -23
- package/src/agent/tools/read_many.ts +0 -27
- package/src/agent/tools/readers.ts +0 -228
- package/src/agent/tools/replace_in_file.ts +0 -32
- package/src/agent/tools/shell.ts +0 -53
- package/src/agent/tools/stat_path.ts +0 -33
- package/src/agent/tools/types.ts +0 -42
- package/src/agent/tools/validate.ts +0 -35
- package/src/agent/tools/write_file.ts +0 -28
- package/src/commands/attach.ts +0 -16
- package/src/commands/bg.ts +0 -9
- package/src/commands/cancel.ts +0 -10
- package/src/commands/compact.ts +0 -23
- package/src/commands/context.ts +0 -25
- package/src/commands/edit.ts +0 -37
- package/src/commands/effort.ts +0 -49
- package/src/commands/expand.ts +0 -8
- package/src/commands/jobs.ts +0 -17
- package/src/commands/kill.ts +0 -12
- package/src/commands/steer.ts +0 -13
- package/src/lib/model-meta.ts +0 -138
- package/src/lib/native-tools.ts +0 -32
- package/src/lib/net.ts +0 -204
- package/src/patch/apply.ts +0 -194
- package/src/patch/parse.ts +0 -131
- package/src/patch/plan.ts +0 -317
- package/src/session/projection.ts +0 -308
- package/src/ui/Picker.ts +0 -24
package/src/cli.ts
CHANGED
|
@@ -12,9 +12,7 @@ import { loadConfig, saveConfig, resolveProviderKey } from "./config/store.js";
|
|
|
12
12
|
import { getPaths } from "./config/paths.js";
|
|
13
13
|
import { PROVIDERS, getProvider } from "./providers/index.js";
|
|
14
14
|
import { collectDiagnostics, formatDiagnostics } from "./lib/diagnostics.js";
|
|
15
|
-
import {
|
|
16
|
-
import { latestSessionFile, listSessions, projectionPath, readSession, resolveSessionFile } from "./session/log.js";
|
|
17
|
-
import { readProjection, sessionMetaFromFile } from "./session/projection.js";
|
|
15
|
+
import { latestSessionFile, listSessions, readSession, resolveSessionFile } from "./session/log.js";
|
|
18
16
|
import { listTrusted, trustProject, untrustProject } from "./config/trust.js";
|
|
19
17
|
const NL = String.fromCharCode(10);
|
|
20
18
|
const HELP = [
|
|
@@ -158,26 +156,13 @@ export async function parseCli(): Promise<any> {
|
|
|
158
156
|
return { done: true };
|
|
159
157
|
}
|
|
160
158
|
if (rest[0] === "show") {
|
|
161
|
-
const
|
|
162
|
-
const entries = readSession(idOrFile);
|
|
159
|
+
const entries = readSession(rest[1] ?? all[0].id);
|
|
163
160
|
for (const entry of entries) {
|
|
164
161
|
if (entry.type === "message")
|
|
165
162
|
console.log("[" + entry.role + "] " + entry.content);
|
|
166
|
-
else if (entry.type === "user" || entry.type === "assistant_raw")
|
|
167
|
-
console.log("[" + entry.type + "] " + String(entry.content ?? "").slice(0, 300));
|
|
168
163
|
else
|
|
169
164
|
console.log("[" + entry.type + "]");
|
|
170
165
|
}
|
|
171
|
-
const file = resolveSessionFile(idOrFile);
|
|
172
|
-
const projection = readProjection(file);
|
|
173
|
-
console.log("");
|
|
174
|
-
if (projection) {
|
|
175
|
-
console.log("[projection] " + projectionPath(file));
|
|
176
|
-
console.log(" messages: " + projection.messages.length + " · activeFiles: " + projection.activeFiles.length + " · lastFactSeq: " + projection.lastFactSeq);
|
|
177
|
-
}
|
|
178
|
-
else {
|
|
179
|
-
console.log("[projection] (none)");
|
|
180
|
-
}
|
|
181
166
|
return { done: true };
|
|
182
167
|
}
|
|
183
168
|
console.log("Sessions (newest first):");
|
|
@@ -192,22 +177,10 @@ export async function parseCli(): Promise<any> {
|
|
|
192
177
|
if (first === "resume") {
|
|
193
178
|
const target = rest[0];
|
|
194
179
|
const file = !target || target === "latest" ? latestSessionFile() : resolveSessionFile(target);
|
|
195
|
-
if (!file
|
|
180
|
+
if (!file) {
|
|
196
181
|
console.error("No saved session to resume. Start one with: fastcode");
|
|
197
182
|
process.exit(1);
|
|
198
183
|
}
|
|
199
|
-
const meta = sessionMetaFromFile(file);
|
|
200
|
-
if (meta.cwd) {
|
|
201
|
-
if (existsSync(meta.cwd)) {
|
|
202
|
-
process.chdir(meta.cwd);
|
|
203
|
-
console.log("Working directory: " + meta.cwd);
|
|
204
|
-
}
|
|
205
|
-
else {
|
|
206
|
-
console.log("Session directory no longer exists: " + meta.cwd + " (staying in " + process.cwd() + ")");
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
if (meta.providerId)
|
|
210
|
-
console.log("Session model: " + meta.providerId + "::" + meta.model);
|
|
211
184
|
console.log("Resuming " + file);
|
|
212
185
|
return { done: false, resume: file };
|
|
213
186
|
}
|
package/src/commands/index.ts
CHANGED
|
@@ -7,7 +7,6 @@ import { clearCommand } from "./clear.js";
|
|
|
7
7
|
import { exitCommand } from "./exit.js";
|
|
8
8
|
import { modelCommand } from "./model.js";
|
|
9
9
|
import { providerCommand } from "./provider.js";
|
|
10
|
-
import { effortCommand } from "./effort.js";
|
|
11
10
|
import { loginCommand } from "./login.js";
|
|
12
11
|
import { logoutCommand } from "./logout.js";
|
|
13
12
|
import { skillsCommand } from "./skills.js";
|
|
@@ -18,16 +17,6 @@ import { systemCommand } from "./system.js";
|
|
|
18
17
|
import { aboutCommand } from "./about.js";
|
|
19
18
|
import { configCommand } from "./config.js";
|
|
20
19
|
import { whoamiCommand } from "./whoami.js";
|
|
21
|
-
import { contextCommand } from "./context.js";
|
|
22
|
-
import { jobsCommand } from "./jobs.js";
|
|
23
|
-
import { killCommand } from "./kill.js";
|
|
24
|
-
import { cancelCommand } from "./cancel.js";
|
|
25
|
-
import { steerCommand } from "./steer.js";
|
|
26
|
-
import { compactCommand } from "./compact.js";
|
|
27
|
-
import { expandCommand } from "./expand.js";
|
|
28
|
-
import { attachCommand } from "./attach.js";
|
|
29
|
-
import { bgCommand } from "./bg.js";
|
|
30
|
-
import { editCommand } from "./edit.js";
|
|
31
20
|
export const COMMANDS = [
|
|
32
21
|
helpCommand,
|
|
33
22
|
agentCommand,
|
|
@@ -38,7 +27,6 @@ export const COMMANDS = [
|
|
|
38
27
|
exitCommand,
|
|
39
28
|
modelCommand,
|
|
40
29
|
providerCommand,
|
|
41
|
-
effortCommand,
|
|
42
30
|
loginCommand,
|
|
43
31
|
logoutCommand,
|
|
44
32
|
skillsCommand,
|
|
@@ -49,16 +37,6 @@ export const COMMANDS = [
|
|
|
49
37
|
aboutCommand,
|
|
50
38
|
configCommand,
|
|
51
39
|
whoamiCommand,
|
|
52
|
-
contextCommand,
|
|
53
|
-
jobsCommand,
|
|
54
|
-
killCommand,
|
|
55
|
-
cancelCommand,
|
|
56
|
-
steerCommand,
|
|
57
|
-
compactCommand,
|
|
58
|
-
expandCommand,
|
|
59
|
-
attachCommand,
|
|
60
|
-
bgCommand,
|
|
61
|
-
editCommand,
|
|
62
40
|
];
|
|
63
41
|
export function findCommand(name) {
|
|
64
42
|
const lower = name.toLowerCase();
|
package/src/commands/login.ts
CHANGED
|
@@ -2,10 +2,8 @@ import { getProvider, PROVIDERS } from "../providers/index.js";
|
|
|
2
2
|
import { maskKey, setCredential } from "../config/store.js";
|
|
3
3
|
import { getPaths } from "../config/paths.js";
|
|
4
4
|
/**
|
|
5
|
-
* Save a provider
|
|
6
|
-
*
|
|
7
|
-
* credential's origin — the key itself is reused by every model of that
|
|
8
|
-
* provider. Shared by the `/login` command and the guided flow in the app.
|
|
5
|
+
* Save a key scoped to one provider + model and describe it for the terminal.
|
|
6
|
+
* Shared by the `/login` command and the guided flow in the app.
|
|
9
7
|
*/
|
|
10
8
|
export function saveScopedKey(providerId, providerLabel, model, key) {
|
|
11
9
|
const trimmed = key.trim();
|
|
@@ -14,16 +12,16 @@ export function saveScopedKey(providerId, providerLabel, model, key) {
|
|
|
14
12
|
return [
|
|
15
13
|
"Saved API key",
|
|
16
14
|
` provider ${providerLabel} (${providerId})`,
|
|
17
|
-
`
|
|
15
|
+
` model ${model}`,
|
|
18
16
|
` key ${maskKey(trimmed)}`,
|
|
19
17
|
` stored ${paths.auth} (mode 0600)`,
|
|
20
18
|
"",
|
|
21
|
-
`Remove with /logout ${providerId}`,
|
|
19
|
+
`Remove with /logout ${providerId} ${model}`,
|
|
22
20
|
].join("\n");
|
|
23
21
|
}
|
|
24
22
|
export const loginCommand = {
|
|
25
23
|
name: "login",
|
|
26
|
-
description: "Bind a provider
|
|
24
|
+
description: "Bind an API key to a provider + model. Usage: /login [provider] [model] [key]",
|
|
27
25
|
run({ args, state, actions }) {
|
|
28
26
|
if (args.length === 0) {
|
|
29
27
|
actions.startLogin({});
|
|
@@ -38,12 +36,12 @@ export const loginCommand = {
|
|
|
38
36
|
}
|
|
39
37
|
if (args.length === 1) {
|
|
40
38
|
actions.startLogin({ provider: provider.id });
|
|
41
|
-
return { message: `Provider: ${provider.label} (${provider.id}).\
|
|
39
|
+
return { message: `Provider: ${provider.label} (${provider.id}).\nNow pick the model this key is for.` };
|
|
42
40
|
}
|
|
43
41
|
const model = args[1];
|
|
44
42
|
if (args.length === 2) {
|
|
45
43
|
actions.startLogin({ provider: provider.id, model });
|
|
46
|
-
return { message: `Provider: ${provider.id} ·
|
|
44
|
+
return { message: `Provider: ${provider.id} · model: ${model}.\nNow paste the API key (input is masked).` };
|
|
47
45
|
}
|
|
48
46
|
const key = args.slice(2).join(" ");
|
|
49
47
|
return { message: saveScopedKey(provider.id, provider.label, model, key) };
|
|
@@ -52,11 +50,11 @@ export const loginCommand = {
|
|
|
52
50
|
function introText(state) {
|
|
53
51
|
const env = getProvider(state.providerId)?.authEnv?.[0] ?? "PROVIDER_API_KEY";
|
|
54
52
|
return [
|
|
55
|
-
"Bind
|
|
53
|
+
"Bind an API key to a specific provider + model.",
|
|
56
54
|
"",
|
|
57
|
-
` current
|
|
55
|
+
` current scope: ${state.providerId} · ${state.model}`,
|
|
58
56
|
"",
|
|
59
|
-
"FastCode will ask for the provider, then
|
|
57
|
+
"FastCode will ask for the provider, then the model, then the key.",
|
|
60
58
|
" one-liner: /login <provider> <model> <key>",
|
|
61
59
|
` or set: export ${env}=<key> (provider-wide, any model)`,
|
|
62
60
|
].join("\n");
|
package/src/commands/logout.ts
CHANGED
|
@@ -8,11 +8,11 @@ export const logoutCommand = {
|
|
|
8
8
|
const credentials = listCredentials();
|
|
9
9
|
if (credentials.length === 0)
|
|
10
10
|
return { message: "No stored keys. Use /login to add one." };
|
|
11
|
-
const lines = ["Stored keys (provider
|
|
11
|
+
const lines = ["Stored keys (provider :: model):"];
|
|
12
12
|
for (const c of credentials)
|
|
13
|
-
lines.push(` ${c.provider} ${maskKey(c.key)}`);
|
|
13
|
+
lines.push(` ${c.provider}::${c.model} ${maskKey(c.key)}`);
|
|
14
14
|
lines.push("");
|
|
15
|
-
lines.push("Remove: /logout <provider>");
|
|
15
|
+
lines.push("Remove: /logout <provider> <model>");
|
|
16
16
|
lines.push(" /logout <provider> --all");
|
|
17
17
|
return { message: lines.join("\n") };
|
|
18
18
|
}
|
|
@@ -29,17 +29,25 @@ export const logoutCommand = {
|
|
|
29
29
|
};
|
|
30
30
|
}
|
|
31
31
|
if (args.length >= 2) {
|
|
32
|
-
const
|
|
32
|
+
const model = args[1];
|
|
33
|
+
const ok = removeCredential(provider.id, model);
|
|
33
34
|
return {
|
|
34
35
|
message: ok
|
|
35
|
-
? `Removed
|
|
36
|
-
: `No stored key for ${provider.id}.`,
|
|
36
|
+
? `Removed stored key for ${provider.id}::${model}.`
|
|
37
|
+
: `No stored key for ${provider.id}::${model}.`,
|
|
37
38
|
};
|
|
38
39
|
}
|
|
39
40
|
const forProvider = listCredentials().filter((c) => c.provider === provider.id);
|
|
40
41
|
if (forProvider.length === 0)
|
|
41
42
|
return { message: `No stored key for ${provider.label}.` };
|
|
42
|
-
|
|
43
|
-
|
|
43
|
+
if (forProvider.length === 1) {
|
|
44
|
+
removeCredential(forProvider[0].provider, forProvider[0].model);
|
|
45
|
+
return { message: `Removed stored key for ${forProvider[0].provider}::${forProvider[0].model}.` };
|
|
46
|
+
}
|
|
47
|
+
const lines = [`${provider.label} has ${forProvider.length} stored keys — pick one to remove:`];
|
|
48
|
+
for (const c of forProvider)
|
|
49
|
+
lines.push(` /logout ${c.provider} ${c.model} (${maskKey(c.key)})`);
|
|
50
|
+
lines.push(` /logout ${provider.id} --all (remove all)`);
|
|
51
|
+
return { message: lines.join("\n") };
|
|
44
52
|
},
|
|
45
53
|
};
|
package/src/commands/model.ts
CHANGED
|
@@ -1,50 +1,30 @@
|
|
|
1
|
-
import { getProviderOrThrow
|
|
1
|
+
import { getProviderOrThrow } from "../providers/index.js";
|
|
2
2
|
import { resolveProviderKey } from "../config/store.js";
|
|
3
3
|
export const modelCommand = {
|
|
4
4
|
name: "model",
|
|
5
|
-
description: "
|
|
5
|
+
description: "List models for the current provider, or switch model. Usage: /model [id]",
|
|
6
6
|
async run({ args, state, actions }) {
|
|
7
7
|
const provider = getProviderOrThrow(state.providerId);
|
|
8
|
-
if (args.length
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
if (args.length === 0) {
|
|
9
|
+
const { key } = resolveProviderKey(provider.id, provider.authEnv);
|
|
10
|
+
const models = await provider.listModels(key);
|
|
11
|
+
if (models.length === 0) {
|
|
12
|
+
return { message: `No models available from ${provider.label}.` };
|
|
13
|
+
}
|
|
14
|
+
const lines = [`Models for ${provider.label}:`];
|
|
15
|
+
for (const m of models.slice(0, 50)) {
|
|
16
|
+
const marker = m.id === state.model ? "● " : " ";
|
|
17
|
+
const label = m.label ? ` — ${m.label}` : "";
|
|
18
|
+
lines.push(`${marker}${m.id}${label}`);
|
|
19
|
+
}
|
|
20
|
+
if (models.length > 50)
|
|
21
|
+
lines.push(` … and ${models.length - 50} more.`);
|
|
22
|
+
lines.push("");
|
|
23
|
+
lines.push("Switch with `/model <id>`.");
|
|
24
|
+
return { message: lines.join("\n") };
|
|
12
25
|
}
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
if (models.length === 0) {
|
|
17
|
-
return { message: `No models available from ${provider.label}.` };
|
|
18
|
-
}
|
|
19
|
-
const online = await probeProvider(provider, key);
|
|
20
|
-
const list = models.slice(0, 50);
|
|
21
|
-
return {
|
|
22
|
-
message: `Models for ${provider.label}: ${list.length}${online ? "" : " (offline — showing the built-in list)"}.\n` +
|
|
23
|
-
"Use ↑/↓, a number, or Enter to select.",
|
|
24
|
-
picker: {
|
|
25
|
-
title: `Select model · ${provider.label}${online ? "" : " (offline)"}`,
|
|
26
|
-
current: state.model,
|
|
27
|
-
items: list.map((m) => ({
|
|
28
|
-
value: m.id,
|
|
29
|
-
label: m.id,
|
|
30
|
-
hint: m.label ? `— ${m.label}` : undefined,
|
|
31
|
-
})),
|
|
32
|
-
onSelect: (id) => {
|
|
33
|
-
actions.setModel(id);
|
|
34
|
-
actions.pushSystemMessage(`Model set to ${id}.`);
|
|
35
|
-
},
|
|
36
|
-
},
|
|
37
|
-
};
|
|
26
|
+
const id = args.join(" ");
|
|
27
|
+
actions.setModel(id);
|
|
28
|
+
return { message: `Model set to ${id}.` };
|
|
38
29
|
},
|
|
39
30
|
};
|
|
40
|
-
function dedupe(models) {
|
|
41
|
-
const seen = new Set();
|
|
42
|
-
const out = [];
|
|
43
|
-
for (const model of models) {
|
|
44
|
-
if (!model?.id || seen.has(model.id))
|
|
45
|
-
continue;
|
|
46
|
-
seen.add(model.id);
|
|
47
|
-
out.push(model);
|
|
48
|
-
}
|
|
49
|
-
return out;
|
|
50
|
-
}
|
package/src/commands/provider.ts
CHANGED
|
@@ -4,26 +4,15 @@ export const providerCommand = {
|
|
|
4
4
|
description: "List or switch the active LLM provider. Usage: /provider [id]",
|
|
5
5
|
run({ args, actions, state }) {
|
|
6
6
|
if (args.length === 0) {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
})),
|
|
17
|
-
onSelect: (id) => {
|
|
18
|
-
const next = getProvider(id);
|
|
19
|
-
if (!next)
|
|
20
|
-
return;
|
|
21
|
-
actions.setProvider(next.id);
|
|
22
|
-
actions.setModel(next.defaultModel);
|
|
23
|
-
actions.pushSystemMessage(`Provider set to ${next.label}. Default model: ${next.defaultModel}.`);
|
|
24
|
-
},
|
|
25
|
-
},
|
|
26
|
-
};
|
|
7
|
+
const lines = ["Available providers:"];
|
|
8
|
+
for (const p of PROVIDERS) {
|
|
9
|
+
const marker = p.id === state.providerId ? "● " : " ";
|
|
10
|
+
const auth = p.authEnv.length === 0 ? "(no key needed)" : `[${p.authEnv.join(", ")}]`;
|
|
11
|
+
lines.push(`${marker}${p.id.padEnd(12)} ${p.label} ${auth}`);
|
|
12
|
+
}
|
|
13
|
+
lines.push("");
|
|
14
|
+
lines.push("Switch with `/provider <id>` (e.g. `/provider openrouter`).");
|
|
15
|
+
return { message: lines.join("\n") };
|
|
27
16
|
}
|
|
28
17
|
const target = args[0];
|
|
29
18
|
const next = getProvider(target);
|
package/src/commands/whoami.ts
CHANGED
|
@@ -10,20 +10,19 @@ export const whoamiCommand = {
|
|
|
10
10
|
lines.push(" " + describeLookup(current, resolveApiKey(current.id, state.model, current.authEnv)));
|
|
11
11
|
const credentials = listCredentials();
|
|
12
12
|
lines.push("");
|
|
13
|
-
lines.push(`Stored keys (${credentials.length}
|
|
13
|
+
lines.push(`Stored keys (${credentials.length}):`);
|
|
14
14
|
if (credentials.length === 0)
|
|
15
15
|
lines.push(" (none) — add one with /login");
|
|
16
16
|
for (const c of credentials) {
|
|
17
|
-
const marker = c.provider === state.providerId ? "● " : " ";
|
|
18
|
-
|
|
19
|
-
lines.push(`${marker}${c.provider} ${maskKey(c.key)}${origin}`);
|
|
17
|
+
const marker = c.provider === state.providerId && c.model === state.model ? "● " : " ";
|
|
18
|
+
lines.push(`${marker}${c.provider}::${c.model} ${maskKey(c.key)}`);
|
|
20
19
|
}
|
|
21
20
|
const legacy = listLegacy();
|
|
22
21
|
if (legacy.length > 0) {
|
|
23
22
|
lines.push("");
|
|
24
|
-
lines.push("
|
|
23
|
+
lines.push("Unbound legacy keys (NOT used — re-bind them to a model):");
|
|
25
24
|
for (const l of legacy)
|
|
26
|
-
lines.push(` ${l.provider} ${maskKey(l.key)}
|
|
25
|
+
lines.push(` ${l.provider} ${maskKey(l.key)} → /login ${l.provider} <model> <key>`);
|
|
27
26
|
}
|
|
28
27
|
const envLines = [];
|
|
29
28
|
for (const p of PROVIDERS) {
|
package/src/config/store.ts
CHANGED
|
@@ -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
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
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
|
|
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,40 +29,20 @@ 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
33
|
export interface StoredCredential {
|
|
50
34
|
provider: string;
|
|
51
|
-
|
|
52
|
-
model?: string;
|
|
35
|
+
model: string;
|
|
53
36
|
key: string;
|
|
54
37
|
addedAt: string;
|
|
55
38
|
}
|
|
56
39
|
export interface AuthStore {
|
|
57
40
|
version: number;
|
|
58
|
-
/**
|
|
41
|
+
/** Scoped credentials, keyed by `provider::model`. */
|
|
59
42
|
keys: Record<string, StoredCredential>;
|
|
60
|
-
/** Old provider-only keys
|
|
43
|
+
/** Old provider-only keys we refuse to use until the user re-binds them. */
|
|
61
44
|
legacy: Record<string, string>;
|
|
62
45
|
}
|
|
63
|
-
export const AUTH_VERSION = 3;
|
|
64
46
|
export type KeyLookup = {
|
|
65
47
|
status: "ok";
|
|
66
48
|
key: string;
|
|
@@ -89,97 +71,58 @@ export function saveConfig(cfg) {
|
|
|
89
71
|
const { config } = getPaths();
|
|
90
72
|
atomicWrite(config, JSON.stringify(cfg, null, 2));
|
|
91
73
|
}
|
|
92
|
-
/** Composite id
|
|
74
|
+
/** Composite scope id for a provider + model credential. */
|
|
93
75
|
export function scopeId(providerId, model) {
|
|
94
76
|
return `${providerId}::${model}`;
|
|
95
77
|
}
|
|
96
|
-
/** Pick the most recently saved credential when several legacy scopes map to one provider. */
|
|
97
|
-
function keepNewest(keys, credential) {
|
|
98
|
-
const existing = keys[credential.provider];
|
|
99
|
-
if (!existing) {
|
|
100
|
-
keys[credential.provider] = credential;
|
|
101
|
-
return;
|
|
102
|
-
}
|
|
103
|
-
const previous = Date.parse(existing.addedAt || "") || 0;
|
|
104
|
-
const next = Date.parse(credential.addedAt || "") || 0;
|
|
105
|
-
if (next > previous)
|
|
106
|
-
keys[credential.provider] = credential;
|
|
107
|
-
}
|
|
108
|
-
let migrationChecked = false;
|
|
109
78
|
export function loadAuth(): AuthStore {
|
|
110
79
|
const { auth } = getPaths();
|
|
111
|
-
const empty: AuthStore = { version:
|
|
80
|
+
const empty: AuthStore = { version: 2, keys: {}, legacy: {} };
|
|
112
81
|
if (!existsSync(auth))
|
|
113
82
|
return empty;
|
|
114
|
-
let raw: any;
|
|
115
83
|
try {
|
|
116
|
-
raw = JSON.parse(readFileSync(auth, "utf8"));
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
if (
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
provider,
|
|
136
|
-
model,
|
|
137
|
-
key: value.key,
|
|
138
|
-
addedAt: typeof value.addedAt === "string" ? value.addedAt : "",
|
|
139
|
-
});
|
|
140
|
-
}
|
|
141
|
-
else if (typeof value === "string" && value.trim()) {
|
|
142
|
-
// v1: keys were plain provider -> key strings.
|
|
143
|
-
keepNewest(keys, { provider: id, key: value, addedAt: "" });
|
|
144
|
-
migrated = true;
|
|
84
|
+
const raw = JSON.parse(readFileSync(auth, "utf8"));
|
|
85
|
+
const keys: Record<string, StoredCredential> = {};
|
|
86
|
+
const legacy: Record<string, string> = {};
|
|
87
|
+
const source = raw && typeof raw === "object" ? (raw.keys ?? {}) : {};
|
|
88
|
+
for (const [id, value] of Object.entries<any>(source)) {
|
|
89
|
+
if (value && typeof value === "object" && typeof value.key === "string") {
|
|
90
|
+
const [idProvider, idModel] = id.split("::");
|
|
91
|
+
const provider = value.provider || idProvider || id;
|
|
92
|
+
const model = value.model || idModel || "";
|
|
93
|
+
keys[scopeId(provider, model)] = {
|
|
94
|
+
provider,
|
|
95
|
+
model,
|
|
96
|
+
key: value.key,
|
|
97
|
+
addedAt: value.addedAt ?? "",
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
else if (typeof value === "string" && value.trim()) {
|
|
101
|
+
legacy[id] = value;
|
|
102
|
+
}
|
|
145
103
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
// Provider-only keys are exactly what v3 wants: migrate instead
|
|
151
|
-
// of leaving them unusable.
|
|
152
|
-
keepNewest(keys, { provider, key: value, addedAt: "" });
|
|
153
|
-
migrated = true;
|
|
104
|
+
if (raw && typeof raw === "object" && raw.legacy && typeof raw.legacy === "object") {
|
|
105
|
+
for (const [id, value] of Object.entries<any>(raw.legacy)) {
|
|
106
|
+
if (typeof value === "string" && value.trim())
|
|
107
|
+
legacy[id] = value;
|
|
154
108
|
}
|
|
155
109
|
}
|
|
110
|
+
return { version: 2, keys, legacy };
|
|
156
111
|
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
// Keep the pre-migration file so a user with several old model
|
|
160
|
-
// keys can recover a credential we had to collapse.
|
|
161
|
-
const backup = auth + ".v2.bak";
|
|
162
|
-
if (!existsSync(backup))
|
|
163
|
-
copyFileSync(auth, backup);
|
|
164
|
-
writeAuth({ version: AUTH_VERSION, keys, legacy });
|
|
165
|
-
}
|
|
166
|
-
catch {
|
|
167
|
-
// Read-only home (or locked file): keep the migrated view in memory.
|
|
168
|
-
}
|
|
112
|
+
catch {
|
|
113
|
+
return empty;
|
|
169
114
|
}
|
|
170
|
-
migrationChecked = true;
|
|
171
|
-
return { version: AUTH_VERSION, keys, legacy };
|
|
172
115
|
}
|
|
173
|
-
function
|
|
116
|
+
export function saveAuth(store: AuthStore) {
|
|
174
117
|
const { auth } = getPaths();
|
|
175
|
-
|
|
118
|
+
const payload: any = { version: 2, keys: store.keys };
|
|
119
|
+
if (Object.keys(store.legacy).length > 0)
|
|
120
|
+
payload.legacy = store.legacy;
|
|
121
|
+
atomicWrite(auth, JSON.stringify(payload, null, 2));
|
|
176
122
|
lockdown(auth);
|
|
177
123
|
}
|
|
178
|
-
export function
|
|
179
|
-
|
|
180
|
-
}
|
|
181
|
-
export function getCredential(providerId, _model?): StoredCredential | undefined {
|
|
182
|
-
return loadAuth().keys[providerId];
|
|
124
|
+
export function getCredential(providerId, model): StoredCredential | undefined {
|
|
125
|
+
return loadAuth().keys[scopeId(providerId, model)];
|
|
183
126
|
}
|
|
184
127
|
export function listCredentials(): StoredCredential[] {
|
|
185
128
|
return Object.values(loadAuth().keys);
|
|
@@ -193,35 +136,38 @@ export function listLegacy(): Array<{
|
|
|
193
136
|
export function getLegacy(providerId): string | undefined {
|
|
194
137
|
return loadAuth().legacy[providerId];
|
|
195
138
|
}
|
|
196
|
-
/** Save/replace the provider
|
|
139
|
+
/** Save/replace the credential for one provider+model scope (drops the legacy key). */
|
|
197
140
|
export function setCredential(providerId, model, key): StoredCredential {
|
|
198
141
|
const store = loadAuth();
|
|
199
|
-
const credential
|
|
142
|
+
const credential = {
|
|
200
143
|
provider: providerId,
|
|
201
144
|
model,
|
|
202
145
|
key: key.trim(),
|
|
203
146
|
addedAt: new Date().toISOString(),
|
|
204
147
|
};
|
|
205
|
-
store.keys[providerId] = credential;
|
|
148
|
+
store.keys[scopeId(providerId, model)] = credential;
|
|
206
149
|
delete store.legacy[providerId];
|
|
207
150
|
saveAuth(store);
|
|
208
151
|
return credential;
|
|
209
152
|
}
|
|
210
|
-
export function removeCredential(providerId,
|
|
153
|
+
export function removeCredential(providerId, model): boolean {
|
|
211
154
|
const store = loadAuth();
|
|
212
|
-
|
|
155
|
+
const id = scopeId(providerId, model);
|
|
156
|
+
if (!store.keys[id])
|
|
213
157
|
return false;
|
|
214
|
-
delete store.keys[
|
|
158
|
+
delete store.keys[id];
|
|
215
159
|
saveAuth(store);
|
|
216
160
|
return true;
|
|
217
161
|
}
|
|
218
|
-
/** Remove
|
|
162
|
+
/** Remove every credential for a provider (all models) plus its legacy key. */
|
|
219
163
|
export function removeCredentialsForProvider(providerId): number {
|
|
220
164
|
const store = loadAuth();
|
|
221
165
|
let removed = 0;
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
166
|
+
for (const id of Object.keys(store.keys)) {
|
|
167
|
+
if (store.keys[id].provider === providerId) {
|
|
168
|
+
delete store.keys[id];
|
|
169
|
+
removed += 1;
|
|
170
|
+
}
|
|
225
171
|
}
|
|
226
172
|
if (store.legacy[providerId]) {
|
|
227
173
|
delete store.legacy[providerId];
|
|
@@ -241,16 +187,16 @@ export function maskKey(key) {
|
|
|
241
187
|
return value.slice(0, 3) + "****" + value.slice(-4);
|
|
242
188
|
}
|
|
243
189
|
/**
|
|
244
|
-
* Resolve the key to use for a provider:
|
|
245
|
-
* 1. the
|
|
190
|
+
* Resolve the key to use for a specific provider+model:
|
|
191
|
+
* 1. the scoped credential in auth.json (exact provider+model match), then
|
|
246
192
|
* 2. the provider's environment variable (provider-scoped, any model), and
|
|
247
|
-
* 3.
|
|
193
|
+
* 3. report an "unbound" legacy key instead of using it silently.
|
|
248
194
|
*/
|
|
249
|
-
export function resolveApiKey(providerId,
|
|
195
|
+
export function resolveApiKey(providerId, model, envVars): KeyLookup {
|
|
250
196
|
const store = loadAuth();
|
|
251
|
-
const
|
|
252
|
-
if (
|
|
253
|
-
return { status: "ok", key:
|
|
197
|
+
const scoped = store.keys[scopeId(providerId, model)];
|
|
198
|
+
if (scoped && scoped.key)
|
|
199
|
+
return { status: "ok", key: scoped.key, source: "file" };
|
|
254
200
|
for (const envVar of envVars ?? []) {
|
|
255
201
|
const value = process.env[envVar];
|
|
256
202
|
if (value && value.trim())
|
|
@@ -267,9 +213,10 @@ export function resolveProviderKey(providerId, envVars): {
|
|
|
267
213
|
envVar?: string;
|
|
268
214
|
} {
|
|
269
215
|
const store = loadAuth();
|
|
270
|
-
const credential
|
|
271
|
-
|
|
272
|
-
|
|
216
|
+
for (const credential of Object.values(store.keys)) {
|
|
217
|
+
if (credential.provider === providerId && credential.key)
|
|
218
|
+
return { key: credential.key, source: "file" };
|
|
219
|
+
}
|
|
273
220
|
for (const envVar of envVars ?? []) {
|
|
274
221
|
const value = process.env[envVar];
|
|
275
222
|
if (value && value.trim())
|