@minniexcode/codex-switch 0.0.1 → 0.0.3

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.
Files changed (39) hide show
  1. package/README.AI.md +105 -0
  2. package/README.CN.md +160 -0
  3. package/README.md +102 -111
  4. package/dist/app/add-provider.js +45 -0
  5. package/dist/app/export-providers.js +62 -0
  6. package/dist/app/get-current-profile.js +14 -0
  7. package/dist/app/get-status.js +75 -0
  8. package/dist/app/import-providers.js +74 -0
  9. package/dist/app/list-providers.js +23 -0
  10. package/dist/app/remove-provider.js +31 -0
  11. package/dist/app/rollback-latest.js +26 -0
  12. package/dist/app/run-doctor.js +130 -0
  13. package/dist/app/run-mutation.js +63 -0
  14. package/dist/app/switch-provider.js +43 -0
  15. package/dist/app/types.js +2 -0
  16. package/dist/cli/add-interactive.js +114 -0
  17. package/dist/cli/args.js +125 -0
  18. package/dist/cli/help.js +220 -0
  19. package/dist/cli/interactive.js +114 -0
  20. package/dist/cli/output.js +156 -0
  21. package/dist/cli/prompt.js +93 -0
  22. package/dist/cli.js +215 -26
  23. package/dist/domain/backup.js +2 -0
  24. package/dist/domain/config.js +106 -0
  25. package/dist/domain/errors.js +36 -0
  26. package/dist/domain/providers.js +92 -0
  27. package/dist/domain/runtime-state.js +56 -0
  28. package/dist/infra/backup-repo.js +151 -0
  29. package/dist/infra/codex-cli.js +53 -0
  30. package/dist/infra/codex-paths.js +58 -0
  31. package/dist/infra/config-repo.js +56 -0
  32. package/dist/infra/fs-utils.js +97 -0
  33. package/dist/infra/lock-repo.js +99 -0
  34. package/dist/infra/providers-repo.js +69 -0
  35. package/docs/codex-switch-command-design.md +646 -0
  36. package/docs/codex-switch-prd.md +24 -3
  37. package/docs/codex-switch-product-overview.md +2 -0
  38. package/docs/codex-switch-technical-architecture.md +1042 -0
  39. package/package.json +7 -4
