@minniexcode/codex-switch 0.0.3 → 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.AI.md +8 -3
- package/README.md +160 -91
- package/dist/app/add-provider.js +32 -1
- package/dist/app/edit-provider.js +137 -0
- package/dist/app/get-status.js +9 -2
- package/dist/app/import-providers.js +47 -2
- package/dist/app/list-backups.js +17 -0
- package/dist/app/list-config-profiles.js +29 -0
- package/dist/app/remove-provider.js +34 -2
- package/dist/app/rollback-backup.js +30 -0
- package/dist/app/run-doctor.js +22 -21
- package/dist/app/setup-codex.js +155 -0
- package/dist/app/show-config.js +34 -0
- package/dist/app/show-provider.js +22 -0
- package/dist/app/switch-provider.js +5 -2
- package/dist/cli/add-interactive.js +25 -31
- package/dist/cli/args.js +19 -5
- package/dist/cli/help.js +109 -14
- package/dist/cli/interactive.js +123 -8
- package/dist/cli/output.js +56 -1
- package/dist/cli/prompt.js +19 -2
- package/dist/cli.js +250 -13
- package/dist/domain/backups.js +103 -0
- package/dist/domain/config.js +471 -39
- package/dist/domain/errors.js +3 -3
- package/dist/domain/providers.js +10 -0
- package/dist/domain/setup.js +30 -0
- package/dist/infra/backup-repo.js +65 -6
- package/dist/infra/codex-cli.js +79 -2
- package/dist/infra/codex-discovery.js +10 -0
- package/dist/infra/codex-paths.js +14 -1
- package/dist/infra/config-repo.js +102 -9
- package/dist/infra/providers-repo.js +29 -0
- package/docs/Design/codex-switch-v0.0.4-design.md +874 -0
- package/docs/Design/codex-switch-v0.0.5-design.md +922 -0
- package/docs/PRD/codex-switch-prd-v0.0.5-to-v0.1.0.md +308 -0
- package/docs/PRD/codex-switch-prd-v0.1.0.md +343 -0
- package/docs/{codex-switch-prd.md → PRD/codex-switch-prd.md} +9 -5
- package/docs/cli-usage.md +580 -0
- package/docs/codex-switch-command-design.md +1 -1
- package/docs/codex-switch-product-overview.md +1 -1
- package/docs/codex-switch-product-research.md +2 -2
- package/docs/codex-switch-technical-architecture.md +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.listConfigProfilesView = listConfigProfilesView;
|
|
4
|
+
const config_1 = require("../domain/config");
|
|
5
|
+
const config_repo_1 = require("../infra/config-repo");
|
|
6
|
+
const providers_repo_1 = require("../infra/providers-repo");
|
|
7
|
+
/**
|
|
8
|
+
* Returns the lightweight config profile listing.
|
|
9
|
+
*/
|
|
10
|
+
function listConfigProfilesView(args) {
|
|
11
|
+
const document = (0, config_repo_1.readStructuredConfig)(args.configPath);
|
|
12
|
+
const providers = (0, providers_repo_1.readProvidersFileIfExists)(args.providersPath);
|
|
13
|
+
const profiles = (0, config_1.buildManagedProfileViews)(document, providers).map((profile) => ({
|
|
14
|
+
name: profile.name,
|
|
15
|
+
managed: profile.managed,
|
|
16
|
+
isActive: profile.isActive,
|
|
17
|
+
linkedProviders: profile.linkedProviders,
|
|
18
|
+
model: profile.model,
|
|
19
|
+
baseUrl: profile.baseUrl,
|
|
20
|
+
source: profile.source,
|
|
21
|
+
}));
|
|
22
|
+
return {
|
|
23
|
+
data: {
|
|
24
|
+
activeProfile: document.activeProfile,
|
|
25
|
+
profiles,
|
|
26
|
+
count: profiles.length,
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
}
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.removeProvider = removeProvider;
|
|
4
4
|
const errors_1 = require("../domain/errors");
|
|
5
|
+
const config_1 = require("../domain/config");
|
|
6
|
+
const config_repo_1 = require("../infra/config-repo");
|
|
5
7
|
const providers_repo_1 = require("../infra/providers-repo");
|
|
6
8
|
const run_mutation_1 = require("./run-mutation");
|
|
7
9
|
/**
|
|
@@ -9,22 +11,52 @@ const run_mutation_1 = require("./run-mutation");
|
|
|
9
11
|
*/
|
|
10
12
|
function removeProvider(args) {
|
|
11
13
|
const providers = (0, providers_repo_1.readProvidersFile)(args.providersPath);
|
|
12
|
-
|
|
14
|
+
const document = (0, config_repo_1.readStructuredConfig)(args.configPath);
|
|
15
|
+
const current = providers.providers[args.providerName];
|
|
16
|
+
if (!current) {
|
|
13
17
|
throw (0, errors_1.cliError)("PROVIDER_NOT_FOUND", `Provider "${args.providerName}" was not found.`);
|
|
14
18
|
}
|
|
15
19
|
const nextProviders = { ...providers.providers };
|
|
16
20
|
// Delete against a copied object so the original parsed state stays untouched.
|
|
17
21
|
delete nextProviders[args.providerName];
|
|
22
|
+
const remainingLinksByProfile = new Map();
|
|
23
|
+
for (const [name, provider] of Object.entries(nextProviders)) {
|
|
24
|
+
const list = remainingLinksByProfile.get(provider.profile) ?? [];
|
|
25
|
+
list.push(name);
|
|
26
|
+
remainingLinksByProfile.set(provider.profile, list);
|
|
27
|
+
}
|
|
28
|
+
const lifecycle = (0, config_1.planProfileLifecycleOutcome)({
|
|
29
|
+
providerName: args.providerName,
|
|
30
|
+
oldProfile: current.profile,
|
|
31
|
+
newProfile: null,
|
|
32
|
+
activeProfile: document.activeProfile,
|
|
33
|
+
remainingLinksByProfile,
|
|
34
|
+
switchToProfile: args.switchToProfile ?? null,
|
|
35
|
+
});
|
|
18
36
|
return (0, run_mutation_1.runMutation)({
|
|
19
37
|
codexDir: args.codexDir,
|
|
20
38
|
backupsDir: args.backupsDir,
|
|
21
39
|
latestBackupPath: args.latestBackupPath,
|
|
22
40
|
operation: "remove",
|
|
23
|
-
files: [
|
|
41
|
+
files: [
|
|
42
|
+
{ absolutePath: args.providersPath, relativePath: "providers.json" },
|
|
43
|
+
{ absolutePath: args.configPath, relativePath: "config.toml" },
|
|
44
|
+
],
|
|
24
45
|
mutate: () => {
|
|
46
|
+
const configPlan = (0, config_repo_1.createConfigMutationPlan)(document, {
|
|
47
|
+
deleteProfiles: lifecycle.deletedProfileSections,
|
|
48
|
+
setActiveProfile: lifecycle.nextActiveProfile,
|
|
49
|
+
});
|
|
25
50
|
(0, providers_repo_1.writeProvidersFile)(args.providersPath, { providers: nextProviders });
|
|
51
|
+
(0, config_repo_1.applyConfigMutation)(args.configPath, document, configPlan);
|
|
26
52
|
return {
|
|
27
53
|
provider: args.providerName,
|
|
54
|
+
createdProfileSections: configPlan.createdProfileSections,
|
|
55
|
+
deletedProfileSections: configPlan.deletedProfileSections,
|
|
56
|
+
keptSharedProfiles: lifecycle.keptSharedProfiles,
|
|
57
|
+
switchedActiveProfile: lifecycle.switchedActiveProfile,
|
|
58
|
+
adoptedProfiles: [],
|
|
59
|
+
repairedProfiles: [],
|
|
28
60
|
};
|
|
29
61
|
},
|
|
30
62
|
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.rollbackBackup = rollbackBackup;
|
|
4
|
+
const errors_1 = require("../domain/errors");
|
|
5
|
+
const backup_repo_1 = require("../infra/backup-repo");
|
|
6
|
+
/**
|
|
7
|
+
* Restores either the latest backup or a specific historical backup by id.
|
|
8
|
+
*/
|
|
9
|
+
function rollbackBackup(args) {
|
|
10
|
+
const manifest = args.backupId
|
|
11
|
+
? (0, backup_repo_1.loadManifestById)(args.backupsDir, args.backupId)
|
|
12
|
+
: (0, backup_repo_1.loadLatestManifest)(args.latestBackupPath);
|
|
13
|
+
try {
|
|
14
|
+
(0, backup_repo_1.restoreManifest)(manifest);
|
|
15
|
+
return {
|
|
16
|
+
data: {
|
|
17
|
+
restoredFiles: manifest.files.map((file) => file.relativePath),
|
|
18
|
+
backupId: args.backupId ?? null,
|
|
19
|
+
backupPath: manifest.backupDir,
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
throw (0, errors_1.cliError)("ROLLBACK_FAILED", "Rollback failed.", {
|
|
25
|
+
cause: (0, errors_1.normalizeError)(error).message,
|
|
26
|
+
backupPath: manifest.backupDir,
|
|
27
|
+
backupId: args.backupId ?? null,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
}
|
package/dist/app/run-doctor.js
CHANGED
|
@@ -38,6 +38,7 @@ const fs = __importStar(require("node:fs"));
|
|
|
38
38
|
const config_1 = require("../domain/config");
|
|
39
39
|
const runtime_state_1 = require("../domain/runtime-state");
|
|
40
40
|
const codex_cli_1 = require("../infra/codex-cli");
|
|
41
|
+
const config_repo_1 = require("../infra/config-repo");
|
|
41
42
|
const providers_repo_1 = require("../infra/providers-repo");
|
|
42
43
|
const errors_1 = require("../domain/errors");
|
|
43
44
|
/**
|
|
@@ -45,9 +46,9 @@ const errors_1 = require("../domain/errors");
|
|
|
45
46
|
*/
|
|
46
47
|
function runDoctor(args) {
|
|
47
48
|
const issues = [];
|
|
48
|
-
let configProfiles = new Set();
|
|
49
49
|
let currentProfile = null;
|
|
50
50
|
let providers = null;
|
|
51
|
+
let document = null;
|
|
51
52
|
if (!fs.existsSync(args.configPath)) {
|
|
52
53
|
issues.push({
|
|
53
54
|
code: "CONFIG_NOT_FOUND",
|
|
@@ -56,9 +57,8 @@ function runDoctor(args) {
|
|
|
56
57
|
});
|
|
57
58
|
}
|
|
58
59
|
else {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
currentProfile = (0, config_1.parseTopLevelProfile)(configContent);
|
|
60
|
+
document = (0, config_repo_1.readStructuredConfig)(args.configPath);
|
|
61
|
+
currentProfile = document.activeProfile;
|
|
62
62
|
if (!currentProfile) {
|
|
63
63
|
issues.push({
|
|
64
64
|
code: "PROFILE_NOT_FOUND",
|
|
@@ -77,14 +77,11 @@ function runDoctor(args) {
|
|
|
77
77
|
else {
|
|
78
78
|
try {
|
|
79
79
|
providers = (0, providers_repo_1.readProvidersFile)(args.providersPath);
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
if (!configProfiles.has(provider.profile)) {
|
|
80
|
+
if (document) {
|
|
81
|
+
for (const issue of (0, config_1.collectConfigConsistencyIssues)(document, providers)) {
|
|
83
82
|
issues.push({
|
|
84
|
-
|
|
85
|
-
message:
|
|
86
|
-
provider: name,
|
|
87
|
-
profile: provider.profile,
|
|
83
|
+
...issue,
|
|
84
|
+
message: renderConfigIssueMessage(issue),
|
|
88
85
|
});
|
|
89
86
|
}
|
|
90
87
|
}
|
|
@@ -99,16 +96,6 @@ function runDoctor(args) {
|
|
|
99
96
|
}
|
|
100
97
|
}
|
|
101
98
|
const drift = (0, runtime_state_1.inspectLiveStateDrift)(currentProfile, providers);
|
|
102
|
-
if (drift.canBackfillActiveProvider) {
|
|
103
|
-
// Distinguish unmanaged live state from hard parse/configuration errors.
|
|
104
|
-
issues.push({
|
|
105
|
-
code: "LIVE_STATE_DRIFT",
|
|
106
|
-
message: `Active profile "${drift.currentProfile}" is present in config.toml but not mapped by providers.json.`,
|
|
107
|
-
currentProfile: drift.currentProfile,
|
|
108
|
-
suggestedAction: "backfill-active-provider",
|
|
109
|
-
storage: (0, runtime_state_1.getStorageRoles)(),
|
|
110
|
-
});
|
|
111
|
-
}
|
|
112
99
|
const codexCheck = (0, codex_cli_1.checkCodexAvailable)();
|
|
113
100
|
if (!codexCheck.ok) {
|
|
114
101
|
issues.push({
|
|
@@ -128,3 +115,17 @@ function runDoctor(args) {
|
|
|
128
115
|
warnings: issues.length === 0 ? [] : [`doctor found ${issues.length} issue(s)`],
|
|
129
116
|
};
|
|
130
117
|
}
|
|
118
|
+
function renderConfigIssueMessage(issue) {
|
|
119
|
+
switch (issue.code) {
|
|
120
|
+
case "ORPHANED_PROFILE_REFERENCE":
|
|
121
|
+
return `Profile "${issue.profile}" is referenced by providers but missing from config.toml.`;
|
|
122
|
+
case "UNMANAGED_ACTIVE_PROFILE":
|
|
123
|
+
return `Active profile "${issue.profile}" is not mapped by providers.json.`;
|
|
124
|
+
case "SHARED_PROFILE_REFERENCE":
|
|
125
|
+
return `Profile "${issue.profile}" is shared by multiple providers.`;
|
|
126
|
+
case "ORPHANED_PROFILE_SECTION":
|
|
127
|
+
return `Profile section "${issue.profile}" is not linked to any provider.`;
|
|
128
|
+
case "DESTRUCTIVE_REMOVE_BLOCKED":
|
|
129
|
+
return `Provider "${issue.provider}" cannot be removed while "${issue.activeProfile}" remains active.`;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
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.setupCodex = setupCodex;
|
|
37
|
+
const fs = __importStar(require("node:fs"));
|
|
38
|
+
const setup_1 = require("../domain/setup");
|
|
39
|
+
const errors_1 = require("../domain/errors");
|
|
40
|
+
const config_1 = require("../domain/config");
|
|
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");
|
|
45
|
+
const run_doctor_1 = require("./run-doctor");
|
|
46
|
+
const run_mutation_1 = require("./run-mutation");
|
|
47
|
+
const MIN_CODEX_VERSION = "0.0.1";
|
|
48
|
+
/**
|
|
49
|
+
* Bootstraps a managed providers.json from the existing Codex directory.
|
|
50
|
+
*/
|
|
51
|
+
function setupCodex(args) {
|
|
52
|
+
const available = (0, codex_cli_1.checkCodexAvailable)();
|
|
53
|
+
if (!available.ok) {
|
|
54
|
+
throw (0, errors_1.cliError)("CODEX_NOT_INSTALLED", "codex CLI is not available.", {
|
|
55
|
+
cause: available.cause,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
const version = (0, codex_cli_1.checkCodexVersion)(MIN_CODEX_VERSION);
|
|
59
|
+
if (!version.ok) {
|
|
60
|
+
throw (0, errors_1.cliError)("CODEX_VERSION_UNSUPPORTED", "codex CLI version is below the supported minimum.", {
|
|
61
|
+
minimumVersion: MIN_CODEX_VERSION,
|
|
62
|
+
currentVersion: version.currentVersion ?? null,
|
|
63
|
+
cause: version.cause,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
if (!fs.existsSync(args.codexDir)) {
|
|
67
|
+
throw (0, errors_1.cliError)("CODEX_DIR_NOT_FOUND", "The requested Codex directory does not exist.", {
|
|
68
|
+
codexDir: args.codexDir,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
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) {
|
|
78
|
+
throw (0, errors_1.cliError)("PROFILE_NOT_FOUND", "No profiles were found in config.toml.", {
|
|
79
|
+
file: args.configPath,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
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);
|
|
95
|
+
const incompleteProfiles = (0, setup_1.findIncompleteSetupProfiles)(drafts);
|
|
96
|
+
if (incompleteProfiles.length > 0) {
|
|
97
|
+
throw (0, errors_1.cliError)("INVALID_ARGUMENT", "setup requires complete provider data for every selected profile.", {
|
|
98
|
+
incompleteProfiles,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
(0, fs_utils_1.ensureDir)(args.codexDir);
|
|
102
|
+
const currentProviders = (0, providers_repo_1.readProvidersFileIfExists)(args.providersPath);
|
|
103
|
+
const providersExists = fs.existsSync(args.providersPath);
|
|
104
|
+
if (providersExists && args.strategy !== "merge" && args.strategy !== "overwrite") {
|
|
105
|
+
throw (0, errors_1.cliError)("PROVIDERS_ALREADY_EXISTS", "providers.json already exists.", {
|
|
106
|
+
file: args.providersPath,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
const nextProviders = {
|
|
110
|
+
providers: drafts.reduce((accumulator, draft) => {
|
|
111
|
+
accumulator[draft.providerName] = draft.record;
|
|
112
|
+
return accumulator;
|
|
113
|
+
}, {}),
|
|
114
|
+
};
|
|
115
|
+
const finalProviders = args.strategy === "merge" ? (0, providers_repo_1.mergeProviders)(currentProviders, nextProviders) : nextProviders;
|
|
116
|
+
const result = (0, run_mutation_1.runMutation)({
|
|
117
|
+
codexDir: args.codexDir,
|
|
118
|
+
backupsDir: args.backupsDir,
|
|
119
|
+
latestBackupPath: args.latestBackupPath,
|
|
120
|
+
operation: "setup",
|
|
121
|
+
files: [
|
|
122
|
+
{ absolutePath: args.providersPath, relativePath: "providers.json" },
|
|
123
|
+
{ absolutePath: args.configPath, relativePath: "config.toml" },
|
|
124
|
+
],
|
|
125
|
+
mutate: () => {
|
|
126
|
+
const configPlan = (0, config_repo_1.createConfigMutationPlan)(document, {});
|
|
127
|
+
(0, providers_repo_1.writeProvidersFile)(args.providersPath, finalProviders);
|
|
128
|
+
(0, config_repo_1.applyConfigMutation)(args.configPath, document, configPlan);
|
|
129
|
+
return {
|
|
130
|
+
codexDir: args.codexDir,
|
|
131
|
+
strategy: args.strategy,
|
|
132
|
+
providersInitialized: Object.keys(nextProviders.providers).length,
|
|
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: [],
|
|
140
|
+
};
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
const doctor = (0, run_doctor_1.runDoctor)({
|
|
144
|
+
codexDir: args.codexDir,
|
|
145
|
+
configPath: args.configPath,
|
|
146
|
+
providersPath: args.providersPath,
|
|
147
|
+
});
|
|
148
|
+
return {
|
|
149
|
+
data: {
|
|
150
|
+
...result.data,
|
|
151
|
+
doctor: doctor.data,
|
|
152
|
+
},
|
|
153
|
+
warnings: doctor.warnings,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.showProvider = showProvider;
|
|
4
|
+
const providers_1 = require("../domain/providers");
|
|
5
|
+
const providers_repo_1 = require("../infra/providers-repo");
|
|
6
|
+
/**
|
|
7
|
+
* Returns a single provider record, with text-mode callers able to use a masked preview.
|
|
8
|
+
*/
|
|
9
|
+
function showProvider(args) {
|
|
10
|
+
const provider = (0, providers_repo_1.readProviderRecord)(args.providersPath, args.providerName);
|
|
11
|
+
return {
|
|
12
|
+
data: {
|
|
13
|
+
providerName: args.providerName,
|
|
14
|
+
provider: args.includeSecret
|
|
15
|
+
? provider
|
|
16
|
+
: {
|
|
17
|
+
...provider,
|
|
18
|
+
apiKey: (0, providers_1.maskSecret)(provider.apiKey),
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
}
|
|
@@ -17,7 +17,7 @@ function switchProvider(args) {
|
|
|
17
17
|
availableProviders: Object.keys(providers.providers).sort(),
|
|
18
18
|
});
|
|
19
19
|
}
|
|
20
|
-
const
|
|
20
|
+
const document = (0, config_repo_1.ensureProfileExists)(args.configPath, provider.profile, args.providerName);
|
|
21
21
|
return (0, run_mutation_1.runMutation)({
|
|
22
22
|
codexDir: args.codexDir,
|
|
23
23
|
backupsDir: args.backupsDir,
|
|
@@ -28,8 +28,11 @@ function switchProvider(args) {
|
|
|
28
28
|
{ absolutePath: args.authPath, relativePath: "auth.json" },
|
|
29
29
|
],
|
|
30
30
|
mutate: () => {
|
|
31
|
+
const configPlan = (0, config_repo_1.createConfigMutationPlan)(document, {
|
|
32
|
+
setActiveProfile: provider.profile,
|
|
33
|
+
});
|
|
31
34
|
// Update the runtime profile first so any subsequent login is associated with the new target.
|
|
32
|
-
(0, config_repo_1.
|
|
35
|
+
(0, config_repo_1.applyConfigMutation)(args.configPath, document, configPlan);
|
|
33
36
|
if (!args.noLogin) {
|
|
34
37
|
(0, codex_cli_1.runCodexLogin)(provider.apiKey, args.codexDir);
|
|
35
38
|
}
|
|
@@ -1,29 +1,28 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.COMMON_TAG_CHOICES = void 0;
|
|
3
4
|
exports.collectAddInput = collectAddInput;
|
|
4
5
|
exports.createNonInteractiveAddError = createNonInteractiveAddError;
|
|
5
|
-
|
|
6
|
+
exports.promptTags = promptTags;
|
|
7
|
+
exports.parseTags = parseTags;
|
|
6
8
|
const errors_1 = require("../domain/errors");
|
|
9
|
+
exports.COMMON_TAG_CHOICES = ["free", "paid", "daily", "backup"];
|
|
7
10
|
/**
|
|
8
11
|
* Collects add command inputs interactively when required values are missing.
|
|
9
12
|
*/
|
|
10
|
-
async function collectAddInput(runtime, defaults, providerExists
|
|
13
|
+
async function collectAddInput(runtime, defaults, providerExists) {
|
|
11
14
|
runtime.writeLine("Interactive add mode");
|
|
12
15
|
runtime.writeLine("Provide the missing required fields. Press Enter to skip optional fields.");
|
|
13
16
|
const providerName = defaults.providerName
|
|
14
17
|
? normalizeRequiredValue(defaults.providerName)
|
|
15
18
|
: await promptProviderName(runtime, providerExists);
|
|
16
|
-
const profile = defaults.profile
|
|
17
|
-
? normalizeRequiredValue(defaults.profile)
|
|
18
|
-
: await promptProfile(runtime, configPath);
|
|
19
|
+
const profile = defaults.profile ? normalizeRequiredValue(defaults.profile) : await promptRequiredValue(runtime, "Profile");
|
|
19
20
|
const apiKey = defaults.apiKey
|
|
20
21
|
? normalizeRequiredValue(defaults.apiKey)
|
|
21
22
|
: await promptConfirmedSecret(runtime, "API key", "Confirm API key");
|
|
22
23
|
const baseUrl = defaults.baseUrl ?? normalizeOptionalValue(await runtime.inputText("Base URL (optional)"));
|
|
23
24
|
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)"));
|
|
25
|
+
const tags = defaults.tags.length > 0 ? defaults.tags : await promptTags(runtime);
|
|
27
26
|
return {
|
|
28
27
|
providerName,
|
|
29
28
|
profile,
|
|
@@ -37,7 +36,7 @@ async function collectAddInput(runtime, defaults, providerExists, configPath) {
|
|
|
37
36
|
* Throws a consistent error when interactive add is unavailable.
|
|
38
37
|
*/
|
|
39
38
|
function createNonInteractiveAddError() {
|
|
40
|
-
return (0, errors_1.cliError)("
|
|
39
|
+
return (0, errors_1.cliError)("INVALID_ARGUMENT", "add requires <provider>, --profile, and --api-key when running without an interactive TTY.", {
|
|
41
40
|
suggestion: "Run in a terminal TTY or pass all required values explicitly.",
|
|
42
41
|
});
|
|
43
42
|
}
|
|
@@ -51,26 +50,6 @@ async function promptProviderName(runtime, providerExists) {
|
|
|
51
50
|
return providerName;
|
|
52
51
|
}
|
|
53
52
|
}
|
|
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
53
|
async function promptRequiredValue(runtime, label) {
|
|
75
54
|
while (true) {
|
|
76
55
|
const value = normalizeRequiredValue(await runtime.inputText(label));
|
|
@@ -106,9 +85,24 @@ function normalizeOptionalValue(value) {
|
|
|
106
85
|
const normalized = value.trim();
|
|
107
86
|
return normalized === "" ? null : normalized;
|
|
108
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
|
+
}
|
|
109
97
|
function parseTags(value) {
|
|
110
|
-
return value
|
|
98
|
+
return dedupeTags(value
|
|
111
99
|
.split(",")
|
|
112
100
|
.map((tag) => tag.trim())
|
|
113
|
-
.filter((tag) => tag.length > 0);
|
|
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));
|
|
114
108
|
}
|
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];
|
|
@@ -21,25 +22,28 @@ function parseArgs(argv) {
|
|
|
21
22
|
if (value === "--codex-dir") {
|
|
22
23
|
const next = argv[index + 1];
|
|
23
24
|
if (!next) {
|
|
24
|
-
throw (0, errors_1.cliError)("
|
|
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
|
}
|
|
30
32
|
remaining.push(value);
|
|
31
33
|
}
|
|
32
34
|
if (remaining[0] === "help") {
|
|
35
|
+
const helpTarget = remaining[1] === "backups" && remaining[2] === "list" ? "backups" : remaining[1] ?? null;
|
|
33
36
|
return {
|
|
34
37
|
command: null,
|
|
35
38
|
positionals: [],
|
|
36
39
|
globalOptions: {
|
|
37
40
|
json,
|
|
38
41
|
codexDir,
|
|
42
|
+
codexDirExplicit,
|
|
39
43
|
},
|
|
40
44
|
commandOptions: new Map(),
|
|
41
45
|
helpRequested: true,
|
|
42
|
-
helpTarget
|
|
46
|
+
helpTarget,
|
|
43
47
|
versionRequested: false,
|
|
44
48
|
};
|
|
45
49
|
}
|
|
@@ -51,11 +55,19 @@ function parseArgs(argv) {
|
|
|
51
55
|
versionRequested: true,
|
|
52
56
|
});
|
|
53
57
|
}
|
|
54
|
-
const
|
|
58
|
+
const commandToken = remaining[0] ?? null;
|
|
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;
|
|
55
66
|
const positionals = [];
|
|
56
67
|
const commandOptions = new Map();
|
|
57
68
|
let helpRequested = false;
|
|
58
|
-
|
|
69
|
+
const startIndex = command === "backups-list" || command === "config-show" || command === "config-list-profiles" ? 2 : 1;
|
|
70
|
+
for (let index = startIndex; index < remaining.length; index += 1) {
|
|
59
71
|
const value = remaining[index];
|
|
60
72
|
if (value === "--help" || value === "-h") {
|
|
61
73
|
helpRequested = true;
|
|
@@ -83,10 +95,11 @@ function parseArgs(argv) {
|
|
|
83
95
|
globalOptions: {
|
|
84
96
|
json,
|
|
85
97
|
codexDir,
|
|
98
|
+
codexDirExplicit,
|
|
86
99
|
},
|
|
87
100
|
commandOptions,
|
|
88
101
|
helpRequested,
|
|
89
|
-
helpTarget: helpRequested ? command : null,
|
|
102
|
+
helpTarget: helpRequested ? (command === "backups-list" ? "backups" : command) : null,
|
|
90
103
|
versionRequested: false,
|
|
91
104
|
};
|
|
92
105
|
}
|
|
@@ -100,6 +113,7 @@ function defaultParsed(command, overrides) {
|
|
|
100
113
|
globalOptions: {
|
|
101
114
|
json: overrides?.json ?? false,
|
|
102
115
|
codexDir: overrides?.codexDir ?? (0, codex_paths_1.resolveCodexDir)(),
|
|
116
|
+
codexDirExplicit: false,
|
|
103
117
|
},
|
|
104
118
|
commandOptions: new Map(),
|
|
105
119
|
helpRequested: overrides?.helpRequested ?? false,
|