@minniexcode/codex-switch 0.0.1 → 0.0.2

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.
@@ -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,98 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseArgs = parseArgs;
4
+ exports.hasFlag = hasFlag;
5
+ exports.getSingleOption = getSingleOption;
6
+ const errors_1 = require("../domain/errors");
7
+ const codex_paths_1 = require("../infra/codex-paths");
8
+ /**
9
+ * Parses argv into command positionals, global flags, and command-scoped options.
10
+ */
11
+ function parseArgs(argv) {
12
+ if (argv.includes("--help") || argv.includes("-h")) {
13
+ return defaultParsed("help");
14
+ }
15
+ if (argv.includes("--version") || argv.includes("-v")) {
16
+ return defaultParsed("version");
17
+ }
18
+ let json = false;
19
+ let codexDir = (0, codex_paths_1.resolveCodexDir)();
20
+ const remaining = [];
21
+ for (let index = 0; index < argv.length; index += 1) {
22
+ const value = argv[index];
23
+ if (value === "--json") {
24
+ json = true;
25
+ continue;
26
+ }
27
+ if (value === "--codex-dir") {
28
+ const next = argv[index + 1];
29
+ if (!next) {
30
+ throw (0, errors_1.cliError)("INVALID_IMPORT_FILE", "--codex-dir requires a path value.");
31
+ }
32
+ codexDir = (0, codex_paths_1.resolveCodexDir)(next);
33
+ index += 1;
34
+ continue;
35
+ }
36
+ remaining.push(value);
37
+ }
38
+ const command = remaining[0] ?? null;
39
+ const positionals = [];
40
+ const commandOptions = new Map();
41
+ for (let index = 1; index < remaining.length; index += 1) {
42
+ const value = remaining[index];
43
+ if (value.startsWith("--")) {
44
+ const optionName = value;
45
+ const next = remaining[index + 1];
46
+ if (!next || next.startsWith("--")) {
47
+ // Boolean flags are stored as "true" so later access uses one uniform map shape.
48
+ commandOptions.set(optionName, ["true"]);
49
+ continue;
50
+ }
51
+ const existing = commandOptions.get(optionName) ?? [];
52
+ existing.push(next);
53
+ commandOptions.set(optionName, existing);
54
+ index += 1;
55
+ continue;
56
+ }
57
+ positionals.push(value);
58
+ }
59
+ return {
60
+ command,
61
+ positionals,
62
+ globalOptions: {
63
+ json,
64
+ codexDir,
65
+ },
66
+ commandOptions,
67
+ };
68
+ }
69
+ /**
70
+ * Creates a parsed result for built-in synthetic commands such as help/version.
71
+ */
72
+ function defaultParsed(command) {
73
+ return {
74
+ command,
75
+ positionals: [],
76
+ globalOptions: {
77
+ json: false,
78
+ codexDir: (0, codex_paths_1.resolveCodexDir)(),
79
+ },
80
+ commandOptions: new Map(),
81
+ };
82
+ }
83
+ /**
84
+ * Checks whether a boolean-style option was supplied.
85
+ */
86
+ function hasFlag(options, name) {
87
+ return options.has(name);
88
+ }
89
+ /**
90
+ * Returns the last supplied value for a single-valued command option.
91
+ */
92
+ function getSingleOption(options, name, required = true) {
93
+ const values = options.get(name) ?? [];
94
+ if (values.length === 0) {
95
+ return required ? null : null;
96
+ }
97
+ return values[values.length - 1];
98
+ }
@@ -0,0 +1,156 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.renderSuccess = renderSuccess;
4
+ exports.renderFailure = renderFailure;
5
+ exports.outputSuccess = outputSuccess;
6
+ exports.outputFailure = outputFailure;
7
+ const fs_utils_1 = require("../infra/fs-utils");
8
+ /**
9
+ * Renders a successful command result for either JSON or human-readable output.
10
+ */
11
+ function renderSuccess(ctx, result) {
12
+ const warnings = result.warnings ?? [];
13
+ if (ctx.options.json) {
14
+ const payload = {
15
+ ok: true,
16
+ command: ctx.command,
17
+ data: result.data,
18
+ warnings,
19
+ error: null,
20
+ };
21
+ return {
22
+ stdout: [JSON.stringify(payload, null, 2)],
23
+ stderr: [],
24
+ exitCode: 0,
25
+ };
26
+ }
27
+ return {
28
+ stdout: renderHumanSuccess(ctx.command, result.data, warnings),
29
+ stderr: [],
30
+ exitCode: 0,
31
+ };
32
+ }
33
+ /**
34
+ * Renders a failed command result for either JSON or human-readable output.
35
+ */
36
+ function renderFailure(ctx, error) {
37
+ if (ctx.options.json) {
38
+ const payload = {
39
+ ok: false,
40
+ command: ctx.command,
41
+ data: null,
42
+ warnings: [],
43
+ error,
44
+ };
45
+ return {
46
+ stdout: [],
47
+ stderr: [JSON.stringify(payload, null, 2)],
48
+ exitCode: 1,
49
+ };
50
+ }
51
+ return {
52
+ stdout: [],
53
+ stderr: [`${error.code}: ${error.message}`, ...(0, fs_utils_1.printErrorDetails)(error)],
54
+ exitCode: 1,
55
+ };
56
+ }
57
+ /**
58
+ * Writes successful command output to stdout.
59
+ */
60
+ function outputSuccess(ctx, result) {
61
+ const rendered = renderSuccess(ctx, result);
62
+ for (const line of rendered.stdout) {
63
+ printText(line);
64
+ }
65
+ }
66
+ /**
67
+ * Writes failure output to stderr and exits with the rendered status code.
68
+ */
69
+ function outputFailure(ctx, error) {
70
+ const rendered = renderFailure(ctx, error);
71
+ for (const line of rendered.stderr) {
72
+ printText(line, true);
73
+ }
74
+ process.exit(rendered.exitCode);
75
+ }
76
+ /**
77
+ * Builds the plain-text success view for interactive terminal usage.
78
+ */
79
+ function renderHumanSuccess(command, data, warnings) {
80
+ const lines = [];
81
+ switch (command) {
82
+ case "list": {
83
+ const providers = data?.providers ?? [];
84
+ if (providers.length === 0) {
85
+ lines.push("No providers configured.");
86
+ }
87
+ else {
88
+ for (const provider of providers) {
89
+ const tags = Array.isArray(provider.tags) && provider.tags.length > 0
90
+ ? ` tags=${provider.tags.join(",")}`
91
+ : "";
92
+ const note = provider.note ? ` note=${provider.note}` : "";
93
+ lines.push(`${provider.name} -> ${provider.profile}${tags}${note}`);
94
+ }
95
+ }
96
+ break;
97
+ }
98
+ case "current":
99
+ lines.push(`Current profile: ${String(data?.profile ?? "")}`);
100
+ break;
101
+ case "status":
102
+ lines.push(`codexDir: ${String(data?.codexDir ?? "")}`);
103
+ lines.push(`configExists: ${String(data?.configExists ?? false)}`);
104
+ lines.push(`providersExists: ${String(data?.providersExists ?? false)}`);
105
+ lines.push(`currentProfile: ${String(data?.currentProfile ?? "")}`);
106
+ lines.push(`mappedProvider: ${String(data?.provider ?? "")}`);
107
+ break;
108
+ case "switch":
109
+ lines.push(`Switched to provider ${String(data?.provider ?? "")} using profile ${String(data?.profile ?? "")}.`);
110
+ lines.push(`Backup: ${String(data?.backupPath ?? "")}`);
111
+ lines.push(`Login performed: ${String(data?.loginPerformed ?? false)}`);
112
+ break;
113
+ case "import":
114
+ lines.push(`Imported providers from file. Backup: ${String(data?.backupPath ?? "")}`);
115
+ break;
116
+ case "export":
117
+ lines.push(`Exported providers to ${String(data?.exportedTo ?? "")}.`);
118
+ break;
119
+ case "add":
120
+ lines.push(`Added provider ${String(data?.provider ?? "")}. Backup: ${String(data?.backupPath ?? "")}`);
121
+ break;
122
+ case "remove":
123
+ lines.push(`Removed provider ${String(data?.provider ?? "")}. Backup: ${String(data?.backupPath ?? "")}`);
124
+ break;
125
+ case "doctor": {
126
+ const healthy = Boolean(data?.healthy);
127
+ lines.push(healthy ? "No issues found." : "Issues found:");
128
+ const issues = data?.issues ?? [];
129
+ for (const issue of issues) {
130
+ lines.push(`${issue.code}: ${issue.message}`);
131
+ }
132
+ break;
133
+ }
134
+ case "rollback":
135
+ lines.push(`Rollback restored files from ${String(data?.backupPath ?? "")}.`);
136
+ break;
137
+ default:
138
+ lines.push(JSON.stringify(data, null, 2));
139
+ break;
140
+ }
141
+ // Emit warnings after the primary payload so the main outcome remains easy to scan.
142
+ for (const warning of warnings) {
143
+ lines.push(`Warning: ${warning}`);
144
+ }
145
+ return lines;
146
+ }
147
+ /**
148
+ * Writes one rendered line to either stdout or stderr.
149
+ */
150
+ function printText(message, toStderr = false) {
151
+ if (toStderr) {
152
+ process.stderr.write(`${message}\n`);
153
+ return;
154
+ }
155
+ process.stdout.write(`${message}\n`);
156
+ }
package/dist/cli.js CHANGED
@@ -1,30 +1,190 @@
1
1
  #!/usr/bin/env node
