@minniexcode/codex-switch 0.0.4 → 0.0.6

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 (64) hide show
  1. package/README.md +35 -97
  2. package/dist/app/add-provider.js +40 -3
  3. package/dist/app/edit-provider.js +76 -3
  4. package/dist/app/export-providers.js +2 -2
  5. package/dist/app/get-current-profile.js +1 -1
  6. package/dist/app/get-status.js +10 -3
  7. package/dist/app/import-providers.js +47 -3
  8. package/dist/app/list-backups.js +1 -1
  9. package/dist/app/list-config-profiles.js +30 -0
  10. package/dist/app/list-providers.js +1 -1
  11. package/dist/app/remove-provider.js +35 -3
  12. package/dist/app/rollback-backup.js +1 -1
  13. package/dist/app/rollback-latest.js +1 -1
  14. package/dist/app/run-doctor.js +44 -26
  15. package/dist/app/run-mutation.js +2 -2
  16. package/dist/app/setup-codex.js +37 -20
  17. package/dist/app/show-config.js +34 -0
  18. package/dist/app/show-provider.js +1 -1
  19. package/dist/app/switch-provider.js +8 -5
  20. package/dist/cli/add-interactive.js +7 -106
  21. package/dist/cli/args.js +5 -126
  22. package/dist/cli/help.js +5 -276
  23. package/dist/cli/interactive.js +16 -171
  24. package/dist/cli/output.js +23 -1
  25. package/dist/cli/prompt.js +3 -108
  26. package/dist/cli.js +10 -315
  27. package/dist/commands/args.js +132 -0
  28. package/dist/commands/dispatch.js +16 -0
  29. package/dist/commands/handlers.js +391 -0
  30. package/dist/commands/help.js +119 -0
  31. package/dist/commands/registry.js +291 -0
  32. package/dist/commands/types.js +2 -0
  33. package/dist/domain/config.js +548 -39
  34. package/dist/infra/backup-repo.js +8 -208
  35. package/dist/infra/codex-cli.js +8 -113
  36. package/dist/infra/codex-discovery.js +3 -41
  37. package/dist/infra/codex-paths.js +5 -69
  38. package/dist/infra/config-repo.js +161 -9
  39. package/dist/infra/fs-utils.js +7 -95
  40. package/dist/infra/lock-repo.js +3 -97
  41. package/dist/infra/providers-repo.js +7 -96
  42. package/dist/interaction/add-interactive.js +108 -0
  43. package/dist/interaction/interactive.js +216 -0
  44. package/dist/interaction/prompt.js +110 -0
  45. package/dist/runtime/codex-cli.js +130 -0
  46. package/dist/runtime/codex-probe.js +50 -0
  47. package/dist/runtime/types.js +2 -0
  48. package/dist/storage/backup-repo.js +210 -0
  49. package/dist/storage/codex-paths.js +71 -0
  50. package/dist/storage/config-repo.js +208 -0
  51. package/dist/storage/fs-utils.js +97 -0
  52. package/dist/storage/lock-repo.js +99 -0
  53. package/dist/storage/providers-repo.js +98 -0
  54. package/docs/Design/codex-switch-v0.0.5-design.md +932 -0
  55. package/docs/Design/codex-switch-v0.0.6-design.md +708 -0
  56. package/docs/PRD/codex-switch-prd-v0.0.5-to-v0.1.0.md +340 -0
  57. package/docs/PRD/codex-switch-prd-v0.1.0.md +215 -291
  58. package/docs/PRD/codex-switch-prd.md +1 -1
  59. package/docs/cli-usage.md +2 -1
  60. package/docs/codex-switch-technical-architecture.md +73 -4
  61. package/docs/test-report-0.0.5.md +163 -0
  62. package/docs/testing.md +131 -0
  63. package/package.json +1 -1
  64. /package/docs/{codex-switch-v0.0.4-design.md → Design/codex-switch-v0.0.4-design.md} +0 -0
@@ -2,7 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.rollbackBackup = rollbackBackup;
4
4
  const errors_1 = require("../domain/errors");
