@minniexcode/codex-switch 0.0.12 → 0.1.0
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 +110 -110
- package/README.CN.md +179 -179
- package/README.md +183 -183
- package/dist/app/add-provider.js +9 -3
- package/dist/app/edit-provider.js +17 -3
- package/dist/app/run-doctor.js +4 -0
- package/dist/cli/output.js +8 -0
- package/dist/commands/registry.js +9 -4
- package/dist/domain/config.js +26 -1
- package/dist/domain/providers.js +16 -0
- package/docs/Design/codex-switch-v0.1.0-design.md +152 -0
- package/docs/PRD/codex-switch-prd-v0.1.0.md +217 -205
- package/docs/Tests/testing.md +31 -78
- package/docs/cli-usage.md +223 -223
- package/docs/codex-switch-command-design.md +649 -649
- package/docs/codex-switch-product-overview.md +86 -86
- package/docs/codex-switch-technical-architecture.md +1115 -1115
- package/package.json +51 -51
package/README.md
CHANGED
|
@@ -1,183 +1,183 @@
|
|
|
1
|
-
# @minniexcode/codex-switch
|
|
2
|
-
|
|
3
|
-
`@minniexcode/codex-switch` is a local-first CLI for managing and switching Codex provider and profile configuration safely.
|
|
4
|
-
|
|
5
|
-
It keeps `codex-switch` tool state separate from the target Codex runtime, so provider management, backup flow, and runtime projection stay explicit instead of relying on manual file edits.
|
|
6
|
-
|
|
7
|
-
Chinese version: [README.CN.md](./README.CN.md)
|
|
8
|
-
|
|
9
|
-
## Version
|
|
10
|
-
|
|
11
|
-
Current package version: `0.0
|
|
12
|
-
|
|
13
|
-
This
|
|
14
|
-
|
|
15
|
-
## Install
|
|
16
|
-
|
|
17
|
-
```bash
|
|
18
|
-
npm install -g @minniexcode/codex-switch
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
Run without a global install:
|
|
22
|
-
|
|
23
|
-
```bash
|
|
24
|
-
npx @minniexcode/codex-switch --help
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
Built CLI entrypoint:
|
|
28
|
-
|
|
29
|
-
```bash
|
|
30
|
-
codexs --help
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
## Primary Workflows
|
|
34
|
-
|
|
35
|
-
Direct provider workflow:
|
|
36
|
-
|
|
37
|
-
```bash
|
|
38
|
-
codexs init
|
|
39
|
-
codexs add my-provider --profile my-provider --api-key sk-xxx
|
|
40
|
-
codexs switch my-provider
|
|
41
|
-
codexs status
|
|
42
|
-
codexs doctor
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
GitHub Copilot workflow:
|
|
46
|
-
|
|
47
|
-
```bash
|
|
48
|
-
codexs init
|
|
49
|
-
codexs login copilot
|
|
50
|
-
codexs add copilot-main --copilot --profile copilot-main
|
|
51
|
-
codexs switch copilot-main
|
|
52
|
-
codexs status
|
|
53
|
-
codexs doctor
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
Notes:
|
|
57
|
-
|
|
58
|
-
- `init` prepares the `codex-switch` tool home and managed state.
|
|
59
|
-
- `login copilot` handles upstream Copilot onboarding and auth readiness.
|
|
60
|
-
- `add --copilot` does not perform login for you; it assumes Copilot login is already ready.
|
|
61
|
-
- `status` is the main read command after switching.
|
|
62
|
-
- `doctor` is the main repair-oriented diagnostic command.
|
|
63
|
-
|
|
64
|
-
## Advanced Adopt Workflow
|
|
65
|
-
|
|
66
|
-
Use `migrate` only when you already have Codex runtime state that should be adopted into managed `providers.json` state:
|
|
67
|
-
|
|
68
|
-
```bash
|
|
69
|
-
codexs init
|
|
70
|
-
codexs migrate
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
`migrate` is an advanced adopt helper. It is not the default first step for a fresh install.
|
|
74
|
-
|
|
75
|
-
## Command Surface
|
|
76
|
-
|
|
77
|
-
```bash
|
|
78
|
-
codexs init
|
|
79
|
-
codexs login copilot
|
|
80
|
-
codexs migrate
|
|
81
|
-
codexs list
|
|
82
|
-
codexs show <provider>
|
|
83
|
-
codexs current
|
|
84
|
-
codexs status
|
|
85
|
-
codexs config show [profile]
|
|
86
|
-
codexs config list-profiles
|
|
87
|
-
codexs add <provider> --profile <name> --api-key <key>
|
|
88
|
-
codexs add <provider> --copilot --profile <name>
|
|
89
|
-
codexs edit <provider>
|
|
90
|
-
codexs switch <provider>
|
|
91
|
-
codexs remove <provider> [--force] [--switch-to <profile>]
|
|
92
|
-
codexs import <file>
|
|
93
|
-
codexs export <file> [--force]
|
|
94
|
-
codexs bridge start [provider]
|
|
95
|
-
codexs bridge status [provider]
|
|
96
|
-
codexs bridge stop [provider]
|
|
97
|
-
codexs backups list
|
|
98
|
-
codexs rollback [backup-id]
|
|
99
|
-
codexs doctor
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
`setup` still exists only as a deprecated compatibility entry that points callers to `init` or `migrate`.
|
|
103
|
-
|
|
104
|
-
## Runtime Model
|
|
105
|
-
|
|
106
|
-
`codex-switch` uses a dual-path model.
|
|
107
|
-
|
|
108
|
-
Tool home:
|
|
109
|
-
|
|
110
|
-
```text
|
|
111
|
-
~/.config/codex-switch/
|
|
112
|
-
codex-switch.json
|
|
113
|
-
providers.json
|
|
114
|
-
backups/
|
|
115
|
-
runtime/
|
|
116
|
-
runtimes/
|
|
117
|
-
```
|
|
118
|
-
|
|
119
|
-
Target Codex runtime:
|
|
120
|
-
|
|
121
|
-
```text
|
|
122
|
-
~/.codex/
|
|
123
|
-
config.toml
|
|
124
|
-
auth.json
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
Key points:
|
|
128
|
-
|
|
129
|
-
- `providers.json` is the managed provider registry and lives under the tool home.
|
|
130
|
-
- `codex-switch.json` stores tool-level metadata such as `defaultCodexDir`.
|
|
131
|
-
- `config.toml` remains the active runtime routing file in the target Codex directory.
|
|
132
|
-
- `auth.json` remains the active auth projection file in the target Codex directory.
|
|
133
|
-
- Direct providers rewrite `OPENAI_API_KEY` into the active runtime projection.
|
|
134
|
-
- Copilot providers keep upstream GitHub authentication in the official Copilot runtime while `codex-switch` manages local bridge state and routing.
|
|
135
|
-
|
|
136
|
-
Path controls:
|
|
137
|
-
|
|
138
|
-
- `--codex-dir <path>` targets a specific Codex runtime directory.
|
|
139
|
-
- `CODEXS_CODEX_DIR` provides the default target runtime when `--codex-dir` is not passed.
|
|
140
|
-
- `CODEXS_HOME` overrides the tool home location.
|
|
141
|
-
|
|
142
|
-
## Automation Notes
|
|
143
|
-
|
|
144
|
-
This CLI supports both human TTY usage and non-interactive automation.
|
|
145
|
-
|
|
146
|
-
Global flags:
|
|
147
|
-
|
|
148
|
-
```bash
|
|
149
|
-
--json
|
|
150
|
-
--codex-dir <path>
|
|
151
|
-
--help
|
|
152
|
-
--version
|
|
153
|
-
```
|
|
154
|
-
|
|
155
|
-
Operational limits:
|
|
156
|
-
|
|
157
|
-
- `login copilot` requires a real TTY and does not support `--json`.
|
|
158
|
-
- `migrate`
|
|
159
|
-
- Automation should pass explicit arguments and prefer `--json` for stable parsing.
|
|
160
|
-
|
|
161
|
-
## Local Development
|
|
162
|
-
|
|
163
|
-
```bash
|
|
164
|
-
npm run build
|
|
165
|
-
npm test
|
|
166
|
-
npx tsc --noEmit
|
|
167
|
-
node dist/cli.js --help
|
|
168
|
-
npm pack --dry-run
|
|
169
|
-
```
|
|
170
|
-
|
|
171
|
-
## Documentation
|
|
172
|
-
|
|
173
|
-
- [Chinese README](./README.CN.md)
|
|
174
|
-
- [AI README](./README.AI.md)
|
|
175
|
-
- [Detailed CLI Usage](./docs/cli-usage.md)
|
|
176
|
-
- [Testing Guide](./docs/Tests/testing.md)
|
|
177
|
-
- [Product Overview](./docs/codex-switch-product-overview.md)
|
|
178
|
-
- [PRD 0.0
|
|
179
|
-
- [Release Gate PRD 0.1.0](./docs/PRD/codex-switch-prd-v0.1.0.md)
|
|
180
|
-
|
|
181
|
-
## License
|
|
182
|
-
|
|
183
|
-
MIT
|
|
1
|
+
# @minniexcode/codex-switch
|
|
2
|
+
|
|
3
|
+
`@minniexcode/codex-switch` is a local-first CLI for managing and switching Codex provider and profile configuration safely.
|
|
4
|
+
|
|
5
|
+
It keeps `codex-switch` tool state separate from the target Codex runtime, so provider management, backup flow, and runtime projection stay explicit instead of relying on manual file edits.
|
|
6
|
+
|
|
7
|
+
Chinese version: [README.CN.md](./README.CN.md)
|
|
8
|
+
|
|
9
|
+
## Version
|
|
10
|
+
|
|
11
|
+
Current package version: `0.1.0`
|
|
12
|
+
|
|
13
|
+
This is the first stable release line. The current release focuses on keeping the primary workflows, help text, operational boundaries, and release docs aligned with the implementation.
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install -g @minniexcode/codex-switch
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Run without a global install:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npx @minniexcode/codex-switch --help
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Built CLI entrypoint:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
codexs --help
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Primary Workflows
|
|
34
|
+
|
|
35
|
+
Direct provider workflow:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
codexs init
|
|
39
|
+
codexs add my-provider --profile my-provider --api-key sk-xxx
|
|
40
|
+
codexs switch my-provider
|
|
41
|
+
codexs status
|
|
42
|
+
codexs doctor
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
GitHub Copilot workflow:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
codexs init
|
|
49
|
+
codexs login copilot
|
|
50
|
+
codexs add copilot-main --copilot --profile copilot-main
|
|
51
|
+
codexs switch copilot-main
|
|
52
|
+
codexs status
|
|
53
|
+
codexs doctor
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Notes:
|
|
57
|
+
|
|
58
|
+
- `init` prepares the `codex-switch` tool home and managed state.
|
|
59
|
+
- `login copilot` handles upstream Copilot onboarding and auth readiness.
|
|
60
|
+
- `add --copilot` does not perform login for you; it assumes Copilot login is already ready.
|
|
61
|
+
- `status` is the main read command after switching.
|
|
62
|
+
- `doctor` is the main repair-oriented diagnostic command.
|
|
63
|
+
|
|
64
|
+
## Advanced Adopt Workflow
|
|
65
|
+
|
|
66
|
+
Use `migrate` only when you already have Codex runtime state that should be adopted into managed `providers.json` state:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
codexs init
|
|
70
|
+
codexs migrate
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
`migrate` is an advanced adopt helper. It is not the default first step for a fresh install.
|
|
74
|
+
|
|
75
|
+
## Command Surface
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
codexs init
|
|
79
|
+
codexs login copilot
|
|
80
|
+
codexs migrate
|
|
81
|
+
codexs list
|
|
82
|
+
codexs show <provider>
|
|
83
|
+
codexs current
|
|
84
|
+
codexs status
|
|
85
|
+
codexs config show [profile]
|
|
86
|
+
codexs config list-profiles
|
|
87
|
+
codexs add <provider> --profile <name> --api-key <key>
|
|
88
|
+
codexs add <provider> --copilot --profile <name>
|
|
89
|
+
codexs edit <provider>
|
|
90
|
+
codexs switch <provider>
|
|
91
|
+
codexs remove <provider> [--force] [--switch-to <profile>]
|
|
92
|
+
codexs import <file>
|
|
93
|
+
codexs export <file> [--force]
|
|
94
|
+
codexs bridge start [provider]
|
|
95
|
+
codexs bridge status [provider]
|
|
96
|
+
codexs bridge stop [provider]
|
|
97
|
+
codexs backups list
|
|
98
|
+
codexs rollback [backup-id]
|
|
99
|
+
codexs doctor
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
`setup` still exists only as a deprecated compatibility entry that points callers to `init` or `migrate`.
|
|
103
|
+
|
|
104
|
+
## Runtime Model
|
|
105
|
+
|
|
106
|
+
`codex-switch` uses a dual-path model.
|
|
107
|
+
|
|
108
|
+
Tool home:
|
|
109
|
+
|
|
110
|
+
```text
|
|
111
|
+
~/.config/codex-switch/
|
|
112
|
+
codex-switch.json
|
|
113
|
+
providers.json
|
|
114
|
+
backups/
|
|
115
|
+
runtime/
|
|
116
|
+
runtimes/
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Target Codex runtime:
|
|
120
|
+
|
|
121
|
+
```text
|
|
122
|
+
~/.codex/
|
|
123
|
+
config.toml
|
|
124
|
+
auth.json
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Key points:
|
|
128
|
+
|
|
129
|
+
- `providers.json` is the managed provider registry and lives under the tool home.
|
|
130
|
+
- `codex-switch.json` stores tool-level metadata such as `defaultCodexDir`.
|
|
131
|
+
- `config.toml` remains the active runtime routing file in the target Codex directory.
|
|
132
|
+
- `auth.json` remains the active auth projection file in the target Codex directory.
|
|
133
|
+
- Direct providers rewrite `OPENAI_API_KEY` into the active runtime projection.
|
|
134
|
+
- Copilot providers keep upstream GitHub authentication in the official Copilot runtime while `codex-switch` manages local bridge state and routing.
|
|
135
|
+
|
|
136
|
+
Path controls:
|
|
137
|
+
|
|
138
|
+
- `--codex-dir <path>` targets a specific Codex runtime directory.
|
|
139
|
+
- `CODEXS_CODEX_DIR` provides the default target runtime when `--codex-dir` is not passed.
|
|
140
|
+
- `CODEXS_HOME` overrides the tool home location.
|
|
141
|
+
|
|
142
|
+
## Automation Notes
|
|
143
|
+
|
|
144
|
+
This CLI supports both human TTY usage and non-interactive automation.
|
|
145
|
+
|
|
146
|
+
Global flags:
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
--json
|
|
150
|
+
--codex-dir <path>
|
|
151
|
+
--help
|
|
152
|
+
--version
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Operational limits:
|
|
156
|
+
|
|
157
|
+
- `login copilot` requires a real TTY and does not support `--json`.
|
|
158
|
+
- `migrate` remains interactive when provider adoption requires human input.
|
|
159
|
+
- Automation should pass explicit arguments and prefer `--json` for stable parsing.
|
|
160
|
+
|
|
161
|
+
## Local Development
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
npm run build
|
|
165
|
+
npm test
|
|
166
|
+
npx tsc --noEmit
|
|
167
|
+
node dist/cli.js --help
|
|
168
|
+
npm pack --dry-run
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## Documentation
|
|
172
|
+
|
|
173
|
+
- [Chinese README](./README.CN.md)
|
|
174
|
+
- [AI README](./README.AI.md)
|
|
175
|
+
- [Detailed CLI Usage](./docs/cli-usage.md)
|
|
176
|
+
- [Testing Guide](./docs/Tests/testing.md)
|
|
177
|
+
- [Product Overview](./docs/codex-switch-product-overview.md)
|
|
178
|
+
- [Release PRD 0.1.0](./docs/PRD/codex-switch-prd-v0.1.0.md)
|
|
179
|
+
- [Release Gate PRD 0.1.0](./docs/PRD/codex-switch-prd-v0.1.0.md)
|
|
180
|
+
|
|
181
|
+
## License
|
|
182
|
+
|
|
183
|
+
MIT
|
package/dist/app/add-provider.js
CHANGED
|
@@ -99,6 +99,7 @@ async function addProvider(args) {
|
|
|
99
99
|
provider: args.providerName,
|
|
100
100
|
});
|
|
101
101
|
}
|
|
102
|
+
const directBaseUrl = args.baseUrl;
|
|
102
103
|
const upsertProfiles = !existingProfile && args.createProfile
|
|
103
104
|
? {
|
|
104
105
|
[args.profile]: (0, config_1.validateManagedProfileCreation)(args.profile, {
|
|
@@ -107,15 +108,20 @@ async function addProvider(args) {
|
|
|
107
108
|
}),
|
|
108
109
|
}
|
|
109
110
|
: undefined;
|
|
111
|
+
if (!args.copilot && !existingModelProvider && args.createProfile && (!directBaseUrl || directBaseUrl.trim() === "")) {
|
|
112
|
+
throw (0, errors_1.cliError)("MANAGED_PROFILE_FIELDS_MISSING", `Model provider "${args.profile}" requires base_url.`, {
|
|
113
|
+
profile: args.profile,
|
|
114
|
+
modelProvider: args.profile,
|
|
115
|
+
missingFields: ["base_url"],
|
|
116
|
+
});
|
|
117
|
+
}
|
|
110
118
|
const upsertModelProviders = args.copilot
|
|
111
119
|
? {
|
|
112
120
|
[args.profile]: (0, providers_1.buildCopilotModelProviderProjection)(runtime),
|
|
113
121
|
}
|
|
114
122
|
: !existingModelProvider && args.createProfile
|
|
115
123
|
? {
|
|
116
|
-
[args.profile]:
|
|
117
|
-
baseUrl: args.baseUrl ?? undefined,
|
|
118
|
-
},
|
|
124
|
+
[args.profile]: (0, providers_1.buildDirectModelProviderProjection)(args.profile, directBaseUrl),
|
|
119
125
|
}
|
|
120
126
|
: undefined;
|
|
121
127
|
if (existingProfile) {
|
|
@@ -53,6 +53,13 @@ function editProvider(args) {
|
|
|
53
53
|
provider: args.providerName,
|
|
54
54
|
});
|
|
55
55
|
}
|
|
56
|
+
if (!args.baseUrl || args.baseUrl.trim() === "") {
|
|
57
|
+
throw (0, errors_1.cliError)("MANAGED_PROFILE_FIELDS_MISSING", `Model provider "${newProfile}" requires base_url.`, {
|
|
58
|
+
profile: newProfile,
|
|
59
|
+
modelProvider: newProfile,
|
|
60
|
+
missingFields: ["base_url"],
|
|
61
|
+
});
|
|
62
|
+
}
|
|
56
63
|
upsertProfiles = {
|
|
57
64
|
[newProfile]: (0, config_1.validateManagedProfileCreation)(newProfile, {
|
|
58
65
|
model: args.model ?? undefined,
|
|
@@ -60,14 +67,21 @@ function editProvider(args) {
|
|
|
60
67
|
}),
|
|
61
68
|
};
|
|
62
69
|
upsertModelProviders = {
|
|
63
|
-
[newProfile]:
|
|
64
|
-
baseUrl: args.baseUrl ?? undefined,
|
|
65
|
-
},
|
|
70
|
+
[newProfile]: (0, providers_1.buildDirectModelProviderProjection)(newProfile, args.baseUrl),
|
|
66
71
|
};
|
|
67
72
|
}
|
|
68
73
|
else {
|
|
69
74
|
(0, config_repo_1.requireManagedProfileRuntime)(document, providers, newProfile);
|
|
70
75
|
}
|
|
76
|
+
if (targetProfileExists &&
|
|
77
|
+
!current.runtime &&
|
|
78
|
+
args.baseUrl !== undefined &&
|
|
79
|
+
args.baseUrl !== null) {
|
|
80
|
+
upsertModelProviders = {
|
|
81
|
+
...(upsertModelProviders ?? {}),
|
|
82
|
+
[newProfile]: (0, providers_1.buildDirectModelProviderProjection)(newProfile, args.baseUrl),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
71
85
|
const nextRecord = (0, providers_1.cleanProviderRecord)({
|
|
72
86
|
profile: newProfile,
|
|
73
87
|
apiKey: args.apiKey ?? current.apiKey,
|
package/dist/app/run-doctor.js
CHANGED
|
@@ -245,6 +245,10 @@ function renderConfigIssueMessage(issue) {
|
|
|
245
245
|
return `Model provider section "${issue.modelProvider}" for profile "${issue.profile}" is missing from config.toml.`;
|
|
246
246
|
case "MODEL_PROVIDER_BASE_URL_MISSING":
|
|
247
247
|
return `Model provider section "${issue.modelProvider}" for profile "${issue.profile}" is missing base_url.`;
|
|
248
|
+
case "PROVIDER_BASE_URL_MISMATCH":
|
|
249
|
+
return issue.providerType === "direct"
|
|
250
|
+
? `Direct provider "${issue.provider}" baseUrl does not match config.toml model provider "${issue.profile}" base_url.`
|
|
251
|
+
: String(issue.code ?? "UNKNOWN_ISSUE");
|
|
248
252
|
case "ACTIVE_PROVIDER_UNRESOLVED":
|
|
249
253
|
return `Active profile "${issue.profile}" maps to multiple providers, so the active managed provider cannot be resolved uniquely.`;
|
|
250
254
|
case "AUTH_JSON_INVALID":
|
package/dist/cli/output.js
CHANGED
|
@@ -93,6 +93,9 @@ function renderHumanSuccess(command, data, warnings) {
|
|
|
93
93
|
if (!activeProviderResolvable && activeCandidates.length > 1) {
|
|
94
94
|
lines.push(`Current provider: ambiguous (${activeCandidates.join(", ")})`);
|
|
95
95
|
}
|
|
96
|
+
else if (!activeProviderResolvable) {
|
|
97
|
+
lines.push("Current provider: unmanaged or unresolved");
|
|
98
|
+
}
|
|
96
99
|
}
|
|
97
100
|
for (const provider of providers) {
|
|
98
101
|
const tags = Array.isArray(provider.tags) && provider.tags.length > 0
|
|
@@ -265,6 +268,9 @@ function renderStatusHealth(data) {
|
|
|
265
268
|
if (issues.some((issue) => issue.code === "ACTIVE_PROVIDER_UNRESOLVED")) {
|
|
266
269
|
return "active provider ambiguous";
|
|
267
270
|
}
|
|
271
|
+
if (issues.some((issue) => issue.code === "PROVIDER_BASE_URL_MISMATCH")) {
|
|
272
|
+
return "provider projection drift";
|
|
273
|
+
}
|
|
268
274
|
if (activePathUsesCopilot && copilotSdk.installed === false) {
|
|
269
275
|
return "copilot sdk missing";
|
|
270
276
|
}
|
|
@@ -335,6 +341,8 @@ function renderDoctorIssueNextStep(issue) {
|
|
|
335
341
|
case "ACTIVE_PROVIDER_UNRESOLVED":
|
|
336
342
|
case "SHARED_PROFILE_REFERENCE":
|
|
337
343
|
return "make provider-to-profile mappings unique before relying on current-provider detection";
|
|
344
|
+
case "PROVIDER_BASE_URL_MISMATCH":
|
|
345
|
+
return "rerun `codexs edit <provider> --base-url <url>` or `codexs switch <provider>` to repair the runtime projection";
|
|
338
346
|
default:
|
|
339
347
|
return "inspect the issue details and rerun `codexs doctor` after fixing the state";
|
|
340
348
|
}
|
|
@@ -150,9 +150,13 @@ exports.COMMANDS = [
|
|
|
150
150
|
tokens: ["list"],
|
|
151
151
|
handler: handlers_1.handleRegisteredCommand,
|
|
152
152
|
group: "read",
|
|
153
|
-
summary: "List
|
|
153
|
+
summary: "List managed providers with profile, type, and current-state hints.",
|
|
154
154
|
usage: ["codexs list [--json] [--codex-dir <path>]"],
|
|
155
|
-
details: [
|
|
155
|
+
details: [
|
|
156
|
+
"Reads providers.json and prints provider-to-profile mappings together with provider type.",
|
|
157
|
+
"When the active profile is shared by multiple providers, list surfaces the ambiguity instead of inventing one current provider.",
|
|
158
|
+
"Use --json for machine-readable automation output.",
|
|
159
|
+
],
|
|
156
160
|
examples: ["codexs list", "codexs list --json"],
|
|
157
161
|
},
|
|
158
162
|
{
|
|
@@ -184,12 +188,13 @@ exports.COMMANDS = [
|
|
|
184
188
|
tokens: ["status"],
|
|
185
189
|
handler: handlers_1.handleRegisteredCommand,
|
|
186
190
|
group: "read",
|
|
187
|
-
summary: "Show tool-home, target-runtime, provider, and runtime-health status.",
|
|
191
|
+
summary: "Show tool-home, target-runtime, provider-path, and runtime-health status.",
|
|
188
192
|
usage: ["codexs status [--json] [--codex-dir <path>]"],
|
|
189
193
|
details: [
|
|
190
194
|
"Reports the target Codex runtime, tool-home storage roles, current profile, and whether the live profile is mapped.",
|
|
191
195
|
"When the active provider uses a local runtime bridge, status also reports bridge, Copilot SDK, and upstream auth state.",
|
|
192
196
|
"Surfaces dual-path config consistency signals without mutating any files.",
|
|
197
|
+
"Organizes the human-readable view around current state, health impact, and next step.",
|
|
193
198
|
"Use doctor for deeper diagnostics.",
|
|
194
199
|
],
|
|
195
200
|
examples: ["codexs status", "codexs status --json --codex-dir ./.tmp-codex"],
|
|
@@ -324,7 +329,7 @@ exports.COMMANDS = [
|
|
|
324
329
|
tokens: ["doctor"],
|
|
325
330
|
handler: handlers_1.handleRegisteredCommand,
|
|
326
331
|
group: "recovery",
|
|
327
|
-
summary: "Run
|
|
332
|
+
summary: "Run issue-first diagnostics across tool-home and target-runtime state.",
|
|
328
333
|
usage: ["codexs doctor [--json] [--codex-dir <path>]"],
|
|
329
334
|
details: [
|
|
330
335
|
"Checks the expected config files, provider/profile consistency, and Codex CLI availability.",
|
package/dist/domain/config.js
CHANGED
|
@@ -268,6 +268,8 @@ function buildManagedProfileViews(document, providers) {
|
|
|
268
268
|
*/
|
|
269
269
|
function collectConfigConsistencyIssues(document, providers) {
|
|
270
270
|
const issues = [];
|
|
271
|
+
const providerMap = providers?.providers ?? null;
|
|
272
|
+
const profileLinkMap = buildProfileLinkMap(providers);
|
|
271
273
|
for (const view of buildManagedProfileViews(document, providers)) {
|
|
272
274
|
if (view.source === "orphaned-reference") {
|
|
273
275
|
issues.push({
|
|
@@ -319,11 +321,34 @@ function collectConfigConsistencyIssues(document, providers) {
|
|
|
319
321
|
modelProvider: view.modelProvider,
|
|
320
322
|
});
|
|
321
323
|
}
|
|
324
|
+
else {
|
|
325
|
+
const profileLinkInfo = profileLinkMap.get(view.name);
|
|
326
|
+
if (profileLinkInfo &&
|
|
327
|
+
profileLinkInfo.linkedProviders.length === 1 &&
|
|
328
|
+
providerMap) {
|
|
329
|
+
const providerName = profileLinkInfo.linkedProviders[0];
|
|
330
|
+
const provider = providerMap[providerName];
|
|
331
|
+
if (provider &&
|
|
332
|
+
!provider.runtime &&
|
|
333
|
+
typeof provider.baseUrl === "string" &&
|
|
334
|
+
provider.baseUrl.trim() !== "" &&
|
|
335
|
+
provider.baseUrl !== modelProviderSection.baseUrl) {
|
|
336
|
+
issues.push({
|
|
337
|
+
code: "PROVIDER_BASE_URL_MISMATCH",
|
|
338
|
+
profile: view.name,
|
|
339
|
+
provider: providerName,
|
|
340
|
+
providerBaseUrl: provider.baseUrl,
|
|
341
|
+
configBaseUrl: modelProviderSection.baseUrl,
|
|
342
|
+
providerType: "direct",
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
322
347
|
}
|
|
323
348
|
}
|
|
324
349
|
}
|
|
325
350
|
if (document.activeProfile) {
|
|
326
|
-
const activeLinkInfo =
|
|
351
|
+
const activeLinkInfo = profileLinkMap.get(document.activeProfile);
|
|
327
352
|
if (!activeLinkInfo) {
|
|
328
353
|
issues.push({
|
|
329
354
|
code: "UNMANAGED_ACTIVE_PROFILE",
|
package/dist/domain/providers.js
CHANGED
|
@@ -10,6 +10,7 @@ exports.isRuntimeBackedProvider = isRuntimeBackedProvider;
|
|
|
10
10
|
exports.isCopilotBridgeProvider = isCopilotBridgeProvider;
|
|
11
11
|
exports.buildCopilotBridgeBaseUrl = buildCopilotBridgeBaseUrl;
|
|
12
12
|
exports.buildCopilotModelProviderProjection = buildCopilotModelProviderProjection;
|
|
13
|
+
exports.buildDirectModelProviderProjection = buildDirectModelProviderProjection;
|
|
13
14
|
/**
|
|
14
15
|
* Validates and normalizes unknown JSON into the providers.json domain model.
|
|
15
16
|
*/
|
|
@@ -162,6 +163,21 @@ function buildCopilotModelProviderProjection(runtime) {
|
|
|
162
163
|
wireApi: "responses",
|
|
163
164
|
};
|
|
164
165
|
}
|
|
166
|
+
/**
|
|
167
|
+
* Builds the Codex-facing custom model_provider projection for a direct provider.
|
|
168
|
+
*/
|
|
169
|
+
function buildDirectModelProviderProjection(profile, baseUrl) {
|
|
170
|
+
const normalizedBaseUrl = baseUrl.trim();
|
|
171
|
+
if (!normalizedBaseUrl) {
|
|
172
|
+
throw new Error(`Direct model provider "${profile}" requires a non-empty base_url.`);
|
|
173
|
+
}
|
|
174
|
+
return {
|
|
175
|
+
baseUrl: normalizedBaseUrl,
|
|
176
|
+
name: profile.trim(),
|
|
177
|
+
requiresOpenAiAuth: true,
|
|
178
|
+
wireApi: "responses",
|
|
179
|
+
};
|
|
180
|
+
}
|
|
165
181
|
/**
|
|
166
182
|
* Validates one runtime-backed provider block.
|
|
167
183
|
*/
|