@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.
@@ -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: [{ absolutePath: args.providersPath, relativePath: "providers.json" }],
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: [{ absolutePath: args.providersPath, relativePath: "providers.json" }],
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
  });
@@ -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 configContent = fs.readFileSync(configPath, "utf8");
52
- currentProfile = (0, config_1.parseTopLevelProfile)(configContent);
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: [{ absolutePath: args.providersPath, relativePath: "providers.json" }],
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
- if (!providers.providers[args.providerName]) {
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: [{ absolutePath: args.providersPath, relativePath: "providers.json" }],
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
  });
@@ -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
- const configContent = fs.readFileSync(args.configPath, "utf8");
60
- configProfiles = (0, config_1.parseProfileNames)(configContent);
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
- // Every managed provider must map to a profile that still exists in config.toml.
81
- for (const [name, provider] of Object.entries(providers.providers)) {
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
- code: "PROFILE_NOT_FOUND",
85
- message: `Provider "${name}" maps to missing profile "${provider.profile}".`,
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
+ }
@@ -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 codex_discovery_1 = require("../infra/codex-discovery");
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 profiles = Array.from((0, config_repo_1.listConfigProfiles)(args.configPath)).sort();
83
- if (profiles.length === 0) {
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 drafts = (0, setup_1.buildSetupDrafts)(profiles, args.providerDetailsByProfile);
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: [{ absolutePath: args.providersPath, relativePath: "providers.json" }],
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 configContent = (0, config_repo_1.ensureProfileExists)(args.configPath, provider.profile, args.providerName);
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.updateTopLevelProfile)(args.configPath, configContent, provider.profile);
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" ? "backups-list" : commandToken;
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,