@minniexcode/codex-switch 0.0.6 → 0.0.7
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 +21 -3
- package/dist/app/edit-provider.js +39 -11
- package/dist/app/get-status.js +8 -1
- package/dist/app/init-codex.js +68 -0
- package/dist/app/list-providers.js +1 -0
- package/dist/app/run-doctor.js +60 -0
- package/dist/app/setup-codex.js +17 -8
- package/dist/app/show-config.js +9 -1
- package/dist/app/switch-provider.js +14 -7
- 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 +80 -11
- package/dist/commands/help.js +2 -1
- package/dist/commands/registry.js +73 -13
- package/dist/domain/config.js +137 -0
- package/dist/domain/providers.js +16 -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/storage/auth-repo.js +160 -0
- package/dist/storage/config-repo.js +58 -0
- package/docs/Design/codex-switch-v0.0.7-design.md +862 -0
- package/docs/PRD/codex-switch-prd-v0.0.5-to-v0.1.0.md +131 -25
- 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
package/README.AI.md
CHANGED
|
@@ -17,7 +17,8 @@ Primary goals:
|
|
|
17
17
|
## Main Command Surface
|
|
18
18
|
|
|
19
19
|
```bash
|
|
20
|
-
codexs
|
|
20
|
+
codexs init
|
|
21
|
+
codexs migrate
|
|
21
22
|
codexs list
|
|
22
23
|
codexs show <provider>
|
|
23
24
|
codexs current
|
|
@@ -92,11 +93,12 @@ codexs status --json
|
|
|
92
93
|
Current package version in this repository:
|
|
93
94
|
|
|
94
95
|
```text
|
|
95
|
-
0.0.
|
|
96
|
+
0.0.7
|
|
96
97
|
```
|
|
97
98
|
|
|
98
99
|
Recent version summary:
|
|
99
100
|
|
|
101
|
+
- `0.0.7`: command-surface refactor, env_key/auth-mirror model corrections, and the `setup` split into `init` plus `migrate`
|
|
100
102
|
- `0.0.4`: setup/show/edit/backups list/specific rollback/import merge and clearer CLI semantics
|
|
101
103
|
- `0.0.3`: interactive TTY flows and improved help
|
|
102
104
|
- `0.0.2`: mutation orchestration, backups, rollback, locks, drift detection improvements
|
|
@@ -107,4 +109,5 @@ Recent version summary:
|
|
|
107
109
|
- Prefer `--json` when invoking commands programmatically
|
|
108
110
|
- Treat `providers.json` as sensitive because it may contain API keys
|
|
109
111
|
- Do not assume silent write-back from runtime files into `providers.json`
|
|
112
|
+
- Prefer `init` for repeatable machine setup and `migrate` for human-led adopt flows
|
|
110
113
|
- Use `docs/` for deeper product and architecture context
|
package/README.md
CHANGED
|
@@ -10,14 +10,15 @@ It is designed for users who work with multiple Codex providers, API keys, or pr
|
|
|
10
10
|
|
|
11
11
|
What it does:
|
|
12
12
|
|
|
13
|
-
- Initialize `providers.json`
|
|
13
|
+
- Initialize an empty managed `providers.json`
|
|
14
|
+
- Migrate unmanaged runtime profiles from an existing Codex directory
|
|
14
15
|
- List, show, add, edit, and remove provider records
|
|
15
16
|
- Switch the active provider/profile safely
|
|
16
17
|
- Import and export provider definitions
|
|
17
18
|
- Run diagnostics and detect local drift
|
|
18
19
|
- List backups and roll back to a previous managed state
|
|
19
20
|
|
|
20
|
-
Current version: `0.0.
|
|
21
|
+
Current version: `0.0.7`
|
|
21
22
|
|
|
22
23
|
## Install
|
|
23
24
|
|
|
@@ -44,7 +45,8 @@ codexs --help
|
|
|
44
45
|
Take over an existing Codex directory:
|
|
45
46
|
|
|
46
47
|
```bash
|
|
47
|
-
codexs
|
|
48
|
+
codexs init
|
|
49
|
+
codexs migrate
|
|
48
50
|
```
|
|
49
51
|
|
|
50
52
|
Inspect managed providers:
|
|
@@ -72,7 +74,8 @@ codexs doctor
|
|
|
72
74
|
## Common Commands
|
|
73
75
|
|
|
74
76
|
```bash
|
|
75
|
-
codexs
|
|
77
|
+
codexs init
|
|
78
|
+
codexs migrate
|
|
76
79
|
codexs list
|
|
77
80
|
codexs show <provider>
|
|
78
81
|
codexs current
|
|
@@ -92,6 +95,8 @@ Command help:
|
|
|
92
95
|
|
|
93
96
|
```bash
|
|
94
97
|
codexs help switch
|
|
98
|
+
codexs help init
|
|
99
|
+
codexs help migrate
|
|
95
100
|
codexs help setup
|
|
96
101
|
```
|
|
97
102
|
|
|
@@ -120,8 +125,9 @@ Notes:
|
|
|
120
125
|
|
|
121
126
|
This CLI supports both human TTY use and non-interactive automation.
|
|
122
127
|
|
|
123
|
-
Current
|
|
124
|
-
- `
|
|
128
|
+
Current exceptions:
|
|
129
|
+
- `init` is automation-friendly and idempotent, but still returns a structured error in non-interactive or `--json` mode when the resolved target directory does not exist.
|
|
130
|
+
- `migrate` remains intentionally TTY-only for adopt initialization. It requires interactive profile selection and provider detail collection, and non-interactive/`--json` runs fail fast with a structured error.
|
|
125
131
|
|
|
126
132
|
Recommended global flags:
|
|
127
133
|
|
package/dist/app/add-provider.js
CHANGED
|
@@ -7,6 +7,7 @@ const errors_1 = require("../domain/errors");
|
|
|
7
7
|
const config_repo_1 = require("../storage/config-repo");
|
|
8
8
|
const fs_utils_1 = require("../storage/fs-utils");
|
|
9
9
|
const providers_repo_1 = require("../storage/providers-repo");
|
|
10
|
+
const auth_repo_1 = require("../storage/auth-repo");
|
|
10
11
|
const run_mutation_1 = require("./run-mutation");
|
|
11
12
|
/**
|
|
12
13
|
* Adds a new provider record to the managed providers registry.
|
|
@@ -19,6 +20,7 @@ function addProvider(args) {
|
|
|
19
20
|
}
|
|
20
21
|
const document = (0, config_repo_1.readStructuredConfig)(args.configPath);
|
|
21
22
|
const existingProfile = document.profiles.find((profile) => profile.name === args.profile);
|
|
23
|
+
const existingModelProvider = document.modelProviders.find((entry) => entry.name === args.profile);
|
|
22
24
|
if (!existingProfile && !args.createProfile) {
|
|
23
25
|
throw (0, errors_1.cliError)("PROFILE_NOT_FOUND", `Profile "${args.profile}" does not exist in config.toml.`, {
|
|
24
26
|
profile: args.profile,
|
|
@@ -33,18 +35,25 @@ function addProvider(args) {
|
|
|
33
35
|
}),
|
|
34
36
|
}
|
|
35
37
|
: undefined;
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
38
|
+
const upsertModelProviders = !existingModelProvider && args.createProfile
|
|
39
|
+
? {
|
|
40
|
+
[args.profile]: {
|
|
41
|
+
baseUrl: args.baseUrl ?? undefined,
|
|
42
|
+
envKey: (0, config_1.buildManagedProfileEnvKey)(args.profile),
|
|
43
|
+
},
|
|
44
|
+
}
|
|
45
|
+
: undefined;
|
|
39
46
|
if (existingProfile) {
|
|
40
47
|
(0, config_repo_1.requireManagedProfileRuntime)(document, providers, args.profile);
|
|
41
48
|
}
|
|
49
|
+
const envKey = existingModelProvider?.envKey ?? (0, config_1.buildManagedProfileEnvKey)(args.profile);
|
|
42
50
|
const next = {
|
|
43
51
|
providers: {
|
|
44
52
|
...providers.providers,
|
|
45
53
|
[args.providerName]: (0, providers_1.cleanProviderRecord)({
|
|
46
54
|
profile: args.profile,
|
|
47
55
|
apiKey: args.apiKey,
|
|
56
|
+
envKey,
|
|
48
57
|
baseUrl: args.baseUrl ?? undefined,
|
|
49
58
|
note: args.note ?? undefined,
|
|
50
59
|
tags: args.tags,
|
|
@@ -59,18 +68,27 @@ function addProvider(args) {
|
|
|
59
68
|
files: [
|
|
60
69
|
{ absolutePath: args.providersPath, relativePath: "providers.json" },
|
|
61
70
|
{ absolutePath: args.configPath, relativePath: "config.toml" },
|
|
71
|
+
{ absolutePath: args.authPath, relativePath: "auth.json" },
|
|
62
72
|
],
|
|
63
73
|
mutate: () => {
|
|
64
74
|
const configPlan = (0, config_repo_1.createConfigMutationPlan)(document, {
|
|
65
75
|
upsertProfiles,
|
|
76
|
+
upsertModelProviders,
|
|
66
77
|
});
|
|
67
78
|
// Persist only the normalized provider payload so later reads are deterministic.
|
|
68
79
|
(0, providers_repo_1.writeProvidersFile)(args.providersPath, next);
|
|
69
80
|
(0, config_repo_1.applyConfigMutation)(args.configPath, document, configPlan);
|
|
81
|
+
if (document.activeProfile === args.profile) {
|
|
82
|
+
const activeProviderName = (0, config_repo_1.resolveActiveProviderName)(document, next);
|
|
83
|
+
const existingAuth = (0, auth_repo_1.readAuthFileIfExists)(args.authPath);
|
|
84
|
+
(0, auth_repo_1.writeAuthFile)(args.authPath, next.providers[activeProviderName], existingAuth ?? undefined);
|
|
85
|
+
}
|
|
70
86
|
return {
|
|
71
87
|
provider: args.providerName,
|
|
72
88
|
profile: args.profile,
|
|
89
|
+
envKey,
|
|
73
90
|
createdProfileSections: configPlan.createdProfileSections,
|
|
91
|
+
createdModelProviderSections: configPlan.createdModelProviderSections,
|
|
74
92
|
deletedProfileSections: configPlan.deletedProfileSections,
|
|
75
93
|
keptSharedProfiles: [],
|
|
76
94
|
switchedActiveProfile: configPlan.switchedActiveProfile,
|
|
@@ -7,6 +7,7 @@ const providers_1 = require("../domain/providers");
|
|
|
7
7
|
const config_repo_1 = require("../storage/config-repo");
|
|
8
8
|
const fs_utils_1 = require("../storage/fs-utils");
|
|
9
9
|
const providers_repo_1 = require("../storage/providers-repo");
|
|
10
|
+
const auth_repo_1 = require("../storage/auth-repo");
|
|
10
11
|
const run_mutation_1 = require("./run-mutation");
|
|
11
12
|
/**
|
|
12
13
|
* Updates selected fields on a single managed provider.
|
|
@@ -23,13 +24,7 @@ function editProvider(args) {
|
|
|
23
24
|
});
|
|
24
25
|
}
|
|
25
26
|
const updatedFields = [];
|
|
26
|
-
const
|
|
27
|
-
profile: args.profile ?? current.profile,
|
|
28
|
-
apiKey: args.apiKey ?? current.apiKey,
|
|
29
|
-
baseUrl: args.baseUrl === null ? undefined : args.baseUrl ?? current.baseUrl,
|
|
30
|
-
note: args.note === null ? undefined : args.note ?? current.note,
|
|
31
|
-
tags: args.tags ?? current.tags,
|
|
32
|
-
});
|
|
27
|
+
const nextProfile = args.profile ?? current.profile;
|
|
33
28
|
if (args.profile !== undefined && args.profile !== current.profile) {
|
|
34
29
|
updatedFields.push("profile");
|
|
35
30
|
}
|
|
@@ -46,10 +41,12 @@ function editProvider(args) {
|
|
|
46
41
|
updatedFields.push("tags");
|
|
47
42
|
}
|
|
48
43
|
const oldProfile = current.profile;
|
|
49
|
-
const newProfile =
|
|
44
|
+
const newProfile = nextProfile;
|
|
50
45
|
const targetSection = document.profiles.find((profile) => profile.name === newProfile) ?? null;
|
|
46
|
+
const targetModelProviderSection = document.modelProviders.find((entry) => entry.name === newProfile) ?? null;
|
|
51
47
|
const targetProfileExists = Boolean(targetSection);
|
|
52
48
|
let upsertProfiles;
|
|
49
|
+
let upsertModelProviders;
|
|
53
50
|
if (!targetProfileExists) {
|
|
54
51
|
if (!args.createProfile) {
|
|
55
52
|
throw (0, errors_1.cliError)("PROFILE_NOT_FOUND", `Profile "${newProfile}" does not exist in config.toml.`, {
|
|
@@ -63,11 +60,30 @@ function editProvider(args) {
|
|
|
63
60
|
modelProvider: newProfile,
|
|
64
61
|
}),
|
|
65
62
|
};
|
|
66
|
-
|
|
63
|
+
upsertModelProviders = {
|
|
64
|
+
[newProfile]: {
|
|
65
|
+
baseUrl: args.baseUrl ?? undefined,
|
|
66
|
+
envKey: (0, config_1.buildManagedProfileEnvKey)(newProfile),
|
|
67
|
+
},
|
|
68
|
+
};
|
|
67
69
|
}
|
|
68
70
|
else {
|
|
69
71
|
(0, config_repo_1.requireManagedProfileRuntime)(document, providers, newProfile);
|
|
70
72
|
}
|
|
73
|
+
const nextEnvKey = args.profile !== undefined && args.profile !== current.profile
|
|
74
|
+
? targetModelProviderSection?.envKey ?? (0, config_1.buildManagedProfileEnvKey)(newProfile)
|
|
75
|
+
: current.envKey;
|
|
76
|
+
if (nextEnvKey !== current.envKey) {
|
|
77
|
+
updatedFields.push("envKey");
|
|
78
|
+
}
|
|
79
|
+
const nextRecord = (0, providers_1.cleanProviderRecord)({
|
|
80
|
+
profile: newProfile,
|
|
81
|
+
apiKey: args.apiKey ?? current.apiKey,
|
|
82
|
+
envKey: nextEnvKey,
|
|
83
|
+
baseUrl: args.baseUrl === null ? undefined : args.baseUrl ?? current.baseUrl,
|
|
84
|
+
note: args.note === null ? undefined : args.note ?? current.note,
|
|
85
|
+
tags: args.tags ?? current.tags,
|
|
86
|
+
});
|
|
71
87
|
if (targetProfileExists && args.model !== undefined) {
|
|
72
88
|
upsertProfiles = {
|
|
73
89
|
[newProfile]: {
|
|
@@ -78,6 +94,7 @@ function editProvider(args) {
|
|
|
78
94
|
updatedFields.push("model");
|
|
79
95
|
}
|
|
80
96
|
}
|
|
97
|
+
// Compute profile link ownership after the edit so lifecycle planning can decide whether sections stay, move, or delete.
|
|
81
98
|
const remainingLinksByProfile = new Map();
|
|
82
99
|
for (const [name, provider] of Object.entries(providers.providers)) {
|
|
83
100
|
if (name === args.providerName) {
|
|
@@ -108,24 +125,35 @@ function editProvider(args) {
|
|
|
108
125
|
files: [
|
|
109
126
|
{ absolutePath: args.providersPath, relativePath: "providers.json" },
|
|
110
127
|
{ absolutePath: args.configPath, relativePath: "config.toml" },
|
|
128
|
+
{ absolutePath: args.authPath, relativePath: "auth.json" },
|
|
111
129
|
],
|
|
112
130
|
mutate: () => {
|
|
113
131
|
const configPlan = (0, config_repo_1.createConfigMutationPlan)(document, {
|
|
114
132
|
upsertProfiles,
|
|
133
|
+
upsertModelProviders,
|
|
115
134
|
deleteProfiles: lifecycle.deletedProfileSections,
|
|
116
135
|
setActiveProfile: lifecycle.nextActiveProfile,
|
|
117
136
|
});
|
|
118
|
-
|
|
137
|
+
const nextProviders = {
|
|
119
138
|
providers: {
|
|
120
139
|
...providers.providers,
|
|
121
140
|
[args.providerName]: nextRecord,
|
|
122
141
|
},
|
|
123
|
-
}
|
|
142
|
+
};
|
|
143
|
+
// Write providers first so the registry and config move together inside the managed backup boundary.
|
|
144
|
+
(0, providers_repo_1.writeProvidersFile)(args.providersPath, nextProviders);
|
|
124
145
|
(0, config_repo_1.applyConfigMutation)(args.configPath, document, configPlan);
|
|
146
|
+
const updatedDocument = (0, config_repo_1.readStructuredConfig)(args.configPath);
|
|
147
|
+
if (updatedDocument.activeProfile) {
|
|
148
|
+
const activeProviderName = (0, config_repo_1.resolveActiveProviderName)(updatedDocument, nextProviders);
|
|
149
|
+
const existingAuth = (0, auth_repo_1.readAuthFileIfExists)(args.authPath);
|
|
150
|
+
(0, auth_repo_1.writeAuthFile)(args.authPath, nextProviders.providers[activeProviderName], existingAuth ?? undefined);
|
|
151
|
+
}
|
|
125
152
|
return {
|
|
126
153
|
provider: args.providerName,
|
|
127
154
|
updatedFields,
|
|
128
155
|
createdProfileSections: configPlan.createdProfileSections,
|
|
156
|
+
createdModelProviderSections: configPlan.createdModelProviderSections,
|
|
129
157
|
deletedProfileSections: configPlan.deletedProfileSections,
|
|
130
158
|
keptSharedProfiles: lifecycle.keptSharedProfiles,
|
|
131
159
|
switchedActiveProfile: lifecycle.switchedActiveProfile,
|
package/dist/app/get-status.js
CHANGED
|
@@ -37,12 +37,14 @@ exports.getStatus = getStatus;
|
|
|
37
37
|
const fs = __importStar(require("node:fs"));
|
|
38
38
|
const config_1 = require("../domain/config");
|
|
39
39
|
const runtime_state_1 = require("../domain/runtime-state");
|
|
40
|
+
const providers_1 = require("../domain/providers");
|
|
40
41
|
const config_repo_1 = require("../storage/config-repo");
|
|
41
42
|
const providers_repo_1 = require("../storage/providers-repo");
|
|
43
|
+
const auth_repo_1 = require("../storage/auth-repo");
|
|
42
44
|
/**
|
|
43
45
|
* Reports the current on-disk runtime state and how it maps back to managed providers.
|
|
44
46
|
*/
|
|
45
|
-
function getStatus(codexDir, configPath, providersPath) {
|
|
47
|
+
function getStatus(codexDir, configPath, providersPath, authPath) {
|
|
46
48
|
const configExists = fs.existsSync(configPath);
|
|
47
49
|
const providersExists = fs.existsSync(providersPath);
|
|
48
50
|
let currentProfile = null;
|
|
@@ -50,6 +52,7 @@ function getStatus(codexDir, configPath, providersPath) {
|
|
|
50
52
|
const providers = providersExists ? (0, providers_repo_1.readProvidersFile)(providersPath) : null;
|
|
51
53
|
let configViews = [];
|
|
52
54
|
let consistencyIssues = [];
|
|
55
|
+
const authState = (0, auth_repo_1.readManagedAuthState)(authPath);
|
|
53
56
|
if (configExists) {
|
|
54
57
|
const document = (0, config_repo_1.readStructuredConfig)(configPath);
|
|
55
58
|
currentProfile = document.activeProfile;
|
|
@@ -60,6 +63,7 @@ function getStatus(codexDir, configPath, providersPath) {
|
|
|
60
63
|
}
|
|
61
64
|
}
|
|
62
65
|
const liveState = (0, runtime_state_1.inspectLiveStateDrift)(currentProfile, providers);
|
|
66
|
+
const activeProviderCandidates = currentProfile && providers ? (0, providers_1.findProvidersByProfile)(providers, currentProfile) : [];
|
|
63
67
|
if (liveState.canBackfillActiveProvider) {
|
|
64
68
|
// Surface unmanaged live state without mutating anything during a read-only status call.
|
|
65
69
|
warnings.push("Current config profile is not mapped in providers.json. Backfill would be required before treating live state as managed.");
|
|
@@ -74,7 +78,10 @@ function getStatus(codexDir, configPath, providersPath) {
|
|
|
74
78
|
currentProfile,
|
|
75
79
|
currentProfileMapped: liveState.profileMapped,
|
|
76
80
|
provider: liveState.mappedProvider,
|
|
81
|
+
activeProviderResolvable: activeProviderCandidates.length === 1,
|
|
82
|
+
activeProviderCandidates,
|
|
77
83
|
liveState,
|
|
84
|
+
auth: authState,
|
|
78
85
|
configProfiles: configViews,
|
|
79
86
|
issues: consistencyIssues,
|
|
80
87
|
},
|
|
@@ -0,0 +1,68 @@
|
|
|
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.initCodex = initCodex;
|
|
37
|
+
const fs = __importStar(require("node:fs"));
|
|
38
|
+
const errors_1 = require("../domain/errors");
|
|
39
|
+
const fs_utils_1 = require("../storage/fs-utils");
|
|
40
|
+
const providers_repo_1 = require("../storage/providers-repo");
|
|
41
|
+
/**
|
|
42
|
+
* Initializes a Codex directory for managed providers.json usage without requiring live Codex state.
|
|
43
|
+
*/
|
|
44
|
+
function initCodex(args) {
|
|
45
|
+
const codexDirExists = fs.existsSync(args.codexDir);
|
|
46
|
+
if (!codexDirExists && !args.createCodexDir) {
|
|
47
|
+
throw (0, errors_1.cliError)("CODEX_DIR_NOT_FOUND", "The requested Codex directory does not exist.", {
|
|
48
|
+
codexDir: args.codexDir,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
if (!codexDirExists) {
|
|
52
|
+
(0, fs_utils_1.ensureDir)(args.codexDir);
|
|
53
|
+
}
|
|
54
|
+
const providersExists = fs.existsSync(args.providersPath);
|
|
55
|
+
if (!providersExists) {
|
|
56
|
+
(0, providers_repo_1.writeProvidersFile)(args.providersPath, { providers: {} });
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
data: {
|
|
60
|
+
codexDir: args.codexDir,
|
|
61
|
+
createdCodexDir: !codexDirExists,
|
|
62
|
+
createdProvidersFile: !providersExists,
|
|
63
|
+
providersAlreadyExisted: providersExists,
|
|
64
|
+
configExists: fs.existsSync(args.configPath),
|
|
65
|
+
authExists: fs.existsSync(args.authPath),
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
}
|
|
@@ -11,6 +11,7 @@ function listProviders(providersPath) {
|
|
|
11
11
|
const items = names.map((name) => ({
|
|
12
12
|
name,
|
|
13
13
|
profile: providers.providers[name].profile,
|
|
14
|
+
envKey: providers.providers[name].envKey,
|
|
14
15
|
note: providers.providers[name].note ?? null,
|
|
15
16
|
tags: providers.providers[name].tags ?? [],
|
|
16
17
|
}));
|
package/dist/app/run-doctor.js
CHANGED
|
@@ -41,6 +41,8 @@ const config_repo_1 = require("../storage/config-repo");
|
|
|
41
41
|
const providers_repo_1 = require("../storage/providers-repo");
|
|
42
42
|
const errors_1 = require("../domain/errors");
|
|
43
43
|
const codex_probe_1 = require("../runtime/codex-probe");
|
|
44
|
+
const auth_repo_1 = require("../storage/auth-repo");
|
|
45
|
+
const providers_1 = require("../domain/providers");
|
|
44
46
|
/**
|
|
45
47
|
* Performs consistency checks across config.toml, providers.json, and the local Codex CLI.
|
|
46
48
|
*/
|
|
@@ -78,6 +80,7 @@ function runDoctor(args) {
|
|
|
78
80
|
try {
|
|
79
81
|
providers = (0, providers_repo_1.readProvidersFile)(args.providersPath);
|
|
80
82
|
if (document) {
|
|
83
|
+
// Preserve domain issue codes while translating them into user-facing diagnostic messages.
|
|
81
84
|
for (const issue of (0, config_1.collectConfigConsistencyIssues)(document, providers)) {
|
|
82
85
|
issues.push({
|
|
83
86
|
...issue,
|
|
@@ -95,6 +98,45 @@ function runDoctor(args) {
|
|
|
95
98
|
});
|
|
96
99
|
}
|
|
97
100
|
}
|
|
101
|
+
const authState = (0, auth_repo_1.readManagedAuthState)(args.authPath);
|
|
102
|
+
if (authState.exists && !authState.valid) {
|
|
103
|
+
issues.push({
|
|
104
|
+
code: "AUTH_JSON_INVALID",
|
|
105
|
+
message: authState.parseError ?? "auth.json is invalid.",
|
|
106
|
+
file: args.authPath,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
if (document?.activeProfile && providers) {
|
|
110
|
+
const matches = (0, providers_1.findProvidersByProfile)(providers, document.activeProfile);
|
|
111
|
+
if (matches.length === 1) {
|
|
112
|
+
const activeProvider = providers.providers[matches[0]];
|
|
113
|
+
const payload = authState.payload ?? {};
|
|
114
|
+
const actualKeys = authState.managedSecretKeys;
|
|
115
|
+
if (authState.authMode !== null && authState.authMode !== "apikey") {
|
|
116
|
+
issues.push({
|
|
117
|
+
code: "AUTH_JSON_INVALID",
|
|
118
|
+
message: `auth.json auth_mode must be "apikey", found "${authState.authMode}".`,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
if (!actualKeys.includes(activeProvider.envKey) || actualKeys.length !== 1) {
|
|
122
|
+
issues.push({
|
|
123
|
+
code: "AUTH_JSON_ENV_KEY_MISMATCH",
|
|
124
|
+
message: `auth.json managed env key does not match active provider "${matches[0]}".`,
|
|
125
|
+
provider: matches[0],
|
|
126
|
+
expectedEnvKey: activeProvider.envKey,
|
|
127
|
+
actualEnvKeys: actualKeys,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
if (payload[activeProvider.envKey] !== activeProvider.apiKey) {
|
|
131
|
+
issues.push({
|
|
132
|
+
code: "AUTH_JSON_APIKEY_MISMATCH",
|
|
133
|
+
message: `auth.json secret value does not match active provider "${matches[0]}".`,
|
|
134
|
+
provider: matches[0],
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
// Drift inspection still runs when files are missing so status output can explain partial state.
|
|
98
140
|
const drift = (0, runtime_state_1.inspectLiveStateDrift)(currentProfile, providers);
|
|
99
141
|
const codexCheck = (0, codex_probe_1.probeCodexRuntime)();
|
|
100
142
|
if (!codexCheck.ok) {
|
|
@@ -120,10 +162,14 @@ function runDoctor(args) {
|
|
|
120
162
|
codexDir: args.codexDir,
|
|
121
163
|
storage: (0, runtime_state_1.getStorageRoles)(),
|
|
122
164
|
liveState: drift,
|
|
165
|
+
auth: authState,
|
|
123
166
|
},
|
|
124
167
|
warnings: issues.length === 0 ? [] : [`doctor found ${issues.length} issue(s)`],
|
|
125
168
|
};
|
|
126
169
|
}
|
|
170
|
+
/**
|
|
171
|
+
* Maps structured config consistency issues onto stable human-readable diagnostic text.
|
|
172
|
+
*/
|
|
127
173
|
function renderConfigIssueMessage(issue) {
|
|
128
174
|
switch (issue.code) {
|
|
129
175
|
case "ORPHANED_PROFILE_REFERENCE":
|
|
@@ -142,7 +188,21 @@ function renderConfigIssueMessage(issue) {
|
|
|
142
188
|
return `Model provider section "${issue.modelProvider}" for profile "${issue.profile}" is missing from config.toml.`;
|
|
143
189
|
case "MODEL_PROVIDER_BASE_URL_MISSING":
|
|
144
190
|
return `Model provider section "${issue.modelProvider}" for profile "${issue.profile}" is missing base_url.`;
|
|
191
|
+
case "MODEL_PROVIDER_ENV_KEY_MISSING":
|
|
192
|
+
return `Model provider section "${issue.modelProvider}" for profile "${issue.profile}" is missing env_key.`;
|
|
193
|
+
case "PROVIDER_ENV_KEY_MISMATCH":
|
|
194
|
+
return `Provider "${issue.provider}" envKey does not match runtime env_key for profile "${issue.profile}".`;
|
|
195
|
+
case "ACTIVE_PROVIDER_UNRESOLVED":
|
|
196
|
+
return `Active profile "${issue.profile}" maps to multiple providers and cannot determine the current auth mirror owner.`;
|
|
197
|
+
case "AUTH_JSON_INVALID":
|
|
198
|
+
return String(issue.message ?? issue.reason ?? "auth.json is invalid.");
|
|
199
|
+
case "AUTH_JSON_ENV_KEY_MISMATCH":
|
|
200
|
+
return `auth.json managed env key does not match provider "${String(issue.provider ?? "")}".`;
|
|
201
|
+
case "AUTH_JSON_APIKEY_MISMATCH":
|
|
202
|
+
return `auth.json secret does not match provider "${String(issue.provider ?? "")}".`;
|
|
145
203
|
case "DESTRUCTIVE_REMOVE_BLOCKED":
|
|
146
204
|
return `Provider "${issue.provider}" cannot be removed while "${issue.activeProfile}" remains active.`;
|
|
205
|
+
default:
|
|
206
|
+
return String(issue.code ?? "UNKNOWN_ISSUE");
|
|
147
207
|
}
|
|
148
208
|
}
|
package/dist/app/setup-codex.js
CHANGED
|
@@ -33,7 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.
|
|
36
|
+
exports.migrateCodex = migrateCodex;
|
|
37
37
|
const fs = __importStar(require("node:fs"));
|
|
38
38
|
const setup_1 = require("../domain/setup");
|
|
39
39
|
const errors_1 = require("../domain/errors");
|
|
@@ -42,13 +42,14 @@ const codex_cli_1 = require("../runtime/codex-cli");
|
|
|
42
42
|
const config_repo_1 = require("../storage/config-repo");
|
|
43
43
|
const fs_utils_1 = require("../storage/fs-utils");
|
|
44
44
|
const providers_repo_1 = require("../storage/providers-repo");
|
|
45
|
+
const auth_repo_1 = require("../storage/auth-repo");
|
|
45
46
|
const run_doctor_1 = require("./run-doctor");
|
|
46
47
|
const run_mutation_1 = require("./run-mutation");
|
|
47
48
|
const MIN_CODEX_VERSION = "0.0.1";
|
|
48
49
|
/**
|
|
49
|
-
*
|
|
50
|
+
* Migrates unmanaged Codex config profiles into a managed providers.json registry.
|
|
50
51
|
*/
|
|
51
|
-
function
|
|
52
|
+
function migrateCodex(args) {
|
|
52
53
|
const available = (0, codex_cli_1.checkCodexAvailable)();
|
|
53
54
|
if (!available.ok) {
|
|
54
55
|
throw (0, errors_1.cliError)("CODEX_NOT_INSTALLED", "codex CLI is not available.", {
|
|
@@ -70,8 +71,9 @@ function setupCodex(args) {
|
|
|
70
71
|
}
|
|
71
72
|
const document = (0, config_repo_1.readStructuredConfig)(args.configPath);
|
|
72
73
|
const profileViews = (0, config_1.buildManagedProfileViews)(document, null);
|
|
74
|
+
// Migrate can only adopt unmanaged profiles that already contain enough runtime data to become managed.
|
|
73
75
|
const adoptableProfiles = profileViews
|
|
74
|
-
.filter((view) => view.source === "unmanaged" && view.model && view.modelProvider === view.name && view.baseUrl)
|
|
76
|
+
.filter((view) => view.source === "unmanaged" && view.model && view.modelProvider === view.name && view.baseUrl && view.envKey)
|
|
75
77
|
.map((view) => view.name)
|
|
76
78
|
.sort();
|
|
77
79
|
if (profileViews.length === 0) {
|
|
@@ -81,20 +83,20 @@ function setupCodex(args) {
|
|
|
81
83
|
}
|
|
82
84
|
const invalidAdoptProfiles = args.adoptProfiles.filter((profile) => !adoptableProfiles.includes(profile));
|
|
83
85
|
if (invalidAdoptProfiles.length > 0) {
|
|
84
|
-
throw (0, errors_1.cliError)("INVALID_ARGUMENT", "
|
|
86
|
+
throw (0, errors_1.cliError)("INVALID_ARGUMENT", "migrate only adopts unmanaged profiles that already contain model, model_provider, and matching model_providers base_url/env_key.", {
|
|
85
87
|
invalidProfiles: invalidAdoptProfiles.sort(),
|
|
86
88
|
adoptableProfiles,
|
|
87
89
|
});
|
|
88
90
|
}
|
|
89
91
|
if (args.adoptProfiles.length === 0) {
|
|
90
|
-
throw (0, errors_1.cliError)("INVALID_ARGUMENT", "
|
|
92
|
+
throw (0, errors_1.cliError)("INVALID_ARGUMENT", "migrate requires at least one explicit profile to adopt.", {
|
|
91
93
|
adoptableProfiles,
|
|
92
94
|
});
|
|
93
95
|
}
|
|
94
96
|
const drafts = (0, setup_1.buildSetupDrafts)(args.adoptProfiles, args.providerDetailsByProfile);
|
|
95
97
|
const incompleteProfiles = (0, setup_1.findIncompleteSetupProfiles)(drafts);
|
|
96
98
|
if (incompleteProfiles.length > 0) {
|
|
97
|
-
throw (0, errors_1.cliError)("INVALID_ARGUMENT", "
|
|
99
|
+
throw (0, errors_1.cliError)("INVALID_ARGUMENT", "migrate requires complete provider data for every selected profile.", {
|
|
98
100
|
incompleteProfiles,
|
|
99
101
|
});
|
|
100
102
|
}
|
|
@@ -117,15 +119,20 @@ function setupCodex(args) {
|
|
|
117
119
|
codexDir: args.codexDir,
|
|
118
120
|
backupsDir: args.backupsDir,
|
|
119
121
|
latestBackupPath: args.latestBackupPath,
|
|
120
|
-
operation: "
|
|
122
|
+
operation: "migrate",
|
|
121
123
|
files: [
|
|
122
124
|
{ absolutePath: args.providersPath, relativePath: "providers.json" },
|
|
123
125
|
{ absolutePath: args.configPath, relativePath: "config.toml" },
|
|
126
|
+
{ absolutePath: args.authPath, relativePath: "auth.json" },
|
|
124
127
|
],
|
|
125
128
|
mutate: () => {
|
|
129
|
+
// migrate currently preserves config structure and only asserts that the file remains writable inside the mutation flow.
|
|
126
130
|
const configPlan = (0, config_repo_1.createConfigMutationPlan)(document, {});
|
|
127
131
|
(0, providers_repo_1.writeProvidersFile)(args.providersPath, finalProviders);
|
|
128
132
|
(0, config_repo_1.applyConfigMutation)(args.configPath, document, configPlan);
|
|
133
|
+
const activeProviderName = (0, config_repo_1.resolveActiveProviderName)(document, finalProviders);
|
|
134
|
+
const existingAuth = (0, auth_repo_1.readAuthFileIfExists)(args.authPath);
|
|
135
|
+
(0, auth_repo_1.writeAuthFile)(args.authPath, finalProviders.providers[activeProviderName], existingAuth ?? undefined);
|
|
129
136
|
return {
|
|
130
137
|
codexDir: args.codexDir,
|
|
131
138
|
strategy: args.strategy,
|
|
@@ -140,10 +147,12 @@ function setupCodex(args) {
|
|
|
140
147
|
};
|
|
141
148
|
},
|
|
142
149
|
});
|
|
150
|
+
// Re-run doctor on the final state so migrate returns immediate post-migration diagnostics.
|
|
143
151
|
const doctor = (0, run_doctor_1.runDoctor)({
|
|
144
152
|
codexDir: args.codexDir,
|
|
145
153
|
configPath: args.configPath,
|
|
146
154
|
providersPath: args.providersPath,
|
|
155
|
+
authPath: args.authPath,
|
|
147
156
|
});
|
|
148
157
|
return {
|
|
149
158
|
data: {
|
package/dist/app/show-config.js
CHANGED
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.showConfig = showConfig;
|
|
4
4
|
const config_1 = require("../domain/config");
|
|
5
5
|
const errors_1 = require("../domain/errors");
|
|
6
|
+
const providers_1 = require("../domain/providers");
|
|
6
7
|
const config_repo_1 = require("../storage/config-repo");
|
|
7
8
|
const providers_repo_1 = require("../storage/providers-repo");
|
|
8
9
|
/**
|
|
@@ -28,7 +29,14 @@ function showConfig(args) {
|
|
|
28
29
|
data: {
|
|
29
30
|
activeProfile: document.activeProfile,
|
|
30
31
|
selectedProfile,
|
|
31
|
-
profiles
|
|
32
|
+
profiles: profiles.map((profile) => ({
|
|
33
|
+
...profile,
|
|
34
|
+
managedProviderEnvKeys: (0, providers_1.findProvidersByProfile)(providers, profile.name).map((providerName) => ({
|
|
35
|
+
providerName,
|
|
36
|
+
envKey: providers.providers[providerName].envKey,
|
|
37
|
+
matchesRuntime: providers.providers[providerName].envKey === profile.envKey,
|
|
38
|
+
})),
|
|
39
|
+
})),
|
|
32
40
|
},
|
|
33
41
|
};
|
|
34
42
|
}
|
|
@@ -4,10 +4,10 @@ exports.switchProvider = switchProvider;
|
|
|
4
4
|
const errors_1 = require("../domain/errors");
|
|
5
5
|
const config_repo_1 = require("../storage/config-repo");
|
|
6
6
|
const providers_repo_1 = require("../storage/providers-repo");
|
|
7
|
+
const auth_repo_1 = require("../storage/auth-repo");
|
|
7
8
|
const run_mutation_1 = require("./run-mutation");
|
|
8
|
-
const codex_cli_1 = require("../runtime/codex-cli");
|
|
9
9
|
/**
|
|
10
|
-
* Switches the active Codex profile and
|
|
10
|
+
* Switches the active Codex profile and rewrites auth.json for the target provider.
|
|
11
11
|
*/
|
|
12
12
|
function switchProvider(args) {
|
|
13
13
|
const providers = (0, providers_repo_1.readProvidersFile)(args.providersPath);
|
|
@@ -18,6 +18,15 @@ function switchProvider(args) {
|
|
|
18
18
|
});
|
|
19
19
|
}
|
|
20
20
|
const document = (0, config_repo_1.ensureProfileExists)(args.configPath, provider.profile, args.providerName);
|
|
21
|
+
const envKey = (0, config_repo_1.requireRuntimeEnvKey)(document, provider.profile);
|
|
22
|
+
if (provider.envKey !== envKey) {
|
|
23
|
+
throw (0, errors_1.cliError)("PROVIDER_ENV_KEY_MISMATCH", `Provider "${args.providerName}" envKey does not match runtime env_key.`, {
|
|
24
|
+
provider: args.providerName,
|
|
25
|
+
profile: provider.profile,
|
|
26
|
+
providerEnvKey: provider.envKey,
|
|
27
|
+
runtimeEnvKey: envKey,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
21
30
|
return (0, run_mutation_1.runMutation)({
|
|
22
31
|
codexDir: args.codexDir,
|
|
23
32
|
backupsDir: args.backupsDir,
|
|
@@ -31,15 +40,13 @@ function switchProvider(args) {
|
|
|
31
40
|
const configPlan = (0, config_repo_1.createConfigMutationPlan)(document, {
|
|
32
41
|
setActiveProfile: provider.profile,
|
|
33
42
|
});
|
|
34
|
-
// Update the runtime profile first so any subsequent login is associated with the new target.
|
|
35
43
|
(0, config_repo_1.applyConfigMutation)(args.configPath, document, configPlan);
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
44
|
+
const existingAuth = (0, auth_repo_1.readAuthFileIfExists)(args.authPath);
|
|
45
|
+
(0, auth_repo_1.writeAuthFile)(args.authPath, provider, existingAuth ?? undefined);
|
|
39
46
|
return {
|
|
40
47
|
provider: args.providerName,
|
|
41
48
|
profile: provider.profile,
|
|
42
|
-
|
|
49
|
+
envKey: provider.envKey,
|
|
43
50
|
};
|
|
44
51
|
},
|
|
45
52
|
});
|