@bjesuiter/codex-switcher 1.8.0 → 1.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -7
- package/cdx.mjs +203 -16
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,19 +6,15 @@ Switch the coding-agents [pi](https://pi.dev/), [codex](https://developers.opena
|
|
|
6
6
|
|
|
7
7
|
## Latest Changes
|
|
8
8
|
|
|
9
|
-
### 1.8.
|
|
9
|
+
### 1.8.2
|
|
10
10
|
|
|
11
11
|
#### Features
|
|
12
12
|
|
|
13
|
-
- Added
|
|
14
|
-
- Added cross-platform clipboard copy strategies for auth URLs (local clipboard commands + OSC52 terminal fallback).
|
|
15
|
-
- Added tmux/screen OSC52 framing support and fallback copy-command hints when auto-copy is unavailable.
|
|
13
|
+
- Added `update-self` command aliases: `self-update`, `update`, and `updte`.
|
|
16
14
|
|
|
17
15
|
#### Fixes
|
|
18
16
|
|
|
19
|
-
-
|
|
20
|
-
- Linux secure-store login reliability: treat native Secret Service "no matching entry" responses as missing-entry cases and improve unavailable-store error guidance.
|
|
21
|
-
- Added Mosh-specific clipboard heuristics/warnings so OSC52 copy reports are less misleading when clipboard updates are unreliable.
|
|
17
|
+
- Added an interactive Linux troubleshooting checklist in `cdx doctor` when the secure-store probe fails, guiding sequential checks for `gnome-keyring`, `secret-tool`, and running `gnome-keyring-daemon`.
|
|
22
18
|
|
|
23
19
|
see full changelog here: https://github.com/bjesuiter/codex-switcher/blob/main/CHANGELOG.md
|
|
24
20
|
|
|
@@ -177,6 +173,8 @@ cdx migrate-secrets
|
|
|
177
173
|
| `cdx doctor --check-keychain-acl` | Detect macOS keychain ACL/runtime mismatches (`cdx`/Bun vs legacy `security` CLI), warn about prompt-heavy setups, and suggest `cdx migrate-secrets` (slow) |
|
|
178
174
|
| `cdx usage` | Show usage overview for all accounts |
|
|
179
175
|
| `cdx usage <account>` | Show detailed usage for a specific account |
|
|
176
|
+
| `cdx update-self` | Update cdx to the latest version |
|
|
177
|
+
| `cdx self-update` / `cdx update` / `cdx updte` | Aliases for `cdx update-self` |
|
|
180
178
|
| `cdx help [command]` | Show help for all commands or one command |
|
|
181
179
|
| `cdx complete <shell>` | Generate shell completion script (`zsh`, `bash`, `fish`, `powershell`) |
|
|
182
180
|
| `cdx version` | Show CLI version |
|
package/cdx.mjs
CHANGED
|
@@ -15,7 +15,7 @@ import { generatePKCE } from "@openauthjs/openauth/pkce";
|
|
|
15
15
|
import http from "node:http";
|
|
16
16
|
|
|
17
17
|
//#region package.json
|
|
18
|
-
var version = "1.8.
|
|
18
|
+
var version = "1.8.2";
|
|
19
19
|
|
|
20
20
|
//#endregion
|
|
21
21
|
//#region lib/platform/path-resolver.ts
|
|
@@ -256,7 +256,7 @@ const getBrowserLauncher = (platform = process.platform, url) => {
|
|
|
256
256
|
label: "xdg-open"
|
|
257
257
|
};
|
|
258
258
|
};
|
|
259
|
-
const isCommandAvailable$
|
|
259
|
+
const isCommandAvailable$2 = (command, platform = process.platform) => {
|
|
260
260
|
const probe = platform === "win32" ? "where" : "which";
|
|
261
261
|
return Bun.spawnSync({
|
|
262
262
|
cmd: [probe, command],
|
|
@@ -269,13 +269,13 @@ const getBrowserLauncherCapability = (platform = process.platform) => {
|
|
|
269
269
|
return {
|
|
270
270
|
command: launcher.command,
|
|
271
271
|
label: launcher.label,
|
|
272
|
-
available: isCommandAvailable$
|
|
272
|
+
available: isCommandAvailable$2(launcher.command, platform)
|
|
273
273
|
};
|
|
274
274
|
};
|
|
275
275
|
const openBrowserUrl = (url, options = {}) => {
|
|
276
276
|
const platform = options.platform ?? process.platform;
|
|
277
277
|
const spawnImpl = options.spawnImpl ?? spawn;
|
|
278
|
-
const commandAvailable = options.isCommandAvailableImpl ?? isCommandAvailable$
|
|
278
|
+
const commandAvailable = options.isCommandAvailableImpl ?? isCommandAvailable$2;
|
|
279
279
|
const launcher = getBrowserLauncher(platform, url);
|
|
280
280
|
if (!commandAvailable(launcher.command, platform)) return {
|
|
281
281
|
ok: false,
|
|
@@ -306,7 +306,7 @@ const openBrowserUrl = (url, options = {}) => {
|
|
|
306
306
|
|
|
307
307
|
//#endregion
|
|
308
308
|
//#region lib/platform/clipboard.ts
|
|
309
|
-
const isCommandAvailable = (command, platform = process.platform) => {
|
|
309
|
+
const isCommandAvailable$1 = (command, platform = process.platform) => {
|
|
310
310
|
const probe = platform === "win32" ? "where" : "which";
|
|
311
311
|
return Bun.spawnSync({
|
|
312
312
|
cmd: [probe, command],
|
|
@@ -392,7 +392,7 @@ const getLocalClipboardTargets = (platform, env, commandExists) => {
|
|
|
392
392
|
});
|
|
393
393
|
return targets;
|
|
394
394
|
};
|
|
395
|
-
const resolveClipboardTargets = (context = {}, commandExists = isCommandAvailable) => {
|
|
395
|
+
const resolveClipboardTargets = (context = {}, commandExists = isCommandAvailable$1) => {
|
|
396
396
|
const platform = context.platform ?? process.platform;
|
|
397
397
|
const env = context.env ?? process.env;
|
|
398
398
|
const isTTY = context.isTTY ?? (Boolean(process.stdin.isTTY) && Boolean(process.stdout.isTTY));
|
|
@@ -433,7 +433,7 @@ const defaultRunCommand = (command, args, input) => {
|
|
|
433
433
|
};
|
|
434
434
|
};
|
|
435
435
|
const tryCopyToClipboard = (text, options = {}) => {
|
|
436
|
-
const commandExists = options.commandExistsImpl ?? isCommandAvailable;
|
|
436
|
+
const commandExists = options.commandExistsImpl ?? isCommandAvailable$1;
|
|
437
437
|
const runCommand = options.runCommandImpl ?? defaultRunCommand;
|
|
438
438
|
const writeStdout = options.writeStdoutImpl ?? ((chunk) => {
|
|
439
439
|
process.stdout.write(chunk);
|
|
@@ -479,7 +479,7 @@ const tryCopyToClipboard = (text, options = {}) => {
|
|
|
479
479
|
};
|
|
480
480
|
};
|
|
481
481
|
const escapePosixSingleQuoted = (value) => `'${value.replace(/'/g, `'"'"'`)}'`;
|
|
482
|
-
const buildClipboardHelperCommand = (text, context = {}, commandExists = isCommandAvailable) => {
|
|
482
|
+
const buildClipboardHelperCommand = (text, context = {}, commandExists = isCommandAvailable$1) => {
|
|
483
483
|
const commandTarget = resolveClipboardTargets(context, commandExists).find((target) => target.kind === "command");
|
|
484
484
|
if (!commandTarget) return null;
|
|
485
485
|
if (commandTarget.method === "powershell") {
|
|
@@ -663,13 +663,15 @@ const MISSING_ENTRY_MARKERS = [
|
|
|
663
663
|
"no matching entry found in secure storage",
|
|
664
664
|
"password not found",
|
|
665
665
|
"no stored credentials found",
|
|
666
|
-
"credential not found"
|
|
666
|
+
"credential not found",
|
|
667
|
+
"no result found"
|
|
667
668
|
];
|
|
668
669
|
const STORE_UNAVAILABLE_MARKERS = [
|
|
669
670
|
"unable to initialize linux secure-store backend",
|
|
670
671
|
"no keyring backend could be initialized",
|
|
671
672
|
"native keyring module not available",
|
|
672
673
|
"secret service operation failed",
|
|
674
|
+
"couldn't access platform secure storage",
|
|
673
675
|
"dbus",
|
|
674
676
|
"d-bus",
|
|
675
677
|
"org.freedesktop.secrets",
|
|
@@ -1061,14 +1063,16 @@ const windowsCrossKeychainPayloadExists = async (accountId) => withWindowsBacken
|
|
|
1061
1063
|
//#endregion
|
|
1062
1064
|
//#region lib/secrets/store.ts
|
|
1063
1065
|
const MISSING_SECRET_STORE_ERROR_MARKERS = [
|
|
1064
|
-
"
|
|
1065
|
-
"
|
|
1066
|
-
"
|
|
1067
|
-
"no matching entry found in secure storage"
|
|
1066
|
+
"no stored credentials found",
|
|
1067
|
+
"no keychain payload found",
|
|
1068
|
+
"password not found",
|
|
1069
|
+
"no matching entry found in secure storage",
|
|
1070
|
+
"no result found"
|
|
1068
1071
|
];
|
|
1069
1072
|
const isMissingSecretStoreEntryError = (error) => {
|
|
1070
1073
|
if (!(error instanceof Error)) return false;
|
|
1071
|
-
|
|
1074
|
+
const message = error.message.toLowerCase();
|
|
1075
|
+
return MISSING_SECRET_STORE_ERROR_MARKERS.some((marker) => message.includes(marker));
|
|
1072
1076
|
};
|
|
1073
1077
|
const createMissingSecretStoreEntryError = (accountId) => /* @__PURE__ */ new Error(`No stored credentials found for account ${accountId}.`);
|
|
1074
1078
|
const CACHED_ADAPTER_SYMBOL = Symbol.for("cdx.secretStore.cachedAdapter");
|
|
@@ -2711,6 +2715,56 @@ const getKeychainDecryptAccessByServiceAsync = async (services) => {
|
|
|
2711
2715
|
return parseKeychainDecryptAccessFromDump(dumpResult.output, dedupedServices);
|
|
2712
2716
|
};
|
|
2713
2717
|
|
|
2718
|
+
//#endregion
|
|
2719
|
+
//#region lib/secrets/probe.ts
|
|
2720
|
+
const toError = (error) => error instanceof Error ? error : new Error(String(error));
|
|
2721
|
+
const createProbePayload = (accountId, now) => ({
|
|
2722
|
+
refresh: `probe-refresh-${accountId}`,
|
|
2723
|
+
access: `probe-access-${accountId}`,
|
|
2724
|
+
expires: now + 6e4,
|
|
2725
|
+
accountId
|
|
2726
|
+
});
|
|
2727
|
+
const payloadMatches = (expected, actual) => expected.accountId === actual.accountId && expected.refresh === actual.refresh && expected.access === actual.access && expected.expires === actual.expires;
|
|
2728
|
+
const runSecretStoreWriteReadProbe = async (secretStore, options = {}) => {
|
|
2729
|
+
const probeAccountId = options.probeAccountId ?? `cdx-doctor-probe-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
|
|
2730
|
+
const payload = createProbePayload(probeAccountId, options.now ?? Date.now());
|
|
2731
|
+
let saveSucceeded = false;
|
|
2732
|
+
let result = { ok: true };
|
|
2733
|
+
try {
|
|
2734
|
+
await secretStore.save(probeAccountId, payload);
|
|
2735
|
+
saveSucceeded = true;
|
|
2736
|
+
} catch (error) {
|
|
2737
|
+
result = {
|
|
2738
|
+
ok: false,
|
|
2739
|
+
stage: "save",
|
|
2740
|
+
error: toError(error)
|
|
2741
|
+
};
|
|
2742
|
+
}
|
|
2743
|
+
if (result.ok) try {
|
|
2744
|
+
if (!payloadMatches(payload, await secretStore.load(probeAccountId))) result = {
|
|
2745
|
+
ok: false,
|
|
2746
|
+
stage: "verify",
|
|
2747
|
+
error: /* @__PURE__ */ new Error("Secure-store probe loaded payload does not match the saved payload.")
|
|
2748
|
+
};
|
|
2749
|
+
} catch (error) {
|
|
2750
|
+
result = {
|
|
2751
|
+
ok: false,
|
|
2752
|
+
stage: "load",
|
|
2753
|
+
error: toError(error)
|
|
2754
|
+
};
|
|
2755
|
+
}
|
|
2756
|
+
if (saveSucceeded) try {
|
|
2757
|
+
await secretStore.delete(probeAccountId);
|
|
2758
|
+
} catch (error) {
|
|
2759
|
+
if (result.ok) result = {
|
|
2760
|
+
ok: false,
|
|
2761
|
+
stage: "delete",
|
|
2762
|
+
error: toError(error)
|
|
2763
|
+
};
|
|
2764
|
+
}
|
|
2765
|
+
return result;
|
|
2766
|
+
};
|
|
2767
|
+
|
|
2714
2768
|
//#endregion
|
|
2715
2769
|
//#region lib/commands/doctor.ts
|
|
2716
2770
|
const hasRuntimeTrustedApp = (trustedApplications, runtimeExecutablePath) => {
|
|
@@ -2720,6 +2774,123 @@ const hasRuntimeTrustedApp = (trustedApplications, runtimeExecutablePath) => {
|
|
|
2720
2774
|
return path.basename(trustedApp).toLowerCase() === runtimeBaseName;
|
|
2721
2775
|
});
|
|
2722
2776
|
};
|
|
2777
|
+
const getSecretStoreProbeHeading = (platform) => {
|
|
2778
|
+
if (platform === "linux") return "Linux secure-store probe";
|
|
2779
|
+
if (platform === "darwin") return "macOS secure-store probe";
|
|
2780
|
+
if (platform === "win32") return "Windows secure-store probe";
|
|
2781
|
+
return null;
|
|
2782
|
+
};
|
|
2783
|
+
const createProbeAdapterForCurrentPlatform = () => {
|
|
2784
|
+
const currentAdapter = getSecretStoreAdapter();
|
|
2785
|
+
if (process.platform === "darwin" && currentAdapter.id === "macos-legacy-keychain") return createSecretStoreAdapterFromSelection("legacy-keychain", "darwin");
|
|
2786
|
+
return createRuntimeSecretStoreAdapter(process.platform);
|
|
2787
|
+
};
|
|
2788
|
+
const getSecretStoreProbeGuidance = (platform) => {
|
|
2789
|
+
if (platform === "linux") return "Suggested fix: ensure Secret Service is running/unlocked (for example gnome-keyring + secret-tool), then retry login.";
|
|
2790
|
+
if (platform === "darwin") return "Suggested fix: ensure Keychain Access is unlocked and allows this runtime/toolchain to store/read passwords, then retry login.";
|
|
2791
|
+
if (platform === "win32") return "Suggested fix: ensure Windows Credential Manager is available for this user session, then retry login.";
|
|
2792
|
+
return null;
|
|
2793
|
+
};
|
|
2794
|
+
const isInteractiveTerminal = () => Boolean(process.stdin.isTTY) && Boolean(process.stdout.isTTY);
|
|
2795
|
+
const runCommandCapture = async (command, args) => await new Promise((resolve) => {
|
|
2796
|
+
const child = spawn(command, args, { stdio: [
|
|
2797
|
+
"ignore",
|
|
2798
|
+
"pipe",
|
|
2799
|
+
"pipe"
|
|
2800
|
+
] });
|
|
2801
|
+
let stdout = "";
|
|
2802
|
+
let stderr = "";
|
|
2803
|
+
let spawnError = null;
|
|
2804
|
+
child.stdout?.on("data", (chunk) => {
|
|
2805
|
+
stdout += chunk.toString();
|
|
2806
|
+
});
|
|
2807
|
+
child.stderr?.on("data", (chunk) => {
|
|
2808
|
+
stderr += chunk.toString();
|
|
2809
|
+
});
|
|
2810
|
+
child.once("error", (error) => {
|
|
2811
|
+
spawnError = error.message;
|
|
2812
|
+
});
|
|
2813
|
+
child.once("close", (code) => {
|
|
2814
|
+
resolve({
|
|
2815
|
+
ok: spawnError === null && code === 0,
|
|
2816
|
+
stdout: stdout.trim(),
|
|
2817
|
+
stderr: stderr.trim(),
|
|
2818
|
+
...spawnError ? { error: spawnError } : {}
|
|
2819
|
+
});
|
|
2820
|
+
});
|
|
2821
|
+
});
|
|
2822
|
+
const extractCommandFailureDetails = (result) => result.error || result.stderr || result.stdout || void 0;
|
|
2823
|
+
const isCommandAvailable = async (commandName) => {
|
|
2824
|
+
return (await runCommandCapture("sh", ["-lc", `command -v ${commandName} >/dev/null 2>&1`])).ok;
|
|
2825
|
+
};
|
|
2826
|
+
const checkGnomeKeyringRunning = async () => {
|
|
2827
|
+
if (await isCommandAvailable("pgrep")) {
|
|
2828
|
+
const pgrepResult = await runCommandCapture("pgrep", ["-x", "gnome-keyring-daemon"]);
|
|
2829
|
+
if (pgrepResult.ok) return { ok: true };
|
|
2830
|
+
return {
|
|
2831
|
+
ok: false,
|
|
2832
|
+
details: extractCommandFailureDetails(pgrepResult) ?? "No gnome-keyring-daemon process found."
|
|
2833
|
+
};
|
|
2834
|
+
}
|
|
2835
|
+
const psFallback = await runCommandCapture("sh", ["-lc", "ps -A -o comm= | grep -q '^gnome-keyring-daemon$'"]);
|
|
2836
|
+
if (psFallback.ok) return { ok: true };
|
|
2837
|
+
return {
|
|
2838
|
+
ok: false,
|
|
2839
|
+
details: extractCommandFailureDetails(psFallback) ?? "No gnome-keyring-daemon process found."
|
|
2840
|
+
};
|
|
2841
|
+
};
|
|
2842
|
+
const runLinuxSecretStoreChecklist = async () => {
|
|
2843
|
+
const gnomeKeyringInstalled = await isCommandAvailable("gnome-keyring-daemon");
|
|
2844
|
+
const secretToolInstalled = await isCommandAvailable("secret-tool");
|
|
2845
|
+
const gnomeKeyringRunning = await checkGnomeKeyringRunning();
|
|
2846
|
+
return [
|
|
2847
|
+
{
|
|
2848
|
+
question: "Is gnome-keyring installed?",
|
|
2849
|
+
ok: gnomeKeyringInstalled,
|
|
2850
|
+
hint: "Install the `gnome-keyring` package, then log out/in (or restart your session)."
|
|
2851
|
+
},
|
|
2852
|
+
{
|
|
2853
|
+
question: "Is secret-tool installed?",
|
|
2854
|
+
ok: secretToolInstalled,
|
|
2855
|
+
hint: "Install the package that provides `secret-tool` (often `libsecret-tools`)."
|
|
2856
|
+
},
|
|
2857
|
+
{
|
|
2858
|
+
question: "Is gnome-keyring running?",
|
|
2859
|
+
ok: gnomeKeyringRunning.ok,
|
|
2860
|
+
details: gnomeKeyringRunning.details,
|
|
2861
|
+
hint: "Start/unlock gnome-keyring-daemon in your session (for a quick test: `gnome-keyring-daemon --start --components=secrets`)."
|
|
2862
|
+
}
|
|
2863
|
+
];
|
|
2864
|
+
};
|
|
2865
|
+
const maybeRunLinuxSecretStoreChecklist = async () => {
|
|
2866
|
+
if (!isInteractiveTerminal()) {
|
|
2867
|
+
process.stdout.write(" Tip: run `cdx doctor` in an interactive terminal to start guided Linux secret-store checks.\n");
|
|
2868
|
+
return;
|
|
2869
|
+
}
|
|
2870
|
+
const shouldRunChecklist = await p.confirm({
|
|
2871
|
+
message: "Run guided Linux secret-store checks now? (gnome-keyring installed, secret-tool installed, gnome-keyring running)",
|
|
2872
|
+
initialValue: true
|
|
2873
|
+
});
|
|
2874
|
+
if (p.isCancel(shouldRunChecklist) || !shouldRunChecklist) {
|
|
2875
|
+
process.stdout.write(" Guided Linux checks skipped.\n");
|
|
2876
|
+
return;
|
|
2877
|
+
}
|
|
2878
|
+
process.stdout.write(" Guided Linux checks:\n");
|
|
2879
|
+
const checklist = await runLinuxSecretStoreChecklist();
|
|
2880
|
+
let passed = 0;
|
|
2881
|
+
for (let i = 0; i < checklist.length; i++) {
|
|
2882
|
+
const item = checklist[i];
|
|
2883
|
+
if (item.ok) {
|
|
2884
|
+
passed += 1;
|
|
2885
|
+
process.stdout.write(` ${i + 1}/3 ${item.question} yes\n`);
|
|
2886
|
+
continue;
|
|
2887
|
+
}
|
|
2888
|
+
process.stdout.write(` ${i + 1}/3 ${item.question} no\n`);
|
|
2889
|
+
if (item.details) process.stdout.write(` details: ${item.details}\n`);
|
|
2890
|
+
if (item.hint) process.stdout.write(` hint: ${item.hint}\n`);
|
|
2891
|
+
}
|
|
2892
|
+
process.stdout.write(` Guided checklist summary: ${passed}/${checklist.length} checks passed.\n`);
|
|
2893
|
+
};
|
|
2723
2894
|
const registerDoctorCommand = (program) => {
|
|
2724
2895
|
program.command("doctor").description("Show auth file paths and runtime capabilities").option("--check-keychain-acl", "Run keychain trusted-app/ACL checks on macOS (can be slow)").action(async (options) => {
|
|
2725
2896
|
try {
|
|
@@ -2770,6 +2941,18 @@ const registerDoctorCommand = (program) => {
|
|
|
2770
2941
|
process.stdout.write(` Summary: ${okCount}/${status.accounts.length} configured account(s) passed secure-store load checks.\n`);
|
|
2771
2942
|
}
|
|
2772
2943
|
}
|
|
2944
|
+
const probeHeading = getSecretStoreProbeHeading(process.platform);
|
|
2945
|
+
if (probeHeading) {
|
|
2946
|
+
process.stdout.write(`\n${probeHeading}:\n`);
|
|
2947
|
+
const probeResult = await runSecretStoreWriteReadProbe(createProbeAdapterForCurrentPlatform());
|
|
2948
|
+
if (probeResult.ok) process.stdout.write(" write/read/delete probe: OK\n");
|
|
2949
|
+
else {
|
|
2950
|
+
process.stdout.write(` ⚠ ${probeResult.stage} failed: ${probeResult.error.message}\n`);
|
|
2951
|
+
const guidance = getSecretStoreProbeGuidance(process.platform);
|
|
2952
|
+
if (guidance) process.stdout.write(` ${guidance}\n`);
|
|
2953
|
+
if (process.platform === "linux") await maybeRunLinuxSecretStoreChecklist();
|
|
2954
|
+
}
|
|
2955
|
+
}
|
|
2773
2956
|
if (process.platform === "darwin" && !options.checkKeychainAcl) {
|
|
2774
2957
|
process.stdout.write(" ┌─ Optional keychain ACL check\n");
|
|
2775
2958
|
process.stdout.write(" │ Run: cdx doctor --check-keychain-acl\n");
|
|
@@ -2827,7 +3010,7 @@ const registerDoctorCommand = (program) => {
|
|
|
2827
3010
|
const registerHelpCommand = (program) => {
|
|
2828
3011
|
program.command("help").description("Show available commands and usage information").argument("[command]", "Show help for a specific command").action((commandName) => {
|
|
2829
3012
|
if (commandName) {
|
|
2830
|
-
const command = program.commands.find((entry) => entry.name() === commandName);
|
|
3013
|
+
const command = program.commands.find((entry) => entry.name() === commandName || entry.aliases().includes(commandName));
|
|
2831
3014
|
if (command) {
|
|
2832
3015
|
command.outputHelp();
|
|
2833
3016
|
return;
|
|
@@ -3447,7 +3630,11 @@ const executeUpdate = async (command, args) => {
|
|
|
3447
3630
|
});
|
|
3448
3631
|
};
|
|
3449
3632
|
const registerUpdateSelfCommand = (program) => {
|
|
3450
|
-
program.command("update-self").
|
|
3633
|
+
program.command("update-self").aliases([
|
|
3634
|
+
"self-update",
|
|
3635
|
+
"update",
|
|
3636
|
+
"updte"
|
|
3637
|
+
]).description("Update cdx to the latest version").option("--manager <manager>", "Select update manager (auto|bun|npm|deno)", "auto").option("--dry-run", "Print selected manager and update command without executing").option("-y, --yes", "Skip confirmation prompt").action(async (options) => {
|
|
3451
3638
|
try {
|
|
3452
3639
|
const requestedManager = options.manager ?? "auto";
|
|
3453
3640
|
if (![
|