@minniexcode/codex-switch 0.0.1 → 0.0.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.
- package/README.AI.md +105 -0
- package/README.CN.md +160 -0
- package/README.md +102 -111
- package/dist/app/add-provider.js +45 -0
- package/dist/app/export-providers.js +62 -0
- package/dist/app/get-current-profile.js +14 -0
- package/dist/app/get-status.js +75 -0
- package/dist/app/import-providers.js +74 -0
- package/dist/app/list-providers.js +23 -0
- package/dist/app/remove-provider.js +31 -0
- package/dist/app/rollback-latest.js +26 -0
- package/dist/app/run-doctor.js +130 -0
- package/dist/app/run-mutation.js +63 -0
- package/dist/app/switch-provider.js +43 -0
- package/dist/app/types.js +2 -0
- package/dist/cli/add-interactive.js +114 -0
- package/dist/cli/args.js +125 -0
- package/dist/cli/help.js +220 -0
- package/dist/cli/interactive.js +114 -0
- package/dist/cli/output.js +156 -0
- package/dist/cli/prompt.js +93 -0
- package/dist/cli.js +215 -26
- package/dist/domain/backup.js +2 -0
- package/dist/domain/config.js +106 -0
- package/dist/domain/errors.js +36 -0
- package/dist/domain/providers.js +92 -0
- package/dist/domain/runtime-state.js +56 -0
- package/dist/infra/backup-repo.js +151 -0
- package/dist/infra/codex-cli.js +53 -0
- package/dist/infra/codex-paths.js +58 -0
- package/dist/infra/config-repo.js +56 -0
- package/dist/infra/fs-utils.js +97 -0
- package/dist/infra/lock-repo.js +99 -0
- package/dist/infra/providers-repo.js +69 -0
- package/docs/codex-switch-command-design.md +646 -0
- package/docs/codex-switch-prd.md +24 -3
- package/docs/codex-switch-product-overview.md +2 -0
- package/docs/codex-switch-technical-architecture.md +1042 -0
- package/package.json +7 -4
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.createBackup = createBackup;
|
|
37
|
+
exports.restoreManifest = restoreManifest;
|
|
38
|
+
exports.saveLatestManifest = saveLatestManifest;
|
|
39
|
+
exports.loadLatestManifest = loadLatestManifest;
|
|
40
|
+
const fs = __importStar(require("node:fs"));
|
|
41
|
+
const path = __importStar(require("node:path"));
|
|
42
|
+
const errors_1 = require("../domain/errors");
|
|
43
|
+
const fs_utils_1 = require("./fs-utils");
|
|
44
|
+
/**
|
|
45
|
+
* Creates a point-in-time backup for the managed files involved in a mutation.
|
|
46
|
+
*/
|
|
47
|
+
function createBackup(codexDir, backupsDir, reason, files) {
|
|
48
|
+
try {
|
|
49
|
+
const backupDir = path.join(backupsDir, `${createTimestamp()}-${reason}`);
|
|
50
|
+
(0, fs_utils_1.ensureDir)(backupsDir);
|
|
51
|
+
(0, fs_utils_1.ensureDir)(backupDir);
|
|
52
|
+
const entries = [];
|
|
53
|
+
for (const file of files) {
|
|
54
|
+
const exists = fs.existsSync(file.absolutePath);
|
|
55
|
+
const backupFileName = exists ? file.relativePath.replace(/[\\/]/g, "__") : null;
|
|
56
|
+
if (exists && backupFileName) {
|
|
57
|
+
// Flatten relative paths into a single filename inside the backup directory.
|
|
58
|
+
fs.copyFileSync(file.absolutePath, path.join(backupDir, backupFileName));
|
|
59
|
+
}
|
|
60
|
+
entries.push({
|
|
61
|
+
relativePath: file.relativePath,
|
|
62
|
+
existed: exists,
|
|
63
|
+
backupFileName,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
const manifest = {
|
|
67
|
+
version: 1,
|
|
68
|
+
createdAt: new Date().toISOString(),
|
|
69
|
+
reason,
|
|
70
|
+
rootDir: codexDir,
|
|
71
|
+
backupDir,
|
|
72
|
+
files: entries,
|
|
73
|
+
};
|
|
74
|
+
(0, fs_utils_1.writeTextFileAtomic)(path.join(backupDir, "manifest.json"), `${JSON.stringify(manifest, null, 2)}\n`);
|
|
75
|
+
return manifest;
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
throw (0, errors_1.cliError)("BACKUP_FAILED", "Failed to create backup.", {
|
|
79
|
+
cause: (0, errors_1.normalizeError)(error).message,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Restores all files described by a backup manifest back into the Codex directory.
|
|
85
|
+
*/
|
|
86
|
+
function restoreManifest(manifest) {
|
|
87
|
+
for (const entry of manifest.files) {
|
|
88
|
+
const targetPath = path.join(manifest.rootDir, entry.relativePath);
|
|
89
|
+
if (!entry.existed) {
|
|
90
|
+
if (fs.existsSync(targetPath)) {
|
|
91
|
+
// Remove files that were created by the failed mutation but were absent before it.
|
|
92
|
+
fs.rmSync(targetPath, { force: true });
|
|
93
|
+
}
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
if (!entry.backupFileName) {
|
|
97
|
+
throw new Error(`Backup file for ${entry.relativePath} is missing from manifest.`);
|
|
98
|
+
}
|
|
99
|
+
const sourcePath = path.join(manifest.backupDir, entry.backupFileName);
|
|
100
|
+
if (!fs.existsSync(sourcePath)) {
|
|
101
|
+
throw new Error(`Backup file not found: ${sourcePath}`);
|
|
102
|
+
}
|
|
103
|
+
(0, fs_utils_1.ensureDir)(path.dirname(targetPath));
|
|
104
|
+
fs.copyFileSync(sourcePath, targetPath);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Persists the latest successful backup manifest for manual rollback.
|
|
109
|
+
*/
|
|
110
|
+
function saveLatestManifest(latestBackupPath, manifest) {
|
|
111
|
+
(0, fs_utils_1.writeTextFileAtomic)(latestBackupPath, `${JSON.stringify(manifest, null, 2)}\n`);
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Loads and validates the latest rollback manifest file.
|
|
115
|
+
*/
|
|
116
|
+
function loadLatestManifest(latestBackupPath) {
|
|
117
|
+
if (!fs.existsSync(latestBackupPath)) {
|
|
118
|
+
throw (0, errors_1.cliError)("ROLLBACK_FAILED", "No rollback backup is available.", {
|
|
119
|
+
file: latestBackupPath,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
try {
|
|
123
|
+
const manifest = JSON.parse(fs.readFileSync(latestBackupPath, "utf8"));
|
|
124
|
+
if (!manifest || typeof manifest !== "object" || !Array.isArray(manifest.files)) {
|
|
125
|
+
throw new Error("Invalid latest backup manifest.");
|
|
126
|
+
}
|
|
127
|
+
return manifest;
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
throw (0, errors_1.cliError)("ROLLBACK_FAILED", "Failed to read latest backup manifest.", {
|
|
131
|
+
file: latestBackupPath,
|
|
132
|
+
cause: (0, errors_1.normalizeError)(error).message,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Formats a filesystem-safe timestamp for backup directory names.
|
|
138
|
+
*/
|
|
139
|
+
function createTimestamp() {
|
|
140
|
+
const now = new Date();
|
|
141
|
+
const pad = (value) => value.toString().padStart(2, "0");
|
|
142
|
+
return [
|
|
143
|
+
now.getFullYear().toString(),
|
|
144
|
+
pad(now.getMonth() + 1),
|
|
145
|
+
pad(now.getDate()),
|
|
146
|
+
"-",
|
|
147
|
+
pad(now.getHours()),
|
|
148
|
+
pad(now.getMinutes()),
|
|
149
|
+
pad(now.getSeconds()),
|
|
150
|
+
].join("");
|
|
151
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.setCodexSpawnImplementation = setCodexSpawnImplementation;
|
|
4
|
+
exports.resetCodexSpawnImplementation = resetCodexSpawnImplementation;
|
|
5
|
+
exports.runCodexLogin = runCodexLogin;
|
|
6
|
+
exports.checkCodexAvailable = checkCodexAvailable;
|
|
7
|
+
const node_child_process_1 = require("node:child_process");
|
|
8
|
+
const errors_1 = require("../domain/errors");
|
|
9
|
+
let spawnImplementation = node_child_process_1.spawnSync;
|
|
10
|
+
/**
|
|
11
|
+
* Overrides the spawn implementation for tests.
|
|
12
|
+
*/
|
|
13
|
+
function setCodexSpawnImplementation(spawnLike) {
|
|
14
|
+
spawnImplementation = spawnLike;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Restores the default Node spawn implementation after tests.
|
|
18
|
+
*/
|
|
19
|
+
function resetCodexSpawnImplementation() {
|
|
20
|
+
spawnImplementation = node_child_process_1.spawnSync;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Runs `codex login --with-api-key` in the target Codex directory.
|
|
24
|
+
*/
|
|
25
|
+
function runCodexLogin(apiKey, workingDir) {
|
|
26
|
+
const result = spawnImplementation("codex", ["login", "--with-api-key"], {
|
|
27
|
+
cwd: workingDir,
|
|
28
|
+
input: `${apiKey}\n`,
|
|
29
|
+
stdio: "pipe",
|
|
30
|
+
encoding: "utf8",
|
|
31
|
+
});
|
|
32
|
+
if (result.error || result.status !== 0) {
|
|
33
|
+
throw (0, errors_1.cliError)("CODEX_LOGIN_FAILED", "codex login --with-api-key failed.", {
|
|
34
|
+
cause: result.error?.message ?? (result.stderr.trim() || "Unknown codex login failure"),
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Checks whether the Codex CLI is available on PATH.
|
|
40
|
+
*/
|
|
41
|
+
function checkCodexAvailable() {
|
|
42
|
+
const result = spawnImplementation("codex", ["--version"], {
|
|
43
|
+
stdio: "pipe",
|
|
44
|
+
encoding: "utf8",
|
|
45
|
+
});
|
|
46
|
+
if (result.error || result.status !== 0) {
|
|
47
|
+
return {
|
|
48
|
+
ok: false,
|
|
49
|
+
cause: result.error?.message ?? (result.stderr.trim() || "Unknown failure"),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
return { ok: true };
|
|
53
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.resolveCodexDir = resolveCodexDir;
|
|
37
|
+
exports.createCodexPaths = createCodexPaths;
|
|
38
|
+
const os = __importStar(require("node:os"));
|
|
39
|
+
const path = __importStar(require("node:path"));
|
|
40
|
+
/**
|
|
41
|
+
* Resolves the working Codex directory, defaulting to `~/.codex`.
|
|
42
|
+
*/
|
|
43
|
+
function resolveCodexDir(codexDir) {
|
|
44
|
+
return codexDir ? path.resolve(codexDir) : path.join(os.homedir(), ".codex");
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Expands a Codex home directory into the file paths used by the CLI.
|
|
48
|
+
*/
|
|
49
|
+
function createCodexPaths(codexDir) {
|
|
50
|
+
return {
|
|
51
|
+
codexDir,
|
|
52
|
+
configPath: path.join(codexDir, "config.toml"),
|
|
53
|
+
providersPath: path.join(codexDir, "providers.json"),
|
|
54
|
+
authPath: path.join(codexDir, "auth.json"),
|
|
55
|
+
backupsDir: path.join(codexDir, "backups"),
|
|
56
|
+
latestBackupPath: path.join(codexDir, "backups", "latest.json"),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.readConfigFile = readConfigFile;
|
|
4
|
+
exports.readCurrentProfile = readCurrentProfile;
|
|
5
|
+
exports.listConfigProfiles = listConfigProfiles;
|
|
6
|
+
exports.ensureProfileExists = ensureProfileExists;
|
|
7
|
+
exports.updateTopLevelProfile = updateTopLevelProfile;
|
|
8
|
+
const errors_1 = require("../domain/errors");
|
|
9
|
+
const config_1 = require("../domain/config");
|
|
10
|
+
const fs_utils_1 = require("./fs-utils");
|
|
11
|
+
/**
|
|
12
|
+
* Reads config.toml and throws a typed error when the file is missing.
|
|
13
|
+
*/
|
|
14
|
+
function readConfigFile(configPath) {
|
|
15
|
+
return (0, fs_utils_1.readRequiredFile)(configPath, "CONFIG_NOT_FOUND", "config.toml");
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Reads the active top-level profile from config.toml.
|
|
19
|
+
*/
|
|
20
|
+
function readCurrentProfile(configPath) {
|
|
21
|
+
const content = readConfigFile(configPath);
|
|
22
|
+
const profile = (0, config_1.parseTopLevelProfile)(content);
|
|
23
|
+
if (!profile) {
|
|
24
|
+
throw (0, errors_1.cliError)("PROFILE_NOT_FOUND", "No top-level profile is set in config.toml.", {
|
|
25
|
+
file: configPath,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
return profile;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Lists all named profile sections declared in config.toml.
|
|
32
|
+
*/
|
|
33
|
+
function listConfigProfiles(configPath) {
|
|
34
|
+
return (0, config_1.parseProfileNames)(readConfigFile(configPath));
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Verifies that a provider's target profile exists before a switch operation proceeds.
|
|
38
|
+
*/
|
|
39
|
+
function ensureProfileExists(configPath, profile, provider) {
|
|
40
|
+
const configContent = readConfigFile(configPath);
|
|
41
|
+
const profiles = (0, config_1.parseProfileNames)(configContent);
|
|
42
|
+
if (!profiles.has(profile)) {
|
|
43
|
+
throw (0, errors_1.cliError)("PROFILE_NOT_FOUND", `Profile "${profile}" does not exist in config.toml.`, {
|
|
44
|
+
file: configPath,
|
|
45
|
+
provider,
|
|
46
|
+
profile,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
return configContent;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Rewrites config.toml so the requested profile becomes the active top-level profile.
|
|
53
|
+
*/
|
|
54
|
+
function updateTopLevelProfile(configPath, configContent, profile) {
|
|
55
|
+
(0, fs_utils_1.writeTextFileAtomic)(configPath, (0, config_1.replaceTopLevelProfile)(configContent, profile));
|
|
56
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.ensureDir = ensureDir;
|
|
37
|
+
exports.writeTextFileAtomic = writeTextFileAtomic;
|
|
38
|
+
exports.readRequiredFile = readRequiredFile;
|
|
39
|
+
exports.formatDetail = formatDetail;
|
|
40
|
+
exports.printErrorDetails = printErrorDetails;
|
|
41
|
+
const fs = __importStar(require("node:fs"));
|
|
42
|
+
const path = __importStar(require("node:path"));
|
|
43
|
+
const errors_1 = require("../domain/errors");
|
|
44
|
+
/**
|
|
45
|
+
* Creates a directory tree when it does not already exist.
|
|
46
|
+
*/
|
|
47
|
+
function ensureDir(directoryPath) {
|
|
48
|
+
fs.mkdirSync(directoryPath, { recursive: true });
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Writes a text file via a temporary sibling file and atomic rename.
|
|
52
|
+
*/
|
|
53
|
+
function writeTextFileAtomic(filePath, contents) {
|
|
54
|
+
ensureDir(path.dirname(filePath));
|
|
55
|
+
// Use the current process id in the temp name to reduce collision risk.
|
|
56
|
+
const tempPath = `${filePath}.tmp-${process.pid}`;
|
|
57
|
+
fs.writeFileSync(tempPath, contents, "utf8");
|
|
58
|
+
fs.renameSync(tempPath, filePath);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Reads a required text file and throws a typed error when it is missing.
|
|
62
|
+
*/
|
|
63
|
+
function readRequiredFile(filePath, code, label) {
|
|
64
|
+
if (!fs.existsSync(filePath)) {
|
|
65
|
+
throw (0, errors_1.cliError)(code, `${label} does not exist.`, { file: filePath });
|
|
66
|
+
}
|
|
67
|
+
return fs.readFileSync(filePath, "utf8");
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Formats arbitrary error detail values for human-readable output.
|
|
71
|
+
*/
|
|
72
|
+
function formatDetail(value) {
|
|
73
|
+
if (Array.isArray(value)) {
|
|
74
|
+
return value.join(", ");
|
|
75
|
+
}
|
|
76
|
+
if (value && typeof value === "object") {
|
|
77
|
+
return JSON.stringify(value);
|
|
78
|
+
}
|
|
79
|
+
return String(value);
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Renders structured error details while suppressing secret-looking API key fields.
|
|
83
|
+
*/
|
|
84
|
+
function printErrorDetails(error) {
|
|
85
|
+
if (!error.details) {
|
|
86
|
+
return [];
|
|
87
|
+
}
|
|
88
|
+
const lines = [];
|
|
89
|
+
for (const [key, value] of Object.entries(error.details)) {
|
|
90
|
+
if (typeof value === "string" && key.toLowerCase().includes("apikey")) {
|
|
91
|
+
// Do not leak API keys back into the terminal if one appears in error details.
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
lines.push(` ${key}: ${formatDetail(value)}`);
|
|
95
|
+
}
|
|
96
|
+
return lines;
|
|
97
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.withCodexLock = withCodexLock;
|
|
37
|
+
const fs = __importStar(require("node:fs"));
|
|
38
|
+
const path = __importStar(require("node:path"));
|
|
39
|
+
const errors_1 = require("../domain/errors");
|
|
40
|
+
const fs_utils_1 = require("./fs-utils");
|
|
41
|
+
/**
|
|
42
|
+
* Executes a mutation while holding an exclusive codex-switch lock file.
|
|
43
|
+
*/
|
|
44
|
+
function withCodexLock(codexDir, operation, run) {
|
|
45
|
+
(0, fs_utils_1.ensureDir)(codexDir);
|
|
46
|
+
const lockPath = path.join(codexDir, ".codex-switch.lock");
|
|
47
|
+
acquireLock(lockPath, operation);
|
|
48
|
+
try {
|
|
49
|
+
return run();
|
|
50
|
+
}
|
|
51
|
+
finally {
|
|
52
|
+
releaseLock(lockPath);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Acquires the lock file using exclusive create semantics.
|
|
57
|
+
*/
|
|
58
|
+
function acquireLock(lockPath, operation) {
|
|
59
|
+
const payload = {
|
|
60
|
+
pid: process.pid,
|
|
61
|
+
operation,
|
|
62
|
+
createdAt: new Date().toISOString(),
|
|
63
|
+
};
|
|
64
|
+
try {
|
|
65
|
+
fs.writeFileSync(lockPath, `${JSON.stringify(payload, null, 2)}\n`, { encoding: "utf8", flag: "wx" });
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
const existing = readLockRecord(lockPath);
|
|
69
|
+
throw (0, errors_1.cliError)("LOCK_CONFLICT", "Another codex-switch write operation is already running.", {
|
|
70
|
+
file: lockPath,
|
|
71
|
+
activeOperation: existing?.operation ?? "unknown",
|
|
72
|
+
activePid: existing?.pid ?? null,
|
|
73
|
+
activeSince: existing?.createdAt ?? null,
|
|
74
|
+
requestedOperation: operation,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Removes the lock file when the mutation completes.
|
|
80
|
+
*/
|
|
81
|
+
function releaseLock(lockPath) {
|
|
82
|
+
if (fs.existsSync(lockPath)) {
|
|
83
|
+
fs.rmSync(lockPath, { force: true });
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Reads the current lock metadata when a lock conflict occurs.
|
|
88
|
+
*/
|
|
89
|
+
function readLockRecord(lockPath) {
|
|
90
|
+
if (!fs.existsSync(lockPath)) {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
try {
|
|
94
|
+
return JSON.parse(fs.readFileSync(lockPath, "utf8"));
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.readProvidersFile = readProvidersFile;
|
|
37
|
+
exports.readProvidersFileIfExists = readProvidersFileIfExists;
|
|
38
|
+
exports.writeProvidersFile = writeProvidersFile;
|
|
39
|
+
const fs = __importStar(require("node:fs"));
|
|
40
|
+
const errors_1 = require("../domain/errors");
|
|
41
|
+
const providers_1 = require("../domain/providers");
|
|
42
|
+
const fs_utils_1 = require("./fs-utils");
|
|
43
|
+
/**
|
|
44
|
+
* Reads and validates providers.json from disk.
|
|
45
|
+
*/
|
|
46
|
+
function readProvidersFile(providersPath) {
|
|
47
|
+
const raw = (0, fs_utils_1.readRequiredFile)(providersPath, "PROVIDERS_NOT_FOUND", "providers.json");
|
|
48
|
+
try {
|
|
49
|
+
return (0, providers_1.validateProvidersShape)(JSON.parse(raw));
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
throw (0, errors_1.cliError)("PROVIDERS_PARSE_ERROR", "Failed to parse providers.json.", {
|
|
53
|
+
file: providersPath,
|
|
54
|
+
cause: (0, errors_1.normalizeError)(error).message,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Reads providers.json when it exists, otherwise returns an empty registry.
|
|
60
|
+
*/
|
|
61
|
+
function readProvidersFileIfExists(providersPath) {
|
|
62
|
+
return fs.existsSync(providersPath) ? readProvidersFile(providersPath) : { providers: {} };
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Persists providers.json using deterministic key ordering.
|
|
66
|
+
*/
|
|
67
|
+
function writeProvidersFile(providersPath, providers) {
|
|
68
|
+
(0, fs_utils_1.writeTextFileAtomic)(providersPath, `${JSON.stringify((0, providers_1.sortProviders)(providers), null, 2)}\n`);
|
|
69
|
+
}
|