@@ -0,0 +1,75 @@
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.getStatus = getStatus;
37
+ const fs = __importStar(require("node:fs"));
38
+ const config_1 = require("../domain/config");
39
+ const runtime_state_1 = require("../domain/runtime-state");
40
+ const providers_repo_1 = require("../infra/providers-repo");
41
+ /**
42
+ * Reports the current on-disk runtime state and how it maps back to managed providers.
43
+ */
44
+ function getStatus(codexDir, configPath, providersPath) {
45
+ const configExists = fs.existsSync(configPath);
46
+ const providersExists = fs.existsSync(providersPath);
47
+ let currentProfile = null;
48
+ const warnings = [];
49
+ const providers = providersExists ? (0, providers_repo_1.readProvidersFile)(providersPath) : null;
50
+ if (configExists) {
51
+ const configContent = fs.readFileSync(configPath, "utf8");
52
+ currentProfile = (0, config_1.parseTopLevelProfile)(configContent);
53
+ if (!currentProfile) {
54
+ warnings.push("config.toml exists but has no top-level profile.");
55
+ }
56
+ }
57
+ const liveState = (0, runtime_state_1.inspectLiveStateDrift)(currentProfile, providers);
58
+ if (liveState.canBackfillActiveProvider) {
59
+ // Surface unmanaged live state without mutating anything during a read-only status call.
60
+ warnings.push("Current config profile is not mapped in providers.json. Backfill would be required before treating live state as managed.");
61
+ }
62
+ return {
63
+ warnings,
64
+ data: {
65
+ codexDir,
66
+ storage: (0, runtime_state_1.getStorageRoles)(),
67
+ configExists,
68
+ providersExists,
69
+ currentProfile,
70
+ currentProfileMapped: liveState.profileMapped,
71
+ provider: liveState.mappedProvider,
72
+ liveState,
73
+ },
74
+ };
75
+ }
@@ -0,0 +1,74 @@
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.importProviders = importProviders;
37
+ const fs = __importStar(require("node:fs"));
38
+ const path = __importStar(require("node:path"));
39
+ const providers_1 = require("../domain/providers");
40
+ const errors_1 = require("../domain/errors");
41
+ const fs_utils_1 = require("../infra/fs-utils");
42
+ const providers_repo_1 = require("../infra/providers-repo");
43
+ const run_mutation_1 = require("./run-mutation");
44
+ /**
45
+ * Imports provider definitions from an external JSON file into the managed registry.
46
+ */
47
+ function importProviders(args) {
48
+ const absoluteSource = path.resolve(args.sourceFile);
49
+ let imported;
50
+ try {
51
+ // Validate before writing so malformed imports never touch the managed file.
52
+ imported = (0, providers_1.validateProvidersShape)(JSON.parse(fs.readFileSync(absoluteSource, "utf8")));
53
+ }
54
+ catch (error) {
55
+ throw (0, errors_1.cliError)("INVALID_IMPORT_FILE", "Import file is not valid providers.json data.", {
56
+ file: absoluteSource,
57
+ cause: (0, errors_1.normalizeError)(error).message,
58
+ });
59
+ }
60
+ (0, fs_utils_1.ensureDir)(args.codexDir);
61
+ return (0, run_mutation_1.runMutation)({
62
+ codexDir: args.codexDir,
63
+ backupsDir: args.backupsDir,
64
+ latestBackupPath: args.latestBackupPath,
65
+ operation: "import",
66
+ files: [{ absolutePath: args.providersPath, relativePath: "providers.json" }],
67
+ mutate: () => {
68
+ (0, providers_repo_1.writeProvidersFile)(args.providersPath, imported);
69
+ return {
70
+ importedProviders: Object.keys(imported.providers).sort(),
71
+ };
72
+ },
73
+ });
74
+ }
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.listProviders = listProviders;
4
+ const providers_repo_1 = require("../infra/providers-repo");
5
+ /**
6
+ * Returns the sorted list of configured providers for display.
7
+ */
8
+ function listProviders(providersPath) {
9
+ const providers = (0, providers_repo_1.readProvidersFile)(providersPath);
10
+ const names = Object.keys(providers.providers).sort();
11
+ const items = names.map((name) => ({
12
+ name,
13
+ profile: providers.providers[name].profile,
14
+ note: providers.providers[name].note ?? null,
15
+ tags: providers.providers[name].tags ?? [],
16
+ }));
17
+ return {
18
+ data: {
19
+ providers: items,
20
+ count: items.length,
21
+ },
22
+ };
23
+ }
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.removeProvider = removeProvider;
4
+ const errors_1 = require("../domain/errors");
5
+ const providers_repo_1 = require("../infra/providers-repo");
6
+ const run_mutation_1 = require("./run-mutation");
7
+ /**
8
+ * Removes a provider from the managed providers registry.
9
+ */
10
+ function removeProvider(args) {
11
+ const providers = (0, providers_repo_1.readProvidersFile)(args.providersPath);
12
+ if (!providers.providers[args.providerName]) {
13
+ throw (0, errors_1.cliError)("PROVIDER_NOT_FOUND", `Provider "${args.providerName}" was not found.`);
14
+ }
15
+ const nextProviders = { ...providers.providers };
16
+ // Delete against a copied object so the original parsed state stays untouched.
17
+ delete nextProviders[args.providerName];
18
+ return (0, run_mutation_1.runMutation)({
19
+ codexDir: args.codexDir,
20
+ backupsDir: args.backupsDir,
21
+ latestBackupPath: args.latestBackupPath,
22
+ operation: "remove",
23
+ files: [{ absolutePath: args.providersPath, relativePath: "providers.json" }],
24
+ mutate: () => {
25
+ (0, providers_repo_1.writeProvidersFile)(args.providersPath, { providers: nextProviders });
26
+ return {
27
+ provider: args.providerName,
28
+ };
29
+ },
30
+ });
31
+ }
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.rollbackLatest = rollbackLatest;
4
+ const errors_1 = require("../domain/errors");
5
+ const backup_repo_1 = require("../infra/backup-repo");
6
+ /**
7
+ * Restores the most recent mutation backup recorded by codex-switch.
8
+ */
9
+ function rollbackLatest(latestBackupPath) {
10
+ const manifest = (0, backup_repo_1.loadLatestManifest)(latestBackupPath);
11
+ try {
12
+ (0, backup_repo_1.restoreManifest)(manifest);
13
+ return {
14
+ data: {
15
+ restoredFiles: manifest.files.map((file) => file.relativePath),
16
+ backupPath: manifest.backupDir,
17
+ },
18
+ };
19
+ }
20
+ catch (error) {
21
+ throw (0, errors_1.cliError)("ROLLBACK_FAILED", "Rollback failed.", {
22
+ cause: (0, errors_1.normalizeError)(error).message,
23
+ backupPath: manifest.backupDir,
24
+ });
25
+ }
26
+ }
@@ -0,0 +1,130 @@
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.runDoctor = runDoctor;
37
+ const fs = __importStar(require("node:fs"));
38
+ const config_1 = require("../domain/config");
39
+ const runtime_state_1 = require("../domain/runtime-state");
40
+ const codex_cli_1 = require("../infra/codex-cli");
41
+ const providers_repo_1 = require("../infra/providers-repo");
42
+ const errors_1 = require("../domain/errors");
43
+ /**
44
+ * Performs consistency checks across config.toml, providers.json, and the local Codex CLI.
45
+ */
46
+ function runDoctor(args) {
47
+ const issues = [];
48
+ let configProfiles = new Set();
49
+ let currentProfile = null;
50
+ let providers = null;
51
+ if (!fs.existsSync(args.configPath)) {
52
+ issues.push({
53
+ code: "CONFIG_NOT_FOUND",
54
+ message: "config.toml does not exist.",
55
+ file: args.configPath,
56
+ });
57
+ }
58
+ else {
59
+ const configContent = fs.readFileSync(args.configPath, "utf8");
60
+ configProfiles = (0, config_1.parseProfileNames)(configContent);
61
+ currentProfile = (0, config_1.parseTopLevelProfile)(configContent);
62
+ if (!currentProfile) {
63
+ issues.push({
64
+ code: "PROFILE_NOT_FOUND",
65
+ message: "config.toml has no top-level profile.",
66
+ file: args.configPath,
67
+ });
68
+ }
69
+ }
70
+ if (!fs.existsSync(args.providersPath)) {
71
+ issues.push({
72
+ code: "PROVIDERS_NOT_FOUND",
73
+ message: "providers.json does not exist.",
74
+ file: args.providersPath,
75
+ });
76
+ }
77
+ else {
78
+ try {
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)) {
83
+ issues.push({
84
+ code: "PROFILE_NOT_FOUND",
85
+ message: `Provider "${name}" maps to missing profile "${provider.profile}".`,
86
+ provider: name,
87
+ profile: provider.profile,
88
+ });
89
+ }
90
+ }
91
+ }
92
+ catch (error) {
93
+ const normalized = (0, errors_1.normalizeError)(error);
94
+ issues.push({
95
+ code: normalized.code,
96
+ message: normalized.message,
97
+ ...(normalized.details ?? {}),
98
+ });
99
+ }
100
+ }
101
+ 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
+ const codexCheck = (0, codex_cli_1.checkCodexAvailable)();
113
+ if (!codexCheck.ok) {
114
+ issues.push({
115
+ code: "CODEX_LOGIN_FAILED",
116
+ message: "codex CLI is not available.",
117
+ cause: codexCheck.cause,
118
+ });
119
+ }
120
+ return {
121
+ data: {
122
+ healthy: issues.length === 0,
123
+ issues,
124
+ codexDir: args.codexDir,
125
+ storage: (0, runtime_state_1.getStorageRoles)(),
126
+ liveState: drift,
127
+ },
128
+ warnings: issues.length === 0 ? [] : [`doctor found ${issues.length} issue(s)`],
129
+ };
130
+ }
@@ -0,0 +1,63 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runMutation = runMutation;
4
+ const errors_1 = require("../domain/errors");
5
+ const backup_repo_1 = require("../infra/backup-repo");
6
+ const lock_repo_1 = require("../infra/lock-repo");
7
+ /**
8
+ * Runs a write operation under a lock with automatic backup and rollback handling.
9
+ */
10
+ function runMutation(args) {
11
+ return (0, lock_repo_1.withCodexLock)(args.codexDir, args.operation, () => {
12
+ const backup = (0, backup_repo_1.createBackup)(args.codexDir, args.backupsDir, args.operation, args.files);
13
+ try {
14
+ const data = args.mutate({ backup });
15
+ // Record the successful backup only after the mutation completes.
16
+ (0, backup_repo_1.saveLatestManifest)(args.latestBackupPath, backup);
17
+ return {
18
+ data: {
19
+ ...data,
20
+ backupPath: backup.backupDir,
21
+ managedState: {
22
+ transaction: "single-process-file-lock",
23
+ backupFiles: listBackedUpFiles(backup.files),
24
+ },
25
+ },
26
+ };
27
+ }
28
+ catch (error) {
29
+ try {
30
+ // Roll back the managed files to their pre-mutation state on any failure.
31
+ (0, backup_repo_1.restoreManifest)(backup);
32
+ }
33
+ catch (rollbackError) {
34
+ throw (0, errors_1.cliError)("ROLLBACK_FAILED", `${capitalize(args.operation)} failed and rollback was not successful.`, {
35
+ cause: (0, errors_1.normalizeError)(error).message,
36
+ rollbackReason: (0, errors_1.normalizeError)(rollbackError).message,
37
+ backupPath: backup.backupDir,
38
+ });
39
+ }
40
+ const baseError = (0, errors_1.normalizeError)(error);
41
+ throw (0, errors_1.cliError)(baseError.code, baseError.message, {
42
+ ...(baseError.details ?? {}),
43
+ rollbackApplied: true,
44
+ backupPath: backup.backupDir,
45
+ });
46
+ }
47
+ });
48
+ }
49
+ /**
50
+ * Lists the files that existed before the mutation and were captured in the backup.
51
+ */
52
+ function listBackedUpFiles(files) {
53
+ return files.filter((entry) => entry.existed).map((entry) => entry.relativePath);
54
+ }
55
+ /**
56
+ * Uppercases the first character for human-readable operation names.
57
+ */
58
+ function capitalize(value) {
59
+ if (value.length === 0) {
60
+ return value;
61
+ }
62
+ return `${value[0].toUpperCase()}${value.slice(1)}`;
63
+ }
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.switchProvider = switchProvider;
4
+ const errors_1 = require("../domain/errors");
5
+ const config_repo_1 = require("../infra/config-repo");
6
+ const codex_cli_1 = require("../infra/codex-cli");
7
+ const providers_repo_1 = require("../infra/providers-repo");
8
+ const run_mutation_1 = require("./run-mutation");
9
+ /**
10
+ * Switches the active Codex profile and optionally refreshes the CLI login session.
11
+ */
12
+ function switchProvider(args) {
13
+ const providers = (0, providers_repo_1.readProvidersFile)(args.providersPath);
14
+ const provider = providers.providers[args.providerName];
15
+ if (!provider) {
16
+ throw (0, errors_1.cliError)("PROVIDER_NOT_FOUND", `Provider "${args.providerName}" was not found.`, {
17
+ availableProviders: Object.keys(providers.providers).sort(),
18
+ });
19
+ }
20
+ const configContent = (0, config_repo_1.ensureProfileExists)(args.configPath, provider.profile, args.providerName);
21
+ return (0, run_mutation_1.runMutation)({
22
+ codexDir: args.codexDir,
23
+ backupsDir: args.backupsDir,
24
+ latestBackupPath: args.latestBackupPath,
25
+ operation: "switch",
26
+ files: [
27
+ { absolutePath: args.configPath, relativePath: "config.toml" },
28
+ { absolutePath: args.authPath, relativePath: "auth.json" },
29
+ ],
30
+ mutate: () => {
31
+ // 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);
33
+ if (!args.noLogin) {
34
+ (0, codex_cli_1.runCodexLogin)(provider.apiKey, args.codexDir);
35
+ }
36
+ return {
37
+ provider: args.providerName,
38
+ profile: provider.profile,
39
+ loginPerformed: !args.noLogin,
40
+ };
41
+ },
42
+ });
43
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,114 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.collectAddInput = collectAddInput;
4
+ exports.createNonInteractiveAddError = createNonInteractiveAddError;
5
+ const config_repo_1 = require("../infra/config-repo");
6
+ const errors_1 = require("../domain/errors");
7
+ /**
8
+ * Collects add command inputs interactively when required values are missing.
9
+ */
10
+ async function collectAddInput(runtime, defaults, providerExists, configPath) {
11
+ runtime.writeLine("Interactive add mode");
12
+ runtime.writeLine("Provide the missing required fields. Press Enter to skip optional fields.");
13
+ const providerName = defaults.providerName
14
+ ? normalizeRequiredValue(defaults.providerName)
15
+ : await promptProviderName(runtime, providerExists);
16
+ const profile = defaults.profile
17
+ ? normalizeRequiredValue(defaults.profile)
18
+ : await promptProfile(runtime, configPath);
19
+ const apiKey = defaults.apiKey
20
+ ? normalizeRequiredValue(defaults.apiKey)
21
+ : await promptConfirmedSecret(runtime, "API key", "Confirm API key");
22
+ const baseUrl = defaults.baseUrl ?? normalizeOptionalValue(await runtime.inputText("Base URL (optional)"));
23
+ const note = defaults.note ?? normalizeOptionalValue(await runtime.inputText("Note (optional)"));
24
+ const tags = defaults.tags.length > 0
25
+ ? defaults.tags
26
+ : parseTags(await runtime.inputText("Tags (optional, comma-separated)"));
27
+ return {
28
+ providerName,
29
+ profile,
30
+ apiKey,
31
+ baseUrl,
32
+ note,
33
+ tags,
34
+ };
35
+ }
36
+ /**
37
+ * Throws a consistent error when interactive add is unavailable.
38
+ */
39
+ function createNonInteractiveAddError() {
40
+ return (0, errors_1.cliError)("INVALID_IMPORT_FILE", "add requires <provider>, --profile, and --api-key when running without an interactive TTY.", {
41
+ suggestion: "Run in a terminal TTY or pass all required values explicitly.",
42
+ });
43
+ }
44
+ async function promptProviderName(runtime, providerExists) {
45
+ while (true) {
46
+ const providerName = await promptRequiredValue(runtime, "Provider name");
47
+ if (providerExists(providerName)) {
48
+ runtime.writeLine(`Provider "${providerName}" already exists. Choose a different name.`);
49
+ continue;
50
+ }
51
+ return providerName;
52
+ }
53
+ }
54
+ async function promptProfile(runtime, configPath) {
55
+ const profileChoices = loadProfileChoices(configPath);
56
+ if (profileChoices.length > 0) {
57
+ return runtime.selectOne("Profile", profileChoices);
58
+ }
59
+ return promptRequiredValue(runtime, "Profile");
60
+ }
61
+ function loadProfileChoices(configPath) {
62
+ try {
63
+ return Array.from((0, config_repo_1.listConfigProfiles)(configPath))
64
+ .sort()
65
+ .map((profileName) => ({
66
+ value: profileName,
67
+ label: profileName,
68
+ }));
69
+ }
70
+ catch {
71
+ return [];
72
+ }
73
+ }
74
+ async function promptRequiredValue(runtime, label) {
75
+ while (true) {
76
+ const value = normalizeRequiredValue(await runtime.inputText(label));
77
+ if (value.length > 0) {
78
+ return value;
79
+ }
80
+ runtime.writeLine(`${label} is required.`);
81
+ }
82
+ }
83
+ async function promptConfirmedSecret(runtime, label, confirmationLabel) {
84
+ while (true) {
85
+ const first = normalizeRequiredValue(await runtime.inputSecret(label));
86
+ if (first.length === 0) {
87
+ runtime.writeLine(`${label} is required.`);
88
+ continue;
89
+ }
90
+ const second = normalizeRequiredValue(await runtime.inputSecret(confirmationLabel));
91
+ if (second.length === 0) {
92
+ runtime.writeLine(`${confirmationLabel} is required.`);
93
+ continue;
94
+ }
95
+ if (first !== second) {
96
+ runtime.writeLine("API key entries did not match. Try again.");
97
+ continue;
98
+ }
99
+ return first;
100
+ }
101
+ }
102
+ function normalizeRequiredValue(value) {
103
+ return value.trim();
104
+ }
105
+ function normalizeOptionalValue(value) {
106
+ const normalized = value.trim();
107
+ return normalized === "" ? null : normalized;
108
+ }
109
+ function parseTags(value) {
110
+ return value
111
+ .split(",")
112
+ .map((tag) => tag.trim())
113
+ .filter((tag) => tag.length > 0);
114
+ }