5
- const backup_repo_1 = require("../infra/backup-repo");
5
+ const backup_repo_1 = require("../storage/backup-repo");
6
6
  /**
7
7
  * Restores either the latest backup or a specific historical backup by id.
8
8
  */
@@ -2,7 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.rollbackLatest = rollbackLatest;
4
4
  const errors_1 = require("../domain/errors");
5
- const backup_repo_1 = require("../infra/backup-repo");
5
+ const backup_repo_1 = require("../storage/backup-repo");
6
6
  /**
7
7
  * Restores the most recent mutation backup recorded by codex-switch.
8
8
  */
@@ -37,17 +37,18 @@ exports.runDoctor = runDoctor;
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 codex_cli_1 = require("../infra/codex-cli");
41
- const providers_repo_1 = require("../infra/providers-repo");
40
+ const config_repo_1 = require("../storage/config-repo");
41
+ const providers_repo_1 = require("../storage/providers-repo");
42
42
  const errors_1 = require("../domain/errors");
43
+ const codex_probe_1 = require("../runtime/codex-probe");
43
44
  /**
44
45
  * Performs consistency checks across config.toml, providers.json, and the local Codex CLI.
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,21 +96,20 @@ 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
- const codexCheck = (0, codex_cli_1.checkCodexAvailable)();
99
+ const codexCheck = (0, codex_probe_1.probeCodexRuntime)();
113
100
  if (!codexCheck.ok) {
101
+ const message = codexCheck.reason === "missing"
102
+ ? "codex CLI is not available on PATH."
103
+ : codexCheck.reason === "unsupported"
104
+ ? "codex CLI version is below the supported minimum."
105
+ : "codex CLI probe failed.";
114
106
  issues.push({
115
- code: "CODEX_LOGIN_FAILED",
116
- message: "codex CLI is not available.",
107
+ code: codexCheck.reason === "unsupported"
108
+ ? "CODEX_VERSION_UNSUPPORTED"
109
+ : codexCheck.reason === "missing"
110
+ ? "CODEX_NOT_INSTALLED"
111
+ : "CODEX_LOGIN_FAILED",
112
+ message,
117
113
  cause: codexCheck.cause,
118
114
  });
119
115
  }
@@ -128,3 +124,25 @@ function runDoctor(args) {
128
124
  warnings: issues.length === 0 ? [] : [`doctor found ${issues.length} issue(s)`],
129
125
  };
130
126
  }
127
+ function renderConfigIssueMessage(issue) {
128
+ switch (issue.code) {
129
+ case "ORPHANED_PROFILE_REFERENCE":
130
+ return `Profile "${issue.profile}" is referenced by providers but missing from config.toml.`;
131
+ case "UNMANAGED_ACTIVE_PROFILE":
132
+ return `Active profile "${issue.profile}" is not mapped by providers.json.`;
133
+ case "SHARED_PROFILE_REFERENCE":
134
+ return `Profile "${issue.profile}" is shared by multiple providers.`;
135
+ case "ORPHANED_PROFILE_SECTION":
136
+ return `Profile section "${issue.profile}" is not linked to any provider.`;
137
+ case "MODEL_PROVIDER_MISSING":
138
+ return `Profile "${issue.profile}" is missing model_provider.`;
139
+ case "MODEL_PROVIDER_NAME_MISMATCH":
140
+ return `Profile "${issue.profile}" must use matching model_provider name "${issue.profile}", found "${issue.modelProvider}".`;
141
+ case "MODEL_PROVIDER_SECTION_MISSING":
142
+ return `Model provider section "${issue.modelProvider}" for profile "${issue.profile}" is missing from config.toml.`;
143
+ case "MODEL_PROVIDER_BASE_URL_MISSING":
144
+ return `Model provider section "${issue.modelProvider}" for profile "${issue.profile}" is missing base_url.`;
145
+ case "DESTRUCTIVE_REMOVE_BLOCKED":
146
+ return `Provider "${issue.provider}" cannot be removed while "${issue.activeProfile}" remains active.`;
147
+ }
148
+ }
@@ -2,8 +2,8 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.runMutation = runMutation;
4
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");
5
+ const backup_repo_1 = require("../storage/backup-repo");
6
+ const lock_repo_1 = require("../storage/lock-repo");
7
7
  /**
8
8
  * Runs a write operation under a lock with automatic backup and rollback handling.
9
9
  */
