@minniexcode/codex-switch 0.0.4 → 0.0.6
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.md +35 -97
- package/dist/app/add-provider.js +40 -3
- package/dist/app/edit-provider.js +76 -3
- package/dist/app/export-providers.js +2 -2
- package/dist/app/get-current-profile.js +1 -1
- package/dist/app/get-status.js +10 -3
- package/dist/app/import-providers.js +47 -3
- package/dist/app/list-backups.js +1 -1
- package/dist/app/list-config-profiles.js +30 -0
- package/dist/app/list-providers.js +1 -1
- package/dist/app/remove-provider.js +35 -3
- package/dist/app/rollback-backup.js +1 -1
- package/dist/app/rollback-latest.js +1 -1
- package/dist/app/run-doctor.js +44 -26
- package/dist/app/run-mutation.js +2 -2
- package/dist/app/setup-codex.js +37 -20
- package/dist/app/show-config.js +34 -0
- package/dist/app/show-provider.js +1 -1
- package/dist/app/switch-provider.js +8 -5
- package/dist/cli/add-interactive.js +7 -106
- package/dist/cli/args.js +5 -126
- package/dist/cli/help.js +5 -276
- package/dist/cli/interactive.js +16 -171
- package/dist/cli/output.js +23 -1
- package/dist/cli/prompt.js +3 -108
- package/dist/cli.js +10 -315
- package/dist/commands/args.js +132 -0
- package/dist/commands/dispatch.js +16 -0
- package/dist/commands/handlers.js +391 -0
- package/dist/commands/help.js +119 -0
- package/dist/commands/registry.js +291 -0
- package/dist/commands/types.js +2 -0
- package/dist/domain/config.js +548 -39
- package/dist/infra/backup-repo.js +8 -208
- package/dist/infra/codex-cli.js +8 -113
- package/dist/infra/codex-discovery.js +3 -41
- package/dist/infra/codex-paths.js +5 -69
- package/dist/infra/config-repo.js +161 -9
- package/dist/infra/fs-utils.js +7 -95
- package/dist/infra/lock-repo.js +3 -97
- package/dist/infra/providers-repo.js +7 -96
- package/dist/interaction/add-interactive.js +108 -0
- package/dist/interaction/interactive.js +216 -0
- package/dist/interaction/prompt.js +110 -0
- package/dist/runtime/codex-cli.js +130 -0
- package/dist/runtime/codex-probe.js +50 -0
- package/dist/runtime/types.js +2 -0
- package/dist/storage/backup-repo.js +210 -0
- package/dist/storage/codex-paths.js +71 -0
- package/dist/storage/config-repo.js +208 -0
- package/dist/storage/fs-utils.js +97 -0
- package/dist/storage/lock-repo.js +99 -0
- package/dist/storage/providers-repo.js +98 -0
- package/docs/Design/codex-switch-v0.0.5-design.md +932 -0
- package/docs/Design/codex-switch-v0.0.6-design.md +708 -0
- package/docs/PRD/codex-switch-prd-v0.0.5-to-v0.1.0.md +340 -0
- package/docs/PRD/codex-switch-prd-v0.1.0.md +215 -291
- package/docs/PRD/codex-switch-prd.md +1 -1
- package/docs/cli-usage.md +2 -1
- package/docs/codex-switch-technical-architecture.md +73 -4
- package/docs/test-report-0.0.5.md +163 -0
- package/docs/testing.md +131 -0
- package/package.json +1 -1
- /package/docs/{codex-switch-v0.0.4-design.md → Design/codex-switch-v0.0.4-design.md} +0 -0
|
@@ -0,0 +1,216 @@
|
|
|
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.canPrompt = canPrompt;
|
|
37
|
+
exports.promptForProviderSelection = promptForProviderSelection;
|
|
38
|
+
exports.confirmProviderRemoval = confirmProviderRemoval;
|
|
39
|
+
exports.confirmImport = confirmImport;
|
|
40
|
+
exports.confirmExportOverwrite = confirmExportOverwrite;
|
|
41
|
+
exports.exportTargetExists = exportTargetExists;
|
|
42
|
+
exports.getRollbackSummary = getRollbackSummary;
|
|
43
|
+
exports.getRollbackSummaryById = getRollbackSummaryById;
|
|
44
|
+
exports.confirmRollback = confirmRollback;
|
|
45
|
+
exports.chooseSetupStrategy = chooseSetupStrategy;
|
|
46
|
+
exports.chooseCodexDir = chooseCodexDir;
|
|
47
|
+
exports.chooseSetupProfiles = chooseSetupProfiles;
|
|
48
|
+
exports.collectSetupProviderDetails = collectSetupProviderDetails;
|
|
49
|
+
exports.collectEditInput = collectEditInput;
|
|
50
|
+
const fs = __importStar(require("node:fs"));
|
|
51
|
+
const path = __importStar(require("node:path"));
|
|
52
|
+
const errors_1 = require("../domain/errors");
|
|
53
|
+
const backups_1 = require("../domain/backups");
|
|
54
|
+
const codex_paths_1 = require("../storage/codex-paths");
|
|
55
|
+
const providers_repo_1 = require("../storage/providers-repo");
|
|
56
|
+
const backup_repo_1 = require("../storage/backup-repo");
|
|
57
|
+
const add_interactive_1 = require("./add-interactive");
|
|
58
|
+
/**
|
|
59
|
+
* Keeps CLI-side interactivity rules in one place so automation paths remain explicit.
|
|
60
|
+
*/
|
|
61
|
+
function canPrompt(runtime, jsonMode) {
|
|
62
|
+
return !jsonMode && runtime.isInteractive();
|
|
63
|
+
}
|
|
64
|
+
async function promptForProviderSelection(runtime, providersPath, message) {
|
|
65
|
+
const providers = (0, providers_repo_1.readProvidersFile)(providersPath);
|
|
66
|
+
const choices = Object.entries(providers.providers)
|
|
67
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
68
|
+
.map(([providerName, provider]) => ({
|
|
69
|
+
value: providerName,
|
|
70
|
+
label: providerName,
|
|
71
|
+
hint: provider.profile,
|
|
72
|
+
}));
|
|
73
|
+
if (choices.length === 0) {
|
|
74
|
+
throw (0, errors_1.cliError)("PROVIDER_NOT_FOUND", "No providers are configured.");
|
|
75
|
+
}
|
|
76
|
+
return runtime.selectOne(message, choices);
|
|
77
|
+
}
|
|
78
|
+
async function confirmProviderRemoval(runtime, providerName) {
|
|
79
|
+
const confirmed = await runtime.confirmAction(`Remove provider "${providerName}"?`, {
|
|
80
|
+
defaultValue: false,
|
|
81
|
+
});
|
|
82
|
+
if (!confirmed) {
|
|
83
|
+
throw (0, errors_1.cliError)("PROMPT_CANCELLED", `Removal cancelled for provider "${providerName}".`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
async function confirmImport(runtime, sourceFile, merge = false) {
|
|
87
|
+
const confirmed = await runtime.confirmAction(merge
|
|
88
|
+
? `Import providers from ${path.resolve(sourceFile)} and merge into the current registry?`
|
|
89
|
+
: `Import providers from ${path.resolve(sourceFile)} and replace the current registry?`, { defaultValue: false });
|
|
90
|
+
if (!confirmed) {
|
|
91
|
+
throw (0, errors_1.cliError)("PROMPT_CANCELLED", "Import cancelled.");
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
async function confirmExportOverwrite(runtime, targetFile) {
|
|
95
|
+
return runtime.confirmAction(`Overwrite existing export target ${path.resolve(targetFile)}?`, {
|
|
96
|
+
defaultValue: false,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
function exportTargetExists(targetFile) {
|
|
100
|
+
return fs.existsSync(path.resolve(targetFile));
|
|
101
|
+
}
|
|
102
|
+
function getRollbackSummary(latestBackupPath) {
|
|
103
|
+
const manifest = (0, backup_repo_1.loadLatestManifest)(latestBackupPath);
|
|
104
|
+
return buildRollbackSummary(manifest);
|
|
105
|
+
}
|
|
106
|
+
function getRollbackSummaryById(backupsDir, backupId) {
|
|
107
|
+
const manifest = (0, backup_repo_1.loadManifestById)(backupsDir, backupId);
|
|
108
|
+
return buildRollbackSummary(manifest);
|
|
109
|
+
}
|
|
110
|
+
function buildRollbackSummary(manifest) {
|
|
111
|
+
const previewLines = [
|
|
112
|
+
"Rollback preview",
|
|
113
|
+
`Backup ID: ${(0, backups_1.getBackupId)(manifest.backupDir)}`,
|
|
114
|
+
`Backup: ${manifest.backupDir}`,
|
|
115
|
+
...manifest.files.map((file) => {
|
|
116
|
+
const suffix = file.existed ? "restore" : "remove";
|
|
117
|
+
return `- ${file.relativePath} (${suffix})`;
|
|
118
|
+
}),
|
|
119
|
+
];
|
|
120
|
+
return { manifest, previewLines };
|
|
121
|
+
}
|
|
122
|
+
async function confirmRollback(runtime, latestBackupPath, backupsDir, backupId) {
|
|
123
|
+
const { previewLines } = backupId && backupsDir
|
|
124
|
+
? getRollbackSummaryById(backupsDir, backupId)
|
|
125
|
+
: getRollbackSummary(latestBackupPath);
|
|
126
|
+
for (const line of previewLines) {
|
|
127
|
+
runtime.writeLine(line);
|
|
128
|
+
}
|
|
129
|
+
const confirmed = await runtime.confirmAction(backupId ? `Restore files from backup "${backupId}"?` : "Restore files from the latest backup?", {
|
|
130
|
+
defaultValue: false,
|
|
131
|
+
});
|
|
132
|
+
if (!confirmed) {
|
|
133
|
+
throw (0, errors_1.cliError)("PROMPT_CANCELLED", "Rollback cancelled.");
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
async function chooseSetupStrategy(runtime) {
|
|
137
|
+
return runtime.selectOne("providers.json already exists. Choose a setup strategy.", [
|
|
138
|
+
{ value: "merge", label: "merge", hint: "keep existing providers and override by imported names" },
|
|
139
|
+
{ value: "overwrite", label: "overwrite", hint: "replace the existing registry" },
|
|
140
|
+
{ value: "cancel", label: "cancel", hint: "abort setup without writing" },
|
|
141
|
+
]);
|
|
142
|
+
}
|
|
143
|
+
async function chooseCodexDir(runtime, candidates) {
|
|
144
|
+
if (candidates.length === 0) {
|
|
145
|
+
const manual = (await runtime.inputText("Codex directory path")).trim();
|
|
146
|
+
if (!manual) {
|
|
147
|
+
throw (0, errors_1.cliError)("CODEX_DIR_NOT_FOUND", "No Codex directory was provided.");
|
|
148
|
+
}
|
|
149
|
+
return (0, codex_paths_1.resolveCodexDir)(manual);
|
|
150
|
+
}
|
|
151
|
+
if (candidates.length === 1) {
|
|
152
|
+
return candidates[0];
|
|
153
|
+
}
|
|
154
|
+
const selected = await runtime.selectOne("Choose a Codex directory", [
|
|
155
|
+
...candidates.map((candidate) => ({
|
|
156
|
+
value: candidate,
|
|
157
|
+
label: candidate,
|
|
158
|
+
})),
|
|
159
|
+
{
|
|
160
|
+
value: "__manual__",
|
|
161
|
+
label: "Enter manually",
|
|
162
|
+
},
|
|
163
|
+
]);
|
|
164
|
+
if (selected !== "__manual__") {
|
|
165
|
+
return selected;
|
|
166
|
+
}
|
|
167
|
+
const manual = (await runtime.inputText("Codex directory path")).trim();
|
|
168
|
+
if (!manual) {
|
|
169
|
+
throw (0, errors_1.cliError)("CODEX_DIR_NOT_FOUND", "No Codex directory was provided.");
|
|
170
|
+
}
|
|
171
|
+
return (0, codex_paths_1.resolveCodexDir)(manual);
|
|
172
|
+
}
|
|
173
|
+
async function chooseSetupProfiles(runtime, profiles) {
|
|
174
|
+
if (profiles.length === 0) {
|
|
175
|
+
return [];
|
|
176
|
+
}
|
|
177
|
+
return runtime.selectMany("Choose unmanaged config profiles to adopt into providers.json.", profiles.map((profile) => ({
|
|
178
|
+
value: profile.name,
|
|
179
|
+
label: profile.name,
|
|
180
|
+
hint: `${profile.model} | ${profile.baseUrl}`,
|
|
181
|
+
})));
|
|
182
|
+
}
|
|
183
|
+
async function collectSetupProviderDetails(runtime, profiles) {
|
|
184
|
+
const result = {};
|
|
185
|
+
for (const profile of profiles) {
|
|
186
|
+
const providerName = (await runtime.inputText(`Provider name for profile "${profile}"`, {
|
|
187
|
+
defaultValue: profile,
|
|
188
|
+
})).trim();
|
|
189
|
+
const apiKey = (await runtime.inputSecret(`API key for profile "${profile}"`)).trim();
|
|
190
|
+
const baseUrl = (await runtime.inputText(`Base URL for profile "${profile}" (optional)`)).trim();
|
|
191
|
+
const note = (await runtime.inputText(`Note for profile "${profile}" (optional)`)).trim();
|
|
192
|
+
const tags = await (0, add_interactive_1.promptTags)(runtime);
|
|
193
|
+
result[profile] = {
|
|
194
|
+
providerName: providerName || profile,
|
|
195
|
+
apiKey,
|
|
196
|
+
baseUrl: baseUrl || undefined,
|
|
197
|
+
note: note || undefined,
|
|
198
|
+
tags: tags.length > 0 ? tags : undefined,
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
return result;
|
|
202
|
+
}
|
|
203
|
+
async function collectEditInput(runtime, current) {
|
|
204
|
+
const profile = (await runtime.inputText("Profile", { defaultValue: current.profile })).trim();
|
|
205
|
+
const apiKey = (await runtime.inputSecret("API key")).trim() || current.apiKey;
|
|
206
|
+
const baseUrl = (await runtime.inputText("Base URL (optional)", { defaultValue: current.baseUrl ?? "" })).trim();
|
|
207
|
+
const note = (await runtime.inputText("Note (optional)", { defaultValue: current.note ?? "" })).trim();
|
|
208
|
+
const tags = await (0, add_interactive_1.promptTags)(runtime, current.tags ?? []);
|
|
209
|
+
return {
|
|
210
|
+
profile,
|
|
211
|
+
apiKey,
|
|
212
|
+
baseUrl: baseUrl || undefined,
|
|
213
|
+
note: note || undefined,
|
|
214
|
+
tags,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.createPromptRuntime = createPromptRuntime;
|
|
7
|
+
const inquirer_1 = __importDefault(require("inquirer"));
|
|
8
|
+
const errors_1 = require("../domain/errors");
|
|
9
|
+
/**
|
|
10
|
+
* Creates the default prompt runtime backed by inquirer on the current process TTY.
|
|
11
|
+
*/
|
|
12
|
+
function createPromptRuntime() {
|
|
13
|
+
return {
|
|
14
|
+
isInteractive: () => Boolean(process.stdin.isTTY && process.stdout.isTTY),
|
|
15
|
+
inputText: async (message, options) => {
|
|
16
|
+
return handlePromptCancellation(async () => {
|
|
17
|
+
const answer = await inquirer_1.default.prompt([
|
|
18
|
+
{
|
|
19
|
+
type: "input",
|
|
20
|
+
name: "value",
|
|
21
|
+
message,
|
|
22
|
+
default: options?.defaultValue ?? undefined,
|
|
23
|
+
},
|
|
24
|
+
]);
|
|
25
|
+
return String(answer.value ?? "");
|
|
26
|
+
});
|
|
27
|
+
},
|
|
28
|
+
inputSecret: async (message) => {
|
|
29
|
+
return handlePromptCancellation(async () => {
|
|
30
|
+
const answer = await inquirer_1.default.prompt([
|
|
31
|
+
{
|
|
32
|
+
type: "password",
|
|
33
|
+
name: "value",
|
|
34
|
+
message,
|
|
35
|
+
mask: "*",
|
|
36
|
+
},
|
|
37
|
+
]);
|
|
38
|
+
return String(answer.value ?? "");
|
|
39
|
+
});
|
|
40
|
+
},
|
|
41
|
+
selectOne: async (message, choices) => {
|
|
42
|
+
return handlePromptCancellation(async () => {
|
|
43
|
+
const answer = await inquirer_1.default.prompt([
|
|
44
|
+
{
|
|
45
|
+
type: "select",
|
|
46
|
+
name: "value",
|
|
47
|
+
message,
|
|
48
|
+
choices: choices.map((choice) => ({
|
|
49
|
+
value: choice.value,
|
|
50
|
+
name: choice.hint ? `${choice.label} (${choice.hint})` : choice.label,
|
|
51
|
+
})),
|
|
52
|
+
},
|
|
53
|
+
]);
|
|
54
|
+
return answer.value;
|
|
55
|
+
});
|
|
56
|
+
},
|
|
57
|
+
selectMany: async (message, choices, options) => {
|
|
58
|
+
return handlePromptCancellation(async () => {
|
|
59
|
+
const answer = await inquirer_1.default.prompt([
|
|
60
|
+
{
|
|
61
|
+
type: "checkbox",
|
|
62
|
+
name: "value",
|
|
63
|
+
message,
|
|
64
|
+
choices: choices.map((choice) => ({
|
|
65
|
+
value: choice.value,
|
|
66
|
+
name: choice.hint ? `${choice.label} (${choice.hint})` : choice.label,
|
|
67
|
+
checked: Boolean(options?.defaultValues?.includes(choice.value)),
|
|
68
|
+
})),
|
|
69
|
+
},
|
|
70
|
+
]);
|
|
71
|
+
return (Array.isArray(answer.value) ? answer.value : []);
|
|
72
|
+
});
|
|
73
|
+
},
|
|
74
|
+
confirmAction: async (message, options) => {
|
|
75
|
+
return handlePromptCancellation(async () => {
|
|
76
|
+
const answer = await inquirer_1.default.prompt([
|
|
77
|
+
{
|
|
78
|
+
type: "confirm",
|
|
79
|
+
name: "value",
|
|
80
|
+
message,
|
|
81
|
+
default: options?.defaultValue ?? false,
|
|
82
|
+
},
|
|
83
|
+
]);
|
|
84
|
+
return Boolean(answer.value);
|
|
85
|
+
});
|
|
86
|
+
},
|
|
87
|
+
writeLine: (message) => {
|
|
88
|
+
process.stdout.write(`${message}\n`);
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
async function handlePromptCancellation(run) {
|
|
93
|
+
try {
|
|
94
|
+
return await run();
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
if (isPromptCancellation(error)) {
|
|
98
|
+
throw (0, errors_1.cliError)("PROMPT_CANCELLED", "Interactive prompt was cancelled.");
|
|
99
|
+
}
|
|
100
|
+
throw (0, errors_1.cliError)("INVALID_ARGUMENT", "Interactive prompt failed.", {
|
|
101
|
+
cause: error instanceof Error ? error.message : String(error),
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
function isPromptCancellation(error) {
|
|
106
|
+
if (!(error instanceof Error)) {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
return error.name === "ExitPromptError" || error.message.includes("force closed");
|
|
110
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
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
|
+
exports.readCodexVersion = readCodexVersion;
|
|
8
|
+
exports.checkCodexVersion = checkCodexVersion;
|
|
9
|
+
const node_child_process_1 = require("node:child_process");
|
|
10
|
+
const errors_1 = require("../domain/errors");
|
|
11
|
+
let spawnImplementation = node_child_process_1.spawnSync;
|
|
12
|
+
function getCodexInvocation(args) {
|
|
13
|
+
if (process.platform === "win32") {
|
|
14
|
+
return {
|
|
15
|
+
command: process.env.ComSpec || "cmd.exe",
|
|
16
|
+
args: ["/d", "/s", "/c", ["codex", ...args].join(" ")],
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
return {
|
|
20
|
+
command: "codex",
|
|
21
|
+
args,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Overrides the spawn implementation for tests.
|
|
26
|
+
*/
|
|
27
|
+
function setCodexSpawnImplementation(spawnLike) {
|
|
28
|
+
spawnImplementation = spawnLike;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Restores the default Node spawn implementation after tests.
|
|
32
|
+
*/
|
|
33
|
+
function resetCodexSpawnImplementation() {
|
|
34
|
+
spawnImplementation = node_child_process_1.spawnSync;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Runs `codex login --with-api-key` in the target Codex directory.
|
|
38
|
+
*/
|
|
39
|
+
function runCodexLogin(apiKey, workingDir) {
|
|
40
|
+
const invocation = getCodexInvocation(["login", "--with-api-key"]);
|
|
41
|
+
const result = spawnImplementation(invocation.command, invocation.args, {
|
|
42
|
+
cwd: workingDir,
|
|
43
|
+
input: `${apiKey}\n`,
|
|
44
|
+
stdio: "pipe",
|
|
45
|
+
encoding: "utf8",
|
|
46
|
+
});
|
|
47
|
+
if (result.error || result.status !== 0) {
|
|
48
|
+
throw (0, errors_1.cliError)("CODEX_LOGIN_FAILED", "codex login --with-api-key failed.", {
|
|
49
|
+
cause: result.error?.message ?? (result.stderr.trim() || "Unknown codex login failure"),
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Checks whether the Codex CLI is available on PATH.
|
|
55
|
+
*/
|
|
56
|
+
function checkCodexAvailable() {
|
|
57
|
+
const invocation = getCodexInvocation(["--version"]);
|
|
58
|
+
const result = spawnImplementation(invocation.command, invocation.args, {
|
|
59
|
+
stdio: "pipe",
|
|
60
|
+
encoding: "utf8",
|
|
61
|
+
});
|
|
62
|
+
if (result.error || result.status !== 0) {
|
|
63
|
+
return {
|
|
64
|
+
ok: false,
|
|
65
|
+
cause: result.error?.message ?? (result.stderr.trim() || "Unknown failure"),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
return { ok: true };
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Reads the installed codex CLI version string.
|
|
72
|
+
*/
|
|
73
|
+
function readCodexVersion() {
|
|
74
|
+
const invocation = getCodexInvocation(["--version"]);
|
|
75
|
+
const result = spawnImplementation(invocation.command, invocation.args, {
|
|
76
|
+
stdio: "pipe",
|
|
77
|
+
encoding: "utf8",
|
|
78
|
+
});
|
|
79
|
+
if (result.error || result.status !== 0) {
|
|
80
|
+
return {
|
|
81
|
+
ok: false,
|
|
82
|
+
cause: result.error?.message ?? (result.stderr.trim() || "Unknown failure"),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
const raw = `${result.stdout ?? ""} ${result.stderr ?? ""}`.trim();
|
|
86
|
+
const match = raw.match(/(\d+\.\d+\.\d+)/);
|
|
87
|
+
if (!match) {
|
|
88
|
+
return {
|
|
89
|
+
ok: false,
|
|
90
|
+
cause: `Unable to parse codex version from output: ${raw || "(empty output)"}`,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
return { ok: true, version: match[1] };
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Compares the installed codex version against a minimum required version.
|
|
97
|
+
*/
|
|
98
|
+
function checkCodexVersion(minVersion) {
|
|
99
|
+
const current = readCodexVersion();
|
|
100
|
+
if (!current.ok) {
|
|
101
|
+
return {
|
|
102
|
+
ok: false,
|
|
103
|
+
cause: current.cause,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
if (compareVersions(current.version, minVersion) < 0) {
|
|
107
|
+
return {
|
|
108
|
+
ok: false,
|
|
109
|
+
currentVersion: current.version,
|
|
110
|
+
cause: `codex ${current.version} is below required ${minVersion}`,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
return {
|
|
114
|
+
ok: true,
|
|
115
|
+
currentVersion: current.version,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
function compareVersions(left, right) {
|
|
119
|
+
const leftParts = left.split(".").map((value) => Number.parseInt(value, 10));
|
|
120
|
+
const rightParts = right.split(".").map((value) => Number.parseInt(value, 10));
|
|
121
|
+
const length = Math.max(leftParts.length, rightParts.length);
|
|
122
|
+
for (let index = 0; index < length; index += 1) {
|
|
123
|
+
const leftValue = leftParts[index] ?? 0;
|
|
124
|
+
const rightValue = rightParts[index] ?? 0;
|
|
125
|
+
if (leftValue !== rightValue) {
|
|
126
|
+
return leftValue - rightValue;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return 0;
|
|
130
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.codexRuntimeProbe = void 0;
|
|
4
|
+
exports.probeCodexRuntime = probeCodexRuntime;
|
|
5
|
+
const codex_cli_1 = require("./codex-cli");
|
|
6
|
+
exports.codexRuntimeProbe = {
|
|
7
|
+
probe(options) {
|
|
8
|
+
if (options?.minVersion) {
|
|
9
|
+
return probeCodexRuntime(options.minVersion);
|
|
10
|
+
}
|
|
11
|
+
return probeCodexRuntime();
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
function probeCodexRuntime(minVersion) {
|
|
15
|
+
const availability = (0, codex_cli_1.checkCodexAvailable)();
|
|
16
|
+
if (!availability.ok) {
|
|
17
|
+
return {
|
|
18
|
+
ok: false,
|
|
19
|
+
runtime: "codex",
|
|
20
|
+
reason: "missing",
|
|
21
|
+
cause: availability.cause ?? "Unknown codex availability failure",
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
const versionInfo = (0, codex_cli_1.readCodexVersion)();
|
|
25
|
+
if (!versionInfo.ok) {
|
|
26
|
+
return {
|
|
27
|
+
ok: false,
|
|
28
|
+
runtime: "codex",
|
|
29
|
+
reason: "failed",
|
|
30
|
+
cause: versionInfo.cause,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
if (minVersion) {
|
|
34
|
+
const versionCheck = (0, codex_cli_1.checkCodexVersion)(minVersion);
|
|
35
|
+
if (!versionCheck.ok) {
|
|
36
|
+
return {
|
|
37
|
+
ok: false,
|
|
38
|
+
runtime: "codex",
|
|
39
|
+
reason: "unsupported",
|
|
40
|
+
cause: versionCheck.cause ?? `codex ${versionInfo.version} is below required ${minVersion}`,
|
|
41
|
+
version: versionCheck.currentVersion,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
ok: true,
|
|
47
|
+
runtime: "codex",
|
|
48
|
+
version: versionInfo.version,
|
|
49
|
+
};
|
|
50
|
+
}
|