llm-switch 0.1.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.
- package/LICENSE +21 -0
- package/bin/llm-switch.js +2 -0
- package/dist/cli.js +542 -0
- package/dist/cli.js.map +1 -0
- package/package.json +41 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Xavier
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,542 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/cli.ts
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
|
|
6
|
+
// src/logger.ts
|
|
7
|
+
import pc from "picocolors";
|
|
8
|
+
var noColor = process.env.NO_COLOR !== void 0 || !process.stdout.isTTY;
|
|
9
|
+
var c = noColor ? {
|
|
10
|
+
red: (s) => s,
|
|
11
|
+
green: (s) => s,
|
|
12
|
+
yellow: (s) => s,
|
|
13
|
+
cyan: (s) => s,
|
|
14
|
+
dim: (s) => s,
|
|
15
|
+
bold: (s) => s
|
|
16
|
+
} : pc;
|
|
17
|
+
var log = {
|
|
18
|
+
info: (msg) => {
|
|
19
|
+
process.stdout.write(`${msg}
|
|
20
|
+
`);
|
|
21
|
+
},
|
|
22
|
+
success: (msg) => {
|
|
23
|
+
process.stdout.write(`${c.green(msg)}
|
|
24
|
+
`);
|
|
25
|
+
},
|
|
26
|
+
warn: (msg) => {
|
|
27
|
+
process.stderr.write(`${c.yellow(msg)}
|
|
28
|
+
`);
|
|
29
|
+
},
|
|
30
|
+
error: (msg) => {
|
|
31
|
+
process.stderr.write(`${c.red(msg)}
|
|
32
|
+
`);
|
|
33
|
+
},
|
|
34
|
+
dim: (msg) => {
|
|
35
|
+
process.stdout.write(`${c.dim(msg)}
|
|
36
|
+
`);
|
|
37
|
+
},
|
|
38
|
+
bold: (msg) => {
|
|
39
|
+
process.stdout.write(`${c.bold(msg)}
|
|
40
|
+
`);
|
|
41
|
+
},
|
|
42
|
+
cyan: (msg) => {
|
|
43
|
+
process.stdout.write(`${c.cyan(msg)}
|
|
44
|
+
`);
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// src/errors.ts
|
|
49
|
+
var AppError = class extends Error {
|
|
50
|
+
constructor(message, code) {
|
|
51
|
+
super(message);
|
|
52
|
+
this.code = code;
|
|
53
|
+
this.name = new.target.name;
|
|
54
|
+
}
|
|
55
|
+
code;
|
|
56
|
+
};
|
|
57
|
+
var ConfigDirNotFoundError = class extends AppError {
|
|
58
|
+
constructor(message) {
|
|
59
|
+
super(message, "CONFIG_DIR_NOT_FOUND");
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
var NoProfilesError = class extends AppError {
|
|
63
|
+
constructor(message) {
|
|
64
|
+
super(message, "NO_PROFILES");
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
var ProfileNotFoundError = class extends AppError {
|
|
68
|
+
constructor(message) {
|
|
69
|
+
super(message, "PROFILE_NOT_FOUND");
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
var NoBackupError = class extends AppError {
|
|
73
|
+
constructor(message) {
|
|
74
|
+
super(message, "NO_BACKUP");
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
var NoCurrentSettingsError = class extends AppError {
|
|
78
|
+
constructor(message) {
|
|
79
|
+
super(message, "NO_CURRENT_SETTINGS");
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
var UserCancelledError = class extends AppError {
|
|
83
|
+
constructor(message) {
|
|
84
|
+
super(message, "USER_CANCELLED");
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
var InvalidAliasError = class extends AppError {
|
|
88
|
+
constructor(message) {
|
|
89
|
+
super(message, "INVALID_ALIAS");
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
// src/exit.ts
|
|
94
|
+
function toExitCode(err) {
|
|
95
|
+
if (err == null) return 0;
|
|
96
|
+
if (err instanceof UserCancelledError) return 0;
|
|
97
|
+
if (err instanceof ConfigDirNotFoundError) return 1;
|
|
98
|
+
if (err instanceof NoProfilesError) return 1;
|
|
99
|
+
if (err instanceof NoBackupError) return 1;
|
|
100
|
+
if (err instanceof NoCurrentSettingsError) return 1;
|
|
101
|
+
if (err instanceof ProfileNotFoundError) return 2;
|
|
102
|
+
if (err instanceof InvalidAliasError) return 2;
|
|
103
|
+
if (err instanceof AppError) return 3;
|
|
104
|
+
return 1;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// src/config.ts
|
|
108
|
+
import path from "path";
|
|
109
|
+
import os from "os";
|
|
110
|
+
var ALIAS_RE = /^[a-z0-9][a-z0-9._-]{0,63}$/;
|
|
111
|
+
function homeDir() {
|
|
112
|
+
return process.env.HOME ?? os.homedir();
|
|
113
|
+
}
|
|
114
|
+
function expandHome(p) {
|
|
115
|
+
if (p === "~") return homeDir();
|
|
116
|
+
if (p.startsWith("~/") || p.startsWith("~\\")) {
|
|
117
|
+
return path.join(homeDir(), p.slice(2));
|
|
118
|
+
}
|
|
119
|
+
return p;
|
|
120
|
+
}
|
|
121
|
+
function toConfigDir(s) {
|
|
122
|
+
return s;
|
|
123
|
+
}
|
|
124
|
+
function getConfigDir() {
|
|
125
|
+
const fromEnv = process.env.CLAUDE_CONFIG_DIR;
|
|
126
|
+
if (fromEnv) return toConfigDir(path.resolve(expandHome(fromEnv)));
|
|
127
|
+
return toConfigDir(path.join(homeDir(), ".claude"));
|
|
128
|
+
}
|
|
129
|
+
function getSettingsPath() {
|
|
130
|
+
return path.join(getConfigDir(), "settings.json");
|
|
131
|
+
}
|
|
132
|
+
function getBackupPath() {
|
|
133
|
+
return path.join(getConfigDir(), "settings.json.bak");
|
|
134
|
+
}
|
|
135
|
+
function profilePath(alias) {
|
|
136
|
+
return path.join(getConfigDir(), `settings.json.${alias}`);
|
|
137
|
+
}
|
|
138
|
+
function validateAlias(alias) {
|
|
139
|
+
if (!ALIAS_RE.test(alias)) {
|
|
140
|
+
return `Invalid alias '${alias}'. Must match ${ALIAS_RE} (lowercase, digits, . _ -, start with letter/digit, 1-64 chars).`;
|
|
141
|
+
}
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
function assertAlias(alias) {
|
|
145
|
+
const err = validateAlias(alias);
|
|
146
|
+
if (err) throw new InvalidAliasError(err);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// src/scanner.ts
|
|
150
|
+
import fs from "fs/promises";
|
|
151
|
+
import path2 from "path";
|
|
152
|
+
async function sha256(filePath) {
|
|
153
|
+
try {
|
|
154
|
+
const buf = await fs.readFile(filePath);
|
|
155
|
+
const { createHash } = await import("crypto");
|
|
156
|
+
return createHash("sha256").update(buf).digest("hex");
|
|
157
|
+
} catch (err) {
|
|
158
|
+
if (err.code === "ENOENT") return null;
|
|
159
|
+
throw err;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
async function listProfiles(configDir) {
|
|
163
|
+
let entries;
|
|
164
|
+
try {
|
|
165
|
+
entries = await fs.readdir(configDir);
|
|
166
|
+
} catch (err) {
|
|
167
|
+
if (err.code === "ENOENT") {
|
|
168
|
+
throw new ConfigDirNotFoundError(`Config directory not found: ${configDir}`);
|
|
169
|
+
}
|
|
170
|
+
throw err;
|
|
171
|
+
}
|
|
172
|
+
const settingsHash = await sha256(path2.join(configDir, "settings.json"));
|
|
173
|
+
const matches = entries.filter((name) => name.startsWith("settings.json.")).filter((name) => !name.endsWith(".bak")).map((name) => name.slice("settings.json.".length));
|
|
174
|
+
const profiles = [];
|
|
175
|
+
for (const alias of matches) {
|
|
176
|
+
const profileFile = path2.join(configDir, `settings.json.${alias}`);
|
|
177
|
+
const hash = await sha256(profileFile);
|
|
178
|
+
profiles.push({
|
|
179
|
+
alias,
|
|
180
|
+
path: profileFile,
|
|
181
|
+
active: hash !== null && hash === settingsHash
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
profiles.sort((a, b) => a.alias.localeCompare(b.alias));
|
|
185
|
+
return profiles;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// src/commands/list.ts
|
|
189
|
+
async function run(io) {
|
|
190
|
+
const configDir = getConfigDir();
|
|
191
|
+
const profiles = await listProfiles(configDir);
|
|
192
|
+
if (profiles.length === 0) {
|
|
193
|
+
throw new NoProfilesError(
|
|
194
|
+
"No profiles found. Create one with: llm-switch save <alias>"
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
const lines = ["Available profiles:", ""];
|
|
198
|
+
profiles.forEach((p, i) => {
|
|
199
|
+
const marker = p.active ? "*" : " ";
|
|
200
|
+
lines.push(` ${marker} ${i + 1}. ${p.alias} (${p.path})`);
|
|
201
|
+
});
|
|
202
|
+
lines.push("");
|
|
203
|
+
lines.push("* = currently active");
|
|
204
|
+
io.stdout.write(lines.join("\n") + "\n");
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// src/switcher.ts
|
|
208
|
+
import fs3 from "fs/promises";
|
|
209
|
+
import path3 from "path";
|
|
210
|
+
import crypto from "crypto";
|
|
211
|
+
|
|
212
|
+
// src/backup.ts
|
|
213
|
+
import fs2 from "fs/promises";
|
|
214
|
+
async function backupCurrent(settingsPath, backupPath) {
|
|
215
|
+
try {
|
|
216
|
+
await fs2.copyFile(settingsPath, backupPath);
|
|
217
|
+
} catch (err) {
|
|
218
|
+
if (err.code === "ENOENT") {
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
throw err;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
async function restoreBackup(settingsPath, backupPath) {
|
|
225
|
+
try {
|
|
226
|
+
await fs2.rename(backupPath, settingsPath);
|
|
227
|
+
} catch (err) {
|
|
228
|
+
if (err.code === "ENOENT") {
|
|
229
|
+
throw new NoBackupError(`No backup found at ${backupPath}.`);
|
|
230
|
+
}
|
|
231
|
+
throw err;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
async function isSameContent(a, b) {
|
|
235
|
+
try {
|
|
236
|
+
const [ca, cb] = await Promise.all([fs2.readFile(a), fs2.readFile(b)]);
|
|
237
|
+
return ca.equals(cb);
|
|
238
|
+
} catch {
|
|
239
|
+
return false;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// src/switcher.ts
|
|
244
|
+
async function switchTo(sourcePath, settingsPath, backupPath) {
|
|
245
|
+
await backupCurrent(settingsPath, backupPath);
|
|
246
|
+
const tmpPath = path3.join(
|
|
247
|
+
path3.dirname(settingsPath),
|
|
248
|
+
`.settings.${crypto.randomUUID()}.tmp`
|
|
249
|
+
);
|
|
250
|
+
try {
|
|
251
|
+
await fs3.copyFile(sourcePath, tmpPath);
|
|
252
|
+
await fs3.rename(tmpPath, settingsPath);
|
|
253
|
+
} catch (err) {
|
|
254
|
+
await fs3.rm(tmpPath, { force: true });
|
|
255
|
+
throw err;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// src/ui.ts
|
|
260
|
+
import readline from "readline";
|
|
261
|
+
function makeRl(io) {
|
|
262
|
+
return readline.createInterface({ input: io.input, output: io.output });
|
|
263
|
+
}
|
|
264
|
+
function ask(rl, question) {
|
|
265
|
+
return new Promise((resolve) => rl.question(question, resolve));
|
|
266
|
+
}
|
|
267
|
+
async function pickProfile(profiles, io = { input: process.stdin, output: process.stdout }) {
|
|
268
|
+
const rl = makeRl(io);
|
|
269
|
+
try {
|
|
270
|
+
if (profiles.length === 0) return null;
|
|
271
|
+
process.stdout.write("\n");
|
|
272
|
+
profiles.forEach((p, i) => {
|
|
273
|
+
const marker = p.active ? "*" : " ";
|
|
274
|
+
process.stdout.write(` ${marker} ${i + 1}. ${p.alias}
|
|
275
|
+
`);
|
|
276
|
+
});
|
|
277
|
+
process.stdout.write(`
|
|
278
|
+
Select profile [1-${profiles.length}] (Enter to cancel): `);
|
|
279
|
+
const answer = (await ask(rl, "")).trim();
|
|
280
|
+
if (!answer) return null;
|
|
281
|
+
const idx = Number.parseInt(answer, 10);
|
|
282
|
+
if (!Number.isFinite(idx) || idx < 1 || idx > profiles.length) return null;
|
|
283
|
+
return profiles[idx - 1] ?? null;
|
|
284
|
+
} finally {
|
|
285
|
+
rl.close();
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
async function promptAlias(existing, io = { input: process.stdin, output: process.stdout }) {
|
|
289
|
+
const rl = makeRl(io);
|
|
290
|
+
try {
|
|
291
|
+
process.stdout.write("\nAlias name (Enter to cancel): ");
|
|
292
|
+
const answer = (await ask(rl, "")).trim();
|
|
293
|
+
if (!answer) return null;
|
|
294
|
+
if (!ALIAS_RE.test(answer)) {
|
|
295
|
+
process.stderr.write(
|
|
296
|
+
`Invalid alias. Must match ${ALIAS_RE} (lowercase, digits, . _ -, 1-64 chars).
|
|
297
|
+
`
|
|
298
|
+
);
|
|
299
|
+
return null;
|
|
300
|
+
}
|
|
301
|
+
if (existing.includes(answer)) {
|
|
302
|
+
process.stderr.write(`Alias '${answer}' already exists.
|
|
303
|
+
`);
|
|
304
|
+
return null;
|
|
305
|
+
}
|
|
306
|
+
return answer;
|
|
307
|
+
} finally {
|
|
308
|
+
rl.close();
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// src/commands/switch.ts
|
|
313
|
+
async function run2(io) {
|
|
314
|
+
const configDir = getConfigDir();
|
|
315
|
+
const settingsPath = getSettingsPath();
|
|
316
|
+
const backupPath = getBackupPath();
|
|
317
|
+
if (io.alias !== void 0) {
|
|
318
|
+
assertAlias(io.alias);
|
|
319
|
+
const source = profilePath(io.alias);
|
|
320
|
+
const profiles2 = await listProfiles(configDir);
|
|
321
|
+
if (!profiles2.find((p) => p.alias === io.alias)) {
|
|
322
|
+
throw new ProfileNotFoundError(
|
|
323
|
+
`Profile '${io.alias}' not found. Run 'llm-switch list' to see available profiles.`
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
await switchTo(source, settingsPath, backupPath);
|
|
327
|
+
io.stdout.write(`Switched to ${io.alias}. Restart Claude Code to apply.
|
|
328
|
+
`);
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
if (!io.isTTY) {
|
|
332
|
+
throw new UserCancelledError(
|
|
333
|
+
"Interactive mode requires a TTY. Use: llm-switch <alias>"
|
|
334
|
+
);
|
|
335
|
+
}
|
|
336
|
+
const profiles = await listProfiles(configDir);
|
|
337
|
+
const chosen = await pickProfile(profiles, { input: io.stdin, output: io.stdout });
|
|
338
|
+
if (!chosen) {
|
|
339
|
+
throw new UserCancelledError("Cancelled.");
|
|
340
|
+
}
|
|
341
|
+
await switchTo(chosen.path, settingsPath, backupPath);
|
|
342
|
+
io.stdout.write(`Switched to ${chosen.alias}. Restart Claude Code to apply.
|
|
343
|
+
`);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// src/commands/restore.ts
|
|
347
|
+
import fs4 from "fs/promises";
|
|
348
|
+
async function run3(io) {
|
|
349
|
+
const settingsPath = getSettingsPath();
|
|
350
|
+
const backupPath = getBackupPath();
|
|
351
|
+
if (!await exists(backupPath)) {
|
|
352
|
+
throw new NoBackupError(`No backup found at ${backupPath}.`);
|
|
353
|
+
}
|
|
354
|
+
if (!await exists(settingsPath)) {
|
|
355
|
+
throw new NoCurrentSettingsError(
|
|
356
|
+
`No current settings.json to restore at ${settingsPath}.`
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
if (await isSameContent(settingsPath, backupPath)) {
|
|
360
|
+
io.stdout.write("Already at backup state. Nothing to do.\n");
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
await restoreBackup(settingsPath, backupPath);
|
|
364
|
+
io.stdout.write("Restored from backup.\n");
|
|
365
|
+
}
|
|
366
|
+
async function exists(p) {
|
|
367
|
+
try {
|
|
368
|
+
await fs4.access(p);
|
|
369
|
+
return true;
|
|
370
|
+
} catch {
|
|
371
|
+
return false;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// src/commands/save.ts
|
|
376
|
+
import fs5 from "fs/promises";
|
|
377
|
+
async function run4(io) {
|
|
378
|
+
const configDir = getConfigDir();
|
|
379
|
+
const settingsPath = getSettingsPath();
|
|
380
|
+
if (!await exists2(settingsPath)) {
|
|
381
|
+
throw new NoCurrentSettingsError(
|
|
382
|
+
`No current settings.json at ${settingsPath}. Nothing to save.`
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
let alias = io.alias;
|
|
386
|
+
if (alias === void 0) {
|
|
387
|
+
if (!io.isTTY) {
|
|
388
|
+
throw new UserCancelledError(
|
|
389
|
+
"Interactive mode requires a TTY. Use: llm-switch save <alias>"
|
|
390
|
+
);
|
|
391
|
+
}
|
|
392
|
+
const profiles = await listProfiles(configDir);
|
|
393
|
+
const result = await promptAlias(profiles.map((p) => p.alias), {
|
|
394
|
+
input: io.stdin,
|
|
395
|
+
output: io.stdout
|
|
396
|
+
});
|
|
397
|
+
if (!result) throw new UserCancelledError("Cancelled.");
|
|
398
|
+
alias = result;
|
|
399
|
+
} else {
|
|
400
|
+
assertAlias(alias);
|
|
401
|
+
}
|
|
402
|
+
const target = profilePath(alias);
|
|
403
|
+
const existed = await exists2(target);
|
|
404
|
+
await fs5.copyFile(settingsPath, target);
|
|
405
|
+
if (existed) {
|
|
406
|
+
io.stderr.write(`Overwrote existing profile '${alias}'.
|
|
407
|
+
`);
|
|
408
|
+
}
|
|
409
|
+
io.stdout.write(`Saved current settings as '${alias}'.
|
|
410
|
+
`);
|
|
411
|
+
}
|
|
412
|
+
async function exists2(p) {
|
|
413
|
+
try {
|
|
414
|
+
await fs5.access(p);
|
|
415
|
+
return true;
|
|
416
|
+
} catch {
|
|
417
|
+
return false;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// src/display.ts
|
|
422
|
+
import fs6 from "fs/promises";
|
|
423
|
+
import path4 from "path";
|
|
424
|
+
import crypto2 from "crypto";
|
|
425
|
+
async function sha2562(filePath) {
|
|
426
|
+
try {
|
|
427
|
+
const buf = await fs6.readFile(filePath);
|
|
428
|
+
return crypto2.createHash("sha256").update(buf).digest("hex");
|
|
429
|
+
} catch (err) {
|
|
430
|
+
if (err.code === "ENOENT") return null;
|
|
431
|
+
throw err;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
async function dirExists(dir) {
|
|
435
|
+
try {
|
|
436
|
+
const stat = await fs6.stat(dir);
|
|
437
|
+
return stat.isDirectory();
|
|
438
|
+
} catch (err) {
|
|
439
|
+
if (err.code === "ENOENT") return false;
|
|
440
|
+
throw err;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
function safeParse(json) {
|
|
444
|
+
try {
|
|
445
|
+
const parsed = JSON.parse(json);
|
|
446
|
+
if (parsed === null || typeof parsed !== "object") return null;
|
|
447
|
+
return parsed;
|
|
448
|
+
} catch {
|
|
449
|
+
return null;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
async function summarize(configDir) {
|
|
453
|
+
if (!await dirExists(configDir)) {
|
|
454
|
+
throw new ConfigDirNotFoundError(`Config directory not found: ${configDir}`);
|
|
455
|
+
}
|
|
456
|
+
const settingsPath = path4.join(configDir, "settings.json");
|
|
457
|
+
const settingsHash = await sha2562(settingsPath);
|
|
458
|
+
if (!settingsHash) {
|
|
459
|
+
return { source: "default", sourcePath: settingsPath, hasMcp: false };
|
|
460
|
+
}
|
|
461
|
+
const content = await fs6.readFile(settingsPath, "utf8");
|
|
462
|
+
const data = safeParse(content);
|
|
463
|
+
const entries = await fs6.readdir(configDir);
|
|
464
|
+
const aliases = entries.filter((n) => n.startsWith("settings.json.") && !n.endsWith(".bak")).map((n) => n.slice("settings.json.".length));
|
|
465
|
+
for (const alias of aliases) {
|
|
466
|
+
const profileFile = path4.join(configDir, `settings.json.${alias}`);
|
|
467
|
+
if (await sha2562(profileFile) === settingsHash) {
|
|
468
|
+
return {
|
|
469
|
+
source: alias,
|
|
470
|
+
sourcePath: profileFile,
|
|
471
|
+
baseUrl: data?.env?.ANTHROPIC_BASE_URL,
|
|
472
|
+
model: data?.env?.ANTHROPIC_MODEL,
|
|
473
|
+
hasMcp: data?.mcpServers !== void 0 && Object.keys(data.mcpServers).length > 0
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
return {
|
|
478
|
+
source: "default",
|
|
479
|
+
sourcePath: settingsPath,
|
|
480
|
+
baseUrl: data?.env?.ANTHROPIC_BASE_URL,
|
|
481
|
+
model: data?.env?.ANTHROPIC_MODEL,
|
|
482
|
+
hasMcp: data?.mcpServers !== void 0 && Object.keys(data.mcpServers).length > 0
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// src/commands/current.ts
|
|
487
|
+
async function run5(io) {
|
|
488
|
+
const s = await summarize(getConfigDir());
|
|
489
|
+
const lines = [];
|
|
490
|
+
lines.push(`Source: ${s.source} (${s.sourcePath})`);
|
|
491
|
+
if (s.baseUrl) lines.push(`Base URL: ${s.baseUrl}`);
|
|
492
|
+
if (s.model) lines.push(`Model: ${s.model}`);
|
|
493
|
+
lines.push(`MCP servers: ${s.hasMcp ? "yes" : "no"}`);
|
|
494
|
+
io.stdout.write(lines.join("\n") + "\n");
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// src/cli.ts
|
|
498
|
+
var program = new Command();
|
|
499
|
+
program.name("llm-switch").description("Switch Claude Code settings.json profiles from the command line").version("0.1.0");
|
|
500
|
+
program.command("list").description("List available profiles").action(async () => {
|
|
501
|
+
await run({ stdout: process.stdout });
|
|
502
|
+
});
|
|
503
|
+
program.command("switch [alias]").description("Switch to a profile (interactive if no alias given)").action(async (alias) => {
|
|
504
|
+
await run2({
|
|
505
|
+
alias,
|
|
506
|
+
stdin: process.stdin,
|
|
507
|
+
stdout: process.stdout,
|
|
508
|
+
stderr: process.stderr,
|
|
509
|
+
isTTY: Boolean(process.stdout.isTTY)
|
|
510
|
+
});
|
|
511
|
+
});
|
|
512
|
+
program.command("restore").description("Restore from the most recent backup").action(async () => {
|
|
513
|
+
await run3({ stdout: process.stdout });
|
|
514
|
+
});
|
|
515
|
+
program.command("save [alias]").description("Save current settings.json as a named profile").action(async (alias) => {
|
|
516
|
+
await run4({
|
|
517
|
+
alias,
|
|
518
|
+
stdin: process.stdin,
|
|
519
|
+
stdout: process.stdout,
|
|
520
|
+
stderr: process.stderr,
|
|
521
|
+
isTTY: Boolean(process.stdout.isTTY)
|
|
522
|
+
});
|
|
523
|
+
});
|
|
524
|
+
program.command("current").description("Show the current active profile").action(async () => {
|
|
525
|
+
await run5({ stdout: process.stdout });
|
|
526
|
+
});
|
|
527
|
+
async function main() {
|
|
528
|
+
try {
|
|
529
|
+
await program.parseAsync(process.argv);
|
|
530
|
+
} catch (err) {
|
|
531
|
+
if (err instanceof AppError) {
|
|
532
|
+
log.error(`Error: ${err.message}`);
|
|
533
|
+
} else if (err instanceof Error) {
|
|
534
|
+
log.error(`Unexpected error: ${err.message}`);
|
|
535
|
+
} else {
|
|
536
|
+
log.error("Unexpected error");
|
|
537
|
+
}
|
|
538
|
+
process.exit(toExitCode(err));
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
main();
|
|
542
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts","../src/logger.ts","../src/errors.ts","../src/exit.ts","../src/config.ts","../src/scanner.ts","../src/commands/list.ts","../src/switcher.ts","../src/backup.ts","../src/ui.ts","../src/commands/switch.ts","../src/commands/restore.ts","../src/commands/save.ts","../src/display.ts","../src/commands/current.ts"],"sourcesContent":["import { Command } from 'commander';\nimport { log } from './logger.js';\nimport { toExitCode } from './exit.js';\nimport { AppError } from './errors.js';\nimport * as listCmd from './commands/list.js';\nimport * as switchCmd from './commands/switch.js';\nimport * as restoreCmd from './commands/restore.js';\nimport * as saveCmd from './commands/save.js';\nimport * as currentCmd from './commands/current.js';\n\nconst program = new Command();\nprogram\n .name('llm-switch')\n .description('Switch Claude Code settings.json profiles from the command line')\n .version('0.1.0');\n\nprogram\n .command('list')\n .description('List available profiles')\n .action(async () => {\n await listCmd.run({ stdout: process.stdout });\n });\n\nprogram\n .command('switch [alias]')\n .description('Switch to a profile (interactive if no alias given)')\n .action(async (alias?: string) => {\n await switchCmd.run({\n alias,\n stdin: process.stdin,\n stdout: process.stdout,\n stderr: process.stderr,\n isTTY: Boolean(process.stdout.isTTY),\n });\n });\n\nprogram\n .command('restore')\n .description('Restore from the most recent backup')\n .action(async () => {\n await restoreCmd.run({ stdout: process.stdout });\n });\n\nprogram\n .command('save [alias]')\n .description('Save current settings.json as a named profile')\n .action(async (alias?: string) => {\n await saveCmd.run({\n alias,\n stdin: process.stdin,\n stdout: process.stdout,\n stderr: process.stderr,\n isTTY: Boolean(process.stdout.isTTY),\n });\n });\n\nprogram\n .command('current')\n .description('Show the current active profile')\n .action(async () => {\n await currentCmd.run({ stdout: process.stdout });\n });\n\nasync function main(): Promise<void> {\n try {\n await program.parseAsync(process.argv);\n } catch (err: unknown) {\n if (err instanceof AppError) {\n log.error(`Error: ${err.message}`);\n } else if (err instanceof Error) {\n log.error(`Unexpected error: ${err.message}`);\n } else {\n log.error('Unexpected error');\n }\n process.exit(toExitCode(err));\n }\n}\n\nmain();","import pc from 'picocolors';\n\nconst noColor = process.env.NO_COLOR !== undefined || !process.stdout.isTTY;\n\nconst c = noColor\n ? {\n red: (s: string) => s,\n green: (s: string) => s,\n yellow: (s: string) => s,\n cyan: (s: string) => s,\n dim: (s: string) => s,\n bold: (s: string) => s,\n }\n : pc;\n\nexport const log = {\n info: (msg: string): void => {\n process.stdout.write(`${msg}\\n`);\n },\n success: (msg: string): void => {\n process.stdout.write(`${c.green(msg)}\\n`);\n },\n warn: (msg: string): void => {\n process.stderr.write(`${c.yellow(msg)}\\n`);\n },\n error: (msg: string): void => {\n process.stderr.write(`${c.red(msg)}\\n`);\n },\n dim: (msg: string): void => {\n process.stdout.write(`${c.dim(msg)}\\n`);\n },\n bold: (msg: string): void => {\n process.stdout.write(`${c.bold(msg)}\\n`);\n },\n cyan: (msg: string): void => {\n process.stdout.write(`${c.cyan(msg)}\\n`);\n },\n};\n","export class AppError extends Error {\n constructor(message: string, public readonly code: string) {\n super(message);\n this.name = new.target.name;\n }\n}\n\nexport class ConfigDirNotFoundError extends AppError {\n constructor(message: string) {\n super(message, 'CONFIG_DIR_NOT_FOUND');\n }\n}\n\nexport class NoProfilesError extends AppError {\n constructor(message: string) {\n super(message, 'NO_PROFILES');\n }\n}\n\nexport class ProfileNotFoundError extends AppError {\n constructor(message: string) {\n super(message, 'PROFILE_NOT_FOUND');\n }\n}\n\nexport class NoBackupError extends AppError {\n constructor(message: string) {\n super(message, 'NO_BACKUP');\n }\n}\n\nexport class NoCurrentSettingsError extends AppError {\n constructor(message: string) {\n super(message, 'NO_CURRENT_SETTINGS');\n }\n}\n\nexport class UserCancelledError extends AppError {\n constructor(message: string) {\n super(message, 'USER_CANCELLED');\n }\n}\n\nexport class InvalidAliasError extends AppError {\n constructor(message: string) {\n super(message, 'INVALID_ALIAS');\n }\n}\n","import {\n AppError,\n ConfigDirNotFoundError,\n InvalidAliasError,\n NoBackupError,\n NoCurrentSettingsError,\n NoProfilesError,\n ProfileNotFoundError,\n UserCancelledError,\n} from './errors.js';\n\nexport function toExitCode(err: unknown): number {\n if (err == null) return 0;\n if (err instanceof UserCancelledError) return 0;\n\n if (err instanceof ConfigDirNotFoundError) return 1;\n if (err instanceof NoProfilesError) return 1;\n if (err instanceof NoBackupError) return 1;\n if (err instanceof NoCurrentSettingsError) return 1;\n\n if (err instanceof ProfileNotFoundError) return 2;\n if (err instanceof InvalidAliasError) return 2;\n\n if (err instanceof AppError) return 3;\n return 1;\n}","import path from 'node:path';\nimport os from 'node:os';\nimport { InvalidAliasError } from './errors.js';\n\nexport type ConfigDir = string & { readonly __brand: 'ConfigDir' };\n\nexport const ALIAS_RE = /^[a-z0-9][a-z0-9._-]{0,63}$/;\n\nfunction homeDir(): string {\n return process.env.HOME ?? os.homedir();\n}\n\nfunction expandHome(p: string): string {\n if (p === '~') return homeDir();\n if (p.startsWith('~/') || p.startsWith('~\\\\')) {\n return path.join(homeDir(), p.slice(2));\n }\n return p;\n}\n\nfunction toConfigDir(s: string): ConfigDir {\n return s as ConfigDir;\n}\n\nexport function getConfigDir(): ConfigDir {\n const fromEnv = process.env.CLAUDE_CONFIG_DIR;\n if (fromEnv) return toConfigDir(path.resolve(expandHome(fromEnv)));\n return toConfigDir(path.join(homeDir(), '.claude'));\n}\n\nexport function getSettingsPath(): string {\n return path.join(getConfigDir(), 'settings.json');\n}\n\nexport function getBackupPath(): string {\n return path.join(getConfigDir(), 'settings.json.bak');\n}\n\nexport function profilePath(alias: string): string {\n return path.join(getConfigDir(), `settings.json.${alias}`);\n}\n\nexport function validateAlias(alias: string): string | null {\n if (!ALIAS_RE.test(alias)) {\n return `Invalid alias '${alias}'. Must match ${ALIAS_RE} (lowercase, digits, . _ -, start with letter/digit, 1-64 chars).`;\n }\n return null;\n}\n\nexport function assertAlias(alias: string): void {\n const err = validateAlias(alias);\n if (err) throw new InvalidAliasError(err);\n}\n","import fs from 'node:fs/promises';\nimport path from 'node:path';\nimport { ConfigDirNotFoundError } from './errors.js';\nimport type { ConfigDir } from './config.js';\n\nexport interface Profile {\n alias: string;\n path: string;\n active: boolean;\n}\n\nasync function sha256(filePath: string): Promise<string | null> {\n try {\n const buf = await fs.readFile(filePath);\n const { createHash } = await import('node:crypto');\n return createHash('sha256').update(buf).digest('hex');\n } catch (err: unknown) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') return null;\n throw err;\n }\n}\n\nexport async function listProfiles(configDir: ConfigDir): Promise<Profile[]> {\n let entries: string[];\n try {\n entries = await fs.readdir(configDir);\n } catch (err: unknown) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') {\n throw new ConfigDirNotFoundError(`Config directory not found: ${configDir}`);\n }\n throw err;\n }\n\n const settingsHash = await sha256(path.join(configDir, 'settings.json'));\n\n const matches = entries\n .filter((name) => name.startsWith('settings.json.'))\n .filter((name) => !name.endsWith('.bak'))\n .map((name) => name.slice('settings.json.'.length));\n\n const profiles: Profile[] = [];\n for (const alias of matches) {\n const profileFile = path.join(configDir, `settings.json.${alias}`);\n const hash = await sha256(profileFile);\n profiles.push({\n alias,\n path: profileFile,\n active: hash !== null && hash === settingsHash,\n });\n }\n\n profiles.sort((a, b) => a.alias.localeCompare(b.alias));\n return profiles;\n}\n","import type { ConfigDir } from '../config.js';\nimport { getConfigDir } from '../config.js';\nimport { listProfiles } from '../scanner.js';\nimport { NoProfilesError } from '../errors.js';\n\nexport interface CommandIO {\n stdout: { write(s: string): unknown };\n}\n\nexport async function run(io: CommandIO): Promise<void> {\n const configDir: ConfigDir = getConfigDir();\n const profiles = await listProfiles(configDir);\n\n if (profiles.length === 0) {\n throw new NoProfilesError(\n \"No profiles found. Create one with: llm-switch save <alias>\",\n );\n }\n\n const lines = ['Available profiles:', ''];\n profiles.forEach((p, i) => {\n const marker = p.active ? '*' : ' ';\n lines.push(` ${marker} ${i + 1}. ${p.alias} (${p.path})`);\n });\n lines.push('');\n lines.push('* = currently active');\n io.stdout.write(lines.join('\\n') + '\\n');\n}\n","import fs from 'node:fs/promises';\nimport path from 'node:path';\nimport crypto from 'node:crypto';\nimport { backupCurrent } from './backup.js';\n\nexport async function switchTo(\n sourcePath: string,\n settingsPath: string,\n backupPath: string,\n): Promise<void> {\n await backupCurrent(settingsPath, backupPath);\n\n const tmpPath = path.join(\n path.dirname(settingsPath),\n `.settings.${crypto.randomUUID()}.tmp`,\n );\n\n try {\n await fs.copyFile(sourcePath, tmpPath);\n await fs.rename(tmpPath, settingsPath);\n } catch (err) {\n await fs.rm(tmpPath, { force: true });\n throw err;\n }\n}","import fs from 'node:fs/promises';\nimport { NoBackupError } from './errors.js';\n\nexport async function backupCurrent(settingsPath: string, backupPath: string): Promise<void> {\n try {\n await fs.copyFile(settingsPath, backupPath);\n } catch (err: unknown) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') {\n return;\n }\n throw err;\n }\n}\n\nexport async function restoreBackup(settingsPath: string, backupPath: string): Promise<void> {\n try {\n await fs.rename(backupPath, settingsPath);\n } catch (err: unknown) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') {\n throw new NoBackupError(`No backup found at ${backupPath}.`);\n }\n throw err;\n }\n}\n\nexport async function isSameContent(a: string, b: string): Promise<boolean> {\n try {\n const [ca, cb] = await Promise.all([fs.readFile(a), fs.readFile(b)]);\n return ca.equals(cb);\n } catch {\n return false;\n }\n}\n","import readline from 'node:readline';\nimport type { Readable, Writable } from 'node:stream';\nimport type { Profile } from './scanner.js';\nimport { ALIAS_RE } from './config.js';\n\nexport interface ReadlineIO {\n input: Readable;\n output: Writable;\n}\n\nfunction makeRl(io: ReadlineIO): readline.Interface {\n return readline.createInterface({ input: io.input, output: io.output });\n}\n\nfunction ask(rl: readline.Interface, question: string): Promise<string> {\n return new Promise((resolve) => rl.question(question, resolve));\n}\n\nexport async function pickProfile(\n profiles: Profile[],\n io: ReadlineIO = { input: process.stdin, output: process.stdout },\n): Promise<Profile | null> {\n const rl = makeRl(io);\n try {\n if (profiles.length === 0) return null;\n\n process.stdout.write('\\n');\n profiles.forEach((p, i) => {\n const marker = p.active ? '*' : ' ';\n process.stdout.write(` ${marker} ${i + 1}. ${p.alias}\\n`);\n });\n process.stdout.write(`\\nSelect profile [1-${profiles.length}] (Enter to cancel): `);\n\n const answer = (await ask(rl, '')).trim();\n if (!answer) return null;\n\n const idx = Number.parseInt(answer, 10);\n if (!Number.isFinite(idx) || idx < 1 || idx > profiles.length) return null;\n return profiles[idx - 1] ?? null;\n } finally {\n rl.close();\n }\n}\n\nexport async function promptAlias(\n existing: string[],\n io: ReadlineIO = { input: process.stdin, output: process.stdout },\n): Promise<string | null> {\n const rl = makeRl(io);\n try {\n process.stdout.write('\\nAlias name (Enter to cancel): ');\n const answer = (await ask(rl, '')).trim();\n if (!answer) return null;\n if (!ALIAS_RE.test(answer)) {\n process.stderr.write(\n `Invalid alias. Must match ${ALIAS_RE} (lowercase, digits, . _ -, 1-64 chars).\\n`,\n );\n return null;\n }\n if (existing.includes(answer)) {\n process.stderr.write(`Alias '${answer}' already exists.\\n`);\n return null;\n }\n return answer;\n } finally {\n rl.close();\n }\n}\n","import type { Readable, Writable } from 'node:stream';\nimport {\n getConfigDir,\n getSettingsPath,\n getBackupPath,\n profilePath,\n assertAlias,\n} from '../config.js';\nimport { listProfiles } from '../scanner.js';\nimport { switchTo } from '../switcher.js';\nimport { pickProfile } from '../ui.js';\nimport {\n ProfileNotFoundError,\n UserCancelledError,\n} from '../errors.js';\n\nexport interface SwitchIO {\n alias?: string;\n stdin: Readable;\n stdout: Writable;\n stderr: Writable;\n isTTY: boolean;\n}\n\nexport async function run(io: SwitchIO): Promise<void> {\n const configDir = getConfigDir();\n const settingsPath = getSettingsPath();\n const backupPath = getBackupPath();\n\n if (io.alias !== undefined) {\n assertAlias(io.alias);\n const source = profilePath(io.alias);\n const profiles = await listProfiles(configDir);\n if (!profiles.find((p) => p.alias === io.alias)) {\n throw new ProfileNotFoundError(\n `Profile '${io.alias}' not found. Run 'llm-switch list' to see available profiles.`,\n );\n }\n await switchTo(source, settingsPath, backupPath);\n io.stdout.write(`Switched to ${io.alias}. Restart Claude Code to apply.\\n`);\n return;\n }\n\n if (!io.isTTY) {\n throw new UserCancelledError(\n 'Interactive mode requires a TTY. Use: llm-switch <alias>',\n );\n }\n\n const profiles = await listProfiles(configDir);\n const chosen = await pickProfile(profiles, { input: io.stdin, output: io.stdout });\n if (!chosen) {\n throw new UserCancelledError('Cancelled.');\n }\n await switchTo(chosen.path, settingsPath, backupPath);\n io.stdout.write(`Switched to ${chosen.alias}. Restart Claude Code to apply.\\n`);\n}","import fs from 'node:fs/promises';\nimport { getSettingsPath, getBackupPath } from '../config.js';\nimport { restoreBackup, isSameContent } from '../backup.js';\nimport { NoBackupError, NoCurrentSettingsError } from '../errors.js';\n\nexport interface RestoreIO {\n stdout: { write(s: string): unknown };\n}\n\nexport async function run(io: RestoreIO): Promise<void> {\n const settingsPath = getSettingsPath();\n const backupPath = getBackupPath();\n\n if (!(await exists(backupPath))) {\n throw new NoBackupError(`No backup found at ${backupPath}.`);\n }\n if (!(await exists(settingsPath))) {\n throw new NoCurrentSettingsError(\n `No current settings.json to restore at ${settingsPath}.`,\n );\n }\n if (await isSameContent(settingsPath, backupPath)) {\n io.stdout.write('Already at backup state. Nothing to do.\\n');\n return;\n }\n\n await restoreBackup(settingsPath, backupPath);\n io.stdout.write('Restored from backup.\\n');\n}\n\nasync function exists(p: string): Promise<boolean> {\n try {\n await fs.access(p);\n return true;\n } catch {\n return false;\n }\n}","import fs from 'node:fs/promises';\nimport type { Readable, Writable } from 'node:stream';\nimport { getConfigDir, getSettingsPath, profilePath, assertAlias } from '../config.js';\nimport { listProfiles } from '../scanner.js';\nimport { promptAlias } from '../ui.js';\nimport { NoCurrentSettingsError, UserCancelledError } from '../errors.js';\n\nexport interface SaveIO {\n alias?: string;\n stdin: Readable;\n stdout: Writable;\n stderr: Writable;\n isTTY: boolean;\n}\n\nexport async function run(io: SaveIO): Promise<void> {\n const configDir = getConfigDir();\n const settingsPath = getSettingsPath();\n\n if (!(await exists(settingsPath))) {\n throw new NoCurrentSettingsError(\n `No current settings.json at ${settingsPath}. Nothing to save.`,\n );\n }\n\n let alias = io.alias;\n if (alias === undefined) {\n if (!io.isTTY) {\n throw new UserCancelledError(\n 'Interactive mode requires a TTY. Use: llm-switch save <alias>',\n );\n }\n const profiles = await listProfiles(configDir);\n const result = await promptAlias(profiles.map((p) => p.alias), {\n input: io.stdin,\n output: io.stdout,\n });\n if (!result) throw new UserCancelledError('Cancelled.');\n alias = result;\n } else {\n assertAlias(alias);\n }\n\n const target = profilePath(alias);\n const existed = await exists(target);\n await fs.copyFile(settingsPath, target);\n\n if (existed) {\n io.stderr.write(`Overwrote existing profile '${alias}'.\\n`);\n }\n io.stdout.write(`Saved current settings as '${alias}'.\\n`);\n}\n\nasync function exists(p: string): Promise<boolean> {\n try {\n await fs.access(p);\n return true;\n } catch {\n return false;\n }\n}","import fs from 'node:fs/promises';\nimport path from 'node:path';\nimport crypto from 'node:crypto';\nimport { ConfigDirNotFoundError } from './errors.js';\nimport type { ConfigDir } from './config.js';\n\nexport interface CurrentSummary {\n source: string;\n sourcePath: string;\n baseUrl?: string;\n model?: string;\n hasMcp: boolean;\n}\n\nasync function sha256(filePath: string): Promise<string | null> {\n try {\n const buf = await fs.readFile(filePath);\n return crypto.createHash('sha256').update(buf).digest('hex');\n } catch (err: unknown) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') return null;\n throw err;\n }\n}\n\nasync function dirExists(dir: string): Promise<boolean> {\n try {\n const stat = await fs.stat(dir);\n return stat.isDirectory();\n } catch (err: unknown) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') return false;\n throw err;\n }\n}\n\ninterface SettingsData {\n env?: { ANTHROPIC_BASE_URL?: string; ANTHROPIC_MODEL?: string };\n mcpServers?: Record<string, unknown>;\n}\n\nfunction safeParse(json: string): SettingsData | null {\n try {\n const parsed: unknown = JSON.parse(json);\n if (parsed === null || typeof parsed !== 'object') return null;\n return parsed as SettingsData;\n } catch {\n return null;\n }\n}\n\nexport async function summarize(configDir: ConfigDir): Promise<CurrentSummary> {\n if (!(await dirExists(configDir))) {\n throw new ConfigDirNotFoundError(`Config directory not found: ${configDir}`);\n }\n\n const settingsPath = path.join(configDir, 'settings.json');\n const settingsHash = await sha256(settingsPath);\n\n if (!settingsHash) {\n return { source: 'default', sourcePath: settingsPath, hasMcp: false };\n }\n\n const content = await fs.readFile(settingsPath, 'utf8');\n const data = safeParse(content);\n\n const entries = await fs.readdir(configDir);\n const aliases = entries\n .filter((n) => n.startsWith('settings.json.') && !n.endsWith('.bak'))\n .map((n) => n.slice('settings.json.'.length));\n\n for (const alias of aliases) {\n const profileFile = path.join(configDir, `settings.json.${alias}`);\n if ((await sha256(profileFile)) === settingsHash) {\n return {\n source: alias,\n sourcePath: profileFile,\n baseUrl: data?.env?.ANTHROPIC_BASE_URL,\n model: data?.env?.ANTHROPIC_MODEL,\n hasMcp: data?.mcpServers !== undefined && Object.keys(data.mcpServers).length > 0,\n };\n }\n }\n\n return {\n source: 'default',\n sourcePath: settingsPath,\n baseUrl: data?.env?.ANTHROPIC_BASE_URL,\n model: data?.env?.ANTHROPIC_MODEL,\n hasMcp: data?.mcpServers !== undefined && Object.keys(data.mcpServers).length > 0,\n };\n}","import { getConfigDir } from '../config.js';\nimport { summarize } from '../display.js';\n\nexport interface CurrentIO {\n stdout: { write(s: string): unknown };\n}\n\nexport async function run(io: CurrentIO): Promise<void> {\n const s = await summarize(getConfigDir());\n const lines: string[] = [];\n lines.push(`Source: ${s.source} (${s.sourcePath})`);\n if (s.baseUrl) lines.push(`Base URL: ${s.baseUrl}`);\n if (s.model) lines.push(`Model: ${s.model}`);\n lines.push(`MCP servers: ${s.hasMcp ? 'yes' : 'no'}`);\n io.stdout.write(lines.join('\\n') + '\\n');\n}\n"],"mappings":"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","names":["path","path","fs","path","fs","fs","path","fs","run","profiles","fs","run","fs","fs","run","exists","fs","fs","path","crypto","sha256","fs","crypto","path","run","run"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "llm-switch",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Switch Claude Code settings.json profiles from the command line",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"llm-switch": "./bin/llm-switch.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin",
|
|
11
|
+
"dist"
|
|
12
|
+
],
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "git+https://github.com/xavier2code/llm-switch.git"
|
|
16
|
+
},
|
|
17
|
+
"bugs": {
|
|
18
|
+
"url": "https://github.com/xavier2code/llm-switch/issues"
|
|
19
|
+
},
|
|
20
|
+
"homepage": "https://github.com/xavier2code/llm-switch#readme",
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"commander": "^12.0.0",
|
|
23
|
+
"picocolors": "^1.1.0",
|
|
24
|
+
"zod": "^3.23.0"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@types/node": "^20.0.0",
|
|
28
|
+
"tsup": "^8.0.0",
|
|
29
|
+
"typescript": "^5.4.0",
|
|
30
|
+
"vitest": "^1.6.0"
|
|
31
|
+
},
|
|
32
|
+
"engines": {
|
|
33
|
+
"node": ">=20"
|
|
34
|
+
},
|
|
35
|
+
"scripts": {
|
|
36
|
+
"build": "tsup",
|
|
37
|
+
"test": "vitest run",
|
|
38
|
+
"test:watch": "vitest",
|
|
39
|
+
"typecheck": "tsc --noEmit"
|
|
40
|
+
}
|
|
41
|
+
}
|