@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.
Files changed (3) hide show
  1. package/README.md +8 -3
  2. package/cdx.mjs +115 -10
  3. 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.1
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 platform-native secure-store write/read/delete probes to `cdx doctor` on Linux, macOS, and Windows, so runtime secure-store failures are detected directly instead of only reporting adapter capability.
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.1";
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$1 = (command, platform = process.platform) => {
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$1(launcher.command, platform)
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$1;
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").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) => {
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 (![
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bjesuiter/codex-switcher",
3
- "version": "1.8.1",
3
+ "version": "1.8.2",
4
4
  "type": "module",
5
5
  "description": "CLI tool to switch between multiple OpenAI accounts for OpenCode",
6
6
  "bin": {