2
2
  "use strict";
3
- const args = process.argv.slice(2);
4
- const version = "0.0.1";
5
- const helpText = `codex-switch
6
-
7
- Bootstrap release for the future Codex provider/profile switching CLI.
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.printHelp = printHelp;
5
+ exports.printVersion = printVersion;
6
+ exports.executeCommand = executeCommand;
7
+ const add_provider_1 = require("./app/add-provider");
8
+ const export_providers_1 = require("./app/export-providers");
9
+ const get_current_profile_1 = require("./app/get-current-profile");
10
+ const get_status_1 = require("./app/get-status");
11
+ const import_providers_1 = require("./app/import-providers");
12
+ const list_providers_1 = require("./app/list-providers");
13
+ const remove_provider_1 = require("./app/remove-provider");
14
+ const rollback_latest_1 = require("./app/rollback-latest");
15
+ const run_doctor_1 = require("./app/run-doctor");
16
+ const switch_provider_1 = require("./app/switch-provider");
17
+ const errors_1 = require("./domain/errors");
18
+ const codex_paths_1 = require("./infra/codex-paths");
19
+ const args_1 = require("./cli/args");
20
+ const output_1 = require("./cli/output");
21
+ const VERSION = "0.0.2";
22
+ const HELP_TEXT = `codex-switch
8
23
 