@@ -37,11 +37,11 @@ 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");
41
- const codex_cli_1 = require("../infra/codex-cli");
42
- const config_repo_1 = require("../infra/config-repo");
43
- const fs_utils_1 = require("../infra/fs-utils");
44
- const providers_repo_1 = require("../infra/providers-repo");
40
+ const config_1 = require("../domain/config");
41
+ const codex_cli_1 = require("../runtime/codex-cli");
42
+ const config_repo_1 = require("../storage/config-repo");
43
+ const fs_utils_1 = require("../storage/fs-utils");
44
+ const providers_repo_1 = require("../storage/providers-repo");
45
45
  const run_doctor_1 = require("./run-doctor");
46
46
  const run_mutation_1 = require("./run-mutation");
47
47
  const MIN_CODEX_VERSION = "0.0.1";
@@ -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.modelProvider === view.name && 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, model_provider, and a matching model_providers 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("../storage/config-repo");
7
+ const providers_repo_1 = require("../storage/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
+ }
@@ -2,7 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.showProvider = showProvider;
4
4
  const providers_1 = require("../domain/providers");
5
- const providers_repo_1 = require("../infra/providers-repo");
5
+ const providers_repo_1 = require("../storage/providers-repo");
6
6
  /**
7
7
  * Returns a single provider record, with text-mode callers able to use a masked preview.
8
8
  */
@@ -2,10 +2,10 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.switchProvider = switchProvider;
4
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");
5
+ const config_repo_1 = require("../storage/config-repo");
6
+ const providers_repo_1 = require("../storage/providers-repo");
8
7
  const run_mutation_1 = require("./run-mutation");
8
+ const codex_cli_1 = require("../runtime/codex-cli");
9
9
  /**
10
10
  * Switches the active Codex profile and optionally refreshes the CLI login session.
11
11
  */
@@ -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
  }
