@bjesuiter/codex-switcher 1.8.1 → 1.8.3
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 +6 -4
- package/cdx.mjs +345 -11
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,12 +6,12 @@ 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.3
|
|
10
10
|
|
|
11
|
-
####
|
|
11
|
+
#### Improvements
|
|
12
12
|
|
|
13
|
-
-
|
|
14
|
-
- Linux
|
|
13
|
+
- `cdx update-self` now prints the detected installed version directly after a successful update, so you don't need to run `cdx version` manually.
|
|
14
|
+
- Linux `cdx doctor` guided checks now offer interactive recovery when `gnome-keyring-daemon` is not running: start now, or (when detectable as disabled) enable autostart and start now.
|
|
15
15
|
|
|
16
16
|
see full changelog here: https://github.com/bjesuiter/codex-switcher/blob/main/CHANGELOG.md
|
|
17
17
|
|
|
@@ -170,6 +170,8 @@ cdx migrate-secrets
|
|
|
170
170
|
| `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
171
|
| `cdx usage` | Show usage overview for all accounts |
|
|
172
172
|
| `cdx usage <account>` | Show detailed usage for a specific account |
|
|
173
|
+
| `cdx update-self` | Update cdx to the latest version |
|
|
174
|
+
| `cdx self-update` / `cdx update` / `cdx updte` | Aliases for `cdx update-self` |
|
|
173
175
|
| `cdx help [command]` | Show help for all commands or one command |
|
|
174
176
|
| `cdx complete <shell>` | Generate shell completion script (`zsh`, `bash`, `fish`, `powershell`) |
|
|
175
177
|
| `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.3";
|
|
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,294 @@ 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 parseSystemctlEnabledState = (output) => {
|
|
2843
|
+
const normalized = output.trim().toLowerCase();
|
|
2844
|
+
if ([
|
|
2845
|
+
"enabled",
|
|
2846
|
+
"enabled-runtime",
|
|
2847
|
+
"static",
|
|
2848
|
+
"indirect",
|
|
2849
|
+
"generated"
|
|
2850
|
+
].includes(normalized)) return "enabled";
|
|
2851
|
+
if ([
|
|
2852
|
+
"disabled",
|
|
2853
|
+
"masked",
|
|
2854
|
+
"not-found",
|
|
2855
|
+
"linked",
|
|
2856
|
+
"linked-runtime"
|
|
2857
|
+
].includes(normalized)) return "disabled";
|
|
2858
|
+
return "unknown";
|
|
2859
|
+
};
|
|
2860
|
+
const getLinuxGnomeKeyringAutoStartStatus = async () => {
|
|
2861
|
+
if (!await isCommandAvailable("systemctl")) return {
|
|
2862
|
+
state: "unknown",
|
|
2863
|
+
details: "systemctl is not available; autostart detection depends on your desktop/session config."
|
|
2864
|
+
};
|
|
2865
|
+
const units = ["gnome-keyring-daemon.socket", "gnome-keyring-daemon.service"];
|
|
2866
|
+
let sawDisabled = false;
|
|
2867
|
+
const details = [];
|
|
2868
|
+
for (const unit of units) {
|
|
2869
|
+
const result = await runCommandCapture("systemctl", [
|
|
2870
|
+
"--user",
|
|
2871
|
+
"is-enabled",
|
|
2872
|
+
unit
|
|
2873
|
+
]);
|
|
2874
|
+
const state = parseSystemctlEnabledState(result.stdout || result.stderr);
|
|
2875
|
+
if (state === "enabled") return {
|
|
2876
|
+
state: "enabled",
|
|
2877
|
+
details: `${unit} is enabled (${result.stdout || "enabled"}).`
|
|
2878
|
+
};
|
|
2879
|
+
if (state === "disabled") {
|
|
2880
|
+
sawDisabled = true;
|
|
2881
|
+
details.push(`${unit}: ${result.stdout || result.stderr || "disabled"}`);
|
|
2882
|
+
continue;
|
|
2883
|
+
}
|
|
2884
|
+
const maybeDetail = extractCommandFailureDetails(result);
|
|
2885
|
+
if (maybeDetail) details.push(`${unit}: ${maybeDetail}`);
|
|
2886
|
+
}
|
|
2887
|
+
if (sawDisabled) return {
|
|
2888
|
+
state: "disabled",
|
|
2889
|
+
details: details.join("; ")
|
|
2890
|
+
};
|
|
2891
|
+
return {
|
|
2892
|
+
state: "unknown",
|
|
2893
|
+
details: details.join("; ") || "Unable to determine gnome-keyring autostart state."
|
|
2894
|
+
};
|
|
2895
|
+
};
|
|
2896
|
+
const applyKeyringEnvAssignments = (raw) => {
|
|
2897
|
+
const lines = raw.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
2898
|
+
for (const line of lines) {
|
|
2899
|
+
const match = line.match(/^([A-Z0-9_]+)=(.*);?$/);
|
|
2900
|
+
if (!match) continue;
|
|
2901
|
+
const key = match[1];
|
|
2902
|
+
const value = match[2].replace(/;$/, "");
|
|
2903
|
+
if (key) process.env[key] = value;
|
|
2904
|
+
}
|
|
2905
|
+
};
|
|
2906
|
+
const startGnomeKeyringNow = async () => {
|
|
2907
|
+
const directStart = await runCommandCapture("gnome-keyring-daemon", ["--start", "--components=secrets"]);
|
|
2908
|
+
if (directStart.ok) {
|
|
2909
|
+
applyKeyringEnvAssignments(directStart.stdout);
|
|
2910
|
+
const runningCheck = await checkGnomeKeyringRunning();
|
|
2911
|
+
if (runningCheck.ok) return { ok: true };
|
|
2912
|
+
return {
|
|
2913
|
+
ok: false,
|
|
2914
|
+
details: runningCheck.details ?? "gnome-keyring-daemon start command succeeded, but process was not detected afterwards."
|
|
2915
|
+
};
|
|
2916
|
+
}
|
|
2917
|
+
if (await isCommandAvailable("systemctl")) {
|
|
2918
|
+
const serviceStart = await runCommandCapture("systemctl", [
|
|
2919
|
+
"--user",
|
|
2920
|
+
"start",
|
|
2921
|
+
"gnome-keyring-daemon.service"
|
|
2922
|
+
]);
|
|
2923
|
+
if (serviceStart.ok) {
|
|
2924
|
+
const runningCheck = await checkGnomeKeyringRunning();
|
|
2925
|
+
if (runningCheck.ok) return { ok: true };
|
|
2926
|
+
return {
|
|
2927
|
+
ok: false,
|
|
2928
|
+
details: runningCheck.details ?? "systemctl start succeeded, but gnome-keyring-daemon was not detected afterwards."
|
|
2929
|
+
};
|
|
2930
|
+
}
|
|
2931
|
+
return {
|
|
2932
|
+
ok: false,
|
|
2933
|
+
details: extractCommandFailureDetails(directStart) ?? extractCommandFailureDetails(serviceStart) ?? "Failed to start gnome-keyring-daemon."
|
|
2934
|
+
};
|
|
2935
|
+
}
|
|
2936
|
+
return {
|
|
2937
|
+
ok: false,
|
|
2938
|
+
details: extractCommandFailureDetails(directStart) ?? "Failed to start gnome-keyring-daemon."
|
|
2939
|
+
};
|
|
2940
|
+
};
|
|
2941
|
+
const enableGnomeKeyringAutoStart = async () => {
|
|
2942
|
+
if (!await isCommandAvailable("systemctl")) return {
|
|
2943
|
+
ok: false,
|
|
2944
|
+
details: "systemctl is not available, so cdx cannot automatically enable startup in this session manager."
|
|
2945
|
+
};
|
|
2946
|
+
const units = ["gnome-keyring-daemon.socket", "gnome-keyring-daemon.service"];
|
|
2947
|
+
const failures = [];
|
|
2948
|
+
for (const unit of units) {
|
|
2949
|
+
const result = await runCommandCapture("systemctl", [
|
|
2950
|
+
"--user",
|
|
2951
|
+
"enable",
|
|
2952
|
+
unit
|
|
2953
|
+
]);
|
|
2954
|
+
if (result.ok) return {
|
|
2955
|
+
ok: true,
|
|
2956
|
+
details: `${unit} enabled.`
|
|
2957
|
+
};
|
|
2958
|
+
const detail = extractCommandFailureDetails(result);
|
|
2959
|
+
failures.push(`${unit}: ${detail ?? "enable failed"}`);
|
|
2960
|
+
}
|
|
2961
|
+
return {
|
|
2962
|
+
ok: false,
|
|
2963
|
+
details: failures.join("; ")
|
|
2964
|
+
};
|
|
2965
|
+
};
|
|
2966
|
+
const maybeOfferToStartGnomeKeyring = async () => {
|
|
2967
|
+
const autoStartStatus = await getLinuxGnomeKeyringAutoStartStatus();
|
|
2968
|
+
if (autoStartStatus.state === "enabled") {
|
|
2969
|
+
const shouldStartNow = await p.confirm({
|
|
2970
|
+
message: "gnome-keyring autostart appears enabled, but it is not running right now. Start it now?",
|
|
2971
|
+
initialValue: true
|
|
2972
|
+
});
|
|
2973
|
+
if (p.isCancel(shouldStartNow) || !shouldStartNow) return false;
|
|
2974
|
+
const startResult = await startGnomeKeyringNow();
|
|
2975
|
+
if (!startResult.ok) {
|
|
2976
|
+
process.stdout.write(` failed to start gnome-keyring-daemon: ${startResult.details ?? "unknown error"}\n`);
|
|
2977
|
+
return false;
|
|
2978
|
+
}
|
|
2979
|
+
process.stdout.write(" started gnome-keyring-daemon for this session.\n");
|
|
2980
|
+
return true;
|
|
2981
|
+
}
|
|
2982
|
+
if (autoStartStatus.details) process.stdout.write(` autostart check: ${autoStartStatus.details}\n`);
|
|
2983
|
+
const action = await p.select({
|
|
2984
|
+
message: autoStartStatus.state === "disabled" ? "gnome-keyring autostart seems disabled. What should cdx do?" : "Could not confirm gnome-keyring autostart. What should cdx do?",
|
|
2985
|
+
options: [
|
|
2986
|
+
{
|
|
2987
|
+
value: "start-now",
|
|
2988
|
+
label: "Start now only"
|
|
2989
|
+
},
|
|
2990
|
+
{
|
|
2991
|
+
value: "enable-and-start",
|
|
2992
|
+
label: "Enable on system start and start now"
|
|
2993
|
+
},
|
|
2994
|
+
{
|
|
2995
|
+
value: "skip",
|
|
2996
|
+
label: "Skip"
|
|
2997
|
+
}
|
|
2998
|
+
],
|
|
2999
|
+
initialValue: "start-now"
|
|
3000
|
+
});
|
|
3001
|
+
if (p.isCancel(action) || action === "skip") return false;
|
|
3002
|
+
if (action === "enable-and-start") {
|
|
3003
|
+
const enableResult = await enableGnomeKeyringAutoStart();
|
|
3004
|
+
if (!enableResult.ok) {
|
|
3005
|
+
process.stdout.write(` failed to enable autostart: ${enableResult.details ?? "unknown error"}\n`);
|
|
3006
|
+
return false;
|
|
3007
|
+
}
|
|
3008
|
+
process.stdout.write(` autostart enabled${enableResult.details ? ` (${enableResult.details})` : ""}.\n`);
|
|
3009
|
+
}
|
|
3010
|
+
const startResult = await startGnomeKeyringNow();
|
|
3011
|
+
if (!startResult.ok) {
|
|
3012
|
+
process.stdout.write(` failed to start gnome-keyring-daemon: ${startResult.details ?? "unknown error"}\n`);
|
|
3013
|
+
return false;
|
|
3014
|
+
}
|
|
3015
|
+
process.stdout.write(" started gnome-keyring-daemon for this session.\n");
|
|
3016
|
+
return true;
|
|
3017
|
+
};
|
|
3018
|
+
const runLinuxSecretStoreChecklist = async () => {
|
|
3019
|
+
const gnomeKeyringInstalled = await isCommandAvailable("gnome-keyring-daemon");
|
|
3020
|
+
const secretToolInstalled = await isCommandAvailable("secret-tool");
|
|
3021
|
+
const gnomeKeyringRunning = await checkGnomeKeyringRunning();
|
|
3022
|
+
return [
|
|
3023
|
+
{
|
|
3024
|
+
id: "gnome-keyring-installed",
|
|
3025
|
+
question: "Is gnome-keyring installed?",
|
|
3026
|
+
ok: gnomeKeyringInstalled,
|
|
3027
|
+
hint: "Install the `gnome-keyring` package, then log out/in (or restart your session)."
|
|
3028
|
+
},
|
|
3029
|
+
{
|
|
3030
|
+
id: "secret-tool-installed",
|
|
3031
|
+
question: "Is secret-tool installed?",
|
|
3032
|
+
ok: secretToolInstalled,
|
|
3033
|
+
hint: "Install the package that provides `secret-tool` (often `libsecret-tools`)."
|
|
3034
|
+
},
|
|
3035
|
+
{
|
|
3036
|
+
id: "gnome-keyring-running",
|
|
3037
|
+
question: "Is gnome-keyring running?",
|
|
3038
|
+
ok: gnomeKeyringRunning.ok,
|
|
3039
|
+
details: gnomeKeyringRunning.details,
|
|
3040
|
+
hint: "Start/unlock gnome-keyring-daemon in your session (for a quick test: `gnome-keyring-daemon --start --components=secrets`)."
|
|
3041
|
+
}
|
|
3042
|
+
];
|
|
3043
|
+
};
|
|
3044
|
+
const maybeRunLinuxSecretStoreChecklist = async () => {
|
|
3045
|
+
if (!isInteractiveTerminal()) {
|
|
3046
|
+
process.stdout.write(" Tip: run `cdx doctor` in an interactive terminal to start guided Linux secret-store checks.\n");
|
|
3047
|
+
return;
|
|
3048
|
+
}
|
|
3049
|
+
const shouldRunChecklist = await p.confirm({
|
|
3050
|
+
message: "Run guided Linux secret-store checks now? (gnome-keyring installed, secret-tool installed, gnome-keyring running)",
|
|
3051
|
+
initialValue: true
|
|
3052
|
+
});
|
|
3053
|
+
if (p.isCancel(shouldRunChecklist) || !shouldRunChecklist) {
|
|
3054
|
+
process.stdout.write(" Guided Linux checks skipped.\n");
|
|
3055
|
+
return;
|
|
3056
|
+
}
|
|
3057
|
+
process.stdout.write(" Guided Linux checks:\n");
|
|
3058
|
+
const checklist = await runLinuxSecretStoreChecklist();
|
|
3059
|
+
let passed = 0;
|
|
3060
|
+
for (let i = 0; i < checklist.length; i++) {
|
|
3061
|
+
const item = checklist[i];
|
|
3062
|
+
if (item.ok) {
|
|
3063
|
+
passed += 1;
|
|
3064
|
+
process.stdout.write(` ${i + 1}/3 ${item.question} yes\n`);
|
|
3065
|
+
continue;
|
|
3066
|
+
}
|
|
3067
|
+
process.stdout.write(` ${i + 1}/3 ${item.question} no\n`);
|
|
3068
|
+
if (item.details) process.stdout.write(` details: ${item.details}\n`);
|
|
3069
|
+
if (item.hint) process.stdout.write(` hint: ${item.hint}\n`);
|
|
3070
|
+
if (item.id === "gnome-keyring-running") {
|
|
3071
|
+
if (await maybeOfferToStartGnomeKeyring()) {
|
|
3072
|
+
const runningNow = await checkGnomeKeyringRunning();
|
|
3073
|
+
if (runningNow.ok) {
|
|
3074
|
+
passed += 1;
|
|
3075
|
+
process.stdout.write(" re-check: gnome-keyring-daemon is now running.\n");
|
|
3076
|
+
} else process.stdout.write(` re-check still failing: ${runningNow.details ?? "process not detected"}\n`);
|
|
3077
|
+
}
|
|
3078
|
+
}
|
|
3079
|
+
}
|
|
3080
|
+
process.stdout.write(` Guided checklist summary: ${passed}/${checklist.length} checks passed.\n`);
|
|
3081
|
+
};
|
|
2794
3082
|
const registerDoctorCommand = (program) => {
|
|
2795
3083
|
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
3084
|
try {
|
|
@@ -2850,6 +3138,7 @@ const registerDoctorCommand = (program) => {
|
|
|
2850
3138
|
process.stdout.write(` ⚠ ${probeResult.stage} failed: ${probeResult.error.message}\n`);
|
|
2851
3139
|
const guidance = getSecretStoreProbeGuidance(process.platform);
|
|
2852
3140
|
if (guidance) process.stdout.write(` ${guidance}\n`);
|
|
3141
|
+
if (process.platform === "linux") await maybeRunLinuxSecretStoreChecklist();
|
|
2853
3142
|
}
|
|
2854
3143
|
}
|
|
2855
3144
|
if (process.platform === "darwin" && !options.checkKeychainAcl) {
|
|
@@ -2909,7 +3198,7 @@ const registerDoctorCommand = (program) => {
|
|
|
2909
3198
|
const registerHelpCommand = (program) => {
|
|
2910
3199
|
program.command("help").description("Show available commands and usage information").argument("[command]", "Show help for a specific command").action((commandName) => {
|
|
2911
3200
|
if (commandName) {
|
|
2912
|
-
const command = program.commands.find((entry) => entry.name() === commandName);
|
|
3201
|
+
const command = program.commands.find((entry) => entry.name() === commandName || entry.aliases().includes(commandName));
|
|
2913
3202
|
if (command) {
|
|
2914
3203
|
command.outputHelp();
|
|
2915
3204
|
return;
|
|
@@ -3528,8 +3817,52 @@ const executeUpdate = async (command, args) => {
|
|
|
3528
3817
|
});
|
|
3529
3818
|
});
|
|
3530
3819
|
};
|
|
3820
|
+
const executeCapture = async (command, args) => await new Promise((resolve) => {
|
|
3821
|
+
const child = spawn(command, args, { stdio: [
|
|
3822
|
+
"ignore",
|
|
3823
|
+
"pipe",
|
|
3824
|
+
"pipe"
|
|
3825
|
+
] });
|
|
3826
|
+
let stdout = "";
|
|
3827
|
+
child.stdout?.on("data", (chunk) => {
|
|
3828
|
+
stdout += chunk.toString();
|
|
3829
|
+
});
|
|
3830
|
+
child.once("error", () => {
|
|
3831
|
+
resolve({
|
|
3832
|
+
ok: false,
|
|
3833
|
+
output: ""
|
|
3834
|
+
});
|
|
3835
|
+
});
|
|
3836
|
+
child.once("close", (code) => {
|
|
3837
|
+
resolve({
|
|
3838
|
+
ok: code === 0,
|
|
3839
|
+
output: stdout.trim()
|
|
3840
|
+
});
|
|
3841
|
+
});
|
|
3842
|
+
});
|
|
3843
|
+
const getInstalledCdxVersion = async () => {
|
|
3844
|
+
const attempts = [{
|
|
3845
|
+
command: "cdx",
|
|
3846
|
+
args: ["--version"]
|
|
3847
|
+
}];
|
|
3848
|
+
if (process.argv[0] && process.argv[1]) attempts.push({
|
|
3849
|
+
command: process.argv[0],
|
|
3850
|
+
args: [process.argv[1], "--version"]
|
|
3851
|
+
});
|
|
3852
|
+
for (const attempt of attempts) {
|
|
3853
|
+
const result = await executeCapture(attempt.command, attempt.args);
|
|
3854
|
+
if (!result.ok || !result.output) continue;
|
|
3855
|
+
const version = result.output.split(/\r?\n/).at(-1)?.trim();
|
|
3856
|
+
if (version) return version;
|
|
3857
|
+
}
|
|
3858
|
+
return null;
|
|
3859
|
+
};
|
|
3531
3860
|
const registerUpdateSelfCommand = (program) => {
|
|
3532
|
-
program.command("update-self").
|
|
3861
|
+
program.command("update-self").aliases([
|
|
3862
|
+
"self-update",
|
|
3863
|
+
"update",
|
|
3864
|
+
"updte"
|
|
3865
|
+
]).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
3866
|
try {
|
|
3534
3867
|
const requestedManager = options.manager ?? "auto";
|
|
3535
3868
|
if (![
|
|
@@ -3598,8 +3931,9 @@ const registerUpdateSelfCommand = (program) => {
|
|
|
3598
3931
|
}
|
|
3599
3932
|
}
|
|
3600
3933
|
await executeUpdate(command.command, command.args);
|
|
3934
|
+
const installedVersion = await getInstalledCdxVersion();
|
|
3601
3935
|
process.stdout.write("Update completed.\n");
|
|
3602
|
-
process.stdout.write(
|
|
3936
|
+
process.stdout.write(`Installed version: ${installedVersion ?? "unknown"}\n`);
|
|
3603
3937
|
} catch (error) {
|
|
3604
3938
|
exitWithCommandError(error);
|
|
3605
3939
|
}
|