@minniexcode/codex-switch 0.0.6 → 0.0.8
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 +5 -2
- package/README.md +12 -6
- package/dist/app/add-provider.js +90 -5
- package/dist/app/edit-provider.js +39 -11
- package/dist/app/get-status.js +31 -1
- package/dist/app/init-codex.js +68 -0
- package/dist/app/list-providers.js +1 -0
- package/dist/app/run-doctor.js +96 -1
- package/dist/app/setup-codex.js +18 -9
- package/dist/app/show-config.js +9 -1
- package/dist/app/switch-provider.js +61 -8
- package/dist/cli/add-interactive.js +4 -2
- package/dist/cli/args.js +3 -0
- package/dist/cli/help.js +3 -0
- package/dist/cli/interactive.js +3 -0
- package/dist/cli/output.js +20 -5
- package/dist/cli/prompt.js +3 -0
- package/dist/cli.js +1 -1
- package/dist/commands/handlers.js +107 -13
- package/dist/commands/help.js +2 -1
- package/dist/commands/registry.js +87 -15
- package/dist/domain/config.js +137 -0
- package/dist/domain/providers.js +90 -2
- package/dist/domain/setup.js +1 -0
- package/dist/infra/backup-repo.js +3 -0
- package/dist/infra/codex-cli.js +3 -0
- package/dist/infra/codex-paths.js +3 -0
- package/dist/infra/fs-utils.js +3 -0
- package/dist/infra/lock-repo.js +3 -0
- package/dist/infra/providers-repo.js +3 -0
- package/dist/interaction/add-interactive.js +9 -18
- package/dist/interaction/interactive.js +84 -11
- package/dist/runtime/codex-probe.js +7 -0
- package/dist/runtime/copilot-adapter.js +173 -0
- package/dist/runtime/copilot-bridge-worker.js +25 -0
- package/dist/runtime/copilot-bridge.js +433 -0
- package/dist/runtime/copilot-installer.js +125 -0
- package/dist/runtime/copilot-sdk-loader.js +59 -0
- package/dist/storage/auth-repo.js +160 -0
- package/dist/storage/config-repo.js +58 -0
- package/dist/storage/fs-utils.js +3 -0
- package/dist/storage/runtime-state-repo.js +80 -0
- package/docs/Design/codex-switch-v0.0.7-design.md +862 -0
- package/docs/Design/codex-switch-v0.0.8-design.md +132 -0
- package/docs/Design/codex-switch-v0.0.9-to-v0.0.12-roadmap.md +413 -0
- package/docs/PRD/codex-switch-prd-v0.0.5-to-v0.1.0.md +131 -25
- package/docs/PRD/codex-switch-prd-v0.0.8.md +62 -0
- package/docs/Reference/codex-config-reference.md +604 -0
- package/docs/Reference/codex-config-reference.zh-CN.md +633 -0
- package/docs/cli-usage.md +77 -29
- package/docs/test-report-0.0.7.md +118 -0
- package/docs/testing.md +67 -47
- package/package.json +1 -1
|
@@ -0,0 +1,160 @@
|
|
|
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.readAuthFileIfExists = readAuthFileIfExists;
|
|
37
|
+
exports.buildManagedAuthPayload = buildManagedAuthPayload;
|
|
38
|
+
exports.buildManagedAuthJson = buildManagedAuthJson;
|
|
39
|
+
exports.writeAuthFile = writeAuthFile;
|
|
40
|
+
exports.extractManagedAuthFingerprint = extractManagedAuthFingerprint;
|
|
41
|
+
exports.readManagedAuthState = readManagedAuthState;
|
|
42
|
+
const fs = __importStar(require("node:fs"));
|
|
43
|
+
const errors_1 = require("../domain/errors");
|
|
44
|
+
const fs_utils_1 = require("./fs-utils");
|
|
45
|
+
const LEGACY_MANAGED_SECRET_KEYS = new Set(["api_key"]);
|
|
46
|
+
/**
|
|
47
|
+
* Reads auth.json when it exists and returns null otherwise.
|
|
48
|
+
*/
|
|
49
|
+
function readAuthFileIfExists(authPath) {
|
|
50
|
+
if (!fs.existsSync(authPath)) {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
return JSON.parse(fs.readFileSync(authPath, "utf8"));
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
throw (0, errors_1.cliError)("AUTH_JSON_INVALID", "Failed to parse auth.json.", {
|
|
58
|
+
file: authPath,
|
|
59
|
+
cause: (0, errors_1.normalizeError)(error).message,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Builds the stable managed auth payload for one provider.
|
|
65
|
+
*/
|
|
66
|
+
function buildManagedAuthPayload(provider) {
|
|
67
|
+
return {
|
|
68
|
+
auth_mode: "apikey",
|
|
69
|
+
[provider.envKey]: provider.apiKey,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Builds the next auth.json object while preserving unmanaged metadata.
|
|
74
|
+
*/
|
|
75
|
+
function buildManagedAuthJson(provider, existingAuthJson) {
|
|
76
|
+
const nextManaged = buildManagedAuthPayload(provider);
|
|
77
|
+
const result = {};
|
|
78
|
+
if (existingAuthJson && typeof existingAuthJson === "object" && !Array.isArray(existingAuthJson)) {
|
|
79
|
+
for (const [key, value] of Object.entries(existingAuthJson)) {
|
|
80
|
+
if (key === "auth_mode" || LEGACY_MANAGED_SECRET_KEYS.has(key) || looksLikeManagedSecretKey(key)) {
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
result[key] = value;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
result.auth_mode = nextManaged.auth_mode;
|
|
87
|
+
result[provider.envKey] = provider.apiKey;
|
|
88
|
+
return result;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Writes auth.json atomically using the managed mirror strategy.
|
|
92
|
+
*/
|
|
93
|
+
function writeAuthFile(authPath, provider, existingAuthJson) {
|
|
94
|
+
(0, fs_utils_1.writeTextFileAtomic)(authPath, `${JSON.stringify(buildManagedAuthJson(provider, existingAuthJson), null, 2)}\n`);
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Extracts a lightweight fingerprint used by doctor/status.
|
|
98
|
+
*/
|
|
99
|
+
function extractManagedAuthFingerprint(input) {
|
|
100
|
+
if (!input || typeof input !== "object" || Array.isArray(input)) {
|
|
101
|
+
return {
|
|
102
|
+
authMode: null,
|
|
103
|
+
managedSecretKeys: [],
|
|
104
|
+
payload: null,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
const payload = input;
|
|
108
|
+
const authMode = typeof payload.auth_mode === "string" ? payload.auth_mode : null;
|
|
109
|
+
const managedSecretKeys = Object.keys(payload)
|
|
110
|
+
.filter((key) => key !== "auth_mode" && looksLikeManagedSecretKey(key))
|
|
111
|
+
.sort();
|
|
112
|
+
return {
|
|
113
|
+
authMode,
|
|
114
|
+
managedSecretKeys,
|
|
115
|
+
payload,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Reads auth.json into a doctor-friendly managed state summary.
|
|
120
|
+
*/
|
|
121
|
+
function readManagedAuthState(authPath) {
|
|
122
|
+
if (!fs.existsSync(authPath)) {
|
|
123
|
+
return {
|
|
124
|
+
exists: false,
|
|
125
|
+
valid: false,
|
|
126
|
+
parseError: null,
|
|
127
|
+
authMode: null,
|
|
128
|
+
managedSecretKeys: [],
|
|
129
|
+
payload: null,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
try {
|
|
133
|
+
const payload = readAuthFileIfExists(authPath);
|
|
134
|
+
const fingerprint = extractManagedAuthFingerprint(payload);
|
|
135
|
+
return {
|
|
136
|
+
exists: true,
|
|
137
|
+
valid: Boolean(fingerprint.payload),
|
|
138
|
+
parseError: null,
|
|
139
|
+
authMode: fingerprint.authMode,
|
|
140
|
+
managedSecretKeys: fingerprint.managedSecretKeys,
|
|
141
|
+
payload: fingerprint.payload,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
return {
|
|
146
|
+
exists: true,
|
|
147
|
+
valid: false,
|
|
148
|
+
parseError: (0, errors_1.normalizeError)(error).message,
|
|
149
|
+
authMode: null,
|
|
150
|
+
managedSecretKeys: [],
|
|
151
|
+
payload: null,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
function looksLikeManagedSecretKey(key) {
|
|
156
|
+
if (LEGACY_MANAGED_SECRET_KEYS.has(key)) {
|
|
157
|
+
return true;
|
|
158
|
+
}
|
|
159
|
+
return /^[A-Z0-9_]+$/.test(key);
|
|
160
|
+
}
|
|
@@ -40,6 +40,8 @@ exports.listConfigProfiles = listConfigProfiles;
|
|
|
40
40
|
exports.ensureProfileExists = ensureProfileExists;
|
|
41
41
|
exports.requireManagedProfileRuntime = requireManagedProfileRuntime;
|
|
42
42
|
exports.requireModelProviderRuntimeSection = requireModelProviderRuntimeSection;
|
|
43
|
+
exports.requireRuntimeEnvKey = requireRuntimeEnvKey;
|
|
44
|
+
exports.resolveActiveProviderName = resolveActiveProviderName;
|
|
43
45
|
exports.updateTopLevelProfile = updateTopLevelProfile;
|
|
44
46
|
exports.createConfigMutationPlan = createConfigMutationPlan;
|
|
45
47
|
exports.applyConfigMutation = applyConfigMutation;
|
|
@@ -49,6 +51,7 @@ const os = __importStar(require("node:os"));
|
|
|
49
51
|
const path = __importStar(require("node:path"));
|
|
50
52
|
const config_1 = require("../domain/config");
|
|
51
53
|
const errors_1 = require("../domain/errors");
|
|
54
|
+
const providers_1 = require("../domain/providers");
|
|
52
55
|
const codex_paths_1 = require("./codex-paths");
|
|
53
56
|
const fs_utils_1 = require("./fs-utils");
|
|
54
57
|
/**
|
|
@@ -140,6 +143,13 @@ function requireManagedProfileRuntime(document, providers, profile) {
|
|
|
140
143
|
missingFields: ["base_url"],
|
|
141
144
|
});
|
|
142
145
|
}
|
|
146
|
+
if (!modelProviderSection.envKey) {
|
|
147
|
+
throw (0, errors_1.cliError)("MODEL_PROVIDER_ENV_KEY_MISSING", `Model provider "${view.modelProvider}" requires env_key.`, {
|
|
148
|
+
profile,
|
|
149
|
+
modelProvider: view.modelProvider,
|
|
150
|
+
missingFields: ["env_key"],
|
|
151
|
+
});
|
|
152
|
+
}
|
|
143
153
|
return view;
|
|
144
154
|
}
|
|
145
155
|
/**
|
|
@@ -160,6 +170,54 @@ function requireModelProviderRuntimeSection(document, profile) {
|
|
|
160
170
|
missingFields: ["base_url"],
|
|
161
171
|
});
|
|
162
172
|
}
|
|
173
|
+
if (!modelProviderSection.envKey) {
|
|
174
|
+
throw (0, errors_1.cliError)("MODEL_PROVIDER_ENV_KEY_MISSING", `Model provider "${profile}" requires env_key.`, {
|
|
175
|
+
profile,
|
|
176
|
+
modelProvider: profile,
|
|
177
|
+
missingFields: ["env_key"],
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Returns the runtime env_key for one profile or throws a typed error.
|
|
183
|
+
*/
|
|
184
|
+
function requireRuntimeEnvKey(document, profile) {
|
|
185
|
+
const modelProviderSection = document.modelProviders.find((entry) => entry.name === profile);
|
|
186
|
+
if (!modelProviderSection) {
|
|
187
|
+
throw (0, errors_1.cliError)("PROFILE_NOT_FOUND", `Model provider "${profile}" does not exist in config.toml.`, {
|
|
188
|
+
profile,
|
|
189
|
+
modelProvider: profile,
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
if (!modelProviderSection.envKey) {
|
|
193
|
+
throw (0, errors_1.cliError)("MODEL_PROVIDER_ENV_KEY_MISSING", `Model provider "${profile}" requires env_key.`, {
|
|
194
|
+
profile,
|
|
195
|
+
modelProvider: profile,
|
|
196
|
+
missingFields: ["env_key"],
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
return modelProviderSection.envKey;
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Resolves the current active provider and requires the mapping to be unique.
|
|
203
|
+
*/
|
|
204
|
+
function resolveActiveProviderName(document, providers) {
|
|
205
|
+
if (!document.activeProfile) {
|
|
206
|
+
throw (0, errors_1.cliError)("PROFILE_NOT_FOUND", "No top-level profile is set in config.toml.");
|
|
207
|
+
}
|
|
208
|
+
const matches = (0, providers_1.findProvidersByProfile)(providers, document.activeProfile);
|
|
209
|
+
if (matches.length === 0) {
|
|
210
|
+
throw (0, errors_1.cliError)("UNMANAGED_ACTIVE_PROFILE", `Active profile "${document.activeProfile}" is not mapped by providers.json.`, {
|
|
211
|
+
profile: document.activeProfile,
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
if (matches.length > 1) {
|
|
215
|
+
throw (0, errors_1.cliError)("ACTIVE_PROVIDER_UNRESOLVED", `Active profile "${document.activeProfile}" maps to multiple providers.`, {
|
|
216
|
+
profile: document.activeProfile,
|
|
217
|
+
providers: matches,
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
return matches[0];
|
|
163
221
|
}
|
|
164
222
|
/**
|
|
165
223
|
* Rewrites config.toml so the requested profile becomes the active top-level profile.
|
package/dist/storage/fs-utils.js
CHANGED
|
@@ -55,6 +55,9 @@ function writeTextFileAtomic(filePath, contents) {
|
|
|
55
55
|
// Use the current process id in the temp name to reduce collision risk.
|
|
56
56
|
const tempPath = `${filePath}.tmp-${process.pid}`;
|
|
57
57
|
fs.writeFileSync(tempPath, contents, "utf8");
|
|
58
|
+
if (fs.existsSync(filePath)) {
|
|
59
|
+
fs.rmSync(filePath, { force: true });
|
|
60
|
+
}
|
|
58
61
|
fs.renameSync(tempPath, filePath);
|
|
59
62
|
}
|
|
60
63
|
/**
|
|
@@ -0,0 +1,80 @@
|
|
|
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.getCopilotBridgeStatePath = getCopilotBridgeStatePath;
|
|
37
|
+
exports.readCopilotBridgeState = readCopilotBridgeState;
|
|
38
|
+
exports.writeCopilotBridgeState = writeCopilotBridgeState;
|
|
39
|
+
exports.clearCopilotBridgeState = clearCopilotBridgeState;
|
|
40
|
+
const fs = __importStar(require("node:fs"));
|
|
41
|
+
const os = __importStar(require("node:os"));
|
|
42
|
+
const path = __importStar(require("node:path"));
|
|
43
|
+
const fs_utils_1 = require("./fs-utils");
|
|
44
|
+
/**
|
|
45
|
+
* Returns the user-level runtime state file used by Copilot bridge helpers.
|
|
46
|
+
*/
|
|
47
|
+
function getCopilotBridgeStatePath() {
|
|
48
|
+
const override = process.env.CODEX_SWITCH_RUNTIME_STATE_DIR;
|
|
49
|
+
if (override && override.trim() !== "") {
|
|
50
|
+
return path.join(path.resolve(override), "copilot-bridge-state.json");
|
|
51
|
+
}
|
|
52
|
+
return path.join(os.homedir(), ".codex-switch", "runtime", "copilot-bridge-state.json");
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Reads the Copilot bridge state manifest when present.
|
|
56
|
+
*/
|
|
57
|
+
function readCopilotBridgeState() {
|
|
58
|
+
const statePath = getCopilotBridgeStatePath();
|
|
59
|
+
if (!fs.existsSync(statePath)) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
return JSON.parse(fs.readFileSync(statePath, "utf8"));
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Persists the Copilot bridge state manifest.
|
|
66
|
+
*/
|
|
67
|
+
function writeCopilotBridgeState(state) {
|
|
68
|
+
const statePath = getCopilotBridgeStatePath();
|
|
69
|
+
(0, fs_utils_1.ensureDir)(path.dirname(statePath));
|
|
70
|
+
(0, fs_utils_1.writeTextFileAtomic)(statePath, `${JSON.stringify(state, null, 2)}\n`);
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Deletes the Copilot bridge state manifest when present.
|
|
74
|
+
*/
|
|
75
|
+
function clearCopilotBridgeState() {
|
|
76
|
+
const statePath = getCopilotBridgeStatePath();
|
|
77
|
+
if (fs.existsSync(statePath)) {
|
|
78
|
+
fs.rmSync(statePath, { force: true });
|
|
79
|
+
}
|
|
80
|
+
}
|