@@ -1,108 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.COMMON_TAG_CHOICES = void 0;
4
- exports.collectAddInput = collectAddInput;
5
- exports.createNonInteractiveAddError = createNonInteractiveAddError;
6
- exports.promptTags = promptTags;
7
- exports.parseTags = parseTags;
8
- const errors_1 = require("../domain/errors");
9
- exports.COMMON_TAG_CHOICES = ["free", "paid", "daily", "backup"];
10
- /**
11
- * Collects add command inputs interactively when required values are missing.
12
- */
13
- async function collectAddInput(runtime, defaults, providerExists) {
14
- runtime.writeLine("Interactive add mode");
15
- runtime.writeLine("Provide the missing required fields. Press Enter to skip optional fields.");
16
- const providerName = defaults.providerName
17
- ? normalizeRequiredValue(defaults.providerName)
18
- : await promptProviderName(runtime, providerExists);
19
- const profile = defaults.profile ? normalizeRequiredValue(defaults.profile) : await promptRequiredValue(runtime, "Profile");
20
- const apiKey = defaults.apiKey
21
- ? normalizeRequiredValue(defaults.apiKey)
22
- : await promptConfirmedSecret(runtime, "API key", "Confirm API key");
23
- const baseUrl = defaults.baseUrl ?? normalizeOptionalValue(await runtime.inputText("Base URL (optional)"));
24
- const note = defaults.note ?? normalizeOptionalValue(await runtime.inputText("Note (optional)"));
25
- const tags = defaults.tags.length > 0 ? defaults.tags : await promptTags(runtime);
26
- return {
27
- providerName,
28
- profile,
29
- apiKey,
30
- baseUrl,
31
- note,
32
- tags,
33
- };
34
- }
35
- /**
36
- * Throws a consistent error when interactive add is unavailable.
37
- */
38
- function createNonInteractiveAddError() {
39
- return (0, errors_1.cliError)("INVALID_ARGUMENT", "add requires <provider>, --profile, and --api-key when running without an interactive TTY.", {
40
- suggestion: "Run in a terminal TTY or pass all required values explicitly.",
41
- });
42
- }
43
- async function promptProviderName(runtime, providerExists) {
44
- while (true) {
45
- const providerName = await promptRequiredValue(runtime, "Provider name");
46
- if (providerExists(providerName)) {
47
- runtime.writeLine(`Provider "${providerName}" already exists. Choose a different name.`);
48
- continue;
49
- }
50
- return providerName;
51
- }
52
- }
53
- async function promptRequiredValue(runtime, label) {
54
- while (true) {
55
- const value = normalizeRequiredValue(await runtime.inputText(label));
56
- if (value.length > 0) {
57
- return value;
58
- }
59
- runtime.writeLine(`${label} is required.`);
60
- }
61
- }
62
- async function promptConfirmedSecret(runtime, label, confirmationLabel) {
63
- while (true) {
64
- const first = normalizeRequiredValue(await runtime.inputSecret(label));
65
- if (first.length === 0) {
66
- runtime.writeLine(`${label} is required.`);
67
- continue;
68
- }
69
- const second = normalizeRequiredValue(await runtime.inputSecret(confirmationLabel));
70
- if (second.length === 0) {
71
- runtime.writeLine(`${confirmationLabel} is required.`);
72
- continue;
73
- }
74
- if (first !== second) {
75
- runtime.writeLine("API key entries did not match. Try again.");
76
- continue;
77
- }
78
- return first;
79
- }
80
- }
81
- function normalizeRequiredValue(value) {
82
- return value.trim();
83
- }
84
- function normalizeOptionalValue(value) {
85
- const normalized = value.trim();
86
- return normalized === "" ? null : normalized;
87
- }
88
- async function promptTags(runtime, defaults = []) {
89
- const defaultPresetTags = defaults.filter(isCommonTag);
90
- const defaultCustomTags = defaults.filter((tag) => !isCommonTag(tag));
91
- const presetTags = await runtime.selectMany("Select tags (optional)", exports.COMMON_TAG_CHOICES.map((tag) => ({ value: tag, label: tag })), { defaultValues: defaultPresetTags });
92
- const customTags = parseTags(await runtime.inputText("Custom tags (optional, comma-separated)", {
93
- defaultValue: defaultCustomTags.join(", "),
94
- }));
95
- return dedupeTags([...presetTags, ...customTags]);
96
- }
97
- function parseTags(value) {
98
- return dedupeTags(value
99
- .split(",")
100
- .map((tag) => tag.trim())
101
- .filter((tag) => tag.length > 0));
102
- }
103
- function isCommonTag(tag) {
104
- return exports.COMMON_TAG_CHOICES.includes(tag);
105
- }
106
- function dedupeTags(tags) {
107
- return Array.from(new Set(tags));
108
- }
3
+ exports.promptTags = exports.parseTags = exports.createNonInteractiveAddError = exports.COMMON_TAG_CHOICES = exports.collectAddInput = void 0;
4
+ var add_interactive_1 = require("../interaction/add-interactive");
5
+ Object.defineProperty(exports, "collectAddInput", { enumerable: true, get: function () { return add_interactive_1.collectAddInput; } });
6
+ Object.defineProperty(exports, "COMMON_TAG_CHOICES", { enumerable: true, get: function () { return add_interactive_1.COMMON_TAG_CHOICES; } });
7
+ Object.defineProperty(exports, "createNonInteractiveAddError", { enumerable: true, get: function () { return add_interactive_1.createNonInteractiveAddError; } });
8
+ Object.defineProperty(exports, "parseTags", { enumerable: true, get: function () { return add_interactive_1.parseTags; } });
9
+ Object.defineProperty(exports, "promptTags", { enumerable: true, get: function () { return add_interactive_1.promptTags; } });
package/dist/cli/args.js CHANGED
@@ -1,128 +1,7 @@
1
1
  "use strict";
