@bjesuiter/codex-switcher 1.8.1 → 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 +8 -3
- package/cdx.mjs +115 -10
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,12 +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
|
+
|
|
11
|
+
#### Features
|
|
12
|
+
|
|
13
|
+
- Added `update-self` command aliases: `self-update`, `update`, and `updte`.
|
|
10
14
|
|
|
11
15
|
#### Fixes
|
|
12
16
|
|
|
13
|
-
- Added
|
|
14
|
-
- Linux secure-store error handling now treats Secret Service `no result found` responses as missing-entry cases and classifies generic `Couldn't access platform secure storage` failures as unavailable-store errors with actionable guidance.
|
|
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`.
|
|
15
18
|
|
|
16
19
|
see full changelog here: https://github.com/bjesuiter/codex-switcher/blob/main/CHANGELOG.md
|
|
17
20
|
|
|
@@ -170,6 +173,8 @@ cdx migrate-secrets
|
|
|
170
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) |
|
|
171
174
|
| `cdx usage` | Show usage overview for all accounts |
|
|
172
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` |
|
|
173
178
|
| `cdx help [command]` | Show help for all commands or one command |
|
|
174
179
|
| `cdx complete <shell>` | Generate shell completion script (`zsh`, `bash`, `fish`, `powershell`) |
|
|
175
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") {
|
|
@@ -2791,6 +2791,106 @@ const getSecretStoreProbeGuidance = (platform) => {
|
|
|
2791
2791
|
if (platform === "win32") return "Suggested fix: ensure Windows Credential Manager is available for this user session, then retry login.";
|
|
2792
2792
|
return null;
|
|
2793
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
|
+
};
|
|
2794
2894
|
const registerDoctorCommand = (program) => {
|
|
2795
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) => {
|
|
2796
2896
|
try {
|
|
@@ -2850,6 +2950,7 @@ const registerDoctorCommand = (program) => {
|
|
|
2850
2950
|
process.stdout.write(` ⚠ ${probeResult.stage} failed: ${probeResult.error.message}\n`);
|
|
2851
2951
|
const guidance = getSecretStoreProbeGuidance(process.platform);
|
|
2852
2952
|
if (guidance) process.stdout.write(` ${guidance}\n`);
|
|
2953
|
+
if (process.platform === "linux") await maybeRunLinuxSecretStoreChecklist();
|
|
2853
2954
|
}
|
|
2854
2955
|
}
|
|
2855
2956
|
if (process.platform === "darwin" && !options.checkKeychainAcl) {
|
|
@@ -2909,7 +3010,7 @@ const registerDoctorCommand = (program) => {
|
|
|
2909
3010
|
const registerHelpCommand = (program) => {
|
|
2910
3011
|
program.command("help").description("Show available commands and usage information").argument("[command]", "Show help for a specific command").action((commandName) => {
|
|
2911
3012
|
if (commandName) {
|
|
2912
|
-
const command = program.commands.find((entry) => entry.name() === commandName);
|
|
3013
|
+
const command = program.commands.find((entry) => entry.name() === commandName || entry.aliases().includes(commandName));
|
|
2913
3014
|
if (command) {
|
|
2914
3015
|
command.outputHelp();
|
|
2915
3016
|
return;
|
|
@@ -3529,7 +3630,11 @@ const executeUpdate = async (command, args) => {
|
|
|
3529
3630
|
});
|
|
3530
3631
|
};
|
|
3531
3632
|
const registerUpdateSelfCommand = (program) => {
|
|
3532
|
-
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) => {
|
|
3533
3638
|
try {
|
|
3534
3639
|
const requestedManager = options.manager ?? "auto";
|
|
3535
3640
|
if (![
|