@praeviso/code-env-switch 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/.github/workflows/npm-publish.yml +25 -0
  2. package/AGENTS.md +32 -0
  3. package/PLAN.md +33 -0
  4. package/README.md +24 -0
  5. package/README_zh.md +24 -0
  6. package/bin/cli/args.js +303 -0
  7. package/bin/cli/help.js +77 -0
  8. package/bin/cli/index.js +13 -0
  9. package/bin/commands/add.js +81 -0
  10. package/bin/commands/index.js +21 -0
  11. package/bin/commands/launch.js +330 -0
  12. package/bin/commands/list.js +57 -0
  13. package/bin/commands/show.js +10 -0
  14. package/bin/commands/statusline.js +12 -0
  15. package/bin/commands/unset.js +20 -0
  16. package/bin/commands/use.js +92 -0
  17. package/bin/config/defaults.js +85 -0
  18. package/bin/config/index.js +20 -0
  19. package/bin/config/io.js +72 -0
  20. package/bin/constants.js +27 -0
  21. package/bin/index.js +279 -0
  22. package/bin/profile/display.js +78 -0
  23. package/bin/profile/index.js +26 -0
  24. package/bin/profile/match.js +40 -0
  25. package/bin/profile/resolve.js +79 -0
  26. package/bin/profile/type.js +90 -0
  27. package/bin/shell/detect.js +40 -0
  28. package/bin/shell/index.js +18 -0
  29. package/bin/shell/snippet.js +92 -0
  30. package/bin/shell/utils.js +35 -0
  31. package/bin/statusline/claude.js +153 -0
  32. package/bin/statusline/codex.js +356 -0
  33. package/bin/statusline/index.js +631 -0
  34. package/bin/types.js +5 -0
  35. package/bin/ui/index.js +16 -0
  36. package/bin/ui/interactive.js +189 -0
  37. package/bin/ui/readline.js +76 -0
  38. package/bin/usage/index.js +832 -0
  39. package/code-env.example.json +11 -0
  40. package/package.json +2 -2
  41. package/src/cli/args.ts +318 -0
  42. package/src/cli/help.ts +75 -0
  43. package/src/cli/index.ts +5 -0
  44. package/src/commands/add.ts +91 -0
  45. package/src/commands/index.ts +10 -0
  46. package/src/commands/launch.ts +395 -0
  47. package/src/commands/list.ts +91 -0
  48. package/src/commands/show.ts +12 -0
  49. package/src/commands/statusline.ts +18 -0
  50. package/src/commands/unset.ts +19 -0
  51. package/src/commands/use.ts +121 -0
  52. package/src/config/defaults.ts +88 -0
  53. package/src/config/index.ts +19 -0
  54. package/src/config/io.ts +69 -0
  55. package/src/constants.ts +28 -0
  56. package/src/index.ts +359 -0
  57. package/src/profile/display.ts +77 -0
  58. package/src/profile/index.ts +12 -0
  59. package/src/profile/match.ts +41 -0
  60. package/src/profile/resolve.ts +84 -0
  61. package/src/profile/type.ts +83 -0
  62. package/src/shell/detect.ts +30 -0
  63. package/src/shell/index.ts +6 -0
  64. package/src/shell/snippet.ts +92 -0
  65. package/src/shell/utils.ts +30 -0
  66. package/src/statusline/claude.ts +172 -0
  67. package/src/statusline/codex.ts +393 -0
  68. package/src/statusline/index.ts +920 -0
  69. package/src/types.ts +95 -0
  70. package/src/ui/index.ts +5 -0
  71. package/src/ui/interactive.ts +220 -0
  72. package/src/ui/readline.ts +85 -0
  73. package/src/usage/index.ts +979 -0
  74. package/bin/codenv.js +0 -1316
  75. package/src/codenv.ts +0 -1478
