@praeviso/code-env-switch 0.1.1 → 0.1.2
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/.github/workflows/npm-publish.yml +25 -0
- package/AGENTS.md +32 -0
- package/PLAN.md +33 -0
- package/README.md +24 -0
- package/README_zh.md +24 -0
- package/bin/cli/args.js +303 -0
- package/bin/cli/help.js +77 -0
- package/bin/cli/index.js +13 -0
- package/bin/commands/add.js +81 -0
- package/bin/commands/index.js +21 -0
- package/bin/commands/launch.js +330 -0
- package/bin/commands/list.js +57 -0
- package/bin/commands/show.js +10 -0
- package/bin/commands/statusline.js +12 -0
- package/bin/commands/unset.js +20 -0
- package/bin/commands/use.js +92 -0
- package/bin/config/defaults.js +85 -0
- package/bin/config/index.js +20 -0
- package/bin/config/io.js +72 -0
- package/bin/constants.js +27 -0
- package/bin/index.js +279 -0
- package/bin/profile/display.js +78 -0
- package/bin/profile/index.js +26 -0
- package/bin/profile/match.js +40 -0
- package/bin/profile/resolve.js +79 -0
- package/bin/profile/type.js +90 -0
- package/bin/shell/detect.js +40 -0
- package/bin/shell/index.js +18 -0
- package/bin/shell/snippet.js +92 -0
- package/bin/shell/utils.js +35 -0
- package/bin/statusline/claude.js +153 -0
- package/bin/statusline/codex.js +356 -0
- package/bin/statusline/index.js +469 -0
- package/bin/types.js +5 -0
- package/bin/ui/index.js +16 -0
- package/bin/ui/interactive.js +189 -0
- package/bin/ui/readline.js +76 -0
- package/bin/usage/index.js +709 -0
- package/code-env.example.json +11 -0
- package/package.json +2 -2
- package/src/cli/args.ts +318 -0
- package/src/cli/help.ts +75 -0
- package/src/cli/index.ts +5 -0
- package/src/commands/add.ts +91 -0
- package/src/commands/index.ts +10 -0
- package/src/commands/launch.ts +395 -0
- package/src/commands/list.ts +91 -0
- package/src/commands/show.ts +12 -0
- package/src/commands/statusline.ts +18 -0
- package/src/commands/unset.ts +19 -0
- package/src/commands/use.ts +121 -0
- package/src/config/defaults.ts +88 -0
- package/src/config/index.ts +19 -0
- package/src/config/io.ts +69 -0
- package/src/constants.ts +28 -0
- package/src/index.ts +359 -0
- package/src/profile/display.ts +77 -0
- package/src/profile/index.ts +12 -0
- package/src/profile/match.ts +41 -0
- package/src/profile/resolve.ts +84 -0
- package/src/profile/type.ts +83 -0
- package/src/shell/detect.ts +30 -0
- package/src/shell/index.ts +6 -0
- package/src/shell/snippet.ts +92 -0
- package/src/shell/utils.ts +30 -0
- package/src/statusline/claude.ts +172 -0
- package/src/statusline/codex.ts +393 -0
- package/src/statusline/index.ts +626 -0
- package/src/types.ts +95 -0
- package/src/ui/index.ts +5 -0
- package/src/ui/interactive.ts +220 -0
- package/src/ui/readline.ts +85 -0
- package/src/usage/index.ts +833 -0
- package/bin/codenv.js +0 -1316
- package/src/codenv.ts +0 -1478
package/bin/codenv.js
DELETED
|
@@ -1,1316 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
"use strict";
|
|
3
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
-
const fs = require("fs");
|
|
5
|
-
const path = require("path");
|
|
6
|
-
const os = require("os");
|
|
7
|
-
const readline = require("readline");
|
|
8
|
-
const CODEX_AUTH_PATH = path.join(os.homedir(), ".codex", "auth.json");
|
|
9
|
-
const DEFAULT_PROFILE_TYPES = ["codex", "claude"];
|
|
10
|
-
const DEFAULT_UNSET_KEYS = {
|
|
11
|
-
codex: ["OPENAI_BASE_URL", "OPENAI_API_KEY"],
|
|
12
|
-
claude: ["ANTHROPIC_BASE_URL", "ANTHROPIC_API_KEY", "ANTHROPIC_AUTH_TOKEN"],
|
|
13
|
-
};
|
|
14
|
-
function printHelp() {
|
|
15
|
-
const msg = `codenv - switch Claude/Codex env vars\n\nUsage:\n codenv list\n codenv ls\n codenv config\n codenv auto\n codenv use\n codenv use <profile>\n codenv use <type> <name>\n codenv show <profile>\n codenv show <type> <name>\n codenv default <profile>\n codenv default <type> <name>\n codenv default --clear\n codenv remove <profile> [<profile> ...]\n codenv remove <type> <name> [<type> <name> ...]\n codenv remove --all\n codenv unset\n codenv add <profile> KEY=VALUE [KEY=VALUE ...]\n codenv add\n codenv init\n\nOptions:\n -c, --config <path> Path to config JSON\n -h, --help Show help\n\nInit options:\n --apply Append shell helper to your shell rc (default)\n --print Print helper snippet to stdout\n --shell <bash|zsh|fish> Explicitly set the target shell\n\nAdd options:\n -t, --type <codex|claude> Set profile type (alias: cc)\n -n, --note <text> Set profile note\n -r, --remove-file <path> Add a removeFiles entry (repeat)\n -x, --command <cmd> Add a commands entry (repeat)\n -u, --unset <KEY> Add a global unset key (repeat)\n\nExamples:\n codenv init\n codenv use codex primary\n codenv list\n codenv default codex primary\n codenv remove codex primary\n codenv remove codex primary claude default\n codenv remove --all\n CODE_ENV_CONFIG=~/.config/code-env/config.json codenv use claude default\n codenv add --type codex primary OPENAI_BASE_URL=https://api.example.com/v1 OPENAI_API_KEY=YOUR_API_KEY\n codenv add\n`;
|
|
16
|
-
console.log(msg);
|
|
17
|
-
}
|
|
18
|
-
function parseArgs(argv) {
|
|
19
|
-
let configPath = null;
|
|
20
|
-
const args = [];
|
|
21
|
-
for (let i = 0; i < argv.length; i++) {
|
|
22
|
-
const arg = argv[i];
|
|
23
|
-
if (arg === "-h" || arg === "--help") {
|
|
24
|
-
return { args: [], configPath: null, help: true };
|
|
25
|
-
}
|
|
26
|
-
if (arg === "-c" || arg === "--config") {
|
|
27
|
-
configPath = argv[i + 1];
|
|
28
|
-
i++;
|
|
29
|
-
continue;
|
|
30
|
-
}
|
|
31
|
-
if (arg.startsWith("--config=")) {
|
|
32
|
-
configPath = arg.slice("--config=".length);
|
|
33
|
-
continue;
|
|
34
|
-
}
|
|
35
|
-
args.push(arg);
|
|
36
|
-
}
|
|
37
|
-
return { args, configPath, help: false };
|
|
38
|
-
}
|
|
39
|
-
function normalizeType(value) {
|
|
40
|
-
if (!value)
|
|
41
|
-
return null;
|
|
42
|
-
const raw = String(value).trim().toLowerCase();
|
|
43
|
-
if (!raw)
|
|
44
|
-
return null;
|
|
45
|
-
const compact = raw.replace(/[\s_-]+/g, "");
|
|
46
|
-
if (compact === "codex")
|
|
47
|
-
return "codex";
|
|
48
|
-
if (compact === "claude" || compact === "claudecode" || compact === "cc") {
|
|
49
|
-
return "claude";
|
|
50
|
-
}
|
|
51
|
-
return null;
|
|
52
|
-
}
|
|
53
|
-
function hasTypePrefix(name, type) {
|
|
54
|
-
if (!name)
|
|
55
|
-
return false;
|
|
56
|
-
const lowered = String(name).toLowerCase();
|
|
57
|
-
const prefixes = type === "claude" ? [type, "cc"] : [type];
|
|
58
|
-
for (const prefix of prefixes) {
|
|
59
|
-
for (const sep of ["-", "_", "."]) {
|
|
60
|
-
if (lowered.startsWith(`${prefix}${sep}`))
|
|
61
|
-
return true;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
return false;
|
|
65
|
-
}
|
|
66
|
-
function hasEnvKeyPrefix(profile, prefix) {
|
|
67
|
-
if (!profile || !profile.env)
|
|
68
|
-
return false;
|
|
69
|
-
const normalized = prefix.toUpperCase();
|
|
70
|
-
for (const key of Object.keys(profile.env)) {
|
|
71
|
-
if (key.toUpperCase().startsWith(normalized))
|
|
72
|
-
return true;
|
|
73
|
-
}
|
|
74
|
-
return false;
|
|
75
|
-
}
|
|
76
|
-
function inferProfileType(profileName, profile, requestedType) {
|
|
77
|
-
if (requestedType)
|
|
78
|
-
return requestedType;
|
|
79
|
-
const fromProfile = profile ? normalizeType(profile.type) : null;
|
|
80
|
-
if (fromProfile)
|
|
81
|
-
return fromProfile;
|
|
82
|
-
if (hasEnvKeyPrefix(profile, "OPENAI_"))
|
|
83
|
-
return "codex";
|
|
84
|
-
if (hasEnvKeyPrefix(profile, "ANTHROPIC_"))
|
|
85
|
-
return "claude";
|
|
86
|
-
if (hasTypePrefix(profileName, "codex"))
|
|
87
|
-
return "codex";
|
|
88
|
-
if (hasTypePrefix(profileName, "claude"))
|
|
89
|
-
return "claude";
|
|
90
|
-
return null;
|
|
91
|
-
}
|
|
92
|
-
function shouldRemoveCodexAuth(profileName, profile, requestedType) {
|
|
93
|
-
if (requestedType === "codex")
|
|
94
|
-
return true;
|
|
95
|
-
if (!profile)
|
|
96
|
-
return false;
|
|
97
|
-
if (normalizeType(profile.type) === "codex")
|
|
98
|
-
return true;
|
|
99
|
-
if (hasEnvKeyPrefix(profile, "OPENAI_"))
|
|
100
|
-
return true;
|
|
101
|
-
return hasTypePrefix(profileName, "codex");
|
|
102
|
-
}
|
|
103
|
-
function stripTypePrefixFromName(name, type) {
|
|
104
|
-
if (!name)
|
|
105
|
-
return name;
|
|
106
|
-
const normalizedType = normalizeType(type);
|
|
107
|
-
if (!normalizedType)
|
|
108
|
-
return name;
|
|
109
|
-
const lowered = String(name).toLowerCase();
|
|
110
|
-
const prefixes = normalizedType === "claude" ? [normalizedType, "cc"] : [normalizedType];
|
|
111
|
-
for (const prefix of prefixes) {
|
|
112
|
-
for (const sep of ["-", "_", "."]) {
|
|
113
|
-
const candidate = `${prefix}${sep}`;
|
|
114
|
-
if (lowered.startsWith(candidate)) {
|
|
115
|
-
const stripped = String(name).slice(candidate.length);
|
|
116
|
-
return stripped || name;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
return name;
|
|
121
|
-
}
|
|
122
|
-
function getProfileDisplayName(profileKey, profile, requestedType) {
|
|
123
|
-
if (profile.name)
|
|
124
|
-
return String(profile.name);
|
|
125
|
-
const rawType = profile.type ? String(profile.type) : "";
|
|
126
|
-
if (rawType)
|
|
127
|
-
return stripTypePrefixFromName(profileKey, rawType);
|
|
128
|
-
if (requestedType)
|
|
129
|
-
return stripTypePrefixFromName(profileKey, requestedType);
|
|
130
|
-
return profileKey;
|
|
131
|
-
}
|
|
132
|
-
function findProfileKeysByName(config, name, type) {
|
|
133
|
-
const profiles = config && config.profiles ? config.profiles : {};
|
|
134
|
-
const matches = [];
|
|
135
|
-
for (const [key, profile] of Object.entries(profiles)) {
|
|
136
|
-
const safeProfile = profile || {};
|
|
137
|
-
if (type && !profileMatchesType(safeProfile, type))
|
|
138
|
-
continue;
|
|
139
|
-
const displayName = getProfileDisplayName(key, safeProfile, type || null);
|
|
140
|
-
if (displayName === name)
|
|
141
|
-
matches.push(key);
|
|
142
|
-
}
|
|
143
|
-
return matches;
|
|
144
|
-
}
|
|
145
|
-
function generateProfileKey(config) {
|
|
146
|
-
const profiles = config && config.profiles ? config.profiles : {};
|
|
147
|
-
for (let i = 0; i < 10; i++) {
|
|
148
|
-
const key = `p_${Date.now().toString(36)}_${Math.random()
|
|
149
|
-
.toString(36)
|
|
150
|
-
.slice(2, 8)}`;
|
|
151
|
-
if (!profiles[key])
|
|
152
|
-
return key;
|
|
153
|
-
}
|
|
154
|
-
let idx = 0;
|
|
155
|
-
while (true) {
|
|
156
|
-
const key = `p_${Date.now().toString(36)}_${idx}`;
|
|
157
|
-
if (!profiles[key])
|
|
158
|
-
return key;
|
|
159
|
-
idx++;
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
function normalizeShell(value) {
|
|
163
|
-
if (!value)
|
|
164
|
-
return null;
|
|
165
|
-
const raw = String(value).trim().toLowerCase();
|
|
166
|
-
if (!raw)
|
|
167
|
-
return null;
|
|
168
|
-
if (raw === "bash")
|
|
169
|
-
return "bash";
|
|
170
|
-
if (raw === "zsh")
|
|
171
|
-
return "zsh";
|
|
172
|
-
if (raw === "fish")
|
|
173
|
-
return "fish";
|
|
174
|
-
return null;
|
|
175
|
-
}
|
|
176
|
-
function detectShell(explicitShell) {
|
|
177
|
-
if (explicitShell)
|
|
178
|
-
return normalizeShell(explicitShell);
|
|
179
|
-
const envShell = process.env.SHELL ? path.basename(process.env.SHELL) : "";
|
|
180
|
-
return normalizeShell(envShell);
|
|
181
|
-
}
|
|
182
|
-
function getShellRcPath(shellName) {
|
|
183
|
-
if (shellName === "bash")
|
|
184
|
-
return path.join(os.homedir(), ".bashrc");
|
|
185
|
-
if (shellName === "zsh")
|
|
186
|
-
return path.join(os.homedir(), ".zshrc");
|
|
187
|
-
if (shellName === "fish") {
|
|
188
|
-
return path.join(os.homedir(), ".config", "fish", "config.fish");
|
|
189
|
-
}
|
|
190
|
-
return null;
|
|
191
|
-
}
|
|
192
|
-
function getShellSnippet(shellName) {
|
|
193
|
-
if (shellName === "fish") {
|
|
194
|
-
return [
|
|
195
|
-
"function codenv",
|
|
196
|
-
" if test (count $argv) -ge 1",
|
|
197
|
-
" switch $argv[1]",
|
|
198
|
-
" case use unset auto",
|
|
199
|
-
" command codenv $argv | source",
|
|
200
|
-
" case '*'",
|
|
201
|
-
" command codenv $argv",
|
|
202
|
-
" end",
|
|
203
|
-
" else",
|
|
204
|
-
" command codenv",
|
|
205
|
-
" end",
|
|
206
|
-
"end",
|
|
207
|
-
"codenv auto",
|
|
208
|
-
].join("\n");
|
|
209
|
-
}
|
|
210
|
-
return [
|
|
211
|
-
"codenv() {",
|
|
212
|
-
" if [ \"$1\" = \"use\" ] || [ \"$1\" = \"unset\" ] || [ \"$1\" = \"auto\" ]; then",
|
|
213
|
-
" source <(command codenv \"$@\")",
|
|
214
|
-
" else",
|
|
215
|
-
" command codenv \"$@\"",
|
|
216
|
-
" fi",
|
|
217
|
-
"}",
|
|
218
|
-
"codenv auto",
|
|
219
|
-
].join("\n");
|
|
220
|
-
}
|
|
221
|
-
function escapeRegExp(value) {
|
|
222
|
-
return String(value).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
223
|
-
}
|
|
224
|
-
function upsertShellSnippet(rcPath, snippet) {
|
|
225
|
-
const markerStart = "# >>> codenv >>>";
|
|
226
|
-
const markerEnd = "# <<< codenv <<<";
|
|
227
|
-
const block = `${markerStart}\n${snippet}\n${markerEnd}`;
|
|
228
|
-
const existing = fs.existsSync(rcPath) ? fs.readFileSync(rcPath, "utf8") : "";
|
|
229
|
-
let updated = "";
|
|
230
|
-
if (existing.includes(markerStart) && existing.includes(markerEnd)) {
|
|
231
|
-
const re = new RegExp(`${escapeRegExp(markerStart)}[\\s\\S]*?${escapeRegExp(markerEnd)}`);
|
|
232
|
-
updated = existing.replace(re, block);
|
|
233
|
-
}
|
|
234
|
-
else if (existing.trim().length === 0) {
|
|
235
|
-
updated = `${block}\n`;
|
|
236
|
-
}
|
|
237
|
-
else {
|
|
238
|
-
const sep = existing.endsWith("\n") ? "\n" : "\n\n";
|
|
239
|
-
updated = `${existing}${sep}${block}\n`;
|
|
240
|
-
}
|
|
241
|
-
const dir = path.dirname(rcPath);
|
|
242
|
-
if (!fs.existsSync(dir)) {
|
|
243
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
244
|
-
}
|
|
245
|
-
fs.writeFileSync(rcPath, updated, "utf8");
|
|
246
|
-
}
|
|
247
|
-
function parseInitArgs(args) {
|
|
248
|
-
const result = { apply: true, print: false, shell: null };
|
|
249
|
-
for (let i = 0; i < args.length; i++) {
|
|
250
|
-
const arg = args[i];
|
|
251
|
-
if (arg === "--apply") {
|
|
252
|
-
result.apply = true;
|
|
253
|
-
result.print = false;
|
|
254
|
-
continue;
|
|
255
|
-
}
|
|
256
|
-
if (arg === "--print") {
|
|
257
|
-
result.print = true;
|
|
258
|
-
result.apply = false;
|
|
259
|
-
continue;
|
|
260
|
-
}
|
|
261
|
-
if (arg === "--shell") {
|
|
262
|
-
const val = args[i + 1];
|
|
263
|
-
if (!val)
|
|
264
|
-
throw new Error("Missing value for --shell.");
|
|
265
|
-
result.shell = val;
|
|
266
|
-
i++;
|
|
267
|
-
continue;
|
|
268
|
-
}
|
|
269
|
-
if (arg.startsWith("--shell=")) {
|
|
270
|
-
result.shell = arg.slice("--shell=".length);
|
|
271
|
-
continue;
|
|
272
|
-
}
|
|
273
|
-
throw new Error(`Unknown init argument: ${arg}`);
|
|
274
|
-
}
|
|
275
|
-
return result;
|
|
276
|
-
}
|
|
277
|
-
function profileMatchesType(profile, type) {
|
|
278
|
-
if (!profile)
|
|
279
|
-
return false;
|
|
280
|
-
if (!profile.type)
|
|
281
|
-
return true;
|
|
282
|
-
const t = normalizeType(profile.type);
|
|
283
|
-
if (!t)
|
|
284
|
-
return false;
|
|
285
|
-
return t === type;
|
|
286
|
-
}
|
|
287
|
-
function getTypeDefaultUnsetKeys(type) {
|
|
288
|
-
return DEFAULT_UNSET_KEYS[type] || [];
|
|
289
|
-
}
|
|
290
|
-
function getFilteredUnsetKeys(config, activeType) {
|
|
291
|
-
const keys = Array.isArray(config.unset) ? config.unset : [];
|
|
292
|
-
if (!activeType)
|
|
293
|
-
return [...keys];
|
|
294
|
-
const otherDefaults = new Set(DEFAULT_PROFILE_TYPES.filter((type) => type !== activeType).flatMap((type) => DEFAULT_UNSET_KEYS[type]));
|
|
295
|
-
return keys.filter((key) => !otherDefaults.has(key));
|
|
296
|
-
}
|
|
297
|
-
function resolvePath(p) {
|
|
298
|
-
if (!p)
|
|
299
|
-
return null;
|
|
300
|
-
if (p.startsWith("~")) {
|
|
301
|
-
return path.join(os.homedir(), p.slice(1));
|
|
302
|
-
}
|
|
303
|
-
if (path.isAbsolute(p))
|
|
304
|
-
return p;
|
|
305
|
-
return path.resolve(process.cwd(), p);
|
|
306
|
-
}
|
|
307
|
-
function getDefaultConfigPath() {
|
|
308
|
-
return path.join(os.homedir(), ".config", "code-env", "config.json");
|
|
309
|
-
}
|
|
310
|
-
function findConfigPath(explicitPath) {
|
|
311
|
-
if (explicitPath) {
|
|
312
|
-
const resolved = resolvePath(explicitPath);
|
|
313
|
-
if (fs.existsSync(resolved))
|
|
314
|
-
return resolved;
|
|
315
|
-
return resolved; // let readConfig raise a helpful error
|
|
316
|
-
}
|
|
317
|
-
if (process.env.CODE_ENV_CONFIG) {
|
|
318
|
-
const fromEnv = resolvePath(process.env.CODE_ENV_CONFIG);
|
|
319
|
-
if (fs.existsSync(fromEnv))
|
|
320
|
-
return fromEnv;
|
|
321
|
-
return fromEnv;
|
|
322
|
-
}
|
|
323
|
-
return getDefaultConfigPath();
|
|
324
|
-
}
|
|
325
|
-
function findConfigPathForWrite(explicitPath) {
|
|
326
|
-
if (explicitPath)
|
|
327
|
-
return resolvePath(explicitPath);
|
|
328
|
-
if (process.env.CODE_ENV_CONFIG)
|
|
329
|
-
return resolvePath(process.env.CODE_ENV_CONFIG);
|
|
330
|
-
return getDefaultConfigPath();
|
|
331
|
-
}
|
|
332
|
-
function readConfig(configPath) {
|
|
333
|
-
if (!configPath) {
|
|
334
|
-
throw new Error("No config file found. Use --config or set CODE_ENV_CONFIG.");
|
|
335
|
-
}
|
|
336
|
-
if (!fs.existsSync(configPath)) {
|
|
337
|
-
throw new Error(`Config file not found: ${configPath}`);
|
|
338
|
-
}
|
|
339
|
-
const raw = fs.readFileSync(configPath, "utf8");
|
|
340
|
-
try {
|
|
341
|
-
return JSON.parse(raw);
|
|
342
|
-
}
|
|
343
|
-
catch (err) {
|
|
344
|
-
throw new Error(`Invalid JSON in config: ${configPath}`);
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
function readConfigIfExists(configPath) {
|
|
348
|
-
if (!configPath || !fs.existsSync(configPath)) {
|
|
349
|
-
return { unset: [], profiles: {} };
|
|
350
|
-
}
|
|
351
|
-
return readConfig(configPath);
|
|
352
|
-
}
|
|
353
|
-
function writeConfig(configPath, config) {
|
|
354
|
-
if (!configPath) {
|
|
355
|
-
throw new Error("Missing config path for write.");
|
|
356
|
-
}
|
|
357
|
-
const dir = path.dirname(configPath);
|
|
358
|
-
if (!fs.existsSync(dir)) {
|
|
359
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
360
|
-
}
|
|
361
|
-
const data = JSON.stringify(config, null, 2);
|
|
362
|
-
fs.writeFileSync(configPath, `${data}\n`, "utf8");
|
|
363
|
-
}
|
|
364
|
-
function shellEscape(value) {
|
|
365
|
-
const str = String(value);
|
|
366
|
-
return `'${str.replace(/'/g, `'\\''`)}'`;
|
|
367
|
-
}
|
|
368
|
-
function expandEnv(input) {
|
|
369
|
-
if (!input)
|
|
370
|
-
return input;
|
|
371
|
-
let out = String(input);
|
|
372
|
-
if (out.startsWith("~")) {
|
|
373
|
-
out = path.join(os.homedir(), out.slice(1));
|
|
374
|
-
}
|
|
375
|
-
out = out.replace(/\$\{([^}]+)\}/g, (_, key) => process.env[key] || "");
|
|
376
|
-
out = out.replace(/\$([A-Za-z_][A-Za-z0-9_]*)/g, (_, key) => process.env[key] || "");
|
|
377
|
-
return out;
|
|
378
|
-
}
|
|
379
|
-
function parseAddArgs(args) {
|
|
380
|
-
const result = {
|
|
381
|
-
profile: null,
|
|
382
|
-
pairs: [],
|
|
383
|
-
note: null,
|
|
384
|
-
removeFiles: [],
|
|
385
|
-
commands: [],
|
|
386
|
-
unset: [],
|
|
387
|
-
type: null,
|
|
388
|
-
};
|
|
389
|
-
for (let i = 0; i < args.length; i++) {
|
|
390
|
-
const arg = args[i];
|
|
391
|
-
if (!result.profile && !arg.startsWith("-")) {
|
|
392
|
-
result.profile = arg;
|
|
393
|
-
continue;
|
|
394
|
-
}
|
|
395
|
-
if (arg === "-n" || arg === "--note") {
|
|
396
|
-
const val = args[i + 1];
|
|
397
|
-
if (!val)
|
|
398
|
-
throw new Error("Missing value for --note.");
|
|
399
|
-
result.note = val;
|
|
400
|
-
i++;
|
|
401
|
-
continue;
|
|
402
|
-
}
|
|
403
|
-
if (arg.startsWith("--note=")) {
|
|
404
|
-
result.note = arg.slice("--note=".length);
|
|
405
|
-
continue;
|
|
406
|
-
}
|
|
407
|
-
if (arg === "-t" || arg === "--type") {
|
|
408
|
-
const val = args[i + 1];
|
|
409
|
-
if (!val)
|
|
410
|
-
throw new Error("Missing value for --type.");
|
|
411
|
-
result.type = val;
|
|
412
|
-
i++;
|
|
413
|
-
continue;
|
|
414
|
-
}
|
|
415
|
-
if (arg.startsWith("--type=")) {
|
|
416
|
-
result.type = arg.slice("--type=".length);
|
|
417
|
-
continue;
|
|
418
|
-
}
|
|
419
|
-
if (arg === "-r" || arg === "--remove-file") {
|
|
420
|
-
const val = args[i + 1];
|
|
421
|
-
if (!val)
|
|
422
|
-
throw new Error("Missing value for --remove-file.");
|
|
423
|
-
result.removeFiles.push(val);
|
|
424
|
-
i++;
|
|
425
|
-
continue;
|
|
426
|
-
}
|
|
427
|
-
if (arg.startsWith("--remove-file=")) {
|
|
428
|
-
result.removeFiles.push(arg.slice("--remove-file=".length));
|
|
429
|
-
continue;
|
|
430
|
-
}
|
|
431
|
-
if (arg === "-x" || arg === "--command") {
|
|
432
|
-
const val = args[i + 1];
|
|
433
|
-
if (!val)
|
|
434
|
-
throw new Error("Missing value for --command.");
|
|
435
|
-
result.commands.push(val);
|
|
436
|
-
i++;
|
|
437
|
-
continue;
|
|
438
|
-
}
|
|
439
|
-
if (arg.startsWith("--command=")) {
|
|
440
|
-
result.commands.push(arg.slice("--command=".length));
|
|
441
|
-
continue;
|
|
442
|
-
}
|
|
443
|
-
if (arg === "-u" || arg === "--unset") {
|
|
444
|
-
const val = args[i + 1];
|
|
445
|
-
if (!val)
|
|
446
|
-
throw new Error("Missing value for --unset.");
|
|
447
|
-
result.unset.push(val);
|
|
448
|
-
i++;
|
|
449
|
-
continue;
|
|
450
|
-
}
|
|
451
|
-
if (arg.startsWith("--unset=")) {
|
|
452
|
-
result.unset.push(arg.slice("--unset=".length));
|
|
453
|
-
continue;
|
|
454
|
-
}
|
|
455
|
-
if (arg.includes("=")) {
|
|
456
|
-
result.pairs.push(arg);
|
|
457
|
-
continue;
|
|
458
|
-
}
|
|
459
|
-
throw new Error(`Unknown add argument: ${arg}`);
|
|
460
|
-
}
|
|
461
|
-
if (!result.profile) {
|
|
462
|
-
throw new Error("Missing profile name.");
|
|
463
|
-
}
|
|
464
|
-
if (result.type) {
|
|
465
|
-
const normalized = normalizeType(result.type);
|
|
466
|
-
if (!normalized) {
|
|
467
|
-
throw new Error(`Unknown type: ${result.type}`);
|
|
468
|
-
}
|
|
469
|
-
result.type = normalized;
|
|
470
|
-
}
|
|
471
|
-
return result;
|
|
472
|
-
}
|
|
473
|
-
function createReadline() {
|
|
474
|
-
return readline.createInterface({
|
|
475
|
-
input: process.stdin,
|
|
476
|
-
output: process.stdout,
|
|
477
|
-
});
|
|
478
|
-
}
|
|
479
|
-
function ask(rl, question) {
|
|
480
|
-
return new Promise((resolve) => {
|
|
481
|
-
rl.question(question, (answer) => resolve(answer));
|
|
482
|
-
});
|
|
483
|
-
}
|
|
484
|
-
async function askRequired(rl, question) {
|
|
485
|
-
while (true) {
|
|
486
|
-
const answer = String(await ask(rl, question)).trim();
|
|
487
|
-
if (answer)
|
|
488
|
-
return answer;
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
async function askConfirm(rl, question) {
|
|
492
|
-
const answer = String(await ask(rl, question)).trim().toLowerCase();
|
|
493
|
-
return answer === "y" || answer === "yes";
|
|
494
|
-
}
|
|
495
|
-
async function askType(rl) {
|
|
496
|
-
while (true) {
|
|
497
|
-
const answer = String(await ask(rl, "Select type (1=codex, 2=claude): "))
|
|
498
|
-
.trim()
|
|
499
|
-
.toLowerCase();
|
|
500
|
-
if (answer === "1")
|
|
501
|
-
return "codex";
|
|
502
|
-
if (answer === "2")
|
|
503
|
-
return "claude";
|
|
504
|
-
const normalized = answer.replace(/[\s-]+/g, "");
|
|
505
|
-
if (normalized === "codex")
|
|
506
|
-
return "codex";
|
|
507
|
-
if (normalized === "claude" ||
|
|
508
|
-
normalized === "claudecode" ||
|
|
509
|
-
normalized === "cc")
|
|
510
|
-
return "claude";
|
|
511
|
-
}
|
|
512
|
-
}
|
|
513
|
-
async function askProfileName(rl, config, defaultName, type) {
|
|
514
|
-
while (true) {
|
|
515
|
-
const answer = String(await ask(rl, `Profile name (default: ${defaultName}): `)).trim();
|
|
516
|
-
const baseName = answer || defaultName;
|
|
517
|
-
if (!baseName)
|
|
518
|
-
continue;
|
|
519
|
-
const matches = findProfileKeysByName(config, baseName, type);
|
|
520
|
-
if (matches.length === 0) {
|
|
521
|
-
return { name: baseName, key: null };
|
|
522
|
-
}
|
|
523
|
-
if (matches.length === 1) {
|
|
524
|
-
const overwrite = String(await ask(rl, `Profile "${baseName}" exists. Overwrite? (y/N): `))
|
|
525
|
-
.trim()
|
|
526
|
-
.toLowerCase();
|
|
527
|
-
if (overwrite === "y" || overwrite === "yes") {
|
|
528
|
-
return { name: baseName, key: matches[0] };
|
|
529
|
-
}
|
|
530
|
-
continue;
|
|
531
|
-
}
|
|
532
|
-
console.log(`Multiple profiles named "${baseName}" for type "${type}". ` +
|
|
533
|
-
`Use a unique name or update by key in config.`);
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
|
-
async function runInteractiveAdd(configPath) {
|
|
537
|
-
const config = readConfigIfExists(configPath);
|
|
538
|
-
const rl = createReadline();
|
|
539
|
-
try {
|
|
540
|
-
const type = await askType(rl);
|
|
541
|
-
const defaultName = "default";
|
|
542
|
-
const profileInfo = await askProfileName(rl, config, defaultName, type);
|
|
543
|
-
const profileKey = profileInfo.key || generateProfileKey(config);
|
|
544
|
-
const baseUrl = await askRequired(rl, "Base URL (required): ");
|
|
545
|
-
const apiKey = await askRequired(rl, "API key (required): ");
|
|
546
|
-
if (!config.profiles || typeof config.profiles !== "object") {
|
|
547
|
-
config.profiles = {};
|
|
548
|
-
}
|
|
549
|
-
if (!config.profiles[profileKey]) {
|
|
550
|
-
config.profiles[profileKey] = {};
|
|
551
|
-
}
|
|
552
|
-
const profile = config.profiles[profileKey];
|
|
553
|
-
profile.name = profileInfo.name;
|
|
554
|
-
profile.type = type;
|
|
555
|
-
if (!profile.env || typeof profile.env !== "object") {
|
|
556
|
-
profile.env = {};
|
|
557
|
-
}
|
|
558
|
-
if (type === "codex") {
|
|
559
|
-
profile.env.OPENAI_BASE_URL = baseUrl;
|
|
560
|
-
profile.env.OPENAI_API_KEY = apiKey;
|
|
561
|
-
}
|
|
562
|
-
else {
|
|
563
|
-
profile.env.ANTHROPIC_BASE_URL = baseUrl;
|
|
564
|
-
profile.env.ANTHROPIC_API_KEY = apiKey;
|
|
565
|
-
console.log("Note: ANTHROPIC_AUTH_TOKEN will be set to the same value when applying.");
|
|
566
|
-
}
|
|
567
|
-
writeConfig(configPath, config);
|
|
568
|
-
console.log(`Updated config: ${configPath}`);
|
|
569
|
-
}
|
|
570
|
-
finally {
|
|
571
|
-
rl.close();
|
|
572
|
-
}
|
|
573
|
-
}
|
|
574
|
-
function addConfig(config, addArgs) {
|
|
575
|
-
if (!config.profiles || typeof config.profiles !== "object") {
|
|
576
|
-
config.profiles = {};
|
|
577
|
-
}
|
|
578
|
-
let targetKey = null;
|
|
579
|
-
let matchedByName = false;
|
|
580
|
-
if (Object.prototype.hasOwnProperty.call(config.profiles, addArgs.profile)) {
|
|
581
|
-
targetKey = addArgs.profile;
|
|
582
|
-
}
|
|
583
|
-
else {
|
|
584
|
-
const matches = findProfileKeysByName(config, addArgs.profile, addArgs.type);
|
|
585
|
-
if (matches.length === 1) {
|
|
586
|
-
targetKey = matches[0];
|
|
587
|
-
matchedByName = true;
|
|
588
|
-
}
|
|
589
|
-
else if (matches.length > 1) {
|
|
590
|
-
const hint = addArgs.type
|
|
591
|
-
? `Use profile key: ${matches.join(", ")}`
|
|
592
|
-
: `Use: codenv add --type <type> ${addArgs.profile} ... (or profile key: ${matches.join(", ")})`;
|
|
593
|
-
throw new Error(`Multiple profiles named "${addArgs.profile}". ${hint}`);
|
|
594
|
-
}
|
|
595
|
-
}
|
|
596
|
-
if (!targetKey) {
|
|
597
|
-
targetKey = generateProfileKey(config);
|
|
598
|
-
matchedByName = true;
|
|
599
|
-
}
|
|
600
|
-
if (!config.profiles[targetKey]) {
|
|
601
|
-
config.profiles[targetKey] = {};
|
|
602
|
-
}
|
|
603
|
-
const profile = config.profiles[targetKey];
|
|
604
|
-
if (!profile.env || typeof profile.env !== "object") {
|
|
605
|
-
profile.env = {};
|
|
606
|
-
}
|
|
607
|
-
if (matchedByName) {
|
|
608
|
-
profile.name = addArgs.profile;
|
|
609
|
-
}
|
|
610
|
-
if (addArgs.type) {
|
|
611
|
-
profile.type = addArgs.type;
|
|
612
|
-
}
|
|
613
|
-
for (const pair of addArgs.pairs) {
|
|
614
|
-
const idx = pair.indexOf("=");
|
|
615
|
-
if (idx <= 0)
|
|
616
|
-
throw new Error(`Invalid KEY=VALUE: ${pair}`);
|
|
617
|
-
const key = pair.slice(0, idx);
|
|
618
|
-
const value = pair.slice(idx + 1);
|
|
619
|
-
profile.env[key] = value;
|
|
620
|
-
}
|
|
621
|
-
if (addArgs.note !== null && addArgs.note !== undefined) {
|
|
622
|
-
profile.note = addArgs.note;
|
|
623
|
-
}
|
|
624
|
-
if (addArgs.removeFiles.length > 0) {
|
|
625
|
-
if (!Array.isArray(profile.removeFiles))
|
|
626
|
-
profile.removeFiles = [];
|
|
627
|
-
for (const p of addArgs.removeFiles) {
|
|
628
|
-
if (!profile.removeFiles.includes(p))
|
|
629
|
-
profile.removeFiles.push(p);
|
|
630
|
-
}
|
|
631
|
-
}
|
|
632
|
-
if (addArgs.commands.length > 0) {
|
|
633
|
-
if (!Array.isArray(profile.commands))
|
|
634
|
-
profile.commands = [];
|
|
635
|
-
for (const cmd of addArgs.commands) {
|
|
636
|
-
if (!profile.commands.includes(cmd))
|
|
637
|
-
profile.commands.push(cmd);
|
|
638
|
-
}
|
|
639
|
-
}
|
|
640
|
-
if (addArgs.unset.length > 0) {
|
|
641
|
-
if (!Array.isArray(config.unset))
|
|
642
|
-
config.unset = [];
|
|
643
|
-
for (const key of addArgs.unset) {
|
|
644
|
-
if (!config.unset.includes(key))
|
|
645
|
-
config.unset.push(key);
|
|
646
|
-
}
|
|
647
|
-
}
|
|
648
|
-
return config;
|
|
649
|
-
}
|
|
650
|
-
function buildListRows(config) {
|
|
651
|
-
const profiles = config && config.profiles ? config.profiles : {};
|
|
652
|
-
const entries = Object.entries(profiles);
|
|
653
|
-
if (entries.length === 0)
|
|
654
|
-
return [];
|
|
655
|
-
const defaults = getResolvedDefaultProfileKeys(config);
|
|
656
|
-
const rows = entries.map(([key, profile]) => {
|
|
657
|
-
const safeProfile = profile || {};
|
|
658
|
-
const rawType = safeProfile.type ? String(safeProfile.type) : "";
|
|
659
|
-
const normalizedType = normalizeType(rawType);
|
|
660
|
-
const type = normalizedType || rawType || "-";
|
|
661
|
-
const displayName = getProfileDisplayName(key, safeProfile);
|
|
662
|
-
const note = safeProfile.note ? String(safeProfile.note) : "";
|
|
663
|
-
const defaultTypes = DEFAULT_PROFILE_TYPES.filter((profileType) => defaults[profileType] === key);
|
|
664
|
-
const defaultLabel = defaultTypes.length > 0 ? "default" : "";
|
|
665
|
-
const noteParts = [];
|
|
666
|
-
if (defaultLabel)
|
|
667
|
-
noteParts.push(defaultLabel);
|
|
668
|
-
if (note)
|
|
669
|
-
noteParts.push(note);
|
|
670
|
-
const noteText = noteParts.join(" | ");
|
|
671
|
-
const active = envMatchesProfile(safeProfile);
|
|
672
|
-
return { key, name: displayName, type, note: noteText, active };
|
|
673
|
-
});
|
|
674
|
-
rows.sort((a, b) => {
|
|
675
|
-
const nameCmp = a.name.localeCompare(b.name);
|
|
676
|
-
if (nameCmp !== 0)
|
|
677
|
-
return nameCmp;
|
|
678
|
-
const typeCmp = a.type.localeCompare(b.type);
|
|
679
|
-
if (typeCmp !== 0)
|
|
680
|
-
return typeCmp;
|
|
681
|
-
return a.key.localeCompare(b.key);
|
|
682
|
-
});
|
|
683
|
-
return rows;
|
|
684
|
-
}
|
|
685
|
-
function printList(config) {
|
|
686
|
-
const rows = buildListRows(config);
|
|
687
|
-
if (rows.length === 0) {
|
|
688
|
-
console.log("(no profiles found)");
|
|
689
|
-
return;
|
|
690
|
-
}
|
|
691
|
-
const headerName = "PROFILE";
|
|
692
|
-
const headerType = "TYPE";
|
|
693
|
-
const headerNote = "NOTE";
|
|
694
|
-
const nameWidth = Math.max(headerName.length, ...rows.map((row) => row.name.length));
|
|
695
|
-
const typeWidth = Math.max(headerType.length, ...rows.map((row) => row.type.length));
|
|
696
|
-
const noteWidth = Math.max(headerNote.length, ...rows.map((row) => row.note.length));
|
|
697
|
-
const formatRow = (name, type, note) => `${name.padEnd(nameWidth)} ${type.padEnd(typeWidth)} ${note.padEnd(noteWidth)}`;
|
|
698
|
-
console.log(formatRow(headerName, headerType, headerNote));
|
|
699
|
-
console.log(formatRow("-".repeat(nameWidth), "-".repeat(typeWidth), "-".repeat(noteWidth)));
|
|
700
|
-
for (const row of rows) {
|
|
701
|
-
const line = formatRow(row.name, row.type, row.note);
|
|
702
|
-
if (row.active) {
|
|
703
|
-
console.log(`\x1b[32m${line}\x1b[0m`);
|
|
704
|
-
}
|
|
705
|
-
else {
|
|
706
|
-
console.log(line);
|
|
707
|
-
}
|
|
708
|
-
}
|
|
709
|
-
}
|
|
710
|
-
async function runInteractiveUse(config) {
|
|
711
|
-
if (!process.stdin.isTTY || !process.stderr.isTTY) {
|
|
712
|
-
throw new Error("Interactive selection requires a TTY. Provide a profile name.");
|
|
713
|
-
}
|
|
714
|
-
const rows = buildListRows(config);
|
|
715
|
-
if (rows.length === 0) {
|
|
716
|
-
throw new Error("No profiles found.");
|
|
717
|
-
}
|
|
718
|
-
const nameTypeCounts = new Map();
|
|
719
|
-
for (const row of rows) {
|
|
720
|
-
const key = `${row.name}||${row.type}`;
|
|
721
|
-
nameTypeCounts.set(key, (nameTypeCounts.get(key) || 0) + 1);
|
|
722
|
-
}
|
|
723
|
-
const displayRows = rows.map((row) => {
|
|
724
|
-
const key = `${row.name}||${row.type}`;
|
|
725
|
-
const displayName = (nameTypeCounts.get(key) || 0) > 1 ? `${row.name} [${row.key}]` : row.name;
|
|
726
|
-
const noteText = row.note;
|
|
727
|
-
const profile = config.profiles && config.profiles[row.key];
|
|
728
|
-
const inferredType = inferProfileType(row.key, profile, null);
|
|
729
|
-
const resolvedType = inferredType || normalizeType(row.type) || null;
|
|
730
|
-
return { ...row, displayName, noteText, resolvedType };
|
|
731
|
-
});
|
|
732
|
-
const headerName = "PROFILE";
|
|
733
|
-
const headerType = "TYPE";
|
|
734
|
-
const headerNote = "NOTE";
|
|
735
|
-
const nameWidth = Math.max(headerName.length, ...displayRows.map((row) => row.displayName.length));
|
|
736
|
-
const typeWidth = Math.max(headerType.length, ...displayRows.map((row) => row.type.length));
|
|
737
|
-
const noteWidth = Math.max(headerNote.length, ...displayRows.map((row) => row.noteText.length));
|
|
738
|
-
const formatRow = (name, type, note) => `${name.padEnd(nameWidth)} ${type.padEnd(typeWidth)} ${note.padEnd(noteWidth)}`;
|
|
739
|
-
const activeKeys = new Set();
|
|
740
|
-
const keyToType = new Map();
|
|
741
|
-
for (const row of displayRows) {
|
|
742
|
-
keyToType.set(row.key, row.resolvedType || null);
|
|
743
|
-
if (row.active)
|
|
744
|
-
activeKeys.add(row.key);
|
|
745
|
-
}
|
|
746
|
-
let index = displayRows.findIndex((row) => row.active);
|
|
747
|
-
if (index < 0)
|
|
748
|
-
index = 0;
|
|
749
|
-
const ANSI_CLEAR = "\x1b[2J\x1b[H";
|
|
750
|
-
const ANSI_HIDE_CURSOR = "\x1b[?25l";
|
|
751
|
-
const ANSI_SHOW_CURSOR = "\x1b[?25h";
|
|
752
|
-
const ANSI_INVERT = "\x1b[7m";
|
|
753
|
-
const ANSI_GREEN = "\x1b[32m";
|
|
754
|
-
const ANSI_RESET = "\x1b[0m";
|
|
755
|
-
const render = () => {
|
|
756
|
-
const lines = [];
|
|
757
|
-
lines.push("Select profile (up/down, Enter to apply, q to exit)");
|
|
758
|
-
lines.push(formatRow(headerName, headerType, headerNote));
|
|
759
|
-
lines.push(formatRow("-".repeat(nameWidth), "-".repeat(typeWidth), "-".repeat(noteWidth)));
|
|
760
|
-
for (let i = 0; i < displayRows.length; i++) {
|
|
761
|
-
const row = displayRows[i];
|
|
762
|
-
const isActive = activeKeys.has(row.key);
|
|
763
|
-
const line = ` ${formatRow(row.displayName, row.type, row.noteText)}`;
|
|
764
|
-
if (i === index) {
|
|
765
|
-
const prefix = isActive ? `${ANSI_INVERT}${ANSI_GREEN}` : ANSI_INVERT;
|
|
766
|
-
lines.push(`${prefix}${line}${ANSI_RESET}`);
|
|
767
|
-
}
|
|
768
|
-
else {
|
|
769
|
-
if (isActive) {
|
|
770
|
-
lines.push(`${ANSI_GREEN}${line}${ANSI_RESET}`);
|
|
771
|
-
}
|
|
772
|
-
else {
|
|
773
|
-
lines.push(line);
|
|
774
|
-
}
|
|
775
|
-
}
|
|
776
|
-
}
|
|
777
|
-
process.stderr.write(`${ANSI_CLEAR}${ANSI_HIDE_CURSOR}${lines.join("\n")}\n`);
|
|
778
|
-
};
|
|
779
|
-
return await new Promise((resolve) => {
|
|
780
|
-
readline.emitKeypressEvents(process.stdin);
|
|
781
|
-
const stdin = process.stdin;
|
|
782
|
-
const wasRaw = !!stdin.isRaw;
|
|
783
|
-
stdin.setRawMode(true);
|
|
784
|
-
stdin.resume();
|
|
785
|
-
const cleanup = () => {
|
|
786
|
-
stdin.removeListener("keypress", onKeypress);
|
|
787
|
-
if (!wasRaw)
|
|
788
|
-
stdin.setRawMode(false);
|
|
789
|
-
stdin.pause();
|
|
790
|
-
process.stderr.write(`${ANSI_RESET}${ANSI_SHOW_CURSOR}`);
|
|
791
|
-
};
|
|
792
|
-
const finish = () => {
|
|
793
|
-
cleanup();
|
|
794
|
-
resolve();
|
|
795
|
-
};
|
|
796
|
-
const onKeypress = (str, key) => {
|
|
797
|
-
if (key && key.ctrl && key.name === "c") {
|
|
798
|
-
finish();
|
|
799
|
-
return;
|
|
800
|
-
}
|
|
801
|
-
if (key && key.name === "up") {
|
|
802
|
-
index = (index - 1 + displayRows.length) % displayRows.length;
|
|
803
|
-
render();
|
|
804
|
-
return;
|
|
805
|
-
}
|
|
806
|
-
if (key && key.name === "down") {
|
|
807
|
-
index = (index + 1) % displayRows.length;
|
|
808
|
-
render();
|
|
809
|
-
return;
|
|
810
|
-
}
|
|
811
|
-
if (key && key.name === "home") {
|
|
812
|
-
index = 0;
|
|
813
|
-
render();
|
|
814
|
-
return;
|
|
815
|
-
}
|
|
816
|
-
if (key && key.name === "end") {
|
|
817
|
-
index = displayRows.length - 1;
|
|
818
|
-
render();
|
|
819
|
-
return;
|
|
820
|
-
}
|
|
821
|
-
if (key && (key.name === "return" || key.name === "enter")) {
|
|
822
|
-
const selectedKey = displayRows[index].key;
|
|
823
|
-
const selectedType = keyToType.get(selectedKey) || null;
|
|
824
|
-
if (selectedType) {
|
|
825
|
-
for (const activeKey of Array.from(activeKeys)) {
|
|
826
|
-
if (keyToType.get(activeKey) === selectedType) {
|
|
827
|
-
activeKeys.delete(activeKey);
|
|
828
|
-
}
|
|
829
|
-
}
|
|
830
|
-
}
|
|
831
|
-
activeKeys.add(selectedKey);
|
|
832
|
-
printUse(config, selectedKey, null);
|
|
833
|
-
render();
|
|
834
|
-
return;
|
|
835
|
-
}
|
|
836
|
-
if (key && key.name === "escape") {
|
|
837
|
-
finish();
|
|
838
|
-
return;
|
|
839
|
-
}
|
|
840
|
-
if (str === "q" || str === "Q") {
|
|
841
|
-
finish();
|
|
842
|
-
}
|
|
843
|
-
};
|
|
844
|
-
stdin.on("keypress", onKeypress);
|
|
845
|
-
render();
|
|
846
|
-
});
|
|
847
|
-
}
|
|
848
|
-
function printShow(config, profileName) {
|
|
849
|
-
const profile = config.profiles && config.profiles[profileName];
|
|
850
|
-
if (!profile) {
|
|
851
|
-
throw new Error(`Unknown profile: ${profileName}`);
|
|
852
|
-
}
|
|
853
|
-
console.log(JSON.stringify(profile, null, 2));
|
|
854
|
-
}
|
|
855
|
-
function printUnset(config) {
|
|
856
|
-
const keySet = new Set();
|
|
857
|
-
if (Array.isArray(config.unset)) {
|
|
858
|
-
for (const key of config.unset)
|
|
859
|
-
keySet.add(key);
|
|
860
|
-
}
|
|
861
|
-
for (const type of DEFAULT_PROFILE_TYPES) {
|
|
862
|
-
for (const key of getTypeDefaultUnsetKeys(type))
|
|
863
|
-
keySet.add(key);
|
|
864
|
-
}
|
|
865
|
-
if (keySet.size === 0)
|
|
866
|
-
return;
|
|
867
|
-
const lines = Array.from(keySet, (key) => `unset ${key}`);
|
|
868
|
-
console.log(lines.join("\n"));
|
|
869
|
-
}
|
|
870
|
-
function resolveProfileName(config, params) {
|
|
871
|
-
if (!params || params.length === 0) {
|
|
872
|
-
throw new Error("Missing profile name.");
|
|
873
|
-
}
|
|
874
|
-
if (params.length >= 2) {
|
|
875
|
-
const maybeType = normalizeType(params[0]);
|
|
876
|
-
if (maybeType) {
|
|
877
|
-
const name = params[1];
|
|
878
|
-
return resolveProfileByType(config, maybeType, name, params[0]);
|
|
879
|
-
}
|
|
880
|
-
}
|
|
881
|
-
const name = params[0];
|
|
882
|
-
const profiles = config && config.profiles ? config.profiles : {};
|
|
883
|
-
if (profiles[name])
|
|
884
|
-
return name;
|
|
885
|
-
const matches = findProfileKeysByName(config, name, null);
|
|
886
|
-
if (matches.length === 1)
|
|
887
|
-
return matches[0];
|
|
888
|
-
if (matches.length > 1) {
|
|
889
|
-
throw new Error(`Multiple profiles named "${name}". ` +
|
|
890
|
-
`Use: codenv <type> ${name} (or profile key: ${matches.join(", ")})`);
|
|
891
|
-
}
|
|
892
|
-
return name;
|
|
893
|
-
}
|
|
894
|
-
function resolveProfileByType(config, type, name, rawType) {
|
|
895
|
-
if (!name)
|
|
896
|
-
throw new Error("Missing profile name.");
|
|
897
|
-
const profiles = config && config.profiles ? config.profiles : {};
|
|
898
|
-
if (profiles[name] && profileMatchesType(profiles[name], type)) {
|
|
899
|
-
return name;
|
|
900
|
-
}
|
|
901
|
-
const matches = findProfileKeysByName(config, name, type);
|
|
902
|
-
if (matches.length === 1)
|
|
903
|
-
return matches[0];
|
|
904
|
-
if (matches.length > 1) {
|
|
905
|
-
throw new Error(`Multiple profiles named "${name}" for type "${type}". ` +
|
|
906
|
-
`Use profile key: ${matches.join(", ")}`);
|
|
907
|
-
}
|
|
908
|
-
if (rawType) {
|
|
909
|
-
const prefixes = [];
|
|
910
|
-
const raw = String(rawType).trim();
|
|
911
|
-
if (raw && raw.toLowerCase() !== type)
|
|
912
|
-
prefixes.push(raw);
|
|
913
|
-
prefixes.push(type);
|
|
914
|
-
for (const prefix of prefixes) {
|
|
915
|
-
for (const sep of ["-", "_", "."]) {
|
|
916
|
-
const candidate = `${prefix}${sep}${name}`;
|
|
917
|
-
if (profiles[candidate] && profileMatchesType(profiles[candidate], type)) {
|
|
918
|
-
return candidate;
|
|
919
|
-
}
|
|
920
|
-
}
|
|
921
|
-
}
|
|
922
|
-
}
|
|
923
|
-
throw new Error(`Unknown profile for type "${type}": ${name}`);
|
|
924
|
-
}
|
|
925
|
-
function getDefaultProfiles(config) {
|
|
926
|
-
const defaults = {};
|
|
927
|
-
if (!config || typeof config !== "object")
|
|
928
|
-
return defaults;
|
|
929
|
-
if (!config.defaultProfiles || typeof config.defaultProfiles !== "object") {
|
|
930
|
-
return defaults;
|
|
931
|
-
}
|
|
932
|
-
for (const [rawType, rawValue] of Object.entries(config.defaultProfiles)) {
|
|
933
|
-
const type = normalizeType(rawType);
|
|
934
|
-
if (!type)
|
|
935
|
-
continue;
|
|
936
|
-
const trimmed = String(rawValue !== null && rawValue !== void 0 ? rawValue : "").trim();
|
|
937
|
-
if (trimmed)
|
|
938
|
-
defaults[type] = trimmed;
|
|
939
|
-
}
|
|
940
|
-
return defaults;
|
|
941
|
-
}
|
|
942
|
-
function deleteDefaultProfileEntry(config, type) {
|
|
943
|
-
if (!config.defaultProfiles || typeof config.defaultProfiles !== "object") {
|
|
944
|
-
return false;
|
|
945
|
-
}
|
|
946
|
-
let changed = false;
|
|
947
|
-
for (const key of Object.keys(config.defaultProfiles)) {
|
|
948
|
-
if (normalizeType(key) === type) {
|
|
949
|
-
delete config.defaultProfiles[key];
|
|
950
|
-
changed = true;
|
|
951
|
-
}
|
|
952
|
-
}
|
|
953
|
-
return changed;
|
|
954
|
-
}
|
|
955
|
-
function resolveDefaultProfileForType(config, type, value) {
|
|
956
|
-
const trimmed = String(value !== null && value !== void 0 ? value : "").trim();
|
|
957
|
-
if (!trimmed)
|
|
958
|
-
return null;
|
|
959
|
-
const params = trimmed.split(/\s+/).filter(Boolean);
|
|
960
|
-
if (params.length === 0)
|
|
961
|
-
return null;
|
|
962
|
-
const explicitType = normalizeType(params[0]);
|
|
963
|
-
if (explicitType) {
|
|
964
|
-
if (explicitType !== type) {
|
|
965
|
-
throw new Error(`Default profile for "${type}" must match type "${type}".`);
|
|
966
|
-
}
|
|
967
|
-
return resolveProfileName(config, params);
|
|
968
|
-
}
|
|
969
|
-
return resolveProfileName(config, [type, ...params]);
|
|
970
|
-
}
|
|
971
|
-
function getResolvedDefaultProfileKeys(config) {
|
|
972
|
-
const defaults = getDefaultProfiles(config);
|
|
973
|
-
const resolved = {};
|
|
974
|
-
for (const type of DEFAULT_PROFILE_TYPES) {
|
|
975
|
-
const value = defaults[type];
|
|
976
|
-
if (!value)
|
|
977
|
-
continue;
|
|
978
|
-
try {
|
|
979
|
-
const profileName = resolveDefaultProfileForType(config, type, value);
|
|
980
|
-
if (profileName)
|
|
981
|
-
resolved[type] = profileName;
|
|
982
|
-
}
|
|
983
|
-
catch (err) {
|
|
984
|
-
// ignore invalid defaults for list output
|
|
985
|
-
}
|
|
986
|
-
}
|
|
987
|
-
return resolved;
|
|
988
|
-
}
|
|
989
|
-
function isEnvValueUnset(value) {
|
|
990
|
-
return value === null || value === undefined || value === "";
|
|
991
|
-
}
|
|
992
|
-
function buildEffectiveEnv(profile, activeType) {
|
|
993
|
-
const env = profile && profile.env ? profile.env : {};
|
|
994
|
-
if (!activeType)
|
|
995
|
-
return env;
|
|
996
|
-
if (activeType !== "claude")
|
|
997
|
-
return env;
|
|
998
|
-
const apiKey = env.ANTHROPIC_API_KEY;
|
|
999
|
-
const authToken = env.ANTHROPIC_AUTH_TOKEN;
|
|
1000
|
-
if (isEnvValueUnset(apiKey) || !isEnvValueUnset(authToken))
|
|
1001
|
-
return env;
|
|
1002
|
-
return { ...env, ANTHROPIC_AUTH_TOKEN: apiKey };
|
|
1003
|
-
}
|
|
1004
|
-
function envMatchesProfile(profile) {
|
|
1005
|
-
if (!profile || !profile.env)
|
|
1006
|
-
return false;
|
|
1007
|
-
for (const key of Object.keys(profile.env)) {
|
|
1008
|
-
const expected = profile.env[key];
|
|
1009
|
-
const actual = process.env[key];
|
|
1010
|
-
if (isEnvValueUnset(expected)) {
|
|
1011
|
-
if (actual !== undefined && actual !== "")
|
|
1012
|
-
return false;
|
|
1013
|
-
continue;
|
|
1014
|
-
}
|
|
1015
|
-
if (actual !== String(expected))
|
|
1016
|
-
return false;
|
|
1017
|
-
}
|
|
1018
|
-
return Object.keys(profile.env).length > 0;
|
|
1019
|
-
}
|
|
1020
|
-
function buildUseLines(config, profileName, requestedType, includeGlobalUnset) {
|
|
1021
|
-
const profile = config.profiles && config.profiles[profileName];
|
|
1022
|
-
if (!profile) {
|
|
1023
|
-
throw new Error(`Unknown profile: ${profileName}`);
|
|
1024
|
-
}
|
|
1025
|
-
const env = profile.env || {};
|
|
1026
|
-
const unsetLines = [];
|
|
1027
|
-
const exportLines = [];
|
|
1028
|
-
const postLines = [];
|
|
1029
|
-
const unsetKeys = new Set();
|
|
1030
|
-
const activeType = inferProfileType(profileName, profile, requestedType);
|
|
1031
|
-
const effectiveEnv = buildEffectiveEnv(profile, activeType);
|
|
1032
|
-
const addUnset = (key) => {
|
|
1033
|
-
if (unsetKeys.has(key))
|
|
1034
|
-
return;
|
|
1035
|
-
if (Object.prototype.hasOwnProperty.call(effectiveEnv, key))
|
|
1036
|
-
return;
|
|
1037
|
-
unsetKeys.add(key);
|
|
1038
|
-
unsetLines.push(`unset ${key}`);
|
|
1039
|
-
};
|
|
1040
|
-
if (includeGlobalUnset) {
|
|
1041
|
-
for (const key of getFilteredUnsetKeys(config, activeType)) {
|
|
1042
|
-
addUnset(key);
|
|
1043
|
-
}
|
|
1044
|
-
}
|
|
1045
|
-
if (activeType) {
|
|
1046
|
-
for (const key of getTypeDefaultUnsetKeys(activeType)) {
|
|
1047
|
-
addUnset(key);
|
|
1048
|
-
}
|
|
1049
|
-
}
|
|
1050
|
-
for (const key of Object.keys(effectiveEnv)) {
|
|
1051
|
-
const value = effectiveEnv[key];
|
|
1052
|
-
if (value === null || value === undefined || value === "") {
|
|
1053
|
-
if (!unsetKeys.has(key)) {
|
|
1054
|
-
unsetKeys.add(key);
|
|
1055
|
-
unsetLines.push(`unset ${key}`);
|
|
1056
|
-
}
|
|
1057
|
-
}
|
|
1058
|
-
else {
|
|
1059
|
-
exportLines.push(`export ${key}=${shellEscape(value)}`);
|
|
1060
|
-
}
|
|
1061
|
-
}
|
|
1062
|
-
if (shouldRemoveCodexAuth(profileName, profile, requestedType)) {
|
|
1063
|
-
postLines.push(`rm -f ${shellEscape(CODEX_AUTH_PATH)}`);
|
|
1064
|
-
}
|
|
1065
|
-
if (Array.isArray(profile.removeFiles)) {
|
|
1066
|
-
for (const p of profile.removeFiles) {
|
|
1067
|
-
const expanded = expandEnv(p);
|
|
1068
|
-
if (expanded)
|
|
1069
|
-
postLines.push(`rm -f ${shellEscape(expanded)}`);
|
|
1070
|
-
}
|
|
1071
|
-
}
|
|
1072
|
-
if (Array.isArray(profile.commands)) {
|
|
1073
|
-
for (const cmd of profile.commands) {
|
|
1074
|
-
if (cmd && String(cmd).trim())
|
|
1075
|
-
postLines.push(String(cmd));
|
|
1076
|
-
}
|
|
1077
|
-
}
|
|
1078
|
-
return [...unsetLines, ...exportLines, ...postLines];
|
|
1079
|
-
}
|
|
1080
|
-
function printUse(config, profileName, requestedType = null, includeGlobalUnset = true) {
|
|
1081
|
-
const lines = buildUseLines(config, profileName, requestedType, includeGlobalUnset);
|
|
1082
|
-
console.log(lines.join("\n"));
|
|
1083
|
-
}
|
|
1084
|
-
async function main() {
|
|
1085
|
-
const parsed = parseArgs(process.argv.slice(2));
|
|
1086
|
-
if (parsed.help) {
|
|
1087
|
-
printHelp();
|
|
1088
|
-
return;
|
|
1089
|
-
}
|
|
1090
|
-
const args = parsed.args || [];
|
|
1091
|
-
if (args.length === 0) {
|
|
1092
|
-
printHelp();
|
|
1093
|
-
return;
|
|
1094
|
-
}
|
|
1095
|
-
const cmd = args[0];
|
|
1096
|
-
try {
|
|
1097
|
-
if (cmd === "init") {
|
|
1098
|
-
const initArgs = parseInitArgs(args.slice(1));
|
|
1099
|
-
const shellName = detectShell(initArgs.shell);
|
|
1100
|
-
if (!shellName) {
|
|
1101
|
-
throw new Error("Unknown shell. Use --shell <bash|zsh|fish> to specify.");
|
|
1102
|
-
}
|
|
1103
|
-
const snippet = getShellSnippet(shellName);
|
|
1104
|
-
if (initArgs.apply) {
|
|
1105
|
-
const rcPath = getShellRcPath(shellName);
|
|
1106
|
-
upsertShellSnippet(rcPath, snippet);
|
|
1107
|
-
console.log(`Updated shell config: ${rcPath}`);
|
|
1108
|
-
}
|
|
1109
|
-
else {
|
|
1110
|
-
console.log(snippet);
|
|
1111
|
-
}
|
|
1112
|
-
return;
|
|
1113
|
-
}
|
|
1114
|
-
if (cmd === "add") {
|
|
1115
|
-
const writePath = findConfigPathForWrite(parsed.configPath);
|
|
1116
|
-
const addArgsRaw = args.slice(1);
|
|
1117
|
-
const hasInteractive = addArgsRaw.length === 0;
|
|
1118
|
-
if (hasInteractive) {
|
|
1119
|
-
await runInteractiveAdd(writePath);
|
|
1120
|
-
return;
|
|
1121
|
-
}
|
|
1122
|
-
const addArgs = parseAddArgs(addArgsRaw);
|
|
1123
|
-
const config = readConfigIfExists(writePath);
|
|
1124
|
-
const updated = addConfig(config, addArgs);
|
|
1125
|
-
writeConfig(writePath, updated);
|
|
1126
|
-
console.log(`Updated config: ${writePath}`);
|
|
1127
|
-
return;
|
|
1128
|
-
}
|
|
1129
|
-
if (cmd === "auto") {
|
|
1130
|
-
const configPath = findConfigPath(parsed.configPath);
|
|
1131
|
-
if (!configPath || !fs.existsSync(configPath))
|
|
1132
|
-
return;
|
|
1133
|
-
const config = readConfig(configPath);
|
|
1134
|
-
const defaults = getDefaultProfiles(config);
|
|
1135
|
-
const hasDefaults = DEFAULT_PROFILE_TYPES.some((type) => defaults[type]);
|
|
1136
|
-
if (!hasDefaults)
|
|
1137
|
-
return;
|
|
1138
|
-
let includeGlobalUnset = true;
|
|
1139
|
-
for (const type of DEFAULT_PROFILE_TYPES) {
|
|
1140
|
-
const value = defaults[type];
|
|
1141
|
-
if (!value)
|
|
1142
|
-
continue;
|
|
1143
|
-
try {
|
|
1144
|
-
const profileName = resolveDefaultProfileForType(config, type, value);
|
|
1145
|
-
if (!profileName)
|
|
1146
|
-
continue;
|
|
1147
|
-
printUse(config, profileName, type, includeGlobalUnset);
|
|
1148
|
-
includeGlobalUnset = false;
|
|
1149
|
-
}
|
|
1150
|
-
catch (err) {
|
|
1151
|
-
console.error(`codenv: ${err.message}`);
|
|
1152
|
-
}
|
|
1153
|
-
}
|
|
1154
|
-
return;
|
|
1155
|
-
}
|
|
1156
|
-
const configPath = findConfigPath(parsed.configPath);
|
|
1157
|
-
const config = readConfig(configPath);
|
|
1158
|
-
if (cmd === "default") {
|
|
1159
|
-
const params = args.slice(1);
|
|
1160
|
-
if (params.length === 0) {
|
|
1161
|
-
throw new Error("Missing profile name.");
|
|
1162
|
-
}
|
|
1163
|
-
const clear = params.length === 1 &&
|
|
1164
|
-
(params[0] === "--clear" || params[0] === "--unset");
|
|
1165
|
-
if (clear) {
|
|
1166
|
-
const rl = createReadline();
|
|
1167
|
-
try {
|
|
1168
|
-
const confirmed = await askConfirm(rl, "Clear all default profiles? (y/N): ");
|
|
1169
|
-
if (!confirmed)
|
|
1170
|
-
return;
|
|
1171
|
-
}
|
|
1172
|
-
finally {
|
|
1173
|
-
rl.close();
|
|
1174
|
-
}
|
|
1175
|
-
let changed = false;
|
|
1176
|
-
if (Object.prototype.hasOwnProperty.call(config, "defaultProfiles")) {
|
|
1177
|
-
delete config.defaultProfiles;
|
|
1178
|
-
changed = true;
|
|
1179
|
-
}
|
|
1180
|
-
if (changed) {
|
|
1181
|
-
writeConfig(configPath, config);
|
|
1182
|
-
console.log(`Updated config: ${configPath}`);
|
|
1183
|
-
}
|
|
1184
|
-
return;
|
|
1185
|
-
}
|
|
1186
|
-
const requestedType = params.length >= 2 ? normalizeType(params[0]) : null;
|
|
1187
|
-
const profileName = resolveProfileName(config, params);
|
|
1188
|
-
let targetType = requestedType;
|
|
1189
|
-
if (!targetType) {
|
|
1190
|
-
const profile = config.profiles && config.profiles[profileName];
|
|
1191
|
-
targetType = inferProfileType(profileName, profile, null);
|
|
1192
|
-
}
|
|
1193
|
-
if (!targetType) {
|
|
1194
|
-
throw new Error("Unable to infer profile type. Use: codenv default <type> <name>.");
|
|
1195
|
-
}
|
|
1196
|
-
if (!config.defaultProfiles || typeof config.defaultProfiles !== "object") {
|
|
1197
|
-
config.defaultProfiles = {};
|
|
1198
|
-
}
|
|
1199
|
-
config.defaultProfiles[targetType] = profileName;
|
|
1200
|
-
writeConfig(configPath, config);
|
|
1201
|
-
console.log(`Updated config: ${configPath}`);
|
|
1202
|
-
return;
|
|
1203
|
-
}
|
|
1204
|
-
if (cmd === "remove") {
|
|
1205
|
-
const params = args.slice(1);
|
|
1206
|
-
if (params.length === 0) {
|
|
1207
|
-
throw new Error("Missing profile name.");
|
|
1208
|
-
}
|
|
1209
|
-
const isAll = params.length === 1 && params[0] === "--all";
|
|
1210
|
-
if (isAll) {
|
|
1211
|
-
if (!config.profiles || typeof config.profiles !== "object") {
|
|
1212
|
-
config.profiles = {};
|
|
1213
|
-
}
|
|
1214
|
-
else {
|
|
1215
|
-
config.profiles = {};
|
|
1216
|
-
}
|
|
1217
|
-
if (Object.prototype.hasOwnProperty.call(config, "defaultProfiles")) {
|
|
1218
|
-
delete config.defaultProfiles;
|
|
1219
|
-
}
|
|
1220
|
-
writeConfig(configPath, config);
|
|
1221
|
-
console.log(`Updated config: ${configPath}`);
|
|
1222
|
-
return;
|
|
1223
|
-
}
|
|
1224
|
-
const targets = [];
|
|
1225
|
-
const allPairs = params.length >= 2 &&
|
|
1226
|
-
params.length % 2 === 0 &&
|
|
1227
|
-
params.every((value, idx) => idx % 2 === 0 ? normalizeType(value) : true);
|
|
1228
|
-
if (allPairs) {
|
|
1229
|
-
for (let i = 0; i < params.length; i += 2) {
|
|
1230
|
-
targets.push(resolveProfileName(config, params.slice(i, i + 2)));
|
|
1231
|
-
}
|
|
1232
|
-
}
|
|
1233
|
-
else {
|
|
1234
|
-
for (const param of params) {
|
|
1235
|
-
targets.push(resolveProfileName(config, [param]));
|
|
1236
|
-
}
|
|
1237
|
-
}
|
|
1238
|
-
const uniqueTargets = Array.from(new Set(targets));
|
|
1239
|
-
const missing = uniqueTargets.filter((name) => !config.profiles || !config.profiles[name]);
|
|
1240
|
-
if (missing.length > 0) {
|
|
1241
|
-
throw new Error(`Unknown profile(s): ${missing.join(", ")}`);
|
|
1242
|
-
}
|
|
1243
|
-
for (const name of uniqueTargets) {
|
|
1244
|
-
delete config.profiles[name];
|
|
1245
|
-
}
|
|
1246
|
-
const defaults = getDefaultProfiles(config);
|
|
1247
|
-
let changedDefaults = false;
|
|
1248
|
-
for (const type of DEFAULT_PROFILE_TYPES) {
|
|
1249
|
-
const value = defaults[type];
|
|
1250
|
-
if (!value)
|
|
1251
|
-
continue;
|
|
1252
|
-
try {
|
|
1253
|
-
const resolved = resolveDefaultProfileForType(config, type, value);
|
|
1254
|
-
if (resolved && uniqueTargets.includes(resolved)) {
|
|
1255
|
-
if (deleteDefaultProfileEntry(config, type)) {
|
|
1256
|
-
changedDefaults = true;
|
|
1257
|
-
}
|
|
1258
|
-
}
|
|
1259
|
-
}
|
|
1260
|
-
catch (err) {
|
|
1261
|
-
// keep defaults that cannot be resolved
|
|
1262
|
-
}
|
|
1263
|
-
}
|
|
1264
|
-
if (changedDefaults &&
|
|
1265
|
-
config.defaultProfiles &&
|
|
1266
|
-
Object.keys(config.defaultProfiles).length === 0) {
|
|
1267
|
-
delete config.defaultProfiles;
|
|
1268
|
-
}
|
|
1269
|
-
writeConfig(configPath, config);
|
|
1270
|
-
console.log(`Updated config: ${configPath}`);
|
|
1271
|
-
return;
|
|
1272
|
-
}
|
|
1273
|
-
if (cmd === "config") {
|
|
1274
|
-
const configPath = findConfigPath(parsed.configPath);
|
|
1275
|
-
if (!configPath) {
|
|
1276
|
-
console.log("(no config found)");
|
|
1277
|
-
return;
|
|
1278
|
-
}
|
|
1279
|
-
console.log(configPath);
|
|
1280
|
-
return;
|
|
1281
|
-
}
|
|
1282
|
-
if (cmd === "list" || cmd === "ls") {
|
|
1283
|
-
printList(config);
|
|
1284
|
-
return;
|
|
1285
|
-
}
|
|
1286
|
-
if (cmd === "use") {
|
|
1287
|
-
const params = args.slice(1);
|
|
1288
|
-
if (params.length === 0) {
|
|
1289
|
-
await runInteractiveUse(config);
|
|
1290
|
-
return;
|
|
1291
|
-
}
|
|
1292
|
-
const requestedType = params.length >= 2 ? normalizeType(params[0]) : null;
|
|
1293
|
-
const profileName = resolveProfileName(config, params);
|
|
1294
|
-
printUse(config, profileName, requestedType);
|
|
1295
|
-
return;
|
|
1296
|
-
}
|
|
1297
|
-
if (cmd === "show") {
|
|
1298
|
-
const profileName = resolveProfileName(config, args.slice(1));
|
|
1299
|
-
printShow(config, profileName);
|
|
1300
|
-
return;
|
|
1301
|
-
}
|
|
1302
|
-
if (cmd === "unset") {
|
|
1303
|
-
printUnset(config);
|
|
1304
|
-
return;
|
|
1305
|
-
}
|
|
1306
|
-
throw new Error(`Unknown command: ${cmd}`);
|
|
1307
|
-
}
|
|
1308
|
-
catch (err) {
|
|
1309
|
-
console.error(`codenv: ${err.message}`);
|
|
1310
|
-
process.exit(1);
|
|
1311
|
-
}
|
|
1312
|
-
}
|
|
1313
|
-
main().catch((err) => {
|
|
1314
|
-
console.error(`codenv: ${err.message}`);
|
|
1315
|
-
process.exit(1);
|
|
1316
|
-
});
|