9
24
  Usage:
10
- codexs
11
- codexs --help
12
- codexs --version
25
+ codexs <command> [options]
13
26
 
14
- Status:
15
- This package currently reserves the npm scope and exposes the planned CLI entrypoint.
16
- The full switching workflow is not implemented yet.
27
+ Commands:
28
+ codexs list
29
+ codexs current
30
+ codexs switch <provider> [--no-login]
31
+ codexs status
32
+ codexs import <file>
33
+ codexs export <file> [--force]
34
+ codexs add <provider> --profile <name> --api-key <key> [--base-url <url>] [--note <text>] [--tag <tag> ...]
35
+ codexs remove <provider> --force
36
+ codexs doctor
37
+ codexs rollback
17
38
 
18
- Docs:
19
- https://github.com/minniexcode/codex-switch
20
- `;
21
- if (args.includes("--version") || args.includes("-v")) {
22
- console.log(version);
23
- process.exit(0);
39
+ Global options:
40
+ --json
41
+ --codex-dir <path>
42
+ --help
43
+ --version`;
44
+ /**
45
+ * Prints the command help text to stdout.
46
+ */
47
+ function printHelp() {
48
+ process.stdout.write(`${HELP_TEXT}\n`);
49
+ }
50
+ /**
51
+ * Prints the current CLI version to stdout.
52
+ */
53
+ function printVersion() {
54
+ process.stdout.write(`${VERSION}\n`);
55
+ }
56
+ /**
57
+ * Parses arguments, dispatches the selected command, and renders the final output.
58
+ */
59
+ function main() {
60
+ const parsed = (0, args_1.parseArgs)(process.argv.slice(2));
61
+ if (!parsed.command) {
62
+ printHelp();
63
+ process.exit(0);
64
+ }
65
+ if (parsed.command === "help") {
66
+ printHelp();
67
+ process.exit(0);
68
+ }
69
+ if (parsed.command === "version") {
70
+ printVersion();
71
+ process.exit(0);
72
+ }
73
+ const ctx = {
74
+ command: parsed.command,
75
+ options: parsed.globalOptions,
76
+ };
77
+ executeCommand(ctx, parsed)
78
+ .then((result) => {
79
+ (0, output_1.outputSuccess)(ctx, result);
80
+ })
81
+ .catch((error) => {
82
+ (0, output_1.outputFailure)(ctx, (0, errors_1.normalizeError)(error));
83
+ });
84
+ }
85
+ /**
86
+ * Dispatches a parsed CLI command into the application layer.
87
+ */
88
+ async function executeCommand(ctx, parsed) {
89
+ const paths = (0, codex_paths_1.createCodexPaths)(ctx.options.codexDir);
90
+ switch (ctx.command) {
91
+ case "list":
92
+ return (0, list_providers_1.listProviders)(paths.providersPath);
93
+ case "current":
94
+ return (0, get_current_profile_1.getCurrentProfile)(paths.configPath);
95
+ case "status":
96
+ return (0, get_status_1.getStatus)(paths.codexDir, paths.configPath, paths.providersPath);
97
+ case "switch": {
98
+ const providerName = parsed.positionals[0];
99
+ if (!providerName) {
100
+ throw (0, errors_1.cliError)("PROVIDER_NOT_FOUND", "Missing provider name for switch command.");
101
+ }
102
+ return (0, switch_provider_1.switchProvider)({
103
+ codexDir: paths.codexDir,
104
+ backupsDir: paths.backupsDir,
105
+ latestBackupPath: paths.latestBackupPath,
106
+ configPath: paths.configPath,
107
+ providersPath: paths.providersPath,
108
+ authPath: paths.authPath,
109
+ providerName,
110
+ noLogin: (0, args_1.hasFlag)(parsed.commandOptions, "--no-login"),
111
+ });
112
+ }
113
+ case "import": {
114
+ const sourceFile = parsed.positionals[0];
115
+ if (!sourceFile) {
116
+ throw (0, errors_1.cliError)("INVALID_IMPORT_FILE", "Missing import file path.");
117
+ }
118
+ return (0, import_providers_1.importProviders)({
119
+ codexDir: paths.codexDir,
120
+ backupsDir: paths.backupsDir,
121
+ latestBackupPath: paths.latestBackupPath,
122
+ providersPath: paths.providersPath,
123
+ sourceFile,
124
+ });
125
+ }
126
+ case "export": {
127
+ const targetFile = parsed.positionals[0];
128
+ if (!targetFile) {
129
+ throw (0, errors_1.cliError)("INVALID_IMPORT_FILE", "Missing export file path.");
130
+ }
131
+ return (0, export_providers_1.exportProviders)({
132
+ providersPath: paths.providersPath,
133
+ targetFile,
134
+ force: (0, args_1.hasFlag)(parsed.commandOptions, "--force"),
135
+ });
136
+ }
137
+ case "add": {
138
+ const providerName = parsed.positionals[0];
139
+ if (!providerName) {
140
+ throw (0, errors_1.cliError)("INVALID_IMPORT_FILE", "Missing provider name for add command.");
141
+ }
142
+ const profile = (0, args_1.getSingleOption)(parsed.commandOptions, "--profile");
143
+ const apiKey = (0, args_1.getSingleOption)(parsed.commandOptions, "--api-key");
144
+ if (!profile || !apiKey) {
145
+ throw (0, errors_1.cliError)("INVALID_IMPORT_FILE", "add requires --profile and --api-key.");
146
+ }
147
+ return (0, add_provider_1.addProvider)({
148
+ codexDir: paths.codexDir,
149
+ backupsDir: paths.backupsDir,
150
+ latestBackupPath: paths.latestBackupPath,
151
+ providersPath: paths.providersPath,
152
+ providerName,
153
+ profile,
154
+ apiKey,
155
+ baseUrl: (0, args_1.getSingleOption)(parsed.commandOptions, "--base-url", false),
156
+ note: (0, args_1.getSingleOption)(parsed.commandOptions, "--note", false),
157
+ tags: parsed.commandOptions.get("--tag") ?? [],
158
+ });
159
+ }
160
+ case "remove": {
161
+ const providerName = parsed.positionals[0];
162
+ if (!providerName) {
163
+ throw (0, errors_1.cliError)("PROVIDER_NOT_FOUND", "Missing provider name for remove command.");
164
+ }
165
+ if (!(0, args_1.hasFlag)(parsed.commandOptions, "--force")) {
166
+ throw (0, errors_1.cliError)("INVALID_IMPORT_FILE", "remove requires --force.");
167
+ }
168
+ return (0, remove_provider_1.removeProvider)({
169
+ codexDir: paths.codexDir,
170
+ backupsDir: paths.backupsDir,
171
+ latestBackupPath: paths.latestBackupPath,
172
+ providersPath: paths.providersPath,
173
+ providerName,
174
+ });
175
+ }
176
+ case "doctor":
177
+ return (0, run_doctor_1.runDoctor)({
178
+ codexDir: paths.codexDir,
179
+ configPath: paths.configPath,
180
+ providersPath: paths.providersPath,
181
+ });
182
+ case "rollback":
183
+ return (0, rollback_latest_1.rollbackLatest)(paths.latestBackupPath);
184
+ default:
185
+ throw (0, errors_1.cliError)("INVALID_IMPORT_FILE", `Unknown command: ${ctx.command}`);
186
+ }
24
187
  }
