Skip to content

Commit c1d739d

Browse files
NagyViktNagyVikt
andauthored
Prefer ghx when proxying GitHub CLI calls (#519)
Guardex already centralizes GitHub CLI execution through GH_BIN. This keeps the explicit GUARDEX_GH_BIN override intact, but lets installed ghx act as the default cache proxy before falling back to gh. Status now shows the active proxy command so automation can verify what will run. Constraint: ghx must remain optional and must not replace explicit GUARDEX_GH_BIN values Rejected: Add a Guardex-owned ghx binary alias | that would conflict with the real ghx package and make install ownership unclear Confidence: high Scope-risk: narrow Directive: Keep GitHub CLI selection centralized in src/context.js; do not reintroduce local process.env.GUARDEX_GH_BIN fallbacks Tested: rtk test node --test test/status.test.js test/doctor.test.js Not-tested: live ghx daemon cache behavior Co-authored-by: NagyVikt <nagy.viktordp@gmail.com>
1 parent dd74485 commit c1d739d

5 files changed

Lines changed: 48 additions & 7 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -388,7 +388,7 @@ Install repo skills with `npx skills add recodee/gitguardex`; `npx skills add re
388388
| [**cavekit**](https://github.com/JuliusBrussee/cavekit)`npx skills add JuliusBrussee/cavekit` | Spec-driven build loop with `spec`, `build`, `check`, `caveman`, `backprop` skills bundled in. | [![stars](https://img.shields.io/github/stars/JuliusBrussee/cavekit?style=social)](https://github.com/JuliusBrussee/cavekit) |
389389
| [**caveman**](https://github.com/JuliusBrussee/caveman)`npx skills add JuliusBrussee/caveman` | Ultra-compressed response mode for Claude / Codex. Less output-token churn on long reviews and debug loops. | [![stars](https://img.shields.io/github/stars/JuliusBrussee/caveman?style=social)](https://github.com/JuliusBrussee/caveman) |
390390
| [**codex-account-switcher**](https://github.com/recodeecom/codex-account-switcher-cli)`npm i -g @imdeadpool/codex-account-switcher` | Multi-identity Codex account switcher. Auto-registers accounts on `codex login`; switch with one command. | [![stars](https://img.shields.io/github/stars/recodeecom/codex-account-switcher-cli?style=social)](https://github.com/recodeecom/codex-account-switcher-cli) |
391-
| [**GitHub CLI (`gh`)**](https://github.com/cli/cli) — see [cli.github.com](https://cli.github.com/) | Required for PR / merge automation. `gx branch finish --via-pr --wait-for-merge` depends on it. | [![stars](https://img.shields.io/github/stars/cli/cli?style=social)](https://github.com/cli/cli) |
391+
| [**GitHub CLI (`gh`)**](https://github.com/cli/cli) — see [cli.github.com](https://cli.github.com/) | Required for PR / merge automation. `gx branch finish --via-pr --wait-for-merge` depends on it. If `ghx` is on `PATH`, Guardex uses that GitHub CLI cache proxy automatically; set `GUARDEX_GH_BIN=gh` to force direct `gh`. | [![stars](https://img.shields.io/github/stars/cli/cli?style=social)](https://github.com/cli/cli) |
392392

393393
---
394394

src/cli/main.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1957,6 +1957,7 @@ function collectServicesSnapshot() {
19571957
...requiredSystemTools.map((tool) => ({
19581958
name: tool.name,
19591959
displayName: tool.displayName || tool.name,
1960+
command: tool.command,
19601961
status: tool.status,
19611962
})),
19621963
];

src/context.js

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,11 +61,24 @@ const OPTIONAL_LOCAL_COMPANION_TOOLS = [
6161
installArgs: ['skills', 'add', 'JuliusBrussee/caveman'],
6262
},
6363
];
64-
const GH_BIN = process.env.GUARDEX_GH_BIN || 'gh';
64+
function commandAvailable(command) {
65+
const result = cp.spawnSync(command, ['--version'], { stdio: 'ignore' });
66+
return result.status === 0;
67+
}
68+
69+
function resolveGithubCliBin(env = process.env) {
70+
const explicit = String(env.GUARDEX_GH_BIN || '').trim();
71+
if (explicit) {
72+
return explicit;
73+
}
74+
return commandAvailable('ghx') ? 'ghx' : 'gh';
75+
}
76+
77+
const GH_BIN = resolveGithubCliBin();
6578
const REQUIRED_SYSTEM_TOOLS = [
6679
{
6780
name: 'gh',
68-
displayName: 'GitHub (gh)',
81+
displayName: GH_BIN === 'ghx' ? 'GitHub (ghx proxy)' : 'GitHub (gh)',
6982
command: GH_BIN,
7083
installHint: 'https://cli.github.com/',
7184
},
@@ -698,6 +711,7 @@ module.exports = {
698711
GLOBAL_TOOLCHAIN_SERVICES,
699712
GLOBAL_TOOLCHAIN_PACKAGES,
700713
OPTIONAL_LOCAL_COMPANION_TOOLS,
714+
resolveGithubCliBin,
701715
GH_BIN,
702716
REQUIRED_SYSTEM_TOOLS,
703717
MAINTAINER_RELEASE_REPO,

src/doctor/index.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ const {
33
path,
44
TOOL_NAME,
55
SHORT_TOOL_NAME,
6+
GH_BIN,
67
LOCK_FILE_RELATIVE,
78
REQUIRED_MANAGED_REPO_FILES,
89
OMX_SCAFFOLD_DIRECTORIES,
@@ -442,7 +443,7 @@ function finishDoctorSandboxBranch(blocked, metadata, options = {}) {
442443
};
443444
}
444445

445-
const ghBin = process.env.GUARDEX_GH_BIN || 'gh';
446+
const ghBin = GH_BIN;
446447
if (!isCommandAvailable(ghBin)) {
447448
return {
448449
status: 'skipped',
@@ -890,7 +891,7 @@ function autoFinishReadyAgentBranches(repoRoot, options = {}) {
890891

891892
const originAvailable = hasOriginRemote(repoRoot);
892893
const explicitGhBin = Boolean(String(process.env.GUARDEX_GH_BIN || '').trim());
893-
const ghBin = process.env.GUARDEX_GH_BIN || 'gh';
894+
const ghBin = GH_BIN;
894895
const ghAvailable =
895896
originAvailable &&
896897
(explicitGhBin || originRemoteLooksLikeGithub(repoRoot)) &&

test/status.test.js

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -142,14 +142,39 @@ echo "unexpected gh args: $*" >&2
142142
exit 1
143143
`);
144144

