@minniexcode/codex-switch 0.0.10 → 0.0.11
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 +52 -15
- package/README.CN.md +81 -48
- package/README.md +75 -34
- package/dist/app/add-provider.js +29 -15
- package/dist/app/bridge.js +15 -14
- package/dist/app/edit-provider.js +1 -1
- package/dist/app/get-status.js +13 -6
- package/dist/app/import-providers.js +1 -1
- package/dist/app/init-codex.js +13 -14
- package/dist/app/remove-provider.js +1 -1
- package/dist/app/run-doctor.js +12 -5
- package/dist/app/run-mutation.js +3 -2
- package/dist/app/setup-codex.js +3 -1
- package/dist/app/switch-provider.js +11 -13
- package/dist/cli.js +34 -2
- package/dist/commands/args.js +2 -2
- package/dist/commands/dispatch.js +40 -0
- package/dist/commands/handlers.js +121 -156
- package/dist/commands/help.js +2 -0
- package/dist/commands/registry.js +28 -9
- package/dist/domain/backups.js +4 -4
- package/dist/domain/config.js +110 -5
- package/dist/domain/providers.js +12 -0
- package/dist/domain/runtime-state.js +81 -5
- package/dist/runtime/copilot-adapter.js +12 -12
- package/dist/runtime/copilot-bridge.js +392 -44
- package/dist/runtime/copilot-cli.js +84 -12
- package/dist/runtime/copilot-installer.js +10 -9
- package/dist/runtime/copilot-sdk-loader.js +5 -5
- package/dist/storage/backup-repo.js +4 -4
- package/dist/storage/codex-paths.js +34 -8
- package/dist/storage/lock-repo.js +2 -4
- package/dist/storage/runtime-state-repo.js +14 -13
- package/dist/storage/tool-config-repo.js +111 -0
- package/docs/Design/codex-switch-v0.0.11-design.md +824 -0
- package/docs/PRD/codex-switch-prd-v0.0.11.md +577 -0
- package/docs/cli-usage.md +166 -295
- package/package.json +1 -1
|
@@ -40,10 +40,10 @@ exports.getCopilotSdkPackageName = getCopilotSdkPackageName;
|
|
|
40
40
|
exports.probeCopilotSdkInstall = probeCopilotSdkInstall;
|
|
41
41
|
exports.installCopilotSdk = installCopilotSdk;
|
|
42
42
|
const fs = __importStar(require("node:fs"));
|
|
43
|
-
const os = __importStar(require("node:os"));
|
|
44
43
|
const path = __importStar(require("node:path"));
|
|
45
44
|
const node_child_process_1 = require("node:child_process");
|
|
46
45
|
const errors_1 = require("../domain/errors");
|
|
46
|
+
const codex_paths_1 = require("../storage/codex-paths");
|
|
47
47
|
const COPILOT_SDK_PACKAGE = "@github/copilot-sdk";
|
|
48
48
|
const COPILOT_SDK_VERSION = "latest";
|
|
49
49
|
let spawnImplementation = node_child_process_1.spawnSync;
|
|
@@ -60,14 +60,15 @@ function resetCopilotInstallerSpawnImplementation() {
|
|
|
60
60
|
spawnImplementation = node_child_process_1.spawnSync;
|
|
61
61
|
}
|
|
62
62
|
/**
|
|
63
|
-
* Returns the
|
|
63
|
+
* Returns the tool-home runtime directory used to lazily install the Copilot SDK.
|
|
64
64
|
*/
|
|
65
|
-
function getCopilotRuntimeInstallDir() {
|
|
65
|
+
function getCopilotRuntimeInstallDir(runtimesDir) {
|
|
66
66
|
const override = process.env.CODEX_SWITCH_COPILOT_RUNTIME_DIR;
|
|
67
67
|
if (override && override.trim() !== "") {
|
|
68
68
|
return path.resolve(override);
|
|
69
69
|
}
|
|
70
|
-
|
|
70
|
+
const baseRuntimesDir = runtimesDir ? path.resolve(runtimesDir) : path.join((0, codex_paths_1.resolveCodexSwitchHome)(), "runtimes");
|
|
71
|
+
return path.join(baseRuntimesDir, "copilot");
|
|
71
72
|
}
|
|
72
73
|
/**
|
|
73
74
|
* Returns the package name used by the Copilot runtime installer.
|
|
@@ -78,8 +79,8 @@ function getCopilotSdkPackageName() {
|
|
|
78
79
|
/**
|
|
79
80
|
* Reports whether the optional Copilot SDK runtime is currently installed.
|
|
80
81
|
*/
|
|
81
|
-
function probeCopilotSdkInstall() {
|
|
82
|
-
const installDir = getCopilotRuntimeInstallDir();
|
|
82
|
+
function probeCopilotSdkInstall(runtimesDir) {
|
|
83
|
+
const installDir = getCopilotRuntimeInstallDir(runtimesDir);
|
|
83
84
|
const packageJsonPath = path.join(installDir, "node_modules", "@github", "copilot-sdk", "package.json");
|
|
84
85
|
if (!fs.existsSync(packageJsonPath)) {
|
|
85
86
|
return {
|
|
@@ -100,8 +101,8 @@ function probeCopilotSdkInstall() {
|
|
|
100
101
|
/**
|
|
101
102
|
* Installs the optional Copilot SDK into the user-level runtime directory.
|
|
102
103
|
*/
|
|
103
|
-
function installCopilotSdk() {
|
|
104
|
-
const installDir = getCopilotRuntimeInstallDir();
|
|
104
|
+
function installCopilotSdk(runtimesDir) {
|
|
105
|
+
const installDir = getCopilotRuntimeInstallDir(runtimesDir);
|
|
105
106
|
fs.mkdirSync(installDir, { recursive: true });
|
|
106
107
|
const packageJsonPath = path.join(installDir, "package.json");
|
|
107
108
|
if (!fs.existsSync(packageJsonPath)) {
|
|
@@ -133,7 +134,7 @@ function installCopilotSdk() {
|
|
|
133
134
|
args: installCommand.args,
|
|
134
135
|
});
|
|
135
136
|
}
|
|
136
|
-
return probeCopilotSdkInstall();
|
|
137
|
+
return probeCopilotSdkInstall(runtimesDir);
|
|
137
138
|
}
|
|
138
139
|
/**
|
|
139
140
|
* Resolves a stable npm install invocation for the optional Copilot SDK runtime.
|
|
@@ -41,19 +41,19 @@ const copilot_installer_1 = require("./copilot-installer");
|
|
|
41
41
|
/**
|
|
42
42
|
* Dynamically resolves the lazily installed Copilot SDK entrypoint.
|
|
43
43
|
*/
|
|
44
|
-
function getCopilotSdkEntrypoint() {
|
|
45
|
-
return path.join((0, copilot_installer_1.getCopilotRuntimeInstallDir)(), "node_modules", "@github", "copilot-sdk");
|
|
44
|
+
function getCopilotSdkEntrypoint(runtimesDir) {
|
|
45
|
+
return path.join((0, copilot_installer_1.getCopilotRuntimeInstallDir)(runtimesDir), "node_modules", "@github", "copilot-sdk");
|
|
46
46
|
}
|
|
47
47
|
/**
|
|
48
48
|
* Loads the Copilot SDK only when a Copilot runtime path is exercised.
|
|
49
49
|
*/
|
|
50
|
-
async function loadCopilotSdk() {
|
|
51
|
-
const status = (0, copilot_installer_1.probeCopilotSdkInstall)();
|
|
50
|
+
async function loadCopilotSdk(runtimesDir) {
|
|
51
|
+
const status = (0, copilot_installer_1.probeCopilotSdkInstall)(runtimesDir);
|
|
52
52
|
if (!status.installed) {
|
|
53
53
|
throw (0, errors_1.cliError)("COPILOT_SDK_MISSING", "The optional Copilot SDK runtime is not installed.", {
|
|
54
54
|
installDir: status.installDir,
|
|
55
55
|
packageName: status.packageName,
|
|
56
56
|
});
|
|
57
57
|
}
|
|
58
|
-
return Promise.resolve(`${getCopilotSdkEntrypoint()}`).then(s => __importStar(require(s)));
|
|
58
|
+
return Promise.resolve(`${getCopilotSdkEntrypoint(runtimesDir)}`).then(s => __importStar(require(s)));
|
|
59
59
|
}
|
|
@@ -47,7 +47,7 @@ const fs_utils_1 = require("./fs-utils");
|
|
|
47
47
|
/**
|
|
48
48
|
* Creates a point-in-time backup for the managed files involved in a mutation.
|
|
49
49
|
*/
|
|
50
|
-
function createBackup(
|
|
50
|
+
function createBackup(backupsDir, reason, files) {
|
|
51
51
|
try {
|
|
52
52
|
const backupDir = path.join(backupsDir, `${createTimestamp()}-${reason}`);
|
|
53
53
|
(0, fs_utils_1.ensureDir)(backupsDir);
|
|
@@ -62,6 +62,7 @@ function createBackup(codexDir, backupsDir, reason, files) {
|
|
|
62
62
|
}
|
|
63
63
|
entries.push({
|
|
64
64
|
relativePath: file.relativePath,
|
|
65
|
+
restorePath: file.absolutePath,
|
|
65
66
|
existed: exists,
|
|
66
67
|
backupFileName,
|
|
67
68
|
});
|
|
@@ -70,7 +71,6 @@ function createBackup(codexDir, backupsDir, reason, files) {
|
|
|
70
71
|
version: 1,
|
|
71
72
|
createdAt: new Date().toISOString(),
|
|
72
73
|
reason,
|
|
73
|
-
rootDir: codexDir,
|
|
74
74
|
backupDir,
|
|
75
75
|
files: entries,
|
|
76
76
|
};
|
|
@@ -84,11 +84,11 @@ function createBackup(codexDir, backupsDir, reason, files) {
|
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
86
|
/**
|
|
87
|
-
* Restores all files described by a backup manifest back into
|
|
87
|
+
* Restores all files described by a backup manifest back into their original paths.
|
|
88
88
|
*/
|
|
89
89
|
function restoreManifest(manifest) {
|
|
90
90
|
for (const entry of manifest.files) {
|
|
91
|
-
const targetPath =
|
|
91
|
+
const targetPath = entry.restorePath;
|
|
92
92
|
if (!entry.existed) {
|
|
93
93
|
if (fs.existsSync(targetPath)) {
|
|
94
94
|
// Remove files that were created by the failed mutation but were absent before it.
|
|
@@ -33,17 +33,32 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.CODEX_DIR_ENV_NAME = void 0;
|
|
36
|
+
exports.TOOL_HOME_ENV_NAME = exports.CODEX_DIR_ENV_NAME = void 0;
|
|
37
|
+
exports.resolveCodexSwitchHome = resolveCodexSwitchHome;
|
|
37
38
|
exports.resolveCodexDir = resolveCodexDir;
|
|
38
39
|
exports.createCodexPaths = createCodexPaths;
|
|
39
40
|
const os = __importStar(require("node:os"));
|
|
40
41
|
const path = __importStar(require("node:path"));
|
|
41
42
|
exports.CODEX_DIR_ENV_NAME = "CODEXS_CODEX_DIR";
|
|
43
|
+
exports.TOOL_HOME_ENV_NAME = "CODEXS_HOME";
|
|
42
44
|
const DEVELOPMENT_DEFAULT_CODEX_DIR = path.resolve(process.cwd(), "dev-codex", "local-sandbox");
|
|
43
45
|
/**
|
|
44
|
-
* Resolves the
|
|
46
|
+
* Resolves the tool home directory, defaulting to `~/.config/codex-switch`.
|
|
45
47
|
*/
|
|
46
|
-
function
|
|
48
|
+
function resolveCodexSwitchHome(toolHomeDir) {
|
|
49
|
+
if (toolHomeDir) {
|
|
50
|
+
return path.resolve(toolHomeDir);
|
|
51
|
+
}
|
|
52
|
+
const envToolHome = process.env[exports.TOOL_HOME_ENV_NAME];
|
|
53
|
+
if (envToolHome) {
|
|
54
|
+
return path.resolve(envToolHome);
|
|
55
|
+
}
|
|
56
|
+
return path.join(os.homedir(), ".config", "codex-switch");
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Resolves the working Codex directory using the documented precedence order.
|
|
60
|
+
*/
|
|
61
|
+
function resolveCodexDir(codexDir, toolConfig) {
|
|
47
62
|
if (codexDir) {
|
|
48
63
|
return path.resolve(codexDir);
|
|
49
64
|
}
|
|
@@ -51,21 +66,32 @@ function resolveCodexDir(codexDir) {
|
|
|
51
66
|
if (envCodexDir) {
|
|
52
67
|
return path.resolve(envCodexDir);
|
|
53
68
|
}
|
|
69
|
+
if (toolConfig?.defaultCodexDir) {
|
|
70
|
+
return path.resolve(toolConfig.defaultCodexDir);
|
|
71
|
+
}
|
|
54
72
|
if (process.env.NODE_ENV === "development") {
|
|
55
73
|
return DEVELOPMENT_DEFAULT_CODEX_DIR;
|
|
56
74
|
}
|
|
57
75
|
return path.join(os.homedir(), ".codex");
|
|
58
76
|
}
|
|
59
77
|
/**
|
|
60
|
-
* Expands
|
|
78
|
+
* Expands the tool home and Codex runtime into the file paths used by the CLI.
|
|
61
79
|
*/
|
|
62
|
-
function createCodexPaths(
|
|
80
|
+
function createCodexPaths(args) {
|
|
81
|
+
const input = typeof args === "string" ? { codexDir: args } : args;
|
|
82
|
+
const toolHomeDir = resolveCodexSwitchHome(input.toolHomeDir);
|
|
83
|
+
const codexDir = path.resolve(input.codexDir);
|
|
63
84
|
return {
|
|
85
|
+
toolHomeDir,
|
|
86
|
+
toolConfigPath: path.join(toolHomeDir, "codex-switch.json"),
|
|
87
|
+
providersPath: path.join(toolHomeDir, "providers.json"),
|
|
88
|
+
backupsDir: path.join(toolHomeDir, "backups"),
|
|
89
|
+
latestBackupPath: path.join(toolHomeDir, "backups", "latest.json"),
|
|
90
|
+
lockPath: path.join(toolHomeDir, ".codex-switch.lock"),
|
|
91
|
+
runtimeDir: path.join(toolHomeDir, "runtime"),
|
|
92
|
+
runtimesDir: path.join(toolHomeDir, "runtimes"),
|
|
64
93
|
codexDir,
|
|
65
94
|
configPath: path.join(codexDir, "config.toml"),
|
|
66
|
-
providersPath: path.join(codexDir, "providers.json"),
|
|
67
95
|
authPath: path.join(codexDir, "auth.json"),
|
|
68
|
-
backupsDir: path.join(codexDir, "backups"),
|
|
69
|
-
latestBackupPath: path.join(codexDir, "backups", "latest.json"),
|
|
70
96
|
};
|
|
71
97
|
}
|
|
@@ -35,15 +35,13 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.withCodexLock = withCodexLock;
|
|
37
37
|
const fs = __importStar(require("node:fs"));
|
|
38
|
-
const path = __importStar(require("node:path"));
|
|
39
38
|
const errors_1 = require("../domain/errors");
|
|
40
39
|
const fs_utils_1 = require("./fs-utils");
|
|
41
40
|
/**
|
|
42
41
|
* Executes a mutation while holding an exclusive codex-switch lock file.
|
|
43
42
|
*/
|
|
44
|
-
function withCodexLock(
|
|
45
|
-
(0, fs_utils_1.ensureDir)(
|
|
46
|
-
const lockPath = path.join(codexDir, ".codex-switch.lock");
|
|
43
|
+
function withCodexLock(lockPath, operation, run) {
|
|
44
|
+
(0, fs_utils_1.ensureDir)(require("node:path").dirname(lockPath));
|
|
47
45
|
acquireLock(lockPath, operation);
|
|
48
46
|
try {
|
|
49
47
|
return run();
|
|
@@ -39,25 +39,26 @@ exports.inspectCopilotBridgeState = inspectCopilotBridgeState;
|
|
|
39
39
|
exports.writeCopilotBridgeState = writeCopilotBridgeState;
|
|
40
40
|
exports.clearCopilotBridgeState = clearCopilotBridgeState;
|
|
41
41
|
const fs = __importStar(require("node:fs"));
|
|
42
|
-
const os = __importStar(require("node:os"));
|
|
43
42
|
const path = __importStar(require("node:path"));
|
|
44
43
|
const errors_1 = require("../domain/errors");
|
|
44
|
+
const codex_paths_1 = require("./codex-paths");
|
|
45
45
|
const fs_utils_1 = require("./fs-utils");
|
|
46
46
|
/**
|
|
47
|
-
* Returns the
|
|
47
|
+
* Returns the tool-home runtime state file used by Copilot bridge helpers.
|
|
48
48
|
*/
|
|
49
|
-
function getCopilotBridgeStatePath() {
|
|
49
|
+
function getCopilotBridgeStatePath(runtimeDir) {
|
|
50
50
|
const override = process.env.CODEX_SWITCH_RUNTIME_STATE_DIR;
|
|
51
51
|
if (override && override.trim() !== "") {
|
|
52
52
|
return path.join(path.resolve(override), "copilot-bridge-state.json");
|
|
53
53
|
}
|
|
54
|
-
|
|
54
|
+
const baseRuntimeDir = runtimeDir ? path.resolve(runtimeDir) : path.join((0, codex_paths_1.resolveCodexSwitchHome)(), "runtime");
|
|
55
|
+
return path.join(baseRuntimeDir, "copilot-bridge-state.json");
|
|
55
56
|
}
|
|
56
57
|
/**
|
|
57
58
|
* Reads the Copilot bridge state manifest when present.
|
|
58
59
|
*/
|
|
59
|
-
function readCopilotBridgeState() {
|
|
60
|
-
const statePath = getCopilotBridgeStatePath();
|
|
60
|
+
function readCopilotBridgeState(runtimeDir) {
|
|
61
|
+
const statePath = getCopilotBridgeStatePath(runtimeDir);
|
|
61
62
|
if (!fs.existsSync(statePath)) {
|
|
62
63
|
return null;
|
|
63
64
|
}
|
|
@@ -66,8 +67,8 @@ function readCopilotBridgeState() {
|
|
|
66
67
|
/**
|
|
67
68
|
* Safely inspects the runtime-state file for status/doctor style read paths.
|
|
68
69
|
*/
|
|
69
|
-
function inspectCopilotBridgeState() {
|
|
70
|
-
const statePath = getCopilotBridgeStatePath();
|
|
70
|
+
function inspectCopilotBridgeState(runtimeDir) {
|
|
71
|
+
const statePath = getCopilotBridgeStatePath(runtimeDir);
|
|
71
72
|
if (!fs.existsSync(statePath)) {
|
|
72
73
|
return {
|
|
73
74
|
exists: false,
|
|
@@ -81,7 +82,7 @@ function inspectCopilotBridgeState() {
|
|
|
81
82
|
exists: true,
|
|
82
83
|
valid: true,
|
|
83
84
|
parseError: null,
|
|
84
|
-
state: readCopilotBridgeState(),
|
|
85
|
+
state: readCopilotBridgeState(runtimeDir),
|
|
85
86
|
};
|
|
86
87
|
}
|
|
87
88
|
catch (error) {
|
|
@@ -96,16 +97,16 @@ function inspectCopilotBridgeState() {
|
|
|
96
97
|
/**
|
|
97
98
|
* Persists the Copilot bridge state manifest.
|
|
98
99
|
*/
|
|
99
|
-
function writeCopilotBridgeState(state) {
|
|
100
|
-
const statePath = getCopilotBridgeStatePath();
|
|
100
|
+
function writeCopilotBridgeState(state, runtimeDir) {
|
|
101
|
+
const statePath = getCopilotBridgeStatePath(runtimeDir);
|
|
101
102
|
(0, fs_utils_1.ensureDir)(path.dirname(statePath));
|
|
102
103
|
(0, fs_utils_1.writeTextFileAtomic)(statePath, `${JSON.stringify(state, null, 2)}\n`);
|
|
103
104
|
}
|
|
104
105
|
/**
|
|
105
106
|
* Deletes the Copilot bridge state manifest when present.
|
|
106
107
|
*/
|
|
107
|
-
function clearCopilotBridgeState() {
|
|
108
|
-
const statePath = getCopilotBridgeStatePath();
|
|
108
|
+
function clearCopilotBridgeState(runtimeDir) {
|
|
109
|
+
const statePath = getCopilotBridgeStatePath(runtimeDir);
|
|
109
110
|
if (fs.existsSync(statePath)) {
|
|
110
111
|
fs.rmSync(statePath, { force: true });
|
|
111
112
|
}
|
|
@@ -0,0 +1,111 @@
|
|
|
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.readToolConfigIfExists = readToolConfigIfExists;
|
|
37
|
+
exports.ensureToolConfig = ensureToolConfig;
|
|
38
|
+
exports.writeToolConfig = writeToolConfig;
|
|
39
|
+
const fs = __importStar(require("node:fs"));
|
|
40
|
+
const errors_1 = require("../domain/errors");
|
|
41
|
+
const fs_utils_1 = require("./fs-utils");
|
|
42
|
+
/**
|
|
43
|
+
* Reads the optional tool-level codex-switch config file when present.
|
|
44
|
+
*/
|
|
45
|
+
function readToolConfigIfExists(toolConfigPath) {
|
|
46
|
+
if (!fs.existsSync(toolConfigPath)) {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
try {
|
|
50
|
+
const parsed = JSON.parse(fs.readFileSync(toolConfigPath, "utf8"));
|
|
51
|
+
return validateToolConfig(parsed, toolConfigPath);
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
throw (0, errors_1.cliError)("INVALID_CONFIG", "codex-switch.json is invalid.", {
|
|
55
|
+
file: toolConfigPath,
|
|
56
|
+
cause: error instanceof Error ? error.message : String(error),
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Ensures the tool-level config file exists with the minimum stable fields.
|
|
62
|
+
*/
|
|
63
|
+
function ensureToolConfig(toolConfigPath, version, defaultCodexDir) {
|
|
64
|
+
const current = readToolConfigIfExists(toolConfigPath);
|
|
65
|
+
if (current) {
|
|
66
|
+
return {
|
|
67
|
+
created: false,
|
|
68
|
+
config: current,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
const next = {
|
|
72
|
+
version,
|
|
73
|
+
};
|
|
74
|
+
if (defaultCodexDir) {
|
|
75
|
+
next.defaultCodexDir = defaultCodexDir;
|
|
76
|
+
}
|
|
77
|
+
(0, fs_utils_1.ensureDir)(require("node:path").dirname(toolConfigPath));
|
|
78
|
+
(0, fs_utils_1.writeTextFileAtomic)(toolConfigPath, `${JSON.stringify(next, null, 2)}\n`);
|
|
79
|
+
return {
|
|
80
|
+
created: true,
|
|
81
|
+
config: next,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Writes the tool-level config file with a normalized shape.
|
|
86
|
+
*/
|
|
87
|
+
function writeToolConfig(toolConfigPath, config) {
|
|
88
|
+
const normalized = validateToolConfig(config, toolConfigPath);
|
|
89
|
+
(0, fs_utils_1.writeTextFileAtomic)(toolConfigPath, `${JSON.stringify(normalized, null, 2)}\n`);
|
|
90
|
+
}
|
|
91
|
+
function validateToolConfig(config, toolConfigPath) {
|
|
92
|
+
if (!config || typeof config !== "object") {
|
|
93
|
+
throw (0, errors_1.cliError)("INVALID_CONFIG", "codex-switch.json must contain a JSON object.", {
|
|
94
|
+
file: toolConfigPath,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
if (typeof config.version !== "string" || config.version.trim() === "") {
|
|
98
|
+
throw (0, errors_1.cliError)("INVALID_CONFIG", "codex-switch.json requires a non-empty version field.", {
|
|
99
|
+
file: toolConfigPath,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
if (config.defaultCodexDir !== undefined && typeof config.defaultCodexDir !== "string") {
|
|
103
|
+
throw (0, errors_1.cliError)("INVALID_CONFIG", "codex-switch.json.defaultCodexDir must be a string when provided.", {
|
|
104
|
+
file: toolConfigPath,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
return {
|
|
108
|
+
version: config.version,
|
|
109
|
+
defaultCodexDir: config.defaultCodexDir,
|
|
110
|
+
};
|
|
111
|
+
}
|