2
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
- let json = false;
13
- let codexDir = (0, codex_paths_1.resolveCodexDir)();
14
- const remaining = [];
15
- for (let index = 0; index < argv.length; index += 1) {
16
- const value = argv[index];
17
- if (value === "--json") {
18
- json = true;
19
- continue;
20
- }
21
- if (value === "--codex-dir") {
22
- const next = argv[index + 1];
23
- if (!next) {
24
- throw (0, errors_1.cliError)("INVALID_ARGUMENT", "--codex-dir requires a path value.");
25
- }
26
- codexDir = (0, codex_paths_1.resolveCodexDir)(next);
27
- index += 1;
28
- continue;
29
- }
30
- remaining.push(value);
31
- }
32
- if (remaining[0] === "help") {
33
- const helpTarget = remaining[1] === "backups" && remaining[2] === "list" ? "backups" : remaining[1] ?? null;
34
- return {
35
- command: null,
36
- positionals: [],
37
- globalOptions: {
38
- json,
39
- codexDir,
40
- },
41
- commandOptions: new Map(),
42
- helpRequested: true,
43
- helpTarget,
44
- versionRequested: false,
45
- };
46
- }
47
- const versionRequested = remaining.includes("--version") || remaining.includes("-v");
48
- if (versionRequested) {
49
- return defaultParsed(null, {
50
- json,
51
- codexDir,
52
- versionRequested: true,
53
- });
54
- }
55
- const commandToken = remaining[0] ?? null;
56
- const command = commandToken === "backups" && remaining[1] === "list" ? "backups-list" : commandToken;
57
- const positionals = [];
58
- const commandOptions = new Map();
59
- let helpRequested = false;
60
- const startIndex = command === "backups-list" ? 2 : 1;
61
- for (let index = startIndex; index < remaining.length; index += 1) {
62
- const value = remaining[index];
63
- if (value === "--help" || value === "-h") {
64
- helpRequested = true;
65
- continue;
66
- }
67
- if (value.startsWith("--")) {
68
- const optionName = value;
69
- const next = remaining[index + 1];
70
- if (!next || next.startsWith("--")) {
71
- // Boolean flags are stored as "true" so later access uses one uniform map shape.
72
- commandOptions.set(optionName, ["true"]);
73
- continue;
74
- }
75
- const existing = commandOptions.get(optionName) ?? [];
76
- existing.push(next);
77
- commandOptions.set(optionName, existing);
78
- index += 1;
79
- continue;
80
- }
81
- positionals.push(value);
82
- }
83
- return {
84
- command,
85
- positionals,
86
- globalOptions: {
87
- json,
88
- codexDir,
89
- },
90
- commandOptions,
91
- helpRequested,
92
- helpTarget: helpRequested ? (command === "backups-list" ? "backups" : command) : null,
93
- versionRequested: false,
94
- };
95
- }
96
- /**
97
- * Creates a parsed result for built-in synthetic commands such as help/version.
98
- */
99
- function defaultParsed(command, overrides) {
100
- return {
101
- command,
102
- positionals: [],
103
- globalOptions: {
104
- json: overrides?.json ?? false,
105
- codexDir: overrides?.codexDir ?? (0, codex_paths_1.resolveCodexDir)(),
106
- },
107
- commandOptions: new Map(),
108
- helpRequested: overrides?.helpRequested ?? false,
109
- helpTarget: overrides?.helpTarget ?? null,
110
- versionRequested: overrides?.versionRequested ?? false,
111
- };
112
- }
113
- /**
114
- * Checks whether a boolean-style option was supplied.
115
- */
116
- function hasFlag(options, name) {
117
- return options.has(name);
118
- }
119
- /**
120
- * Returns the last supplied value for a single-valued command option.
121
- */
122
- function getSingleOption(options, name, required = true) {
123
- const values = options.get(name) ?? [];
124
- if (values.length === 0) {
125
- return required ? null : null;
126
- }
127
- return values[values.length - 1];
128
- }
3
+ exports.parseArgs = exports.hasFlag = exports.getSingleOption = void 0;
4
+ var args_1 = require("../commands/args");
5
+ Object.defineProperty(exports, "getSingleOption", { enumerable: true, get: function () { return args_1.getSingleOption; } });
6
+ Object.defineProperty(exports, "hasFlag", { enumerable: true, get: function () { return args_1.hasFlag; } });
7
+ Object.defineProperty(exports, "parseArgs", { enumerable: true, get: function () { return args_1.parseArgs; } });