@minniexcode/codex-switch 0.0.4 → 0.0.5
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/dist/app/add-provider.js +32 -1
- package/dist/app/edit-provider.js +74 -1
- package/dist/app/get-status.js +9 -2
- package/dist/app/import-providers.js +37 -1
- package/dist/app/list-config-profiles.js +29 -0
- package/dist/app/remove-provider.js +34 -2
- package/dist/app/run-doctor.js +22 -21
- package/dist/app/setup-codex.js +33 -16
- package/dist/app/show-config.js +34 -0
- package/dist/app/switch-provider.js +5 -2
- package/dist/cli/args.js +13 -2
- package/dist/cli/help.js +42 -5
- package/dist/cli/interactive.js +56 -0
- package/dist/cli/output.js +22 -0
- package/dist/cli.js +101 -12
- package/dist/domain/config.js +471 -39
- package/dist/infra/codex-cli.js +18 -3
- package/dist/infra/codex-discovery.js +3 -41
- package/dist/infra/codex-paths.js +1 -1
- package/dist/infra/config-repo.js +102 -9
- package/docs/Design/codex-switch-v0.0.5-design.md +922 -0
- package/docs/PRD/codex-switch-prd-v0.0.5-to-v0.1.0.md +308 -0
- package/docs/PRD/codex-switch-prd-v0.1.0.md +210 -260
- package/package.json +1 -1
- /package/docs/{codex-switch-v0.0.4-design.md → Design/codex-switch-v0.0.4-design.md} +0 -0
package/dist/app/add-provider.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.addProvider = addProvider;
|
|
4
|
+
const config_1 = require("../domain/config");
|
|
4
5
|
const providers_1 = require("../domain/providers");
|
|
5
6
|
const errors_1 = require("../domain/errors");
|
|
7
|
+
const config_repo_1 = require("../infra/config-repo");
|
|
6
8
|
const fs_utils_1 = require("../infra/fs-utils");
|
|
7
9
|
const providers_repo_1 = require("../infra/providers-repo");
|
|
8
10
|
const run_mutation_1 = require("./run-mutation");
|
|
@@ -15,6 +17,22 @@ function addProvider(args) {
|
|
|
15
17
|
if (providers.providers[args.providerName]) {
|
|
16
18
|
throw (0, errors_1.cliError)("INVALID_IMPORT_FILE", `Provider "${args.providerName}" already exists.`);
|
|
17
19
|
}
|
|
20
|
+
const document = (0, config_repo_1.readStructuredConfig)(args.configPath);
|
|
21
|
+
const existingProfile = document.profiles.find((profile) => profile.name === args.profile);
|
|
22
|
+
if (!existingProfile && !args.createProfile) {
|
|
23
|
+
throw (0, errors_1.cliError)("PROFILE_NOT_FOUND", `Profile "${args.profile}" does not exist in config.toml.`, {
|
|
24
|
+
profile: args.profile,
|
|
25
|
+
provider: args.providerName,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
const upsertProfiles = !existingProfile && args.createProfile
|
|
29
|
+
? {
|
|
30
|
+
[args.profile]: (0, config_1.validateManagedProfileCreation)(args.profile, {
|
|
31
|
+
model: args.model ?? undefined,
|
|
32
|
+
baseUrl: args.baseUrl ?? undefined,
|
|
33
|
+
}),
|
|
34
|
+
}
|
|
35
|
+
: undefined;
|
|
18
36
|
const next = {
|
|
19
37
|
providers: {
|
|
20
38
|
...providers.providers,
|
|
@@ -32,13 +50,26 @@ function addProvider(args) {
|
|
|
32
50
|
backupsDir: args.backupsDir,
|
|
33
51
|
latestBackupPath: args.latestBackupPath,
|
|
34
52
|
operation: "add",
|
|
35
|
-
files: [
|
|
53
|
+
files: [
|
|
54
|
+
{ absolutePath: args.providersPath, relativePath: "providers.json" },
|
|
55
|
+
{ absolutePath: args.configPath, relativePath: "config.toml" },
|
|
56
|
+
],
|
|
36
57
|
mutate: () => {
|
|
58
|
+
const configPlan = (0, config_repo_1.createConfigMutationPlan)(document, {
|
|
59
|
+
upsertProfiles,
|
|
60
|
+
});
|
|
37
61
|
// Persist only the normalized provider payload so later reads are deterministic.
|
|
38
62
|
(0, providers_repo_1.writeProvidersFile)(args.providersPath, next);
|
|
63
|
+
(0, config_repo_1.applyConfigMutation)(args.configPath, document, configPlan);
|
|
39
64
|
return {
|
|
40
65
|
provider: args.providerName,
|
|
41
66
|
profile: args.profile,
|
|
67
|
+
createdProfileSections: configPlan.createdProfileSections,
|
|
68
|
+
deletedProfileSections: configPlan.deletedProfileSections,
|
|
69
|
+
keptSharedProfiles: [],
|
|
70
|
+
switchedActiveProfile: configPlan.switchedActiveProfile,
|
|
71
|
+
adoptedProfiles: [],
|
|
72
|
+
repairedProfiles: [],
|
|
42
73
|
};
|
|
43
74
|
},
|
|
44
75
|
});
|
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.editProvider = editProvider;
|
|
4
4
|
const errors_1 = require("../domain/errors");
|
|
5
|
+
const config_1 = require("../domain/config");
|
|
5
6
|
const providers_1 = require("../domain/providers");
|
|
7
|
+
const config_repo_1 = require("../infra/config-repo");
|
|
6
8
|
const fs_utils_1 = require("../infra/fs-utils");
|
|
7
9
|
const providers_repo_1 = require("../infra/providers-repo");
|
|
8
10
|
const run_mutation_1 = require("./run-mutation");
|
|
@@ -12,6 +14,7 @@ const run_mutation_1 = require("./run-mutation");
|
|
|
12
14
|
function editProvider(args) {
|
|
13
15
|
(0, fs_utils_1.ensureDir)(args.codexDir);
|
|
14
16
|
const providers = (0, providers_repo_1.readProvidersFile)(args.providersPath);
|
|
17
|
+
const document = (0, config_repo_1.readStructuredConfig)(args.configPath);
|
|
15
18
|
const current = providers.providers[args.providerName];
|
|
16
19
|
if (!current) {
|
|
17
20
|
throw (0, errors_1.cliError)("PROVIDER_NOT_FOUND", `Provider "${args.providerName}" was not found.`, {
|
|
@@ -42,22 +45,92 @@ function editProvider(args) {
|
|
|
42
45
|
if (args.tags !== undefined) {
|
|
43
46
|
updatedFields.push("tags");
|
|
44
47
|
}
|
|
48
|
+
const oldProfile = current.profile;
|
|
49
|
+
const newProfile = nextRecord.profile;
|
|
50
|
+
const targetSection = document.profiles.find((profile) => profile.name === newProfile) ?? null;
|
|
51
|
+
const targetProfileExists = Boolean(targetSection);
|
|
52
|
+
let upsertProfiles;
|
|
53
|
+
if (!targetProfileExists) {
|
|
54
|
+
if (!args.createProfile) {
|
|
55
|
+
throw (0, errors_1.cliError)("PROFILE_NOT_FOUND", `Profile "${newProfile}" does not exist in config.toml.`, {
|
|
56
|
+
profile: newProfile,
|
|
57
|
+
provider: args.providerName,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
upsertProfiles = {
|
|
61
|
+
[newProfile]: (0, config_1.validateManagedProfileCreation)(newProfile, {
|
|
62
|
+
model: args.model ?? undefined,
|
|
63
|
+
baseUrl: args.baseUrl ?? undefined,
|
|
64
|
+
}),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
else if (args.model !== undefined || args.baseUrl !== undefined) {
|
|
68
|
+
upsertProfiles = {
|
|
69
|
+
[newProfile]: {
|
|
70
|
+
...(args.model !== undefined && args.model !== null ? { model: args.model } : {}),
|
|
71
|
+
...(args.baseUrl !== undefined && args.baseUrl !== null ? { baseUrl: args.baseUrl } : {}),
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
if (args.model !== undefined && (targetSection?.model !== args.model) && !updatedFields.includes("model")) {
|
|
75
|
+
updatedFields.push("model");
|
|
76
|
+
}
|
|
77
|
+
if (args.baseUrl !== undefined && targetSection?.baseUrl !== args.baseUrl && !updatedFields.includes("baseUrl")) {
|
|
78
|
+
updatedFields.push("baseUrl");
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
const remainingLinksByProfile = new Map();
|
|
82
|
+
for (const [name, provider] of Object.entries(providers.providers)) {
|
|
83
|
+
if (name === args.providerName) {
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
const list = remainingLinksByProfile.get(provider.profile) ?? [];
|
|
87
|
+
list.push(name);
|
|
88
|
+
remainingLinksByProfile.set(provider.profile, list);
|
|
89
|
+
}
|
|
90
|
+
if (newProfile !== oldProfile) {
|
|
91
|
+
const list = remainingLinksByProfile.get(newProfile) ?? [];
|
|
92
|
+
list.push(args.providerName);
|
|
93
|
+
remainingLinksByProfile.set(newProfile, list);
|
|
94
|
+
}
|
|
95
|
+
const lifecycle = (0, config_1.planProfileLifecycleOutcome)({
|
|
96
|
+
providerName: args.providerName,
|
|
97
|
+
oldProfile,
|
|
98
|
+
newProfile,
|
|
99
|
+
activeProfile: document.activeProfile,
|
|
100
|
+
remainingLinksByProfile,
|
|
101
|
+
switchToProfile: args.switchToProfile ?? null,
|
|
102
|
+
});
|
|
45
103
|
return (0, run_mutation_1.runMutation)({
|
|
46
104
|
codexDir: args.codexDir,
|
|
47
105
|
backupsDir: args.backupsDir,
|
|
48
106
|
latestBackupPath: args.latestBackupPath,
|
|
49
107
|
operation: "edit",
|
|
50
|
-
files: [
|
|
108
|
+
files: [
|
|
109
|
+
{ absolutePath: args.providersPath, relativePath: "providers.json" },
|
|
110
|
+
{ absolutePath: args.configPath, relativePath: "config.toml" },
|
|
111
|
+
],
|
|
51
112
|
mutate: () => {
|
|
113
|
+
const configPlan = (0, config_repo_1.createConfigMutationPlan)(document, {
|
|
114
|
+
upsertProfiles,
|
|
115
|
+
deleteProfiles: lifecycle.deletedProfileSections,
|
|
116
|
+
setActiveProfile: lifecycle.nextActiveProfile,
|
|
117
|
+
});
|
|
52
118
|
(0, providers_repo_1.writeProvidersFile)(args.providersPath, {
|
|
53
119
|
providers: {
|
|
54
120
|
...providers.providers,
|
|
55
121
|
[args.providerName]: nextRecord,
|
|
56
122
|
},
|
|
57
123
|
});
|
|
124
|
+
(0, config_repo_1.applyConfigMutation)(args.configPath, document, configPlan);
|
|
58
125
|
return {
|
|
59
126
|
provider: args.providerName,
|
|
60
127
|
updatedFields,
|
|
128
|
+
createdProfileSections: configPlan.createdProfileSections,
|
|
129
|
+
deletedProfileSections: configPlan.deletedProfileSections,
|
|
130
|
+
keptSharedProfiles: lifecycle.keptSharedProfiles,
|
|
131
|
+
switchedActiveProfile: lifecycle.switchedActiveProfile,
|
|
132
|
+
adoptedProfiles: [],
|
|
133
|
+
repairedProfiles: [],
|
|
61
134
|
};
|
|
62
135
|
},
|
|
63
136
|
});
|
package/dist/app/get-status.js
CHANGED
|
@@ -37,6 +37,7 @@ 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 config_repo_1 = require("../infra/config-repo");
|
|
40
41
|
const providers_repo_1 = require("../infra/providers-repo");
|
|
41
42
|
/**
|
|
42
43
|
* Reports the current on-disk runtime state and how it maps back to managed providers.
|
|
@@ -47,9 +48,13 @@ function getStatus(codexDir, configPath, providersPath) {
|
|
|
47
48
|
let currentProfile = null;
|
|
48
49
|
const warnings = [];
|
|
49
50
|
const providers = providersExists ? (0, providers_repo_1.readProvidersFile)(providersPath) : null;
|
|
51
|
+
let configViews = [];
|
|
52
|
+
let consistencyIssues = [];
|
|
50
53
|
if (configExists) {
|
|
51
|
-
const
|
|
52
|
-
currentProfile =
|
|
54
|
+
const document = (0, config_repo_1.readStructuredConfig)(configPath);
|
|
55
|
+
currentProfile = document.activeProfile;
|
|
56
|
+
configViews = (0, config_1.buildManagedProfileViews)(document, providers);
|
|
57
|
+
consistencyIssues = (0, config_1.collectConfigConsistencyIssues)(document, providers);
|
|
53
58
|
if (!currentProfile) {
|
|
54
59
|
warnings.push("config.toml exists but has no top-level profile.");
|
|
55
60
|
}
|
|
@@ -70,6 +75,8 @@ function getStatus(codexDir, configPath, providersPath) {
|
|
|
70
75
|
currentProfileMapped: liveState.profileMapped,
|
|
71
76
|
provider: liveState.mappedProvider,
|
|
72
77
|
liveState,
|
|
78
|
+
configProfiles: configViews,
|
|
79
|
+
issues: consistencyIssues,
|
|
73
80
|
},
|
|
74
81
|
};
|
|
75
82
|
}
|
|
@@ -36,8 +36,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
exports.importProviders = importProviders;
|
|
37
37
|
const fs = __importStar(require("node:fs"));
|
|
38
38
|
const path = __importStar(require("node:path"));
|
|
39
|
+
const config_1 = require("../domain/config");
|
|
39
40
|
const providers_1 = require("../domain/providers");
|
|
40
41
|
const errors_1 = require("../domain/errors");
|
|
42
|
+
const config_repo_1 = require("../infra/config-repo");
|
|
41
43
|
const fs_utils_1 = require("../infra/fs-utils");
|
|
42
44
|
const providers_repo_1 = require("../infra/providers-repo");
|
|
43
45
|
const run_mutation_1 = require("./run-mutation");
|
|
@@ -58,16 +60,44 @@ function importProviders(args) {
|
|
|
58
60
|
});
|
|
59
61
|
}
|
|
60
62
|
(0, fs_utils_1.ensureDir)(args.codexDir);
|
|
63
|
+
const document = (0, config_repo_1.readStructuredConfig)(args.configPath);
|
|
61
64
|
return (0, run_mutation_1.runMutation)({
|
|
62
65
|
codexDir: args.codexDir,
|
|
63
66
|
backupsDir: args.backupsDir,
|
|
64
67
|
latestBackupPath: args.latestBackupPath,
|
|
65
68
|
operation: "import",
|
|
66
|
-
files: [
|
|
69
|
+
files: [
|
|
70
|
+
{ absolutePath: args.providersPath, relativePath: "providers.json" },
|
|
71
|
+
{ absolutePath: args.configPath, relativePath: "config.toml" },
|
|
72
|
+
],
|
|
67
73
|
mutate: () => {
|
|
68
74
|
const current = (0, providers_repo_1.readProvidersFileIfExists)(args.providersPath);
|
|
69
75
|
const next = args.merge ? (0, providers_repo_1.mergeProviders)(current, imported) : imported;
|
|
76
|
+
const currentViews = (0, config_1.buildManagedProfileViews)(document, current);
|
|
77
|
+
const nextViews = (0, config_1.buildManagedProfileViews)(document, next);
|
|
78
|
+
const adoptedProfiles = currentViews
|
|
79
|
+
.filter((view) => view.source === "unmanaged" && view.linkedProviders.length === 0)
|
|
80
|
+
.filter((view) => nextViews.some((nextView) => nextView.name === view.name && nextView.managed))
|
|
81
|
+
.map((view) => view.name)
|
|
82
|
+
.sort();
|
|
83
|
+
const missingViews = nextViews.filter((view) => view.source === "orphaned-reference");
|
|
84
|
+
const repairedProfiles = [];
|
|
85
|
+
const upsertProfiles = missingViews.reduce((accumulator, view) => {
|
|
86
|
+
const repair = args.repairProfiles?.[view.name];
|
|
87
|
+
if (!repair) {
|
|
88
|
+
throw (0, errors_1.cliError)("MANAGED_PROFILE_FIELDS_MISSING", "Import would create provider references to missing config profiles that need model and base_url.", {
|
|
89
|
+
profilesNeedingRepair: missingViews.map((entry) => entry.name).sort(),
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
accumulator[view.name] = (0, config_1.validateManagedProfileCreation)(view.name, repair);
|
|
93
|
+
repairedProfiles.push(view.name);
|
|
94
|
+
return accumulator;
|
|
95
|
+
}, {});
|
|
96
|
+
const configPlan = (0, config_repo_1.createConfigMutationPlan)(document, {
|
|
97
|
+
upsertProfiles,
|
|
98
|
+
});
|
|
70
99
|
(0, providers_repo_1.writeProvidersFile)(args.providersPath, next);
|
|
100
|
+
(0, config_repo_1.applyConfigMutation)(args.configPath, document, configPlan);
|
|
71
101
|
const replacedProviders = args.merge
|
|
72
102
|
? Object.keys(imported.providers).filter((name) => current.providers[name]).sort()
|
|
73
103
|
: [];
|
|
@@ -77,6 +107,12 @@ function importProviders(args) {
|
|
|
77
107
|
importedCount: Object.keys(imported.providers).length,
|
|
78
108
|
mergedCount: Object.keys(next.providers).length,
|
|
79
109
|
replacedProviders,
|
|
110
|
+
createdProfileSections: configPlan.createdProfileSections,
|
|
111
|
+
deletedProfileSections: [],
|
|
112
|
+
keptSharedProfiles: nextViews.filter((view) => view.linkedProviders.length > 1).map((view) => view.name),
|
|
113
|
+
switchedActiveProfile: false,
|
|
114
|
+
adoptedProfiles,
|
|
115
|
+
repairedProfiles: repairedProfiles.sort(),
|
|
80
116
|
};
|
|
81
117
|
},
|
|
82
118
|
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.listConfigProfilesView = listConfigProfilesView;
|
|
4
|
+
const config_1 = require("../domain/config");
|
|
5
|
+
const config_repo_1 = require("../infra/config-repo");
|
|
6
|
+
const providers_repo_1 = require("../infra/providers-repo");
|
|
7
|
+
/**
|
|
8
|
+
* Returns the lightweight config profile listing.
|
|
9
|
+
*/
|
|
10
|
+
function listConfigProfilesView(args) {
|
|
11
|
+
const document = (0, config_repo_1.readStructuredConfig)(args.configPath);
|
|
12
|
+
const providers = (0, providers_repo_1.readProvidersFileIfExists)(args.providersPath);
|
|
13
|
+
const profiles = (0, config_1.buildManagedProfileViews)(document, providers).map((profile) => ({
|
|
14
|
+
name: profile.name,
|
|
15
|
+
managed: profile.managed,
|
|
16
|
+
isActive: profile.isActive,
|
|
17
|
+
linkedProviders: profile.linkedProviders,
|
|
18
|
+
model: profile.model,
|
|
19
|
+
baseUrl: profile.baseUrl,
|
|
20
|
+
source: profile.source,
|
|
21
|
+
}));
|
|
22
|
+
return {
|
|
23
|
+
data: {
|
|
24
|
+
activeProfile: document.activeProfile,
|
|
25
|
+
profiles,
|
|
26
|
+
count: profiles.length,
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
}
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.removeProvider = removeProvider;
|
|
4
4
|
const errors_1 = require("../domain/errors");
|
|
5
|
+
const config_1 = require("../domain/config");
|
|
6
|
+
const config_repo_1 = require("../infra/config-repo");
|
|
5
7
|
const providers_repo_1 = require("../infra/providers-repo");
|
|
6
8
|
const run_mutation_1 = require("./run-mutation");
|
|
7
9
|
/**
|
|
@@ -9,22 +11,52 @@ const run_mutation_1 = require("./run-mutation");
|
|
|
9
11
|
*/
|
|
10
12
|
function removeProvider(args) {
|
|
11
13
|
const providers = (0, providers_repo_1.readProvidersFile)(args.providersPath);
|
|
12
|
-
|
|
14
|
+
const document = (0, config_repo_1.readStructuredConfig)(args.configPath);
|
|
15
|
+
const current = providers.providers[args.providerName];
|
|
16
|
+
if (!current) {
|
|
13
17
|
throw (0, errors_1.cliError)("PROVIDER_NOT_FOUND", `Provider "${args.providerName}" was not found.`);
|
|
14
18
|
}
|
|
15
19
|
const nextProviders = { ...providers.providers };
|
|
16
20
|
// Delete against a copied object so the original parsed state stays untouched.
|
|
17
21
|
delete nextProviders[args.providerName];
|
|
22
|
+
const remainingLinksByProfile = new Map();
|
|
23
|
+
for (const [name, provider] of Object.entries(nextProviders)) {
|
|
24
|
+
const list = remainingLinksByProfile.get(provider.profile) ?? [];
|
|
25
|
+
list.push(name);
|
|
26
|
+
remainingLinksByProfile.set(provider.profile, list);
|
|
27
|
+
}
|
|
28
|
+
const lifecycle = (0, config_1.planProfileLifecycleOutcome)({
|
|
29
|
+
providerName: args.providerName,
|
|
30
|
+
oldProfile: current.profile,
|
|
31
|
+
newProfile: null,
|
|
32
|
+
activeProfile: document.activeProfile,
|
|
33
|
+
remainingLinksByProfile,
|
|
34
|
+
switchToProfile: args.switchToProfile ?? null,
|
|
35
|
+
});
|
|
18
36
|
return (0, run_mutation_1.runMutation)({
|
|
19
37
|
codexDir: args.codexDir,
|
|
20
38
|
backupsDir: args.backupsDir,
|
|
21
39
|
latestBackupPath: args.latestBackupPath,
|
|
22
40
|
operation: "remove",
|
|
23
|
-
files: [
|
|
41
|
+
files: [
|
|
42
|
+
{ absolutePath: args.providersPath, relativePath: "providers.json" },
|
|
43
|
+
{ absolutePath: args.configPath, relativePath: "config.toml" },
|
|
44
|
+
],
|
|
24
45
|
mutate: () => {
|
|
46
|
+
const configPlan = (0, config_repo_1.createConfigMutationPlan)(document, {
|
|
47
|
+
deleteProfiles: lifecycle.deletedProfileSections,
|
|
48
|
+
setActiveProfile: lifecycle.nextActiveProfile,
|
|
49
|
+
});
|
|
25
50
|
(0, providers_repo_1.writeProvidersFile)(args.providersPath, { providers: nextProviders });
|
|
51
|
+
(0, config_repo_1.applyConfigMutation)(args.configPath, document, configPlan);
|
|
26
52
|
return {
|
|
27
53
|
provider: args.providerName,
|
|
54
|
+
createdProfileSections: configPlan.createdProfileSections,
|
|
55
|
+
deletedProfileSections: configPlan.deletedProfileSections,
|
|
56
|
+
keptSharedProfiles: lifecycle.keptSharedProfiles,
|
|
57
|
+
switchedActiveProfile: lifecycle.switchedActiveProfile,
|
|
58
|
+
adoptedProfiles: [],
|
|
59
|
+
repairedProfiles: [],
|
|
28
60
|
};
|
|
29
61
|
},
|
|
30
62
|
});
|
package/dist/app/run-doctor.js
CHANGED
|
@@ -38,6 +38,7 @@ 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
40
|
const codex_cli_1 = require("../infra/codex-cli");
|
|
41
|
+
const config_repo_1 = require("../infra/config-repo");
|
|
41
42
|
const providers_repo_1 = require("../infra/providers-repo");
|
|
42
43
|
const errors_1 = require("../domain/errors");
|
|
43
44
|
/**
|
|
@@ -45,9 +46,9 @@ const errors_1 = require("../domain/errors");
|
|
|
45
46
|
*/
|
|
46
47
|
function runDoctor(args) {
|
|
47
48
|
const issues = [];
|
|
48
|
-
let configProfiles = new Set();
|
|
49
49
|
let currentProfile = null;
|
|
50
50
|
let providers = null;
|
|
51
|
+
let document = null;
|
|
51
52
|
if (!fs.existsSync(args.configPath)) {
|
|
52
53
|
issues.push({
|
|
53
54
|
code: "CONFIG_NOT_FOUND",
|
|
@@ -56,9 +57,8 @@ function runDoctor(args) {
|
|
|
56
57
|
});
|
|
57
58
|
}
|
|
58
59
|
else {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
currentProfile = (0, config_1.parseTopLevelProfile)(configContent);
|
|
60
|
+
document = (0, config_repo_1.readStructuredConfig)(args.configPath);
|
|
61
|
+
currentProfile = document.activeProfile;
|
|
62
62
|
if (!currentProfile) {
|
|
63
63
|
issues.push({
|
|
64
64
|
code: "PROFILE_NOT_FOUND",
|
|
@@ -77,14 +77,11 @@ function runDoctor(args) {
|
|
|
77
77
|
else {
|
|
78
78
|
try {
|
|
79
79
|
providers = (0, providers_repo_1.readProvidersFile)(args.providersPath);
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
if (!configProfiles.has(provider.profile)) {
|
|
80
|
+
if (document) {
|
|
81
|
+
for (const issue of (0, config_1.collectConfigConsistencyIssues)(document, providers)) {
|
|
83
82
|
issues.push({
|
|
84
|
-
|
|
85
|
-
message:
|
|
86
|
-
provider: name,
|
|
87
|
-
profile: provider.profile,
|
|
83
|
+
...issue,
|
|
84
|
+
message: renderConfigIssueMessage(issue),
|
|
88
85
|
});
|
|
89
86
|
}
|
|
90
87
|
}
|
|
@@ -99,16 +96,6 @@ function runDoctor(args) {
|
|
|
99
96
|
}
|
|
100
97
|
}
|
|
101
98
|
const drift = (0, runtime_state_1.inspectLiveStateDrift)(currentProfile, providers);
|
|
102
|
-
if (drift.canBackfillActiveProvider) {
|
|
103
|
-
// Distinguish unmanaged live state from hard parse/configuration errors.
|
|
104
|
-
issues.push({
|
|
105
|
-
code: "LIVE_STATE_DRIFT",
|
|
106
|
-
message: `Active profile "${drift.currentProfile}" is present in config.toml but not mapped by providers.json.`,
|
|
107
|
-
currentProfile: drift.currentProfile,
|
|
108
|
-
suggestedAction: "backfill-active-provider",
|
|
109
|
-
storage: (0, runtime_state_1.getStorageRoles)(),
|
|
110
|
-
});
|
|
111
|
-
}
|
|
112
99
|
const codexCheck = (0, codex_cli_1.checkCodexAvailable)();
|
|
113
100
|
if (!codexCheck.ok) {
|
|
114
101
|
issues.push({
|
|
@@ -128,3 +115,17 @@ function runDoctor(args) {
|
|
|
128
115
|
warnings: issues.length === 0 ? [] : [`doctor found ${issues.length} issue(s)`],
|
|
129
116
|
};
|
|
130
117
|
}
|
|
118
|
+
function renderConfigIssueMessage(issue) {
|
|
119
|
+
switch (issue.code) {
|
|
120
|
+
case "ORPHANED_PROFILE_REFERENCE":
|
|
121
|
+
return `Profile "${issue.profile}" is referenced by providers but missing from config.toml.`;
|
|
122
|
+
case "UNMANAGED_ACTIVE_PROFILE":
|
|
123
|
+
return `Active profile "${issue.profile}" is not mapped by providers.json.`;
|
|
124
|
+
case "SHARED_PROFILE_REFERENCE":
|
|
125
|
+
return `Profile "${issue.profile}" is shared by multiple providers.`;
|
|
126
|
+
case "ORPHANED_PROFILE_SECTION":
|
|
127
|
+
return `Profile section "${issue.profile}" is not linked to any provider.`;
|
|
128
|
+
case "DESTRUCTIVE_REMOVE_BLOCKED":
|
|
129
|
+
return `Provider "${issue.provider}" cannot be removed while "${issue.activeProfile}" remains active.`;
|
|
130
|
+
}
|
|
131
|
+
}
|
package/dist/app/setup-codex.js
CHANGED
|
@@ -37,7 +37,7 @@ exports.setupCodex = setupCodex;
|
|
|
37
37
|
const fs = __importStar(require("node:fs"));
|
|
38
38
|
const setup_1 = require("../domain/setup");
|
|
39
39
|
const errors_1 = require("../domain/errors");
|
|
40
|
-
const
|
|
40
|
+
const config_1 = require("../domain/config");
|
|
41
41
|
const codex_cli_1 = require("../infra/codex-cli");
|
|
42
42
|
const config_repo_1 = require("../infra/config-repo");
|
|
43
43
|
const fs_utils_1 = require("../infra/fs-utils");
|
|
@@ -63,29 +63,35 @@ function setupCodex(args) {
|
|
|
63
63
|
cause: version.cause,
|
|
64
64
|
});
|
|
65
65
|
}
|
|
66
|
-
const candidates = (0, codex_discovery_1.findCodexDirCandidates)(args.codexDirOption);
|
|
67
|
-
if (candidates.length === 0) {
|
|
68
|
-
throw (0, errors_1.cliError)("CODEX_DIR_NOT_FOUND", "No Codex directory could be found.", {
|
|
69
|
-
codexDir: args.codexDir,
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
if (candidates.length > 1) {
|
|
73
|
-
throw (0, errors_1.cliError)("CODEX_DIR_AMBIGUOUS", "Multiple Codex directories were found.", {
|
|
74
|
-
candidates,
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
66
|
if (!fs.existsSync(args.codexDir)) {
|
|
78
67
|
throw (0, errors_1.cliError)("CODEX_DIR_NOT_FOUND", "The requested Codex directory does not exist.", {
|
|
79
68
|
codexDir: args.codexDir,
|
|
80
69
|
});
|
|
81
70
|
}
|
|
82
|
-
const
|
|
83
|
-
|
|
71
|
+
const document = (0, config_repo_1.readStructuredConfig)(args.configPath);
|
|
72
|
+
const profileViews = (0, config_1.buildManagedProfileViews)(document, null);
|
|
73
|
+
const adoptableProfiles = profileViews
|
|
74
|
+
.filter((view) => view.source === "unmanaged" && view.model && view.baseUrl)
|
|
75
|
+
.map((view) => view.name)
|
|
76
|
+
.sort();
|
|
77
|
+
if (profileViews.length === 0) {
|
|
84
78
|
throw (0, errors_1.cliError)("PROFILE_NOT_FOUND", "No profiles were found in config.toml.", {
|
|
85
79
|
file: args.configPath,
|
|
86
80
|
});
|
|
87
81
|
}
|
|
88
|
-
const
|
|
82
|
+
const invalidAdoptProfiles = args.adoptProfiles.filter((profile) => !adoptableProfiles.includes(profile));
|
|
83
|
+
if (invalidAdoptProfiles.length > 0) {
|
|
84
|
+
throw (0, errors_1.cliError)("INVALID_ARGUMENT", "setup only adopts unmanaged profiles that already contain model and base_url.", {
|
|
85
|
+
invalidProfiles: invalidAdoptProfiles.sort(),
|
|
86
|
+
adoptableProfiles,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
if (args.adoptProfiles.length === 0) {
|
|
90
|
+
throw (0, errors_1.cliError)("INVALID_ARGUMENT", "setup requires at least one explicit profile to adopt.", {
|
|
91
|
+
adoptableProfiles,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
const drafts = (0, setup_1.buildSetupDrafts)(args.adoptProfiles, args.providerDetailsByProfile);
|
|
89
95
|
const incompleteProfiles = (0, setup_1.findIncompleteSetupProfiles)(drafts);
|
|
90
96
|
if (incompleteProfiles.length > 0) {
|
|
91
97
|
throw (0, errors_1.cliError)("INVALID_ARGUMENT", "setup requires complete provider data for every selected profile.", {
|
|
@@ -112,14 +118,25 @@ function setupCodex(args) {
|
|
|
112
118
|
backupsDir: args.backupsDir,
|
|
113
119
|
latestBackupPath: args.latestBackupPath,
|
|
114
120
|
operation: "setup",
|
|
115
|
-
files: [
|
|
121
|
+
files: [
|
|
122
|
+
{ absolutePath: args.providersPath, relativePath: "providers.json" },
|
|
123
|
+
{ absolutePath: args.configPath, relativePath: "config.toml" },
|
|
124
|
+
],
|
|
116
125
|
mutate: () => {
|
|
126
|
+
const configPlan = (0, config_repo_1.createConfigMutationPlan)(document, {});
|
|
117
127
|
(0, providers_repo_1.writeProvidersFile)(args.providersPath, finalProviders);
|
|
128
|
+
(0, config_repo_1.applyConfigMutation)(args.configPath, document, configPlan);
|
|
118
129
|
return {
|
|
119
130
|
codexDir: args.codexDir,
|
|
120
131
|
strategy: args.strategy,
|
|
121
132
|
providersInitialized: Object.keys(nextProviders.providers).length,
|
|
122
133
|
providerNames: Object.keys(finalProviders.providers).sort(),
|
|
134
|
+
createdProfileSections: configPlan.createdProfileSections,
|
|
135
|
+
deletedProfileSections: configPlan.deletedProfileSections,
|
|
136
|
+
keptSharedProfiles: [],
|
|
137
|
+
switchedActiveProfile: false,
|
|
138
|
+
adoptedProfiles: [...args.adoptProfiles].sort(),
|
|
139
|
+
repairedProfiles: [],
|
|
123
140
|
};
|
|
124
141
|
},
|
|
125
142
|
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.showConfig = showConfig;
|
|
4
|
+
const config_1 = require("../domain/config");
|
|
5
|
+
const errors_1 = require("../domain/errors");
|
|
6
|
+
const config_repo_1 = require("../infra/config-repo");
|
|
7
|
+
const providers_repo_1 = require("../infra/providers-repo");
|
|
8
|
+
/**
|
|
9
|
+
* Returns the structured config view, optionally filtered to one profile.
|
|
10
|
+
*/
|
|
11
|
+
function showConfig(args) {
|
|
12
|
+
const document = (0, config_repo_1.readStructuredConfig)(args.configPath);
|
|
13
|
+
const providers = (0, providers_repo_1.readProvidersFileIfExists)(args.providersPath);
|
|
14
|
+
const views = (0, config_1.buildManagedProfileViews)(document, providers);
|
|
15
|
+
let selectedProfile = null;
|
|
16
|
+
let profiles = views;
|
|
17
|
+
if (args.profileName) {
|
|
18
|
+
const found = views.find((view) => view.name === args.profileName);
|
|
19
|
+
if (!found) {
|
|
20
|
+
throw (0, errors_1.cliError)("PROFILE_NOT_FOUND", `Profile "${args.profileName}" was not found.`, {
|
|
21
|
+
profile: args.profileName,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
selectedProfile = args.profileName;
|
|
25
|
+
profiles = [found];
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
data: {
|
|
29
|
+
activeProfile: document.activeProfile,
|
|
30
|
+
selectedProfile,
|
|
31
|
+
profiles,
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
}
|
|
@@ -17,7 +17,7 @@ function switchProvider(args) {
|
|
|
17
17
|
availableProviders: Object.keys(providers.providers).sort(),
|
|
18
18
|
});
|
|
19
19
|
}
|
|
20
|
-
const
|
|
20
|
+
const document = (0, config_repo_1.ensureProfileExists)(args.configPath, provider.profile, args.providerName);
|
|
21
21
|
return (0, run_mutation_1.runMutation)({
|
|
22
22
|
codexDir: args.codexDir,
|
|
23
23
|
backupsDir: args.backupsDir,
|
|
@@ -28,8 +28,11 @@ function switchProvider(args) {
|
|
|
28
28
|
{ absolutePath: args.authPath, relativePath: "auth.json" },
|
|
29
29
|
],
|
|
30
30
|
mutate: () => {
|
|
31
|
+
const configPlan = (0, config_repo_1.createConfigMutationPlan)(document, {
|
|
32
|
+
setActiveProfile: provider.profile,
|
|
33
|
+
});
|
|
31
34
|
// Update the runtime profile first so any subsequent login is associated with the new target.
|
|
32
|
-
(0, config_repo_1.
|
|
35
|
+
(0, config_repo_1.applyConfigMutation)(args.configPath, document, configPlan);
|
|
33
36
|
if (!args.noLogin) {
|
|
34
37
|
(0, codex_cli_1.runCodexLogin)(provider.apiKey, args.codexDir);
|
|
35
38
|
}
|
package/dist/cli/args.js
CHANGED
|
@@ -11,6 +11,7 @@ const codex_paths_1 = require("../infra/codex-paths");
|
|
|
11
11
|
function parseArgs(argv) {
|
|
12
12
|
let json = false;
|
|
13
13
|
let codexDir = (0, codex_paths_1.resolveCodexDir)();
|
|
14
|
+
let codexDirExplicit = false;
|
|
14
15
|
const remaining = [];
|
|
15
16
|
for (let index = 0; index < argv.length; index += 1) {
|
|
16
17
|
const value = argv[index];
|
|
@@ -24,6 +25,7 @@ function parseArgs(argv) {
|
|
|
24
25
|
throw (0, errors_1.cliError)("INVALID_ARGUMENT", "--codex-dir requires a path value.");
|
|
25
26
|
}
|
|
26
27
|
codexDir = (0, codex_paths_1.resolveCodexDir)(next);
|
|
28
|
+
codexDirExplicit = true;
|
|
27
29
|
index += 1;
|
|
28
30
|
continue;
|
|
29
31
|
}
|
|
@@ -37,6 +39,7 @@ function parseArgs(argv) {
|
|
|
37
39
|
globalOptions: {
|
|
38
40
|
json,
|
|
39
41
|
codexDir,
|
|
42
|
+
codexDirExplicit,
|
|
40
43
|
},
|
|
41
44
|
commandOptions: new Map(),
|
|
42
45
|
helpRequested: true,
|
|
@@ -53,11 +56,17 @@ function parseArgs(argv) {
|
|
|
53
56
|
});
|
|
54
57
|
}
|
|
55
58
|
const commandToken = remaining[0] ?? null;
|
|
56
|
-
const command = commandToken === "backups" && remaining[1] === "list"
|
|
59
|
+
const command = commandToken === "backups" && remaining[1] === "list"
|
|
60
|
+
? "backups-list"
|
|
61
|
+
: commandToken === "config" && remaining[1] === "show"
|
|
62
|
+
? "config-show"
|
|
63
|
+
: commandToken === "config" && remaining[1] === "list-profiles"
|
|
64
|
+
? "config-list-profiles"
|
|
65
|
+
: commandToken;
|
|
57
66
|
const positionals = [];
|
|
58
67
|
const commandOptions = new Map();
|
|
59
68
|
let helpRequested = false;
|
|
60
|
-
const startIndex = command === "backups-list" ? 2 : 1;
|
|
69
|
+
const startIndex = command === "backups-list" || command === "config-show" || command === "config-list-profiles" ? 2 : 1;
|
|
61
70
|
for (let index = startIndex; index < remaining.length; index += 1) {
|
|
62
71
|
const value = remaining[index];
|
|
63
72
|
if (value === "--help" || value === "-h") {
|
|
@@ -86,6 +95,7 @@ function parseArgs(argv) {
|
|
|
86
95
|
globalOptions: {
|
|
87
96
|
json,
|
|
88
97
|
codexDir,
|
|
98
|
+
codexDirExplicit,
|
|
89
99
|
},
|
|
90
100
|
commandOptions,
|
|
91
101
|
helpRequested,
|
|
@@ -103,6 +113,7 @@ function defaultParsed(command, overrides) {
|
|
|
103
113
|
globalOptions: {
|
|
104
114
|
json: overrides?.json ?? false,
|
|
105
115
|
codexDir: overrides?.codexDir ?? (0, codex_paths_1.resolveCodexDir)(),
|
|
116
|
+
codexDirExplicit: false,
|
|
106
117
|
},
|
|
107
118
|
commandOptions: new Map(),
|
|
108
119
|
helpRequested: overrides?.helpRequested ?? false,
|