25
- if (args.length === 0 || args.includes("--help") || args.includes("-h")) {
26
- console.log(helpText);
27
- process.exit(0);
188
+ if (require.main === module) {
189
+ main();
28
190
  }
29
- console.error(`Command not implemented yet: ${args.join(" ")}\nRun "codexs --help" for the current bootstrap status.`);
30
- process.exit(1);
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,106 @@
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.parseTopLevelProfile = parseTopLevelProfile;
37
+ exports.parseProfileNames = parseProfileNames;
38
+ exports.replaceTopLevelProfile = replaceTopLevelProfile;
39
+ const os = __importStar(require("node:os"));
40
+ /**
41
+ * Reads the active top-level profile from config.toml content.
42
+ */
43
+ function parseTopLevelProfile(configContent) {
44
+ let inRoot = true;
45
+ for (const line of configContent.split(/\r?\n/)) {
46
+ const trimmed = line.trim();
47
+ if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
48
+ inRoot = false;
49
+ continue;
50
+ }
51
+ if (!inRoot || trimmed === "" || trimmed.startsWith("#")) {
52
+ continue;
53
+ }
54
+ const match = trimmed.match(/^profile\s*=\s*["']([^"']+)["']/);
55
+ if (match) {
56
+ return match[1];
57
+ }
58
+ }
59
+ return null;
60
+ }
61
+ /**
62
+ * Collects all named profile sections declared in config.toml content.
63
+ */
64
+ function parseProfileNames(configContent) {
65
+ const result = new Set();
66
+ for (const line of configContent.split(/\r?\n/)) {
67
+ const trimmed = line.trim();
68
+ const match = trimmed.match(/^\[profiles\.([^\]]+)\]$/);
69
+ if (match) {
70
+ result.add(match[1]);
71
+ }
72
+ }
73
+ return result;
74
+ }
75
+ /**
76
+ * Replaces or inserts the top-level profile assignment while preserving the rest of the file.
77
+ */
78
+ function replaceTopLevelProfile(configContent, profile) {
79
+ const lines = configContent.split(/\r?\n/);
80
+ let inRoot = true;
81
+ let replaced = false;
82
+ const nextLines = lines.map((line) => {
83
+ const trimmed = line.trim();
84
+ if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
85
+ // Only the root section may contain the active `profile = ...` switch.
86
+ inRoot = false;
87
+ return line;
88
+ }
89
+ if (!replaced && inRoot && /^profile\s*=/.test(trimmed)) {
90
+ replaced = true;
91
+ return `profile = "${profile}"`;
92
+ }
93
+ return line;
94
+ });
95
+ if (!replaced) {
96
+ // When no root-level profile exists yet, insert it before the first section header.
97
+ const insertAt = nextLines.findIndex((line) => line.trim().startsWith("["));
98
+ if (insertAt === -1) {
99
+ nextLines.push(`profile = "${profile}"`);
100
+ }
101
+ else {
102
+ nextLines.splice(insertAt, 0, `profile = "${profile}"`);
103
+ }
104
+ }
105
+ return nextLines.join(os.EOL);
106
+ }
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.cliError = cliError;
4
+ exports.normalizeError = normalizeError;
5
+ /**
6
+ * Creates an Error instance enriched with a stable CLI error code and optional details.
7
+ */
8
+ function cliError(code, message, details) {
9
+ const error = new Error(message);
10
+ error.code = code;
11
+ error.details = details;
12
+ return error;
13
+ }
14
+ /**
15
+ * Normalizes unknown thrown values into the shared CLI error shape.
16
+ */
17
+ function normalizeError(error) {
18
+ if (error && typeof error === "object" && "code" in error && "message" in error) {
19
+ const candidate = error;
20
+ return {
21
+ code: candidate.code ?? "INVALID_IMPORT_FILE",
22
+ message: candidate.message ?? "Unknown error.",
23
+ details: candidate.details,
24
+ };
25
+ }
26
+ if (error instanceof Error) {
27
+ return {
28
+ code: "INVALID_IMPORT_FILE",
29
+ message: error.message,
30
+ };
31
+ }
32
+ return {
33
+ code: "INVALID_IMPORT_FILE",
34
+ message: String(error),
35
+ };
36
+ }