codex-team 0.0.6 → 0.0.8
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.md +1 -3
- package/dist/cli.cjs +71 -32
- package/dist/main.cjs +71 -32
- package/dist/main.js +71 -32
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -24,8 +24,6 @@ codexm switch <name>
|
|
|
24
24
|
codexm switch --auto --dry-run
|
|
25
25
|
codexm remove <name> --yes
|
|
26
26
|
codexm rename <old> <new>
|
|
27
|
-
codexm quota refresh [name]
|
|
28
|
-
codexm doctor
|
|
29
27
|
```
|
|
30
28
|
|
|
31
29
|
Use `--json` on query and mutation commands when you need machine-readable output.
|
|
@@ -36,7 +34,7 @@ Use `--json` on query and mutation commands when you need machine-readable outpu
|
|
|
36
34
|
2. Save the current auth snapshot with `codexm save <name>`.
|
|
37
35
|
3. Repeat for other accounts.
|
|
38
36
|
4. Switch between saved accounts with `codexm switch <name>` or let the tool choose with `codexm switch --auto`.
|
|
39
|
-
5. Refresh and inspect quota usage with `codexm list
|
|
37
|
+
5. Refresh and inspect quota usage with `codexm list`.
|
|
40
38
|
|
|
41
39
|
## Development
|
|
42
40
|
|
package/dist/cli.cjs
CHANGED
|
@@ -13,7 +13,7 @@ var __webpack_modules__ = {
|
|
|
13
13
|
const utc_js_namespaceObject = require("dayjs/plugin/utc.js");
|
|
14
14
|
var utc_js_default = /*#__PURE__*/ __webpack_require__.n(utc_js_namespaceObject);
|
|
15
15
|
var package_namespaceObject = {
|
|
16
|
-
rE: "0.0.
|
|
16
|
+
rE: "0.0.8"
|
|
17
17
|
};
|
|
18
18
|
const external_node_crypto_namespaceObject = require("node:crypto");
|
|
19
19
|
const promises_namespaceObject = require("node:fs/promises");
|
|
@@ -456,6 +456,7 @@ var __webpack_modules__ = {
|
|
|
456
456
|
codexDir,
|
|
457
457
|
codexTeamDir,
|
|
458
458
|
currentAuthPath: (0, external_node_path_namespaceObject.join)(codexDir, "auth.json"),
|
|
459
|
+
currentConfigPath: (0, external_node_path_namespaceObject.join)(codexDir, "config.toml"),
|
|
459
460
|
accountsDir: (0, external_node_path_namespaceObject.join)(codexTeamDir, "accounts"),
|
|
460
461
|
backupsDir: (0, external_node_path_namespaceObject.join)(codexTeamDir, "backups"),
|
|
461
462
|
statePath: (0, external_node_path_namespaceObject.join)(codexTeamDir, "state.json")
|
|
@@ -548,12 +549,44 @@ var __webpack_modules__ = {
|
|
|
548
549
|
accountMetaPath(name) {
|
|
549
550
|
return (0, external_node_path_namespaceObject.join)(this.accountDirectory(name), "meta.json");
|
|
550
551
|
}
|
|
552
|
+
accountConfigPath(name) {
|
|
553
|
+
return (0, external_node_path_namespaceObject.join)(this.accountDirectory(name), "config.toml");
|
|
554
|
+
}
|
|
551
555
|
async writeAccountAuthSnapshot(name, snapshot) {
|
|
552
556
|
await atomicWriteFile(this.accountAuthPath(name), stringifyJson(snapshot));
|
|
553
557
|
}
|
|
554
558
|
async writeAccountMeta(name, meta) {
|
|
555
559
|
await atomicWriteFile(this.accountMetaPath(name), stringifyJson(meta));
|
|
556
560
|
}
|
|
561
|
+
validateConfigSnapshot(name, snapshot, rawConfig) {
|
|
562
|
+
if ("apikey" !== snapshot.auth_mode) return;
|
|
563
|
+
if (!rawConfig) throw new Error(`Current ~/.codex/config.toml is required to save apikey account "${name}".`);
|
|
564
|
+
if (!/^\s*model_provider\s*=\s*["'][^"']+["']/mu.test(rawConfig)) throw new Error(`Current ~/.codex/config.toml is missing model_provider for apikey account "${name}".`);
|
|
565
|
+
if (!/^\s*base_url\s*=\s*["'][^"']+["']/mu.test(rawConfig)) throw new Error(`Current ~/.codex/config.toml is missing base_url for apikey account "${name}".`);
|
|
566
|
+
}
|
|
567
|
+
sanitizeConfigForAccountAuth(rawConfig) {
|
|
568
|
+
const lines = rawConfig.split(/\r?\n/u);
|
|
569
|
+
const result = [];
|
|
570
|
+
let skippingProviderSection = false;
|
|
571
|
+
for (const line of lines){
|
|
572
|
+
const trimmed = line.trim();
|
|
573
|
+
if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
|
|
574
|
+
skippingProviderSection = /^\[model_providers\.[^\]]+\]$/u.test(trimmed);
|
|
575
|
+
if (skippingProviderSection) continue;
|
|
576
|
+
}
|
|
577
|
+
if (!skippingProviderSection) {
|
|
578
|
+
if (!/^\s*model_provider\s*=/u.test(line)) {
|
|
579
|
+
if (!/^\s*preferred_auth_method\s*=\s*["']apikey["']\s*$/u.test(line)) result.push(line);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
return `${result.join("\n").replace(/\n{3,}/gu, "\n\n").trimEnd()}\n`;
|
|
584
|
+
}
|
|
585
|
+
async ensureEmptyAccountConfigSnapshot(name) {
|
|
586
|
+
const configPath = this.accountConfigPath(name);
|
|
587
|
+
await atomicWriteFile(configPath, "");
|
|
588
|
+
return configPath;
|
|
589
|
+
}
|
|
557
590
|
async syncCurrentAuthIfMatching(snapshot) {
|
|
558
591
|
if (!await pathExists(this.paths.currentAuthPath)) return;
|
|
559
592
|
try {
|
|
@@ -619,6 +652,7 @@ var __webpack_modules__ = {
|
|
|
619
652
|
...meta,
|
|
620
653
|
authPath,
|
|
621
654
|
metaPath,
|
|
655
|
+
configPath: await pathExists(this.accountConfigPath(name)) ? this.accountConfigPath(name) : null,
|
|
622
656
|
duplicateAccountId: false
|
|
623
657
|
};
|
|
624
658
|
}
|
|
@@ -675,14 +709,28 @@ var __webpack_modules__ = {
|
|
|
675
709
|
if (!await pathExists(this.paths.currentAuthPath)) throw new Error("Current ~/.codex/auth.json does not exist.");
|
|
676
710
|
const rawSnapshot = await readJsonFile(this.paths.currentAuthPath);
|
|
677
711
|
const snapshot = parseAuthSnapshot(rawSnapshot);
|
|
712
|
+
const rawConfig = await pathExists(this.paths.currentConfigPath) ? await readJsonFile(this.paths.currentConfigPath) : null;
|
|
678
713
|
const accountDir = this.accountDirectory(name);
|
|
679
714
|
const authPath = this.accountAuthPath(name);
|
|
680
715
|
const metaPath = this.accountMetaPath(name);
|
|
716
|
+
const configPath = this.accountConfigPath(name);
|
|
717
|
+
const identity = getSnapshotIdentity(snapshot);
|
|
681
718
|
const accountExists = await pathExists(accountDir);
|
|
682
719
|
const existingMeta = accountExists && await pathExists(metaPath) ? parseSnapshotMeta(await readJsonFile(metaPath)) : void 0;
|
|
683
720
|
if (accountExists && !force) throw new Error(`Account "${name}" already exists. Use --force to overwrite it.`);
|
|
721
|
+
const { accounts } = await this.listAccounts();
|
|
722
|
+
const duplicateIdentityAccounts = accounts.filter((account)=>account.name !== name && account.account_id === identity);
|
|
723
|
+
if (duplicateIdentityAccounts.length > 0) {
|
|
724
|
+
const joinedNames = duplicateIdentityAccounts.map((account)=>`"${account.name}"`).join(", ");
|
|
725
|
+
throw new Error(`Identity ${identity} is already managed by ${joinedNames}.`);
|
|
726
|
+
}
|
|
727
|
+
this.validateConfigSnapshot(name, snapshot, rawConfig);
|
|
684
728
|
await ensureDirectory(accountDir, DIRECTORY_MODE);
|
|
685
729
|
await atomicWriteFile(authPath, `${rawSnapshot.trimEnd()}\n`);
|
|
730
|
+
if ("apikey" === snapshot.auth_mode && rawConfig) await atomicWriteFile(configPath, rawConfig.endsWith("\n") ? rawConfig : `${rawConfig}\n`);
|
|
731
|
+
else if (await pathExists(configPath)) await (0, promises_namespaceObject.rm)(configPath, {
|
|
732
|
+
force: true
|
|
733
|
+
});
|
|
686
734
|
const meta = createSnapshotMeta(name, snapshot, new Date(), existingMeta?.created_at);
|
|
687
735
|
meta.last_switched_at = existingMeta?.last_switched_at ?? null;
|
|
688
736
|
meta.quota = existingMeta?.quota ?? meta.quota;
|
|
@@ -698,9 +746,15 @@ var __webpack_modules__ = {
|
|
|
698
746
|
const name = current.matched_accounts[0];
|
|
699
747
|
const currentRawSnapshot = await readJsonFile(this.paths.currentAuthPath);
|
|
700
748
|
const currentSnapshot = parseAuthSnapshot(currentRawSnapshot);
|
|
749
|
+
const currentRawConfig = await pathExists(this.paths.currentConfigPath) ? await readJsonFile(this.paths.currentConfigPath) : null;
|
|
701
750
|
const metaPath = this.accountMetaPath(name);
|
|
702
751
|
const existingMeta = parseSnapshotMeta(await readJsonFile(metaPath));
|
|
752
|
+
this.validateConfigSnapshot(name, currentSnapshot, currentRawConfig);
|
|
703
753
|
await atomicWriteFile(this.accountAuthPath(name), `${currentRawSnapshot.trimEnd()}\n`);
|
|
754
|
+
if ("apikey" === currentSnapshot.auth_mode && currentRawConfig) await atomicWriteFile(this.accountConfigPath(name), currentRawConfig.endsWith("\n") ? currentRawConfig : `${currentRawConfig}\n`);
|
|
755
|
+
else if (await pathExists(this.accountConfigPath(name))) await (0, promises_namespaceObject.rm)(this.accountConfigPath(name), {
|
|
756
|
+
force: true
|
|
757
|
+
});
|
|
704
758
|
await atomicWriteFile(metaPath, stringifyJson({
|
|
705
759
|
...createSnapshotMeta(name, currentSnapshot, new Date(), existingMeta.created_at),
|
|
706
760
|
last_switched_at: existingMeta.last_switched_at,
|
|
@@ -722,8 +776,23 @@ var __webpack_modules__ = {
|
|
|
722
776
|
await (0, promises_namespaceObject.copyFile)(this.paths.currentAuthPath, backupPath);
|
|
723
777
|
await chmodIfPossible(backupPath, FILE_MODE);
|
|
724
778
|
}
|
|
779
|
+
if (await pathExists(this.paths.currentConfigPath)) {
|
|
780
|
+
const configBackupPath = (0, external_node_path_namespaceObject.join)(this.paths.backupsDir, "last-active-config.toml");
|
|
781
|
+
await (0, promises_namespaceObject.copyFile)(this.paths.currentConfigPath, configBackupPath);
|
|
782
|
+
await chmodIfPossible(configBackupPath, FILE_MODE);
|
|
783
|
+
}
|
|
725
784
|
const rawAuth = await readJsonFile(account.authPath);
|
|
726
785
|
await atomicWriteFile(this.paths.currentAuthPath, `${rawAuth.trimEnd()}\n`);
|
|
786
|
+
if ("apikey" === account.auth_mode && account.configPath) {
|
|
787
|
+
const rawConfig = await readJsonFile(account.configPath);
|
|
788
|
+
await atomicWriteFile(this.paths.currentConfigPath, rawConfig.endsWith("\n") ? rawConfig : `${rawConfig}\n`);
|
|
789
|
+
} else if ("apikey" === account.auth_mode) {
|
|
790
|
+
await this.ensureEmptyAccountConfigSnapshot(name);
|
|
791
|
+
warnings.push(`Saved apikey account "${name}" was missing config.toml snapshot. Created an empty snapshot; configure baseUrl manually if needed.`);
|
|
792
|
+
} else if (await pathExists(this.paths.currentConfigPath)) {
|
|
793
|
+
const currentRawConfig = await readJsonFile(this.paths.currentConfigPath);
|
|
794
|
+
await atomicWriteFile(this.paths.currentConfigPath, this.sanitizeConfigForAccountAuth(currentRawConfig));
|
|
795
|
+
}
|
|
727
796
|
const writtenSnapshot = await readAuthSnapshotFile(this.paths.currentAuthPath);
|
|
728
797
|
if (getSnapshotIdentity(writtenSnapshot) !== account.account_id) throw new Error(`Switch verification failed for account "${name}".`);
|
|
729
798
|
const meta = parseSnapshotMeta(await readJsonFile(account.metaPath));
|
|
@@ -881,6 +950,7 @@ var __webpack_modules__ = {
|
|
|
881
950
|
const metaStat = await (0, promises_namespaceObject.stat)(account.metaPath);
|
|
882
951
|
if ((511 & authStat.mode) !== FILE_MODE) issues.push(`Account "${account.name}" auth permissions must be 600.`);
|
|
883
952
|
if ((511 & metaStat.mode) !== FILE_MODE) issues.push(`Account "${account.name}" metadata permissions must be 600.`);
|
|
953
|
+
if ("apikey" === account.auth_mode && !account.configPath) issues.push(`Account "${account.name}" is missing config.toml snapshot required for apikey auth.`);
|
|
884
954
|
if (account.duplicateAccountId) warnings.push(`Account "${account.name}" shares identity ${account.account_id} with another saved account.`);
|
|
885
955
|
}
|
|
886
956
|
let currentAuthPresent = false;
|
|
@@ -949,12 +1019,10 @@ Usage:
|
|
|
949
1019
|
codexm list [name] [--json]
|
|
950
1020
|
codexm save <name> [--force] [--json]
|
|
951
1021
|
codexm update [--json]
|
|
952
|
-
codexm quota refresh [name] [--json]
|
|
953
1022
|
codexm switch <name> [--json]
|
|
954
1023
|
codexm switch --auto [--dry-run] [--json]
|
|
955
1024
|
codexm remove <name> [--yes] [--json]
|
|
956
1025
|
codexm rename <old> <new> [--json]
|
|
957
|
-
codexm doctor [--json]
|
|
958
1026
|
|
|
959
1027
|
Account names must match /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/.
|
|
960
1028
|
`);
|
|
@@ -972,16 +1040,6 @@ Account names must match /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/.
|
|
|
972
1040
|
for (const warning of status.warnings)lines.push(`Warning: ${warning}`);
|
|
973
1041
|
return lines.join("\n");
|
|
974
1042
|
}
|
|
975
|
-
function describeDoctor(report) {
|
|
976
|
-
const lines = [
|
|
977
|
-
report.healthy ? "Doctor checks passed." : "Doctor checks found issues.",
|
|
978
|
-
`Saved accounts: ${report.account_count}`,
|
|
979
|
-
`Current auth present: ${report.current_auth_present ? "yes" : "no"}`
|
|
980
|
-
];
|
|
981
|
-
for (const issue of report.issues)lines.push(`Issue: ${issue}`);
|
|
982
|
-
for (const warning of report.warnings)lines.push(`Warning: ${warning}`);
|
|
983
|
-
return lines.join("\n");
|
|
984
|
-
}
|
|
985
1043
|
function formatUsagePercent(window) {
|
|
986
1044
|
if (!window) return "-";
|
|
987
1045
|
return `${window.used_percent}%`;
|
|
@@ -1245,18 +1303,6 @@ Account names must match /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/.
|
|
|
1245
1303
|
}
|
|
1246
1304
|
return 0;
|
|
1247
1305
|
}
|
|
1248
|
-
case "quota":
|
|
1249
|
-
{
|
|
1250
|
-
const quotaCommand = parsed.positionals[0];
|
|
1251
|
-
if ("refresh" === quotaCommand) {
|
|
1252
|
-
const targetName = parsed.positionals[1];
|
|
1253
|
-
const result = await store.refreshAllQuotas(targetName);
|
|
1254
|
-
if (json) writeJson(streams.stdout, toCliQuotaRefreshResult(result));
|
|
1255
|
-
else streams.stdout.write(`${describeQuotaRefresh(result)}\n`);
|
|
1256
|
-
return 0 === result.failures.length ? 0 : 1;
|
|
1257
|
-
}
|
|
1258
|
-
throw new Error("Usage: codexm quota refresh [name] [--json]");
|
|
1259
|
-
}
|
|
1260
1306
|
case "switch":
|
|
1261
1307
|
{
|
|
1262
1308
|
const auto = parsed.flags.has("--auto");
|
|
@@ -1400,13 +1446,6 @@ Account names must match /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/.
|
|
|
1400
1446
|
else streams.stdout.write(`Renamed "${oldName}" to "${newName}".\n`);
|
|
1401
1447
|
return 0;
|
|
1402
1448
|
}
|
|
1403
|
-
case "doctor":
|
|
1404
|
-
{
|
|
1405
|
-
const report = await store.doctor();
|
|
1406
|
-
if (json) writeJson(streams.stdout, report);
|
|
1407
|
-
else streams.stdout.write(`${describeDoctor(report)}\n`);
|
|
1408
|
-
return report.healthy ? 0 : 1;
|
|
1409
|
-
}
|
|
1410
1449
|
default:
|
|
1411
1450
|
throw new Error(`Unknown command "${parsed.command}".`);
|
|
1412
1451
|
}
|
package/dist/main.cjs
CHANGED
|
@@ -43,7 +43,7 @@ var timezone_js_default = /*#__PURE__*/ __webpack_require__.n(timezone_js_namesp
|
|
|
43
43
|
const utc_js_namespaceObject = require("dayjs/plugin/utc.js");
|
|
44
44
|
var utc_js_default = /*#__PURE__*/ __webpack_require__.n(utc_js_namespaceObject);
|
|
45
45
|
var package_namespaceObject = {
|
|
46
|
-
rE: "0.0.
|
|
46
|
+
rE: "0.0.8"
|
|
47
47
|
};
|
|
48
48
|
const external_node_crypto_namespaceObject = require("node:crypto");
|
|
49
49
|
const promises_namespaceObject = require("node:fs/promises");
|
|
@@ -486,6 +486,7 @@ function defaultPaths(homeDir = (0, external_node_os_namespaceObject.homedir)())
|
|
|
486
486
|
codexDir,
|
|
487
487
|
codexTeamDir,
|
|
488
488
|
currentAuthPath: (0, external_node_path_namespaceObject.join)(codexDir, "auth.json"),
|
|
489
|
+
currentConfigPath: (0, external_node_path_namespaceObject.join)(codexDir, "config.toml"),
|
|
489
490
|
accountsDir: (0, external_node_path_namespaceObject.join)(codexTeamDir, "accounts"),
|
|
490
491
|
backupsDir: (0, external_node_path_namespaceObject.join)(codexTeamDir, "backups"),
|
|
491
492
|
statePath: (0, external_node_path_namespaceObject.join)(codexTeamDir, "state.json")
|
|
@@ -578,12 +579,44 @@ class AccountStore {
|
|
|
578
579
|
accountMetaPath(name) {
|
|
579
580
|
return (0, external_node_path_namespaceObject.join)(this.accountDirectory(name), "meta.json");
|
|
580
581
|
}
|
|
582
|
+
accountConfigPath(name) {
|
|
583
|
+
return (0, external_node_path_namespaceObject.join)(this.accountDirectory(name), "config.toml");
|
|
584
|
+
}
|
|
581
585
|
async writeAccountAuthSnapshot(name, snapshot) {
|
|
582
586
|
await atomicWriteFile(this.accountAuthPath(name), stringifyJson(snapshot));
|
|
583
587
|
}
|
|
584
588
|
async writeAccountMeta(name, meta) {
|
|
585
589
|
await atomicWriteFile(this.accountMetaPath(name), stringifyJson(meta));
|
|
586
590
|
}
|
|
591
|
+
validateConfigSnapshot(name, snapshot, rawConfig) {
|
|
592
|
+
if ("apikey" !== snapshot.auth_mode) return;
|
|
593
|
+
if (!rawConfig) throw new Error(`Current ~/.codex/config.toml is required to save apikey account "${name}".`);
|
|
594
|
+
if (!/^\s*model_provider\s*=\s*["'][^"']+["']/mu.test(rawConfig)) throw new Error(`Current ~/.codex/config.toml is missing model_provider for apikey account "${name}".`);
|
|
595
|
+
if (!/^\s*base_url\s*=\s*["'][^"']+["']/mu.test(rawConfig)) throw new Error(`Current ~/.codex/config.toml is missing base_url for apikey account "${name}".`);
|
|
596
|
+
}
|
|
597
|
+
sanitizeConfigForAccountAuth(rawConfig) {
|
|
598
|
+
const lines = rawConfig.split(/\r?\n/u);
|
|
599
|
+
const result = [];
|
|
600
|
+
let skippingProviderSection = false;
|
|
601
|
+
for (const line of lines){
|
|
602
|
+
const trimmed = line.trim();
|
|
603
|
+
if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
|
|
604
|
+
skippingProviderSection = /^\[model_providers\.[^\]]+\]$/u.test(trimmed);
|
|
605
|
+
if (skippingProviderSection) continue;
|
|
606
|
+
}
|
|
607
|
+
if (!skippingProviderSection) {
|
|
608
|
+
if (!/^\s*model_provider\s*=/u.test(line)) {
|
|
609
|
+
if (!/^\s*preferred_auth_method\s*=\s*["']apikey["']\s*$/u.test(line)) result.push(line);
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
return `${result.join("\n").replace(/\n{3,}/gu, "\n\n").trimEnd()}\n`;
|
|
614
|
+
}
|
|
615
|
+
async ensureEmptyAccountConfigSnapshot(name) {
|
|
616
|
+
const configPath = this.accountConfigPath(name);
|
|
617
|
+
await atomicWriteFile(configPath, "");
|
|
618
|
+
return configPath;
|
|
619
|
+
}
|
|
587
620
|
async syncCurrentAuthIfMatching(snapshot) {
|
|
588
621
|
if (!await pathExists(this.paths.currentAuthPath)) return;
|
|
589
622
|
try {
|
|
@@ -649,6 +682,7 @@ class AccountStore {
|
|
|
649
682
|
...meta,
|
|
650
683
|
authPath,
|
|
651
684
|
metaPath,
|
|
685
|
+
configPath: await pathExists(this.accountConfigPath(name)) ? this.accountConfigPath(name) : null,
|
|
652
686
|
duplicateAccountId: false
|
|
653
687
|
};
|
|
654
688
|
}
|
|
@@ -705,14 +739,28 @@ class AccountStore {
|
|
|
705
739
|
if (!await pathExists(this.paths.currentAuthPath)) throw new Error("Current ~/.codex/auth.json does not exist.");
|
|
706
740
|
const rawSnapshot = await readJsonFile(this.paths.currentAuthPath);
|
|
707
741
|
const snapshot = parseAuthSnapshot(rawSnapshot);
|
|
742
|
+
const rawConfig = await pathExists(this.paths.currentConfigPath) ? await readJsonFile(this.paths.currentConfigPath) : null;
|
|
708
743
|
const accountDir = this.accountDirectory(name);
|
|
709
744
|
const authPath = this.accountAuthPath(name);
|
|
710
745
|
const metaPath = this.accountMetaPath(name);
|
|
746
|
+
const configPath = this.accountConfigPath(name);
|
|
747
|
+
const identity = getSnapshotIdentity(snapshot);
|
|
711
748
|
const accountExists = await pathExists(accountDir);
|
|
712
749
|
const existingMeta = accountExists && await pathExists(metaPath) ? parseSnapshotMeta(await readJsonFile(metaPath)) : void 0;
|
|
713
750
|
if (accountExists && !force) throw new Error(`Account "${name}" already exists. Use --force to overwrite it.`);
|
|
751
|
+
const { accounts } = await this.listAccounts();
|
|
752
|
+
const duplicateIdentityAccounts = accounts.filter((account)=>account.name !== name && account.account_id === identity);
|
|
753
|
+
if (duplicateIdentityAccounts.length > 0) {
|
|
754
|
+
const joinedNames = duplicateIdentityAccounts.map((account)=>`"${account.name}"`).join(", ");
|
|
755
|
+
throw new Error(`Identity ${identity} is already managed by ${joinedNames}.`);
|
|
756
|
+
}
|
|
757
|
+
this.validateConfigSnapshot(name, snapshot, rawConfig);
|
|
714
758
|
await ensureDirectory(accountDir, DIRECTORY_MODE);
|
|
715
759
|
await atomicWriteFile(authPath, `${rawSnapshot.trimEnd()}\n`);
|
|
760
|
+
if ("apikey" === snapshot.auth_mode && rawConfig) await atomicWriteFile(configPath, rawConfig.endsWith("\n") ? rawConfig : `${rawConfig}\n`);
|
|
761
|
+
else if (await pathExists(configPath)) await (0, promises_namespaceObject.rm)(configPath, {
|
|
762
|
+
force: true
|
|
763
|
+
});
|
|
716
764
|
const meta = createSnapshotMeta(name, snapshot, new Date(), existingMeta?.created_at);
|
|
717
765
|
meta.last_switched_at = existingMeta?.last_switched_at ?? null;
|
|
718
766
|
meta.quota = existingMeta?.quota ?? meta.quota;
|
|
@@ -728,9 +776,15 @@ class AccountStore {
|
|
|
728
776
|
const name = current.matched_accounts[0];
|
|
729
777
|
const currentRawSnapshot = await readJsonFile(this.paths.currentAuthPath);
|
|
730
778
|
const currentSnapshot = parseAuthSnapshot(currentRawSnapshot);
|
|
779
|
+
const currentRawConfig = await pathExists(this.paths.currentConfigPath) ? await readJsonFile(this.paths.currentConfigPath) : null;
|
|
731
780
|
const metaPath = this.accountMetaPath(name);
|
|
732
781
|
const existingMeta = parseSnapshotMeta(await readJsonFile(metaPath));
|
|
782
|
+
this.validateConfigSnapshot(name, currentSnapshot, currentRawConfig);
|
|
733
783
|
await atomicWriteFile(this.accountAuthPath(name), `${currentRawSnapshot.trimEnd()}\n`);
|
|
784
|
+
if ("apikey" === currentSnapshot.auth_mode && currentRawConfig) await atomicWriteFile(this.accountConfigPath(name), currentRawConfig.endsWith("\n") ? currentRawConfig : `${currentRawConfig}\n`);
|
|
785
|
+
else if (await pathExists(this.accountConfigPath(name))) await (0, promises_namespaceObject.rm)(this.accountConfigPath(name), {
|
|
786
|
+
force: true
|
|
787
|
+
});
|
|
734
788
|
await atomicWriteFile(metaPath, stringifyJson({
|
|
735
789
|
...createSnapshotMeta(name, currentSnapshot, new Date(), existingMeta.created_at),
|
|
736
790
|
last_switched_at: existingMeta.last_switched_at,
|
|
@@ -752,8 +806,23 @@ class AccountStore {
|
|
|
752
806
|
await (0, promises_namespaceObject.copyFile)(this.paths.currentAuthPath, backupPath);
|
|
753
807
|
await chmodIfPossible(backupPath, FILE_MODE);
|
|
754
808
|
}
|
|
809
|
+
if (await pathExists(this.paths.currentConfigPath)) {
|
|
810
|
+
const configBackupPath = (0, external_node_path_namespaceObject.join)(this.paths.backupsDir, "last-active-config.toml");
|
|
811
|
+
await (0, promises_namespaceObject.copyFile)(this.paths.currentConfigPath, configBackupPath);
|
|
812
|
+
await chmodIfPossible(configBackupPath, FILE_MODE);
|
|
813
|
+
}
|
|
755
814
|
const rawAuth = await readJsonFile(account.authPath);
|
|
756
815
|
await atomicWriteFile(this.paths.currentAuthPath, `${rawAuth.trimEnd()}\n`);
|
|
816
|
+
if ("apikey" === account.auth_mode && account.configPath) {
|
|
817
|
+
const rawConfig = await readJsonFile(account.configPath);
|
|
818
|
+
await atomicWriteFile(this.paths.currentConfigPath, rawConfig.endsWith("\n") ? rawConfig : `${rawConfig}\n`);
|
|
819
|
+
} else if ("apikey" === account.auth_mode) {
|
|
820
|
+
await this.ensureEmptyAccountConfigSnapshot(name);
|
|
821
|
+
warnings.push(`Saved apikey account "${name}" was missing config.toml snapshot. Created an empty snapshot; configure baseUrl manually if needed.`);
|
|
822
|
+
} else if (await pathExists(this.paths.currentConfigPath)) {
|
|
823
|
+
const currentRawConfig = await readJsonFile(this.paths.currentConfigPath);
|
|
824
|
+
await atomicWriteFile(this.paths.currentConfigPath, this.sanitizeConfigForAccountAuth(currentRawConfig));
|
|
825
|
+
}
|
|
757
826
|
const writtenSnapshot = await readAuthSnapshotFile(this.paths.currentAuthPath);
|
|
758
827
|
if (getSnapshotIdentity(writtenSnapshot) !== account.account_id) throw new Error(`Switch verification failed for account "${name}".`);
|
|
759
828
|
const meta = parseSnapshotMeta(await readJsonFile(account.metaPath));
|
|
@@ -911,6 +980,7 @@ class AccountStore {
|
|
|
911
980
|
const metaStat = await (0, promises_namespaceObject.stat)(account.metaPath);
|
|
912
981
|
if ((511 & authStat.mode) !== FILE_MODE) issues.push(`Account "${account.name}" auth permissions must be 600.`);
|
|
913
982
|
if ((511 & metaStat.mode) !== FILE_MODE) issues.push(`Account "${account.name}" metadata permissions must be 600.`);
|
|
983
|
+
if ("apikey" === account.auth_mode && !account.configPath) issues.push(`Account "${account.name}" is missing config.toml snapshot required for apikey auth.`);
|
|
914
984
|
if (account.duplicateAccountId) warnings.push(`Account "${account.name}" shares identity ${account.account_id} with another saved account.`);
|
|
915
985
|
}
|
|
916
986
|
let currentAuthPresent = false;
|
|
@@ -979,12 +1049,10 @@ Usage:
|
|
|
979
1049
|
codexm list [name] [--json]
|
|
980
1050
|
codexm save <name> [--force] [--json]
|
|
981
1051
|
codexm update [--json]
|
|
982
|
-
codexm quota refresh [name] [--json]
|
|
983
1052
|
codexm switch <name> [--json]
|
|
984
1053
|
codexm switch --auto [--dry-run] [--json]
|
|
985
1054
|
codexm remove <name> [--yes] [--json]
|
|
986
1055
|
codexm rename <old> <new> [--json]
|
|
987
|
-
codexm doctor [--json]
|
|
988
1056
|
|
|
989
1057
|
Account names must match /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/.
|
|
990
1058
|
`);
|
|
@@ -1002,16 +1070,6 @@ function describeCurrentStatus(status) {
|
|
|
1002
1070
|
for (const warning of status.warnings)lines.push(`Warning: ${warning}`);
|
|
1003
1071
|
return lines.join("\n");
|
|
1004
1072
|
}
|
|
1005
|
-
function describeDoctor(report) {
|
|
1006
|
-
const lines = [
|
|
1007
|
-
report.healthy ? "Doctor checks passed." : "Doctor checks found issues.",
|
|
1008
|
-
`Saved accounts: ${report.account_count}`,
|
|
1009
|
-
`Current auth present: ${report.current_auth_present ? "yes" : "no"}`
|
|
1010
|
-
];
|
|
1011
|
-
for (const issue of report.issues)lines.push(`Issue: ${issue}`);
|
|
1012
|
-
for (const warning of report.warnings)lines.push(`Warning: ${warning}`);
|
|
1013
|
-
return lines.join("\n");
|
|
1014
|
-
}
|
|
1015
1073
|
function formatUsagePercent(window) {
|
|
1016
1074
|
if (!window) return "-";
|
|
1017
1075
|
return `${window.used_percent}%`;
|
|
@@ -1275,18 +1333,6 @@ async function runCli(argv, options = {}) {
|
|
|
1275
1333
|
}
|
|
1276
1334
|
return 0;
|
|
1277
1335
|
}
|
|
1278
|
-
case "quota":
|
|
1279
|
-
{
|
|
1280
|
-
const quotaCommand = parsed.positionals[0];
|
|
1281
|
-
if ("refresh" === quotaCommand) {
|
|
1282
|
-
const targetName = parsed.positionals[1];
|
|
1283
|
-
const result = await store.refreshAllQuotas(targetName);
|
|
1284
|
-
if (json) writeJson(streams.stdout, toCliQuotaRefreshResult(result));
|
|
1285
|
-
else streams.stdout.write(`${describeQuotaRefresh(result)}\n`);
|
|
1286
|
-
return 0 === result.failures.length ? 0 : 1;
|
|
1287
|
-
}
|
|
1288
|
-
throw new Error("Usage: codexm quota refresh [name] [--json]");
|
|
1289
|
-
}
|
|
1290
1336
|
case "switch":
|
|
1291
1337
|
{
|
|
1292
1338
|
const auto = parsed.flags.has("--auto");
|
|
@@ -1430,13 +1476,6 @@ async function runCli(argv, options = {}) {
|
|
|
1430
1476
|
else streams.stdout.write(`Renamed "${oldName}" to "${newName}".\n`);
|
|
1431
1477
|
return 0;
|
|
1432
1478
|
}
|
|
1433
|
-
case "doctor":
|
|
1434
|
-
{
|
|
1435
|
-
const report = await store.doctor();
|
|
1436
|
-
if (json) writeJson(streams.stdout, report);
|
|
1437
|
-
else streams.stdout.write(`${describeDoctor(report)}\n`);
|
|
1438
|
-
return report.healthy ? 0 : 1;
|
|
1439
|
-
}
|
|
1440
1479
|
default:
|
|
1441
1480
|
throw new Error(`Unknown command "${parsed.command}".`);
|
|
1442
1481
|
}
|
package/dist/main.js
CHANGED
|
@@ -9,7 +9,7 @@ import { basename, dirname, join } from "node:path";
|
|
|
9
9
|
import { execFile } from "node:child_process";
|
|
10
10
|
import { promisify } from "node:util";
|
|
11
11
|
var package_namespaceObject = {
|
|
12
|
-
rE: "0.0.
|
|
12
|
+
rE: "0.0.8"
|
|
13
13
|
};
|
|
14
14
|
function isRecord(value) {
|
|
15
15
|
return "object" == typeof value && null !== value && !Array.isArray(value);
|
|
@@ -446,6 +446,7 @@ function defaultPaths(homeDir = homedir()) {
|
|
|
446
446
|
codexDir,
|
|
447
447
|
codexTeamDir,
|
|
448
448
|
currentAuthPath: join(codexDir, "auth.json"),
|
|
449
|
+
currentConfigPath: join(codexDir, "config.toml"),
|
|
449
450
|
accountsDir: join(codexTeamDir, "accounts"),
|
|
450
451
|
backupsDir: join(codexTeamDir, "backups"),
|
|
451
452
|
statePath: join(codexTeamDir, "state.json")
|
|
@@ -538,12 +539,44 @@ class AccountStore {
|
|
|
538
539
|
accountMetaPath(name) {
|
|
539
540
|
return join(this.accountDirectory(name), "meta.json");
|
|
540
541
|
}
|
|
542
|
+
accountConfigPath(name) {
|
|
543
|
+
return join(this.accountDirectory(name), "config.toml");
|
|
544
|
+
}
|
|
541
545
|
async writeAccountAuthSnapshot(name, snapshot) {
|
|
542
546
|
await atomicWriteFile(this.accountAuthPath(name), stringifyJson(snapshot));
|
|
543
547
|
}
|
|
544
548
|
async writeAccountMeta(name, meta) {
|
|
545
549
|
await atomicWriteFile(this.accountMetaPath(name), stringifyJson(meta));
|
|
546
550
|
}
|
|
551
|
+
validateConfigSnapshot(name, snapshot, rawConfig) {
|
|
552
|
+
if ("apikey" !== snapshot.auth_mode) return;
|
|
553
|
+
if (!rawConfig) throw new Error(`Current ~/.codex/config.toml is required to save apikey account "${name}".`);
|
|
554
|
+
if (!/^\s*model_provider\s*=\s*["'][^"']+["']/mu.test(rawConfig)) throw new Error(`Current ~/.codex/config.toml is missing model_provider for apikey account "${name}".`);
|
|
555
|
+
if (!/^\s*base_url\s*=\s*["'][^"']+["']/mu.test(rawConfig)) throw new Error(`Current ~/.codex/config.toml is missing base_url for apikey account "${name}".`);
|
|
556
|
+
}
|
|
557
|
+
sanitizeConfigForAccountAuth(rawConfig) {
|
|
558
|
+
const lines = rawConfig.split(/\r?\n/u);
|
|
559
|
+
const result = [];
|
|
560
|
+
let skippingProviderSection = false;
|
|
561
|
+
for (const line of lines){
|
|
562
|
+
const trimmed = line.trim();
|
|
563
|
+
if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
|
|
564
|
+
skippingProviderSection = /^\[model_providers\.[^\]]+\]$/u.test(trimmed);
|
|
565
|
+
if (skippingProviderSection) continue;
|
|
566
|
+
}
|
|
567
|
+
if (!skippingProviderSection) {
|
|
568
|
+
if (!/^\s*model_provider\s*=/u.test(line)) {
|
|
569
|
+
if (!/^\s*preferred_auth_method\s*=\s*["']apikey["']\s*$/u.test(line)) result.push(line);
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
return `${result.join("\n").replace(/\n{3,}/gu, "\n\n").trimEnd()}\n`;
|
|
574
|
+
}
|
|
575
|
+
async ensureEmptyAccountConfigSnapshot(name) {
|
|
576
|
+
const configPath = this.accountConfigPath(name);
|
|
577
|
+
await atomicWriteFile(configPath, "");
|
|
578
|
+
return configPath;
|
|
579
|
+
}
|
|
547
580
|
async syncCurrentAuthIfMatching(snapshot) {
|
|
548
581
|
if (!await pathExists(this.paths.currentAuthPath)) return;
|
|
549
582
|
try {
|
|
@@ -609,6 +642,7 @@ class AccountStore {
|
|
|
609
642
|
...meta,
|
|
610
643
|
authPath,
|
|
611
644
|
metaPath,
|
|
645
|
+
configPath: await pathExists(this.accountConfigPath(name)) ? this.accountConfigPath(name) : null,
|
|
612
646
|
duplicateAccountId: false
|
|
613
647
|
};
|
|
614
648
|
}
|
|
@@ -665,14 +699,28 @@ class AccountStore {
|
|
|
665
699
|
if (!await pathExists(this.paths.currentAuthPath)) throw new Error("Current ~/.codex/auth.json does not exist.");
|
|
666
700
|
const rawSnapshot = await readJsonFile(this.paths.currentAuthPath);
|
|
667
701
|
const snapshot = parseAuthSnapshot(rawSnapshot);
|
|
702
|
+
const rawConfig = await pathExists(this.paths.currentConfigPath) ? await readJsonFile(this.paths.currentConfigPath) : null;
|
|
668
703
|
const accountDir = this.accountDirectory(name);
|
|
669
704
|
const authPath = this.accountAuthPath(name);
|
|
670
705
|
const metaPath = this.accountMetaPath(name);
|
|
706
|
+
const configPath = this.accountConfigPath(name);
|
|
707
|
+
const identity = getSnapshotIdentity(snapshot);
|
|
671
708
|
const accountExists = await pathExists(accountDir);
|
|
672
709
|
const existingMeta = accountExists && await pathExists(metaPath) ? parseSnapshotMeta(await readJsonFile(metaPath)) : void 0;
|
|
673
710
|
if (accountExists && !force) throw new Error(`Account "${name}" already exists. Use --force to overwrite it.`);
|
|
711
|
+
const { accounts } = await this.listAccounts();
|
|
712
|
+
const duplicateIdentityAccounts = accounts.filter((account)=>account.name !== name && account.account_id === identity);
|
|
713
|
+
if (duplicateIdentityAccounts.length > 0) {
|
|
714
|
+
const joinedNames = duplicateIdentityAccounts.map((account)=>`"${account.name}"`).join(", ");
|
|
715
|
+
throw new Error(`Identity ${identity} is already managed by ${joinedNames}.`);
|
|
716
|
+
}
|
|
717
|
+
this.validateConfigSnapshot(name, snapshot, rawConfig);
|
|
674
718
|
await ensureDirectory(accountDir, DIRECTORY_MODE);
|
|
675
719
|
await atomicWriteFile(authPath, `${rawSnapshot.trimEnd()}\n`);
|
|
720
|
+
if ("apikey" === snapshot.auth_mode && rawConfig) await atomicWriteFile(configPath, rawConfig.endsWith("\n") ? rawConfig : `${rawConfig}\n`);
|
|
721
|
+
else if (await pathExists(configPath)) await rm(configPath, {
|
|
722
|
+
force: true
|
|
723
|
+
});
|
|
676
724
|
const meta = createSnapshotMeta(name, snapshot, new Date(), existingMeta?.created_at);
|
|
677
725
|
meta.last_switched_at = existingMeta?.last_switched_at ?? null;
|
|
678
726
|
meta.quota = existingMeta?.quota ?? meta.quota;
|
|
@@ -688,9 +736,15 @@ class AccountStore {
|
|
|
688
736
|
const name = current.matched_accounts[0];
|
|
689
737
|
const currentRawSnapshot = await readJsonFile(this.paths.currentAuthPath);
|
|
690
738
|
const currentSnapshot = parseAuthSnapshot(currentRawSnapshot);
|
|
739
|
+
const currentRawConfig = await pathExists(this.paths.currentConfigPath) ? await readJsonFile(this.paths.currentConfigPath) : null;
|
|
691
740
|
const metaPath = this.accountMetaPath(name);
|
|
692
741
|
const existingMeta = parseSnapshotMeta(await readJsonFile(metaPath));
|
|
742
|
+
this.validateConfigSnapshot(name, currentSnapshot, currentRawConfig);
|
|
693
743
|
await atomicWriteFile(this.accountAuthPath(name), `${currentRawSnapshot.trimEnd()}\n`);
|
|
744
|
+
if ("apikey" === currentSnapshot.auth_mode && currentRawConfig) await atomicWriteFile(this.accountConfigPath(name), currentRawConfig.endsWith("\n") ? currentRawConfig : `${currentRawConfig}\n`);
|
|
745
|
+
else if (await pathExists(this.accountConfigPath(name))) await rm(this.accountConfigPath(name), {
|
|
746
|
+
force: true
|
|
747
|
+
});
|
|
694
748
|
await atomicWriteFile(metaPath, stringifyJson({
|
|
695
749
|
...createSnapshotMeta(name, currentSnapshot, new Date(), existingMeta.created_at),
|
|
696
750
|
last_switched_at: existingMeta.last_switched_at,
|
|
@@ -712,8 +766,23 @@ class AccountStore {
|
|
|
712
766
|
await copyFile(this.paths.currentAuthPath, backupPath);
|
|
713
767
|
await chmodIfPossible(backupPath, FILE_MODE);
|
|
714
768
|
}
|
|
769
|
+
if (await pathExists(this.paths.currentConfigPath)) {
|
|
770
|
+
const configBackupPath = join(this.paths.backupsDir, "last-active-config.toml");
|
|
771
|
+
await copyFile(this.paths.currentConfigPath, configBackupPath);
|
|
772
|
+
await chmodIfPossible(configBackupPath, FILE_MODE);
|
|
773
|
+
}
|
|
715
774
|
const rawAuth = await readJsonFile(account.authPath);
|
|
716
775
|
await atomicWriteFile(this.paths.currentAuthPath, `${rawAuth.trimEnd()}\n`);
|
|
776
|
+
if ("apikey" === account.auth_mode && account.configPath) {
|
|
777
|
+
const rawConfig = await readJsonFile(account.configPath);
|
|
778
|
+
await atomicWriteFile(this.paths.currentConfigPath, rawConfig.endsWith("\n") ? rawConfig : `${rawConfig}\n`);
|
|
779
|
+
} else if ("apikey" === account.auth_mode) {
|
|
780
|
+
await this.ensureEmptyAccountConfigSnapshot(name);
|
|
781
|
+
warnings.push(`Saved apikey account "${name}" was missing config.toml snapshot. Created an empty snapshot; configure baseUrl manually if needed.`);
|
|
782
|
+
} else if (await pathExists(this.paths.currentConfigPath)) {
|
|
783
|
+
const currentRawConfig = await readJsonFile(this.paths.currentConfigPath);
|
|
784
|
+
await atomicWriteFile(this.paths.currentConfigPath, this.sanitizeConfigForAccountAuth(currentRawConfig));
|
|
785
|
+
}
|
|
717
786
|
const writtenSnapshot = await readAuthSnapshotFile(this.paths.currentAuthPath);
|
|
718
787
|
if (getSnapshotIdentity(writtenSnapshot) !== account.account_id) throw new Error(`Switch verification failed for account "${name}".`);
|
|
719
788
|
const meta = parseSnapshotMeta(await readJsonFile(account.metaPath));
|
|
@@ -871,6 +940,7 @@ class AccountStore {
|
|
|
871
940
|
const metaStat = await stat(account.metaPath);
|
|
872
941
|
if ((511 & authStat.mode) !== FILE_MODE) issues.push(`Account "${account.name}" auth permissions must be 600.`);
|
|
873
942
|
if ((511 & metaStat.mode) !== FILE_MODE) issues.push(`Account "${account.name}" metadata permissions must be 600.`);
|
|
943
|
+
if ("apikey" === account.auth_mode && !account.configPath) issues.push(`Account "${account.name}" is missing config.toml snapshot required for apikey auth.`);
|
|
874
944
|
if (account.duplicateAccountId) warnings.push(`Account "${account.name}" shares identity ${account.account_id} with another saved account.`);
|
|
875
945
|
}
|
|
876
946
|
let currentAuthPresent = false;
|
|
@@ -939,12 +1009,10 @@ Usage:
|
|
|
939
1009
|
codexm list [name] [--json]
|
|
940
1010
|
codexm save <name> [--force] [--json]
|
|
941
1011
|
codexm update [--json]
|
|
942
|
-
codexm quota refresh [name] [--json]
|
|
943
1012
|
codexm switch <name> [--json]
|
|
944
1013
|
codexm switch --auto [--dry-run] [--json]
|
|
945
1014
|
codexm remove <name> [--yes] [--json]
|
|
946
1015
|
codexm rename <old> <new> [--json]
|
|
947
|
-
codexm doctor [--json]
|
|
948
1016
|
|
|
949
1017
|
Account names must match /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/.
|
|
950
1018
|
`);
|
|
@@ -962,16 +1030,6 @@ function describeCurrentStatus(status) {
|
|
|
962
1030
|
for (const warning of status.warnings)lines.push(`Warning: ${warning}`);
|
|
963
1031
|
return lines.join("\n");
|
|
964
1032
|
}
|
|
965
|
-
function describeDoctor(report) {
|
|
966
|
-
const lines = [
|
|
967
|
-
report.healthy ? "Doctor checks passed." : "Doctor checks found issues.",
|
|
968
|
-
`Saved accounts: ${report.account_count}`,
|
|
969
|
-
`Current auth present: ${report.current_auth_present ? "yes" : "no"}`
|
|
970
|
-
];
|
|
971
|
-
for (const issue of report.issues)lines.push(`Issue: ${issue}`);
|
|
972
|
-
for (const warning of report.warnings)lines.push(`Warning: ${warning}`);
|
|
973
|
-
return lines.join("\n");
|
|
974
|
-
}
|
|
975
1033
|
function formatUsagePercent(window) {
|
|
976
1034
|
if (!window) return "-";
|
|
977
1035
|
return `${window.used_percent}%`;
|
|
@@ -1235,18 +1293,6 @@ async function runCli(argv, options = {}) {
|
|
|
1235
1293
|
}
|
|
1236
1294
|
return 0;
|
|
1237
1295
|
}
|
|
1238
|
-
case "quota":
|
|
1239
|
-
{
|
|
1240
|
-
const quotaCommand = parsed.positionals[0];
|
|
1241
|
-
if ("refresh" === quotaCommand) {
|
|
1242
|
-
const targetName = parsed.positionals[1];
|
|
1243
|
-
const result = await store.refreshAllQuotas(targetName);
|
|
1244
|
-
if (json) writeJson(streams.stdout, toCliQuotaRefreshResult(result));
|
|
1245
|
-
else streams.stdout.write(`${describeQuotaRefresh(result)}\n`);
|
|
1246
|
-
return 0 === result.failures.length ? 0 : 1;
|
|
1247
|
-
}
|
|
1248
|
-
throw new Error("Usage: codexm quota refresh [name] [--json]");
|
|
1249
|
-
}
|
|
1250
1296
|
case "switch":
|
|
1251
1297
|
{
|
|
1252
1298
|
const auto = parsed.flags.has("--auto");
|
|
@@ -1390,13 +1436,6 @@ async function runCli(argv, options = {}) {
|
|
|
1390
1436
|
else streams.stdout.write(`Renamed "${oldName}" to "${newName}".\n`);
|
|
1391
1437
|
return 0;
|
|
1392
1438
|
}
|
|
1393
|
-
case "doctor":
|
|
1394
|
-
{
|
|
1395
|
-
const report = await store.doctor();
|
|
1396
|
-
if (json) writeJson(streams.stdout, report);
|
|
1397
|
-
else streams.stdout.write(`${describeDoctor(report)}\n`);
|
|
1398
|
-
return report.healthy ? 0 : 1;
|
|
1399
|
-
}
|
|
1400
1439
|
default:
|
|
1401
1440
|
throw new Error(`Unknown command "${parsed.command}".`);
|
|
1402
1441
|
}
|