@minniexcode/codex-switch 0.0.11 → 0.0.12
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 +56 -98
- package/README.CN.md +90 -126
- package/README.md +63 -97
- package/dist/app/get-status.js +8 -3
- package/dist/app/list-providers.js +48 -1
- package/dist/cli/output.js +145 -18
- package/dist/commands/handlers.js +10 -6
- package/dist/commands/help.js +9 -5
- package/dist/commands/registry.js +15 -12
- package/dist/domain/runtime-state.js +30 -8
- package/dist/infra/config-repo.js +16 -206
- package/dist/interaction/interactive.js +16 -6
- package/dist/runtime/copilot-bridge.js +2 -1
- package/dist/storage/config-repo.js +0 -23
- package/docs/Design/codex-switch-v0.0.12-design.md +343 -0
- package/docs/PRD/codex-switch-prd-v0.0.12.md +279 -0
- package/docs/PRD/codex-switch-prd-v0.1.0.md +125 -237
- package/docs/Tests/testing.md +39 -112
- package/docs/cli-usage.md +138 -439
- package/docs/codex-switch-command-design.md +3 -0
- package/docs/codex-switch-product-overview.md +52 -207
- package/docs/codex-switch-technical-architecture.md +3 -0
- package/package.json +1 -1
- package/dist/app/rollback-latest.js +0 -26
package/dist/commands/help.js
CHANGED
|
@@ -30,6 +30,10 @@ function buildHelpText(commandName) {
|
|
|
30
30
|
"codex-switch",
|
|
31
31
|
"",
|
|
32
32
|
"Manage and switch local Codex provider/profile configuration safely.",
|
|
33
|
+
"Primary workflows: direct providers use init -> add -> switch -> status -> doctor.",
|
|
34
|
+
"Primary workflows: Copilot providers use init -> login copilot -> add --copilot -> switch -> status -> doctor.",
|
|
35
|
+
"Advanced adopt flows use migrate only when you already have Codex runtime state to import.",
|
|
36
|
+
"Deprecated entry: setup still exists only to point callers to init or migrate.",
|
|
33
37
|
"",
|
|
34
38
|
"Usage:",
|
|
35
39
|
" codexs <command> [options]",
|
|
@@ -61,14 +65,14 @@ function buildHelpText(commandName) {
|
|
|
61
65
|
"",
|
|
62
66
|
"Examples:",
|
|
63
67
|
" codexs init",
|
|
68
|
+
" codexs add packycode --profile packycode --api-key sk-xxx",
|
|
69
|
+
" codexs switch packycode",
|
|
70
|
+
" codexs status",
|
|
71
|
+
" codexs doctor",
|
|
64
72
|
" codexs login copilot",
|
|
73
|
+
" codexs add copilot-main --copilot --profile copilot-main",
|
|
65
74
|
" codexs migrate",
|
|
66
|
-
" codexs list",
|
|
67
|
-
" codexs switch",
|
|
68
|
-
" codexs bridge start",
|
|
69
|
-
" codexs add packycode --profile packycode --api-key sk-xxx",
|
|
70
75
|
" codexs config show",
|
|
71
|
-
" codexs remove freemodel",
|
|
72
76
|
" codexs backups list",
|
|
73
77
|
" codexs rollback",
|
|
74
78
|
" codexs help add",
|
|
@@ -88,13 +88,14 @@ exports.COMMANDS = [
|
|
|
88
88
|
tokens: ["init"],
|
|
89
89
|
handler: handlers_1.handleRegisteredCommand,
|
|
90
90
|
group: "write",
|
|
91
|
-
summary: "Initialize the codex-switch tool home
|
|
91
|
+
summary: "Initialize the codex-switch tool home for the primary workflow.",
|
|
92
92
|
usage: ["codexs init [--json] [--codex-dir <path>]"],
|
|
93
93
|
details: [
|
|
94
94
|
"Creates codex-switch.json and providers.json under the tool home when they do not exist yet.",
|
|
95
95
|
"Does not create or validate config.toml, auth.json, or the target Codex directory.",
|
|
96
96
|
"When --codex-dir is passed explicitly and codex-switch.json does not exist yet, init persists it as defaultCodexDir.",
|
|
97
97
|
"Otherwise init stays scoped to tool-home state and does not persist fallback Codex directory resolution.",
|
|
98
|
+
"Use init first for fresh direct-provider or Copilot setups.",
|
|
98
99
|
],
|
|
99
100
|
examples: ["codexs init", "codexs init --json --codex-dir ~/.codex"],
|
|
100
101
|
},
|
|
@@ -119,13 +120,14 @@ exports.COMMANDS = [
|
|
|
119
120
|
tokens: ["migrate"],
|
|
120
121
|
handler: handlers_1.handleRegisteredCommand,
|
|
121
122
|
group: "write",
|
|
122
|
-
summary: "Adopt
|
|
123
|
+
summary: "Adopt existing Codex runtime profiles into managed providers.json state.",
|
|
123
124
|
usage: ["codexs migrate [--json] [--codex-dir <path>] [--merge|--overwrite]"],
|
|
124
125
|
details: [
|
|
125
126
|
"Reads config.toml profiles, collects complete provider records, then writes providers.json under managed backup flow.",
|
|
126
127
|
"TTY mode can collect missing provider details and choose merge or overwrite when providers.json already exists.",
|
|
127
128
|
"Migrate adopts only runtime profiles that already expose model, model_provider, and matching base_url.",
|
|
128
129
|
"Non-TTY and --json runs still fail fast because migrate profile selection and provider details remain interactive in this release.",
|
|
130
|
+
"Treat migrate as an advanced adopt helper for existing runtime state, not the default first step for fresh installs.",
|
|
129
131
|
],
|
|
130
132
|
examples: ["codexs migrate", "codexs migrate --overwrite --json --codex-dir ~/.codex"],
|
|
131
133
|
},
|
|
@@ -134,12 +136,12 @@ exports.COMMANDS = [
|
|
|
134
136
|
tokens: ["setup"],
|
|
135
137
|
handler: handlers_1.handleRegisteredCommand,
|
|
136
138
|
group: "write",
|
|
137
|
-
summary: "Deprecated.
|
|
139
|
+
summary: "Deprecated. Kept only to point callers to init or migrate.",
|
|
138
140
|
usage: ["codexs setup"],
|
|
139
141
|
details: [
|
|
140
142
|
"setup no longer performs initialization or migration work.",
|
|
141
|
-
"Use init for
|
|
142
|
-
"Use migrate
|
|
143
|
+
"Use init for the primary fresh-install workflow.",
|
|
144
|
+
"Use migrate only when adopting from existing config.toml profiles.",
|
|
143
145
|
],
|
|
144
146
|
examples: ["codexs help init", "codexs help migrate"],
|
|
145
147
|
},
|
|
@@ -182,12 +184,12 @@ exports.COMMANDS = [
|
|
|
182
184
|
tokens: ["status"],
|
|
183
185
|
handler: handlers_1.handleRegisteredCommand,
|
|
184
186
|
group: "read",
|
|
185
|
-
summary: "Show
|
|
187
|
+
summary: "Show tool-home, target-runtime, provider, and runtime-health status.",
|
|
186
188
|
usage: ["codexs status [--json] [--codex-dir <path>]"],
|
|
187
189
|
details: [
|
|
188
|
-
"Reports
|
|
189
|
-
"When the active provider uses a local runtime bridge, status also reports bridge
|
|
190
|
-
"Surfaces config consistency signals without mutating any files.",
|
|
190
|
+
"Reports the target Codex runtime, tool-home storage roles, current profile, and whether the live profile is mapped.",
|
|
191
|
+
"When the active provider uses a local runtime bridge, status also reports bridge, Copilot SDK, and upstream auth state.",
|
|
192
|
+
"Surfaces dual-path config consistency signals without mutating any files.",
|
|
191
193
|
"Use doctor for deeper diagnostics.",
|
|
192
194
|
],
|
|
193
195
|
examples: ["codexs status", "codexs status --json --codex-dir ./.tmp-codex"],
|
|
@@ -216,7 +218,7 @@ exports.COMMANDS = [
|
|
|
216
218
|
tokens: ["add"],
|
|
217
219
|
handler: handlers_1.handleRegisteredCommand,
|
|
218
220
|
group: "write",
|
|
219
|
-
summary: "Add a provider
|
|
221
|
+
summary: "Add a managed provider for the primary direct or Copilot workflows.",
|
|
220
222
|
usage: [
|
|
221
223
|
"codexs add <provider> --profile <name> --api-key <key> [--base-url <url>] [--note <text>] [--tag <tag> ...]",
|
|
222
224
|
"codexs add <provider> --copilot --profile <name> [--bridge-host <host>] [--bridge-port <port>] [--bridge-api-key <secret>] [--install-copilot-sdk]",
|
|
@@ -247,7 +249,7 @@ exports.COMMANDS = [
|
|
|
247
249
|
tokens: ["switch"],
|
|
248
250
|
handler: handlers_1.handleRegisteredCommand,
|
|
249
251
|
group: "write",
|
|
250
|
-
summary: "Switch the active
|
|
252
|
+
summary: "Switch the active runtime to a managed provider.",
|
|
251
253
|
usage: ["codexs switch <provider> [--json] [--codex-dir <path>]"],
|
|
252
254
|
details: [
|
|
253
255
|
"When <provider> is omitted in a TTY, an interactive provider selector is shown.",
|
|
@@ -255,6 +257,7 @@ exports.COMMANDS = [
|
|
|
255
257
|
"Direct providers update the active config profile and rewrite auth.json with auth_mode=apikey plus OPENAI_API_KEY.",
|
|
256
258
|
"Copilot bridge providers also rewrite OPENAI_API_KEY to the local bridge secret while managing runtime routing and bridge state.",
|
|
257
259
|
"Copilot bridge providers probe the optional official SDK before switching and fail fast if it is missing.",
|
|
260
|
+
"Switch succeeds only after the managed profile projection is written to the target runtime.",
|
|
258
261
|
"Backs up config.toml and auth.json and rolls back on failure.",
|
|
259
262
|
],
|
|
260
263
|
examples: ["codexs switch freemodel", "codexs switch packycode --json"],
|
|
@@ -321,7 +324,7 @@ exports.COMMANDS = [
|
|
|
321
324
|
tokens: ["doctor"],
|
|
322
325
|
handler: handlers_1.handleRegisteredCommand,
|
|
323
326
|
group: "recovery",
|
|
324
|
-
summary: "Run
|
|
327
|
+
summary: "Run repair-oriented diagnostics across tool-home and target-runtime state.",
|
|
325
328
|
usage: ["codexs doctor [--json] [--codex-dir <path>]"],
|
|
326
329
|
details: [
|
|
327
330
|
"Checks the expected config files, provider/profile consistency, and Codex CLI availability.",
|
|
@@ -97,7 +97,9 @@ function inspectLiveStateDrift(currentProfile, providers) {
|
|
|
97
97
|
return {
|
|
98
98
|
currentProfile,
|
|
99
99
|
mappedProvider: null,
|
|
100
|
+
mappedProviders: [],
|
|
100
101
|
profileMapped: false,
|
|
102
|
+
providerResolvable: false,
|
|
101
103
|
canBackfillActiveProvider: false,
|
|
102
104
|
reason: providers ? "profile-missing" : "config-missing",
|
|
103
105
|
};
|
|
@@ -106,27 +108,47 @@ function inspectLiveStateDrift(currentProfile, providers) {
|
|
|
106
108
|
return {
|
|
107
109
|
currentProfile,
|
|
108
110
|
mappedProvider: null,
|
|
111
|
+
mappedProviders: [],
|
|
109
112
|
profileMapped: false,
|
|
113
|
+
providerResolvable: false,
|
|
110
114
|
canBackfillActiveProvider: false,
|
|
111
115
|
reason: "providers-missing",
|
|
112
116
|
};
|
|
113
117
|
}
|
|
118
|
+
const mappedProviders = [];
|
|
114
119
|
for (const [name, provider] of Object.entries(providers.providers)) {
|
|
115
|
-
// A direct profile match means the runtime state is still managed.
|
|
116
120
|
if (provider.profile === currentProfile) {
|
|
117
|
-
|
|
118
|
-
currentProfile,
|
|
119
|
-
mappedProvider: name,
|
|
120
|
-
profileMapped: true,
|
|
121
|
-
canBackfillActiveProvider: false,
|
|
122
|
-
reason: "ok",
|
|
123
|
-
};
|
|
121
|
+
mappedProviders.push(name);
|
|
124
122
|
}
|
|
125
123
|
}
|
|
124
|
+
if (mappedProviders.length === 1) {
|
|
125
|
+
return {
|
|
126
|
+
currentProfile,
|
|
127
|
+
mappedProvider: mappedProviders[0],
|
|
128
|
+
mappedProviders,
|
|
129
|
+
profileMapped: true,
|
|
130
|
+
providerResolvable: true,
|
|
131
|
+
canBackfillActiveProvider: false,
|
|
132
|
+
reason: "ok",
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
if (mappedProviders.length > 1) {
|
|
136
|
+
return {
|
|
137
|
+
currentProfile,
|
|
138
|
+
mappedProvider: null,
|
|
139
|
+
mappedProviders,
|
|
140
|
+
profileMapped: true,
|
|
141
|
+
providerResolvable: false,
|
|
142
|
+
canBackfillActiveProvider: false,
|
|
143
|
+
reason: "shared-profile",
|
|
144
|
+
};
|
|
145
|
+
}
|
|
126
146
|
return {
|
|
127
147
|
currentProfile,
|
|
128
148
|
mappedProvider: null,
|
|
149
|
+
mappedProviders: [],
|
|
129
150
|
profileMapped: false,
|
|
151
|
+
providerResolvable: false,
|
|
130
152
|
canBackfillActiveProvider: true,
|
|
131
153
|
reason: "provider-unmapped",
|
|
132
154
|
};
|
|
@@ -1,208 +1,18 @@
|
|
|
1
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
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.readConfigFile =
|
|
37
|
-
|
|
38
|
-
exports
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
exports.
|
|
42
|
-
exports.
|
|
43
|
-
exports.
|
|
44
|
-
exports.
|
|
45
|
-
exports.
|
|
46
|
-
exports.
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const codex_paths_1 = require("./codex-paths");
|
|
53
|
-
const fs_utils_1 = require("./fs-utils");
|
|
54
|
-
/**
|
|
55
|
-
* Reads config.toml and throws a typed error when the file is missing.
|
|
56
|
-
*/
|
|
57
|
-
function readConfigFile(configPath) {
|
|
58
|
-
return (0, fs_utils_1.readRequiredFile)(configPath, "CONFIG_NOT_FOUND", "config.toml");
|
|
59
|
-
}
|
|
60
|
-
/**
|
|
61
|
-
* Reads and parses config.toml into the managed structured document shape.
|
|
62
|
-
*/
|
|
63
|
-
function readStructuredConfig(configPath) {
|
|
64
|
-
const content = readConfigFile(configPath);
|
|
65
|
-
try {
|
|
66
|
-
return (0, config_1.parseStructuredConfig)(content);
|
|
67
|
-
}
|
|
68
|
-
catch (error) {
|
|
69
|
-
throw (0, errors_1.cliError)("CONFIG_PARSE_ERROR", "Failed to parse config.toml.", {
|
|
70
|
-
file: configPath,
|
|
71
|
-
cause: (0, errors_1.normalizeError)(error).message,
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
/**
|
|
76
|
-
* Reads the active top-level profile from config.toml.
|
|
77
|
-
*/
|
|
78
|
-
function readCurrentProfile(configPath) {
|
|
79
|
-
const profile = readStructuredConfig(configPath).activeProfile ?? (0, config_1.parseTopLevelProfile)(readConfigFile(configPath));
|
|
80
|
-
if (!profile) {
|
|
81
|
-
throw (0, errors_1.cliError)("PROFILE_NOT_FOUND", "No top-level profile is set in config.toml.", {
|
|
82
|
-
file: configPath,
|
|
83
|
-
});
|
|
84
|
-
}
|
|
85
|
-
return profile;
|
|
86
|
-
}
|
|
87
|
-
/**
|
|
88
|
-
* Lists all named profile sections declared in config.toml.
|
|
89
|
-
*/
|
|
90
|
-
function listConfigProfiles(configPath) {
|
|
91
|
-
return new Set(readStructuredConfig(configPath).profiles.map((profile) => profile.name));
|
|
92
|
-
}
|
|
93
|
-
/**
|
|
94
|
-
* Verifies that a provider's target profile exists before a switch operation proceeds.
|
|
95
|
-
*/
|
|
96
|
-
function ensureProfileExists(configPath, profile, provider) {
|
|
97
|
-
const document = readStructuredConfig(configPath);
|
|
98
|
-
if (!document.profiles.some((entry) => entry.name === profile)) {
|
|
99
|
-
throw (0, errors_1.cliError)("PROFILE_NOT_FOUND", `Profile "${profile}" does not exist in config.toml.`, {
|
|
100
|
-
file: configPath,
|
|
101
|
-
provider,
|
|
102
|
-
profile,
|
|
103
|
-
});
|
|
104
|
-
}
|
|
105
|
-
return document;
|
|
106
|
-
}
|
|
107
|
-
/**
|
|
108
|
-
* Resolves one profile view and enforces the managed model_provider contract.
|
|
109
|
-
*/
|
|
110
|
-
function requireManagedProfileRuntime(document, providers, profile) {
|
|
111
|
-
const view = (0, config_1.buildManagedProfileViews)(document, providers).find((entry) => entry.name === profile);
|
|
112
|
-
if (!view) {
|
|
113
|
-
throw (0, errors_1.cliError)("PROFILE_NOT_FOUND", `Profile "${profile}" does not exist in config.toml.`, {
|
|
114
|
-
profile,
|
|
115
|
-
});
|
|
116
|
-
}
|
|
117
|
-
if (!view.modelProvider) {
|
|
118
|
-
throw (0, errors_1.cliError)("MANAGED_PROFILE_FIELDS_MISSING", `Managed profile "${profile}" requires model_provider.`, {
|
|
119
|
-
profile,
|
|
120
|
-
missingFields: ["model_provider"],
|
|
121
|
-
});
|
|
122
|
-
}
|
|
123
|
-
if (view.modelProvider !== profile) {
|
|
124
|
-
throw (0, errors_1.cliError)("INVALID_ARGUMENT", `Managed profile "${profile}" must use the same model_provider name.`, {
|
|
125
|
-
profile,
|
|
126
|
-
modelProvider: view.modelProvider,
|
|
127
|
-
});
|
|
128
|
-
}
|
|
129
|
-
const modelProviderSection = document.modelProviders.find((entry) => entry.name === view.modelProvider);
|
|
130
|
-
if (!modelProviderSection) {
|
|
131
|
-
throw (0, errors_1.cliError)("PROFILE_NOT_FOUND", `Model provider "${view.modelProvider}" does not exist in config.toml.`, {
|
|
132
|
-
profile,
|
|
133
|
-
modelProvider: view.modelProvider,
|
|
134
|
-
});
|
|
135
|
-
}
|
|
136
|
-
if (!modelProviderSection.baseUrl) {
|
|
137
|
-
throw (0, errors_1.cliError)("MANAGED_PROFILE_FIELDS_MISSING", `Model provider "${view.modelProvider}" requires base_url.`, {
|
|
138
|
-
profile,
|
|
139
|
-
modelProvider: view.modelProvider,
|
|
140
|
-
missingFields: ["base_url"],
|
|
141
|
-
});
|
|
142
|
-
}
|
|
143
|
-
return view;
|
|
144
|
-
}
|
|
145
|
-
/**
|
|
146
|
-
* Verifies that a same-named model_provider runtime section exists and has base_url.
|
|
147
|
-
*/
|
|
148
|
-
function requireModelProviderRuntimeSection(document, profile) {
|
|
149
|
-
const modelProviderSection = document.modelProviders.find((entry) => entry.name === profile);
|
|
150
|
-
if (!modelProviderSection) {
|
|
151
|
-
throw (0, errors_1.cliError)("PROFILE_NOT_FOUND", `Model provider "${profile}" does not exist in config.toml.`, {
|
|
152
|
-
profile,
|
|
153
|
-
modelProvider: profile,
|
|
154
|
-
});
|
|
155
|
-
}
|
|
156
|
-
if (!modelProviderSection.baseUrl) {
|
|
157
|
-
throw (0, errors_1.cliError)("MANAGED_PROFILE_FIELDS_MISSING", `Model provider "${profile}" requires base_url.`, {
|
|
158
|
-
profile,
|
|
159
|
-
modelProvider: profile,
|
|
160
|
-
missingFields: ["base_url"],
|
|
161
|
-
});
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
/**
|
|
165
|
-
* Rewrites config.toml so the requested profile becomes the active top-level profile.
|
|
166
|
-
*/
|
|
167
|
-
function updateTopLevelProfile(configPath, configContent, profile) {
|
|
168
|
-
(0, fs_utils_1.writeTextFileAtomic)(configPath, (0, config_1.applyPatchOperations)(configContent, (0, config_1.planConfigMutation)((0, config_1.parseStructuredConfig)(configContent), {
|
|
169
|
-
setActiveProfile: profile,
|
|
170
|
-
}).operations));
|
|
171
|
-
}
|
|
172
|
-
/**
|
|
173
|
-
* Exposes the config mutation planner to application services.
|
|
174
|
-
*/
|
|
175
|
-
function createConfigMutationPlan(document, args) {
|
|
176
|
-
return (0, config_1.planConfigMutation)(document, args);
|
|
177
|
-
}
|
|
178
|
-
/**
|
|
179
|
-
* Applies a previously generated mutation plan to config.toml in one write.
|
|
180
|
-
*/
|
|
181
|
-
function applyConfigMutation(configPath, document, plan) {
|
|
182
|
-
(0, fs_utils_1.writeTextFileAtomic)(configPath, (0, config_1.applyPatchOperations)(document.rawText, plan.operations));
|
|
183
|
-
}
|
|
184
|
-
/**
|
|
185
|
-
* Finds candidate Codex directories in a stable, non-recursive order.
|
|
186
|
-
*/
|
|
187
|
-
function findCodexDirCandidates(explicitCodexDir) {
|
|
188
|
-
if (explicitCodexDir) {
|
|
189
|
-
return [(0, codex_paths_1.resolveCodexDir)(explicitCodexDir)];
|
|
190
|
-
}
|
|
191
|
-
const candidates = new Set();
|
|
192
|
-
const ordered = [];
|
|
193
|
-
const envCandidate = process.env[codex_paths_1.CODEX_DIR_ENV_NAME];
|
|
194
|
-
if (envCandidate) {
|
|
195
|
-
ordered.push((0, codex_paths_1.resolveCodexDir)(envCandidate));
|
|
196
|
-
}
|
|
197
|
-
if (process.env.NODE_ENV === "development") {
|
|
198
|
-
ordered.push(path.resolve(process.cwd(), "dev-codex", "local-sandbox"));
|
|
199
|
-
}
|
|
200
|
-
ordered.push(path.join(os.homedir(), ".codex"));
|
|
201
|
-
for (const candidate of ordered) {
|
|
202
|
-
if (!candidate || candidates.has(candidate) || !fs.existsSync(candidate)) {
|
|
203
|
-
continue;
|
|
204
|
-
}
|
|
205
|
-
candidates.add(candidate);
|
|
206
|
-
}
|
|
207
|
-
return [...candidates];
|
|
208
|
-
}
|
|
3
|
+
exports.updateTopLevelProfile = exports.requireModelProviderRuntimeSection = exports.requireManagedProfileRuntime = exports.readStructuredConfig = exports.readCurrentProfile = exports.readConfigFile = exports.listConfigProfiles = exports.findCodexDirCandidates = exports.ensureProfileExists = exports.createConfigMutationPlan = exports.applyConfigMutation = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Compatibility facade that re-exports config repository helpers from storage.
|
|
6
|
+
*/
|
|
7
|
+
var config_repo_1 = require("../storage/config-repo");
|
|
8
|
+
Object.defineProperty(exports, "applyConfigMutation", { enumerable: true, get: function () { return config_repo_1.applyConfigMutation; } });
|
|
9
|
+
Object.defineProperty(exports, "createConfigMutationPlan", { enumerable: true, get: function () { return config_repo_1.createConfigMutationPlan; } });
|
|
10
|
+
Object.defineProperty(exports, "ensureProfileExists", { enumerable: true, get: function () { return config_repo_1.ensureProfileExists; } });
|
|
11
|
+
Object.defineProperty(exports, "findCodexDirCandidates", { enumerable: true, get: function () { return config_repo_1.findCodexDirCandidates; } });
|
|
12
|
+
Object.defineProperty(exports, "listConfigProfiles", { enumerable: true, get: function () { return config_repo_1.listConfigProfiles; } });
|
|
13
|
+
Object.defineProperty(exports, "readConfigFile", { enumerable: true, get: function () { return config_repo_1.readConfigFile; } });
|
|
14
|
+
Object.defineProperty(exports, "readCurrentProfile", { enumerable: true, get: function () { return config_repo_1.readCurrentProfile; } });
|
|
15
|
+
Object.defineProperty(exports, "readStructuredConfig", { enumerable: true, get: function () { return config_repo_1.readStructuredConfig; } });
|
|
16
|
+
Object.defineProperty(exports, "requireManagedProfileRuntime", { enumerable: true, get: function () { return config_repo_1.requireManagedProfileRuntime; } });
|
|
17
|
+
Object.defineProperty(exports, "requireModelProviderRuntimeSection", { enumerable: true, get: function () { return config_repo_1.requireModelProviderRuntimeSection; } });
|
|
18
|
+
Object.defineProperty(exports, "updateTopLevelProfile", { enumerable: true, get: function () { return config_repo_1.updateTopLevelProfile; } });
|
|
@@ -52,7 +52,10 @@ const fs = __importStar(require("node:fs"));
|
|
|
52
52
|
const path = __importStar(require("node:path"));
|
|
53
53
|
const errors_1 = require("../domain/errors");
|
|
54
54
|
const backups_1 = require("../domain/backups");
|
|
55
|
+
const providers_1 = require("../domain/providers");
|
|
56
|
+
const runtime_state_1 = require("../domain/runtime-state");
|
|
55
57
|
const codex_paths_1 = require("../storage/codex-paths");
|
|
58
|
+
const config_repo_1 = require("../storage/config-repo");
|
|
56
59
|
const providers_repo_1 = require("../storage/providers-repo");
|
|
57
60
|
const backup_repo_1 = require("../storage/backup-repo");
|
|
58
61
|
const add_interactive_1 = require("./add-interactive");
|
|
@@ -65,15 +68,22 @@ function canPrompt(runtime, jsonMode) {
|
|
|
65
68
|
/**
|
|
66
69
|
* Prompts the user to choose one configured provider when a command omitted its target.
|
|
67
70
|
*/
|
|
68
|
-
async function promptForProviderSelection(runtime, providersPath, message) {
|
|
71
|
+
async function promptForProviderSelection(runtime, providersPath, configPath, message) {
|
|
69
72
|
const providers = (0, providers_repo_1.readProvidersFile)(providersPath);
|
|
73
|
+
const currentProfile = fs.existsSync(configPath) ? (0, config_repo_1.readStructuredConfig)(configPath).activeProfile : null;
|
|
74
|
+
const liveState = (0, runtime_state_1.inspectLiveStateDrift)(currentProfile, providers);
|
|
70
75
|
const choices = Object.entries(providers.providers)
|
|
71
76
|
.sort(([left], [right]) => left.localeCompare(right))
|
|
72
|
-
.map(([providerName, provider]) =>
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
+
.map(([providerName, provider]) => {
|
|
78
|
+
const providerType = (0, providers_1.isCopilotBridgeProvider)(provider) ? "copilot" : "direct";
|
|
79
|
+
const currentMarker = liveState.providerResolvable && liveState.mappedProvider === providerName ? " | current" : "";
|
|
80
|
+
const ambiguousMarker = !liveState.providerResolvable && liveState.mappedProviders.includes(providerName) ? " | current=ambiguous" : "";
|
|
81
|
+
return {
|
|
82
|
+
value: providerName,
|
|
83
|
+
label: providerName,
|
|
84
|
+
hint: `profile=${provider.profile} | type=${providerType}${currentMarker}${ambiguousMarker}`,
|
|
85
|
+
};
|
|
86
|
+
});
|
|
77
87
|
if (choices.length === 0) {
|
|
78
88
|
throw (0, errors_1.cliError)("PROVIDER_NOT_FOUND", "No providers are configured.");
|
|
79
89
|
}
|
|
@@ -221,7 +221,8 @@ async function startOrReuseCopilotBridge(providerName, provider, runtimeDir) {
|
|
|
221
221
|
}
|
|
222
222
|
child.unref();
|
|
223
223
|
const startedAt = new Date().toISOString();
|
|
224
|
-
|
|
224
|
+
// The worker can take a little longer to become healthy on Windows or under loaded test runs.
|
|
225
|
+
const healthy = await waitForCopilotBridgeStartup(child, runtime.bridgeHost, selectedPort, 25, 200);
|
|
225
226
|
if (!healthy.ok) {
|
|
226
227
|
(0, runtime_state_repo_1.clearCopilotBridgeState)(runtimeDir);
|
|
227
228
|
if (healthy.reason === "start-failed") {
|
|
@@ -40,7 +40,6 @@ exports.listConfigProfiles = listConfigProfiles;
|
|
|
40
40
|
exports.ensureProfileExists = ensureProfileExists;
|
|
41
41
|
exports.requireManagedProfileRuntime = requireManagedProfileRuntime;
|
|
42
42
|
exports.requireModelProviderRuntimeSection = requireModelProviderRuntimeSection;
|
|
43
|
-
exports.resolveActiveProviderName = resolveActiveProviderName;
|
|
44
43
|
exports.updateTopLevelProfile = updateTopLevelProfile;
|
|
45
44
|
exports.createConfigMutationPlan = createConfigMutationPlan;
|
|
46
45
|
exports.applyConfigMutation = applyConfigMutation;
|
|
@@ -50,7 +49,6 @@ const os = __importStar(require("node:os"));
|
|
|
50
49
|
const path = __importStar(require("node:path"));
|
|
51
50
|
const config_1 = require("../domain/config");
|
|
52
51
|
const errors_1 = require("../domain/errors");
|
|
53
|
-
const providers_1 = require("../domain/providers");
|
|
54
52
|
const codex_paths_1 = require("./codex-paths");
|
|
55
53
|
const fs_utils_1 = require("./fs-utils");
|
|
56
54
|
/**
|
|
@@ -163,27 +161,6 @@ function requireModelProviderRuntimeSection(document, profile) {
|
|
|
163
161
|
});
|
|
164
162
|
}
|
|
165
163
|
}
|
|
166
|
-
/**
|
|
167
|
-
* Resolves the current active provider and requires the mapping to be unique.
|
|
168
|
-
*/
|
|
169
|
-
function resolveActiveProviderName(document, providers) {
|
|
170
|
-
if (!document.activeProfile) {
|
|
171
|
-
throw (0, errors_1.cliError)("PROFILE_NOT_FOUND", "No top-level profile is set in config.toml.");
|
|
172
|
-
}
|
|
173
|
-
const matches = (0, providers_1.findProvidersByProfile)(providers, document.activeProfile);
|
|
174
|
-
if (matches.length === 0) {
|
|
175
|
-
throw (0, errors_1.cliError)("UNMANAGED_ACTIVE_PROFILE", `Active profile "${document.activeProfile}" is not mapped by providers.json.`, {
|
|
176
|
-
profile: document.activeProfile,
|
|
177
|
-
});
|
|
178
|
-
}
|
|
179
|
-
if (matches.length > 1) {
|
|
180
|
-
throw (0, errors_1.cliError)("ACTIVE_PROVIDER_UNRESOLVED", `Active profile "${document.activeProfile}" maps to multiple providers, so the active managed provider is ambiguous.`, {
|
|
181
|
-
profile: document.activeProfile,
|
|
182
|
-
providers: matches,
|
|
183
|
-
});
|
|
184
|
-
}
|
|
185
|
-
return matches[0];
|
|
186
|
-
}
|
|
187
164
|
/**
|
|
188
165
|
* Rewrites config.toml so the requested profile becomes the active top-level profile.
|
|
189
166
|
*/
|