145-
const result = runNodeWithEnv([], repoDir, {
145+
const result = runNodeWithEnv(['status', '--verbose'], repoDir, {
146146
GUARDEX_GH_BIN: fakeGh.fakePath,
147147
});
148148
assert.equal(result.status, 0, result.stderr || result.stdout);
149149
assert.match(result.stdout, /GitHub \(gh\): active/);
150150
});
151151

152152

153+
test('status prefers ghx as the GitHub CLI proxy when no explicit gh binary is set', () => {
154+
const repoDir = initRepo();
155+
const fakeGhx = createFakeBin('ghx', `
156+
if [[ "$1" == "--version" ]]; then
157+
echo "ghx version 1.0.0"
158+
exit 0
159+
fi
160+
echo "unexpected ghx args: $*" >&2
161+
exit 1
162+
`);
163+
164+
const result = runNodeWithEnv(['status', '--target', repoDir, '--json'], repoDir, {
165+
PATH: `${fakeGhx.fakeBin}:${process.env.PATH}`,
166+
});
167+
168+
assert.equal(result.status, 0, result.stderr || result.stdout);
169+
const payload = JSON.parse(result.stdout);
170+
const ghService = payload.services.find((service) => service.name === 'gh');
171+
assert.ok(ghService, 'GitHub CLI service should be included in status payload');
172+
assert.equal(ghService.displayName, 'GitHub (ghx proxy)');
173+
assert.equal(ghService.command, 'ghx');
174+
assert.equal(ghService.status, 'active');
175+
});
176+
177+
153178
test('warning-only degraded status avoids zero-error wording and points humans at doctor', () => {
154179
const repoDir = initRepo();
155180

@@ -484,7 +509,7 @@ echo "unexpected npm args: $*" >&2
484509
exit 1
485510
`);
486511

487-
const result = runNodeWithEnv(['status', '--target', targetDir], targetDir, {
512+
const result = runNodeWithEnv(['status', '--target', targetDir, '--verbose'], targetDir, {
488513
GUARDEX_NPM_BIN: fakeNpm,
489514
GUARDEX_HOME_DIR: fakeHome,
490515
});

0 commit comments

Comments
 (0)