@@ -0,0 +1,330 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runLaunch = runLaunch;
4
+ /**
5
+ * Launch codex/claude with session binding
6
+ */
7
+ const fs = require("fs");
8
+ const path = require("path");
9
+ const child_process_1 = require("child_process");
10
+ const constants_1 = require("../constants");
11
+ const type_1 = require("../profile/type");
12
+ const usage_1 = require("../usage");
13
+ const claude_1 = require("../statusline/claude");
14
+ const codex_1 = require("../statusline/codex");
15
+ const SESSION_BINDING_POLL_MS = 1000;
16
+ const SESSION_BINDING_START_GRACE_MS = 5000;
17
+ function isRecord(value) {
18
+ return typeof value === "object" && value !== null && !Array.isArray(value);
19
+ }
20
+ function collectSessionFiles(root) {
21
+ if (!root || !fs.existsSync(root))
22
+ return [];
23
+ const files = [];
24
+ const stack = [root];
25
+ while (stack.length > 0) {
26
+ const current = stack.pop();
27
+ if (!current)
28
+ continue;
29
+ let entries = [];
30
+ try {
31
+ entries = fs.readdirSync(current, { withFileTypes: true });
32
+ }
33
+ catch {
34
+ continue;
35
+ }
36
+ for (const entry of entries) {
37
+ if (entry.name.startsWith("."))
38
+ continue;
39
+ const full = path.join(current, entry.name);
40
+ if (entry.isDirectory()) {
41
+ stack.push(full);
42
+ }
43
+ else if (entry.isFile() && entry.name.endsWith(".jsonl")) {
44
+ files.push(full);
45
+ }
46
+ }
47
+ }
48
+ return files;
49
+ }
50
+ function readFirstJsonLine(filePath) {
51
+ let fd = null;
52
+ try {
53
+ fd = fs.openSync(filePath, "r");
54
+ const buffer = Buffer.alloc(64 * 1024);
55
+ const bytes = fs.readSync(fd, buffer, 0, buffer.length, 0);
56
+ if (bytes <= 0)
57
+ return null;
58
+ const text = buffer.slice(0, bytes).toString("utf8");
59
+ const line = text.split(/\r?\n/)[0];
60
+ if (!line)
61
+ return null;
62
+ return JSON.parse(line);
63
+ }
64
+ catch {
65
+ return null;
66
+ }
67
+ finally {
68
+ if (fd !== null) {
69
+ try {
70
+ fs.closeSync(fd);
71
+ }
72
+ catch {
73
+ // ignore
74
+ }
75
+ }
76
+ }
77
+ }
78
+ function parseCodexFilenameInfo(filePath) {
79
+ const base = path.basename(filePath);
80
+ const match = base.match(/(\d{4}-\d{2}-\d{2})T(\d{2})-(\d{2})-(\d{2})-([0-9a-fA-F-]+)\.jsonl$/);
81
+ if (!match) {
82
+ return { timestamp: null, timestampMs: null, sessionId: null };
83
+ }
84
+ const [, date, hour, minute, second, sessionId] = match;
85
+ const timestamp = `${date}T${hour}:${minute}:${second}`;
86
+ const parsedMs = new Date(timestamp).getTime();
87
+ return {
88
+ timestamp,
89
+ timestampMs: Number.isNaN(parsedMs) ? null : parsedMs,
90
+ sessionId: sessionId ? String(sessionId) : null,
91
+ };
92
+ }
93
+ function readSessionMeta(filePath, type) {
94
+ const first = readFirstJsonLine(filePath);
95
+ if (!isRecord(first))
96
+ return null;
97
+ if (type === "codex") {
98
+ const fromName = parseCodexFilenameInfo(filePath);
99
+ const payload = isRecord(first.payload) ? first.payload : null;
100
+ const ts = payload && payload.timestamp ? payload.timestamp : first.timestamp;
101
+ const timestamp = ts ? String(ts) : fromName.timestamp;
102
+ return {
103
+ filePath,
104
+ sessionId: payload && payload.id
105
+ ? String(payload.id)
106
+ : fromName.sessionId
107
+ ? String(fromName.sessionId)
108
+ : null,
109
+ timestamp,
110
+ fileTimestampMs: fromName.timestampMs,
111
+ cwd: payload && payload.cwd ? String(payload.cwd) : null,
112
+ };
113
+ }
114
+ return {
115
+ filePath,
116
+ sessionId: first.sessionId ? String(first.sessionId) : null,
117
+ timestamp: first.timestamp ? String(first.timestamp) : null,
118
+ fileTimestampMs: null,
119
+ cwd: first.cwd ? String(first.cwd) : null,
120
+ };
121
+ }
122
+ function getSessionCandidateTimestamp(meta, stat) {
123
+ const times = [];
124
+ if (Number.isFinite(stat.mtimeMs))
125
+ times.push(stat.mtimeMs);
126
+ if (meta.timestamp) {
127
+ const tsMs = new Date(meta.timestamp).getTime();
128
+ if (Number.isFinite(tsMs))
129
+ times.push(tsMs);
130
+ }
131
+ if (Number.isFinite(meta.fileTimestampMs))
132
+ times.push(meta.fileTimestampMs);
133
+ if (times.length === 0)
134
+ return null;
135
+ return Math.max(...times);
136
+ }
137
+ function findLatestUnboundSessionMeta(root, type, bound, minTimestampMs, cwd, skipFiles) {
138
+ const files = collectSessionFiles(root);
139
+ let bestMeta = null;
140
+ let bestTs = Number.NEGATIVE_INFINITY;
141
+ let bestCwdMatch = false;
142
+ for (const filePath of files) {
143
+ if (bound.byFile.has(filePath))
144
+ continue;
145
+ if (skipFiles && skipFiles.has(filePath))
146
+ continue;
147
+ let stat = null;
148
+ try {
149
+ stat = fs.statSync(filePath);
150
+ }
151
+ catch {
152
+ continue;
153
+ }
154
+ if (!stat || !stat.isFile())
155
+ continue;
156
+ const meta = readSessionMeta(filePath, type) || {
157
+ filePath,
158
+ sessionId: null,
159
+ timestamp: null,
160
+ fileTimestampMs: null,
161
+ cwd: null,
162
+ };
163
+ if (meta.sessionId && bound.byId.has(meta.sessionId))
164
+ continue;
165
+ const tsMs = getSessionCandidateTimestamp(meta, stat);
166
+ if (tsMs === null || Number.isNaN(tsMs))
167
+ continue;
168
+ if (minTimestampMs !== null && tsMs < minTimestampMs)
169
+ continue;
170
+ const cwdMatch = Boolean(cwd && meta.cwd && meta.cwd === cwd);
171
+ if (!bestMeta ||
172
+ (cwdMatch && !bestCwdMatch) ||
173
+ (cwdMatch === bestCwdMatch && tsMs > bestTs)) {
174
+ bestMeta = meta;
175
+ bestTs = tsMs;
176
+ bestCwdMatch = cwdMatch;
177
+ }
178
+ }
179
+ return bestMeta;
180
+ }
181
+ function getProfileEnv(type) {
182
+ const suffix = type.toUpperCase();
183
+ const key = process.env[`CODE_ENV_PROFILE_KEY_${suffix}`] || null;
184
+ const name = process.env[`CODE_ENV_PROFILE_NAME_${suffix}`] || key;
185
+ return { key, name };
186
+ }
187
+ function writeCodexAuthFromEnv() {
188
+ const apiKey = process.env.OPENAI_API_KEY;
189
+ try {
190
+ fs.mkdirSync(path.dirname(constants_1.CODEX_AUTH_PATH), { recursive: true });
191
+ }
192
+ catch {
193
+ // ignore
194
+ }
195
+ const authJson = apiKey === null || apiKey === undefined || apiKey === ""
196
+ ? "null"
197
+ : JSON.stringify({ OPENAI_API_KEY: String(apiKey) });
198
+ try {
199
+ fs.writeFileSync(constants_1.CODEX_AUTH_PATH, `${authJson}\n`, "utf8");
200
+ }
201
+ catch {
202
+ // ignore
203
+ }
204
+ }
205
+ function parseBooleanEnv(value) {
206
+ if (value === undefined)
207
+ return null;
208
+ const normalized = String(value).trim().toLowerCase();
209
+ if (["1", "true", "yes", "on"].includes(normalized))
210
+ return true;
211
+ if (["0", "false", "no", "off"].includes(normalized))
212
+ return false;
213
+ return null;
214
+ }
215
+ function isStatuslineEnabled(type) {
216
+ if (!process.stdout.isTTY || process.env.TERM === "dumb")
217
+ return false;
218
+ const disable = parseBooleanEnv(process.env.CODE_ENV_STATUSLINE_DISABLE);
219
+ if (disable === true)
220
+ return false;
221
+ const typeFlag = parseBooleanEnv(process.env[`CODE_ENV_STATUSLINE_${type.toUpperCase()}`]);
222
+ if (typeFlag !== null)
223
+ return typeFlag;
224
+ const genericFlag = parseBooleanEnv(process.env.CODE_ENV_STATUSLINE);
225
+ if (genericFlag !== null)
226
+ return genericFlag;
227
+ return true;
228
+ }
229
+ async function ensureClaudeStatuslineConfig(config, enabled) {
230
+ await (0, claude_1.ensureClaudeStatusline)(config, enabled);
231
+ }
232
+ async function runLaunch(config, configPath, target, args) {
233
+ const type = (0, type_1.normalizeType)(target);
234
+ if (!type) {
235
+ throw new Error(`Unknown launch target: ${target}`);
236
+ }
237
+ if (type === "codex") {
238
+ writeCodexAuthFromEnv();
239
+ }
240
+ const { key: profileKey, name: profileName } = getProfileEnv(type);
241
+ const terminalTag = process.env.CODE_ENV_TERMINAL_TAG || null;
242
+ const cwd = process.cwd();
243
+ const startMs = Date.now();
244
+ const minBindingTimestampMs = startMs - SESSION_BINDING_START_GRACE_MS;
245
+ const sessionRoot = type === "codex" ? (0, usage_1.getCodexSessionsPath)(config) : (0, usage_1.getClaudeSessionsPath)(config);
246
+ const initialBindingIndex = (0, usage_1.readSessionBindingIndex)(config, configPath);
247
+ const initialUnboundFiles = new Set();
248
+ for (const filePath of collectSessionFiles(sessionRoot)) {
249
+ if (!initialBindingIndex.byFile.has(filePath)) {
250
+ initialUnboundFiles.add(filePath);
251
+ }
252
+ }
253
+ const statuslineEnabled = isStatuslineEnabled(type);
254
+ if (type === "claude") {
255
+ await ensureClaudeStatuslineConfig(config, statuslineEnabled);
256
+ }
257
+ else if (type === "codex") {
258
+ await (0, codex_1.ensureCodexStatuslineConfig)(config, statuslineEnabled);
259
+ }
260
+ if (profileKey) {
261
+ (0, usage_1.logProfileUse)(config, configPath, profileKey, type, terminalTag, cwd);
262
+ }
263
+ const child = (0, child_process_1.spawn)(target, args, { stdio: "inherit", env: process.env });
264
+ const canBindSession = Boolean(profileKey || profileName);
265
+ let boundSession = null;
266
+ let bindingTimer = null;
267
+ const tryBindSession = () => {
268
+ if (!canBindSession || boundSession)
269
+ return;
270
+ const bindingIndex = (0, usage_1.readSessionBindingIndex)(config, configPath);
271
+ const candidate = findLatestUnboundSessionMeta(sessionRoot, type, bindingIndex, minBindingTimestampMs, cwd, initialUnboundFiles);
272
+ if (!candidate)
273
+ return;
274
+ boundSession = candidate;
275
+ (0, usage_1.logSessionBinding)(config, configPath, type, profileKey, profileName, terminalTag, cwd, boundSession.filePath, boundSession.sessionId, boundSession.timestamp);
276
+ if (bindingTimer) {
277
+ clearInterval(bindingTimer);
278
+ bindingTimer = null;
279
+ }
280
+ };
281
+ if (canBindSession) {
282
+ tryBindSession();
283
+ bindingTimer = setInterval(tryBindSession, SESSION_BINDING_POLL_MS);
284
+ }
285
+ const forwardSignal = (signal) => {
286
+ try {
287
+ child.kill(signal);
288
+ }
289
+ catch {
290
+ // ignore
291
+ }
292
+ };
293
+ process.on("SIGINT", forwardSignal);
294
+ process.on("SIGTERM", forwardSignal);
295
+ const exitCode = await new Promise((resolve) => {
296
+ child.on("error", (err) => {
297
+ process.off("SIGINT", forwardSignal);
298
+ process.off("SIGTERM", forwardSignal);
299
+ if (bindingTimer) {
300
+ clearInterval(bindingTimer);
301
+ bindingTimer = null;
302
+ }
303
+ console.error(`codenv: failed to launch ${target}: ${err.message}`);
304
+ resolve(1);
305
+ });
306
+ child.on("exit", (code, signal) => {
307
+ process.off("SIGINT", forwardSignal);
308
+ process.off("SIGTERM", forwardSignal);
309
+ if (bindingTimer) {
310
+ clearInterval(bindingTimer);
311
+ bindingTimer = null;
312
+ }
313
+ const bindingIndex = (0, usage_1.readSessionBindingIndex)(config, configPath);
314
+ const sessionMeta = findLatestUnboundSessionMeta(sessionRoot, type, bindingIndex, minBindingTimestampMs, cwd, initialUnboundFiles);
315
+ if (!boundSession && sessionMeta && (profileKey || profileName)) {
316
+ (0, usage_1.logSessionBinding)(config, configPath, type, profileKey, profileName, terminalTag, cwd, sessionMeta.filePath, sessionMeta.sessionId, sessionMeta.timestamp);
317
+ }
318
+ if (typeof code === "number") {
319
+ resolve(code);
320
+ return;
321
+ }
322
+ if (signal) {
323
+ resolve(1);
324
+ return;
325
+ }
326
+ resolve(0);
327
+ });
328
+ });
329
+ return exitCode;
330
+ }
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.printList = printList;
4
+ const display_1 = require("../profile/display");
5
+ const defaults_1 = require("../config/defaults");
6
+ const usage_1 = require("../usage");
7
+ function printList(config, configPath) {
8
+ const rows = (0, display_1.buildListRows)(config, defaults_1.getResolvedDefaultProfileKeys);
9
+ if (rows.length === 0) {
10
+ console.log("(no profiles found)");
11
+ return;
12
+ }
13
+ try {
14
+ const usageTotals = (0, usage_1.readUsageTotalsIndex)(config, configPath, true);
15
+ if (usageTotals) {
16
+ for (const row of rows) {
17
+ if (!row.usageType)
18
+ continue;
19
+ const usage = (0, usage_1.resolveUsageTotalsForProfile)(usageTotals, row.usageType, row.key, row.name);
20
+ if (!usage)
21
+ continue;
22
+ row.todayTokens = usage.today;
23
+ row.totalTokens = usage.total;
24
+ }
25
+ }
26
+ }
27
+ catch {
28
+ // ignore usage sync errors
29
+ }
30
+ const headerName = "PROFILE";
31
+ const headerType = "TYPE";
32
+ const headerToday = "TODAY";
33
+ const headerTotal = "TOTAL";
34
+ const headerNote = "NOTE";
35
+ const todayTexts = rows.map((row) => (0, usage_1.formatTokenCount)(row.todayTokens));
36
+ const totalTexts = rows.map((row) => (0, usage_1.formatTokenCount)(row.totalTokens));
37
+ const nameWidth = Math.max(headerName.length, ...rows.map((row) => row.name.length));
38
+ const typeWidth = Math.max(headerType.length, ...rows.map((row) => row.type.length));
39
+ const todayWidth = Math.max(headerToday.length, ...todayTexts.map((v) => v.length));
40
+ const totalWidth = Math.max(headerTotal.length, ...totalTexts.map((v) => v.length));
41
+ const noteWidth = Math.max(headerNote.length, ...rows.map((row) => row.note.length));
42
+ const formatRow = (name, type, today, total, note) => `${name.padEnd(nameWidth)} ${type.padEnd(typeWidth)} ${today.padStart(todayWidth)} ${total.padStart(totalWidth)} ${note.padEnd(noteWidth)}`;
43
+ console.log(formatRow(headerName, headerType, headerToday, headerTotal, headerNote));
44
+ console.log(formatRow("-".repeat(nameWidth), "-".repeat(typeWidth), "-".repeat(todayWidth), "-".repeat(totalWidth), "-".repeat(noteWidth)));
45
+ for (let i = 0; i < rows.length; i++) {
46
+ const row = rows[i];
47
+ const todayText = todayTexts[i] || "-";
48
+ const totalText = totalTexts[i] || "-";
49
+ const line = formatRow(row.name, row.type, todayText, totalText, row.note);
50
+ if (row.active) {
51
+ console.log(`\x1b[32m${line}\x1b[0m`);
52
+ }
53
+ else {
54
+ console.log(line);
55
+ }
56
+ }
57
+ }
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.printShow = printShow;
4
+ function printShow(config, profileName) {
5
+ const profile = config.profiles && config.profiles[profileName];
6
+ if (!profile) {
7
+ throw new Error(`Unknown profile: ${profileName}`);
8
+ }
9
+ console.log(JSON.stringify(profile, null, 2));
10
+ }
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.printStatusline = printStatusline;
4
+ const statusline_1 = require("../statusline");
5
+ function printStatusline(config, configPath, args) {
6
+ const result = (0, statusline_1.buildStatuslineResult)(args, config, configPath);
7
+ if (args.format === "json") {
8
+ console.log(JSON.stringify(result.json));
9
+ return;
10
+ }
11
+ console.log(result.text);
12
+ }
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.printUnset = printUnset;
4
+ const constants_1 = require("../constants");
5
+ const defaults_1 = require("../config/defaults");
6
+ function printUnset(config) {
7
+ const keySet = new Set();
8
+ if (Array.isArray(config.unset)) {
9
+ for (const key of config.unset)
10
+ keySet.add(key);
11
+ }
12
+ for (const type of constants_1.DEFAULT_PROFILE_TYPES) {
13
+ for (const key of (0, defaults_1.getTypeDefaultUnsetKeys)(type))
14
+ keySet.add(key);
15
+ }
16
+ if (keySet.size === 0)
17
+ return;
18
+ const lines = Array.from(keySet, (key) => `unset ${key}`);
19
+ console.log(lines.join("\n"));
20
+ }
@@ -0,0 +1,92 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildUseLines = buildUseLines;
4
+ exports.printUse = printUse;
5
+ /**
6
+ * Use command - apply profile environment
7
+ */
8
+ const path = require("path");
9
+ const constants_1 = require("../constants");
10
+ const utils_1 = require("../shell/utils");
11
+ const type_1 = require("../profile/type");
12
+ const match_1 = require("../profile/match");
13
+ const display_1 = require("../profile/display");
14
+ const defaults_1 = require("../config/defaults");
15
+ function buildUseLines(config, profileName, requestedType, includeGlobalUnset, configPath = null) {
16
+ const profile = config.profiles && config.profiles[profileName];
17
+ if (!profile) {
18
+ throw new Error(`Unknown profile: ${profileName}`);
19
+ }
20
+ const unsetLines = [];
21
+ const exportLines = [];
22
+ const postLines = [];
23
+ const unsetKeys = new Set();
24
+ const activeType = (0, type_1.inferProfileType)(profileName, profile, requestedType);
25
+ const effectiveEnv = (0, display_1.buildEffectiveEnv)(profile, activeType);
26
+ const addUnset = (key) => {
27
+ if (unsetKeys.has(key))
28
+ return;
29
+ if (Object.prototype.hasOwnProperty.call(effectiveEnv, key))
30
+ return;
31
+ unsetKeys.add(key);
32
+ unsetLines.push(`unset ${key}`);
33
+ };
34
+ if (includeGlobalUnset) {
35
+ for (const key of (0, defaults_1.getFilteredUnsetKeys)(config, activeType)) {
36
+ addUnset(key);
37
+ }
38
+ }
39
+ if (activeType) {
40
+ for (const key of (0, defaults_1.getTypeDefaultUnsetKeys)(activeType)) {
41
+ addUnset(key);
42
+ }
43
+ }
44
+ for (const key of Object.keys(effectiveEnv)) {
45
+ const value = effectiveEnv[key];
46
+ if (value === null || value === undefined || value === "") {
47
+ if (!unsetKeys.has(key)) {
48
+ unsetKeys.add(key);
49
+ unsetLines.push(`unset ${key}`);
50
+ }
51
+ }
52
+ else {
53
+ exportLines.push(`export ${key}=${(0, utils_1.shellEscape)(value)}`);
54
+ }
55
+ }
56
+ if (activeType) {
57
+ const typeSuffix = activeType.toUpperCase();
58
+ const displayName = (0, type_1.getProfileDisplayName)(profileName, profile, activeType);
59
+ exportLines.push(`export CODE_ENV_PROFILE_KEY_${typeSuffix}=${(0, utils_1.shellEscape)(profileName)}`);
60
+ exportLines.push(`export CODE_ENV_PROFILE_NAME_${typeSuffix}=${(0, utils_1.shellEscape)(displayName)}`);
61
+ }
62
+ if (configPath) {
63
+ exportLines.push(`export CODE_ENV_CONFIG_PATH=${(0, utils_1.shellEscape)(configPath)}`);
64
+ }
65
+ if ((0, match_1.shouldRemoveCodexAuth)(profileName, profile, requestedType)) {
66
+ const codexApiKey = effectiveEnv.OPENAI_API_KEY;
67
+ const authDir = path.dirname(constants_1.CODEX_AUTH_PATH);
68
+ const authJson = codexApiKey === null || codexApiKey === undefined || codexApiKey === ""
69
+ ? "null"
70
+ : JSON.stringify({ OPENAI_API_KEY: String(codexApiKey) });
71
+ postLines.push(`mkdir -p ${(0, utils_1.shellEscape)(authDir)}`);
72
+ postLines.push(`printf '%s\\n' ${(0, utils_1.shellEscape)(authJson)} > ${(0, utils_1.shellEscape)(constants_1.CODEX_AUTH_PATH)}`);
73
+ }
74
+ if (Array.isArray(profile.removeFiles)) {
75
+ for (const p of profile.removeFiles) {
76
+ const expanded = (0, utils_1.expandEnv)(p);
77
+ if (expanded)
78
+ postLines.push(`rm -f ${(0, utils_1.shellEscape)(expanded)}`);
79
+ }
80
+ }
81
+ if (Array.isArray(profile.commands)) {
82
+ for (const cmd of profile.commands) {
83
+ if (cmd && String(cmd).trim())
84
+ postLines.push(String(cmd));
85
+ }
86
+ }
87
+ return [...unsetLines, ...exportLines, ...postLines];
88
+ }
89
+ function printUse(config, profileName, requestedType = null, includeGlobalUnset = true, configPath = null) {
90
+ const lines = buildUseLines(config, profileName, requestedType, includeGlobalUnset, configPath);
91
+ console.log(lines.join("\n"));
92
+ }
@@ -0,0 +1,85 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getDefaultProfiles = getDefaultProfiles;
4
+ exports.deleteDefaultProfileEntry = deleteDefaultProfileEntry;
5
+ exports.resolveDefaultProfileForType = resolveDefaultProfileForType;
6
+ exports.getResolvedDefaultProfileKeys = getResolvedDefaultProfileKeys;
7
+ exports.getTypeDefaultUnsetKeys = getTypeDefaultUnsetKeys;
8
+ exports.getFilteredUnsetKeys = getFilteredUnsetKeys;
9
+ const constants_1 = require("../constants");
10
+ const type_1 = require("../profile/type");
11
+ const resolve_1 = require("../profile/resolve");
12
+ function getDefaultProfiles(config) {
13
+ const defaults = {};
14
+ if (!config || typeof config !== "object")
15
+ return defaults;
16
+ if (!config.defaultProfiles || typeof config.defaultProfiles !== "object") {
17
+ return defaults;
18
+ }
19
+ for (const [rawType, rawValue] of Object.entries(config.defaultProfiles)) {
20
+ const type = (0, type_1.normalizeType)(rawType);
21
+ if (!type)
22
+ continue;
23
+ const trimmed = String(rawValue !== null && rawValue !== void 0 ? rawValue : "").trim();
24
+ if (trimmed)
25
+ defaults[type] = trimmed;
26
+ }
27
+ return defaults;
28
+ }
29
+ function deleteDefaultProfileEntry(config, type) {
30
+ if (!config.defaultProfiles || typeof config.defaultProfiles !== "object") {
31
+ return false;
32
+ }
33
+ let changed = false;
34
+ for (const key of Object.keys(config.defaultProfiles)) {
35
+ if ((0, type_1.normalizeType)(key) === type) {
36
+ delete config.defaultProfiles[key];
37
+ changed = true;
38
+ }
39
+ }
40
+ return changed;
41
+ }
42
+ function resolveDefaultProfileForType(config, type, value) {
43
+ const trimmed = String(value !== null && value !== void 0 ? value : "").trim();
44
+ if (!trimmed)
45
+ return null;
46
+ const params = trimmed.split(/\s+/).filter(Boolean);
47
+ if (params.length === 0)
48
+ return null;
49
+ const explicitType = (0, type_1.normalizeType)(params[0]);
50
+ if (explicitType) {
51
+ if (explicitType !== type) {
52
+ throw new Error(`Default profile for "${type}" must match type "${type}".`);
53
+ }
54
+ return (0, resolve_1.resolveProfileName)(config, params);
55
+ }
56
+ return (0, resolve_1.resolveProfileName)(config, [type, ...params]);
57
+ }
58
+ function getResolvedDefaultProfileKeys(config) {
59
+ const defaults = getDefaultProfiles(config);
60
+ const resolved = {};
61
+ for (const type of constants_1.DEFAULT_PROFILE_TYPES) {
62
+ const value = defaults[type];
63
+ if (!value)
64
+ continue;
65
+ try {
66
+ const profileName = resolveDefaultProfileForType(config, type, value);
67
+ if (profileName)
68
+ resolved[type] = profileName;
69
+ }
70
+ catch (err) {
71
+ // ignore invalid defaults for list output
72
+ }
73
+ }
74
+ return resolved;
75
+ }
76
+ function getTypeDefaultUnsetKeys(type) {
77
+ return constants_1.DEFAULT_UNSET_KEYS[type] || [];
78
+ }
79
+ function getFilteredUnsetKeys(config, activeType) {
80
+ const keys = Array.isArray(config.unset) ? config.unset : [];
81
+ if (!activeType)
82
+ return [...keys];
83
+ const otherDefaults = new Set(constants_1.DEFAULT_PROFILE_TYPES.filter((type) => type !== activeType).flatMap((type) => constants_1.DEFAULT_UNSET_KEYS[type]));
84
+ return keys.filter((key) => !otherDefaults.has(key));
85
+ }
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getFilteredUnsetKeys = exports.getTypeDefaultUnsetKeys = exports.getResolvedDefaultProfileKeys = exports.resolveDefaultProfileForType = exports.deleteDefaultProfileEntry = exports.getDefaultProfiles = exports.writeConfig = exports.readConfigIfExists = exports.readConfig = exports.findConfigPathForWrite = exports.findConfigPath = exports.getDefaultConfigPath = void 0;
4
+ /**
5
+ * Config module exports
6
+ */
7
+ var io_1 = require("./io");
8
+ Object.defineProperty(exports, "getDefaultConfigPath", { enumerable: true, get: function () { return io_1.getDefaultConfigPath; } });
9
+ Object.defineProperty(exports, "findConfigPath", { enumerable: true, get: function () { return io_1.findConfigPath; } });
10
+ Object.defineProperty(exports, "findConfigPathForWrite", { enumerable: true, get: function () { return io_1.findConfigPathForWrite; } });
11
+ Object.defineProperty(exports, "readConfig", { enumerable: true, get: function () { return io_1.readConfig; } });
12
+ Object.defineProperty(exports, "readConfigIfExists", { enumerable: true, get: function () { return io_1.readConfigIfExists; } });
13
+ Object.defineProperty(exports, "writeConfig", { enumerable: true, get: function () { return io_1.writeConfig; } });
14
+ var defaults_1 = require("./defaults");
15
+ Object.defineProperty(exports, "getDefaultProfiles", { enumerable: true, get: function () { return defaults_1.getDefaultProfiles; } });
16
+ Object.defineProperty(exports, "deleteDefaultProfileEntry", { enumerable: true, get: function () { return defaults_1.deleteDefaultProfileEntry; } });
17
+ Object.defineProperty(exports, "resolveDefaultProfileForType", { enumerable: true, get: function () { return defaults_1.resolveDefaultProfileForType; } });
18
+ Object.defineProperty(exports, "getResolvedDefaultProfileKeys", { enumerable: true, get: function () { return defaults_1.getResolvedDefaultProfileKeys; } });
19
+ Object.defineProperty(exports, "getTypeDefaultUnsetKeys", { enumerable: true, get: function () { return defaults_1.getTypeDefaultUnsetKeys; } });
20
+ Object.defineProperty(exports, "getFilteredUnsetKeys", { enumerable: true, get: function () { return defaults_1.getFilteredUnsetKeys; } });