@golproductions/check 3.2.4 → 3.2.6
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/package.json +1 -1
- package/src/index.js +106 -2
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { readFileSync, writeFileSync, mkdirSync, existsSync, createWriteStream, chmodSync, unlinkSync, rmSync } from "node:fs";
|
|
3
|
+
import { spawnSync } from "node:child_process";
|
|
3
4
|
import { join } from "node:path";
|
|
4
5
|
import { homedir, platform, arch } from "node:os";
|
|
5
6
|
import { pipeline } from "node:stream/promises";
|
|
6
7
|
import { Readable } from "node:stream";
|
|
7
8
|
|
|
8
|
-
const VERSION = "3.2.
|
|
9
|
+
const VERSION = "3.2.6";
|
|
9
10
|
const BINARY_VERSION = "3.0.0";
|
|
10
11
|
const API = "https://triage.golproductions.com/preflight";
|
|
11
12
|
const CDN = "https://pub-e55366a7f5994be9be04f0e205179f4a.r2.dev/releases";
|
|
@@ -723,6 +724,103 @@ function outCreditsExhausted(f, reason) {
|
|
|
723
724
|
process.exit(0);
|
|
724
725
|
}
|
|
725
726
|
|
|
727
|
+
// The machine's own PATH resolution is the ground truth for whether a
|
|
728
|
+
// command exists. This is the same lookup the shell performs at launch:
|
|
729
|
+
// walk PATH (plus PATHEXT on Windows), pure filesystem reads, deterministic.
|
|
730
|
+
// The server honors binary_exists as proof, so real-but-unlisted tools
|
|
731
|
+
// (claude, codex, aider, internal CLIs) are never falsely blocked.
|
|
732
|
+
const PREFIX_WORDS = new Set(["sudo", "nohup", "nice", "time", "timeout", "env"]);
|
|
733
|
+
|
|
734
|
+
// POSIX shell builtins and keywords. Frozen by spec since 1992: these are
|
|
735
|
+
// provided by the shell itself, not the filesystem, and are treated as
|
|
736
|
+
// present. This set is evergreen — it does not rot the way a tool list does.
|
|
737
|
+
const SHELL_BUILTINS = new Set([
|
|
738
|
+
"cd", "echo", "printf", "pwd", "export", "set", "unset", "alias", "unalias",
|
|
739
|
+
"true", "false", "test", "[", "[[", "exit", "return", "break", "continue",
|
|
740
|
+
"shift", "source", ".", "eval", "exec", "trap", "wait", "jobs", "fg", "bg",
|
|
741
|
+
"read", "readonly", "local", "declare", "typeset", "let", "getopts", "hash",
|
|
742
|
+
"type", "command", "builtin", "ulimit", "umask", "times", "dirs", "pushd",
|
|
743
|
+
"popd", "for", "while", "until", "if", "then", "else", "elif", "fi", "do",
|
|
744
|
+
"done", "case", "esac", "in", "function", "select", "{", "}", "!",
|
|
745
|
+
]);
|
|
746
|
+
|
|
747
|
+
function segmentBases(command) {
|
|
748
|
+
const bases = [];
|
|
749
|
+
for (const seg of command.split(/\|\||&&|[|;\n]/)) {
|
|
750
|
+
const words = seg.trim().split(/\s+/).filter(Boolean);
|
|
751
|
+
let base = null;
|
|
752
|
+
for (const w of words) {
|
|
753
|
+
const bare = w.replace(/^["']+|["']+$/g, "");
|
|
754
|
+
if (!bare) continue;
|
|
755
|
+
if (PREFIX_WORDS.has(bare)) continue;
|
|
756
|
+
if (/^[\d.]+[smhd]?$/.test(bare)) continue;
|
|
757
|
+
if (bare.includes("=") && !bare.startsWith("-")) continue;
|
|
758
|
+
if (bare.startsWith("-")) continue;
|
|
759
|
+
base = bare;
|
|
760
|
+
break;
|
|
761
|
+
}
|
|
762
|
+
if (base) bases.push(base);
|
|
763
|
+
}
|
|
764
|
+
return bases;
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
// THE ROD: do not model the shell — ask it. `type` is the shell reporting
|
|
768
|
+
// on itself: builtins, keywords, functions, and PATH binaries all answer
|
|
769
|
+
// through the exact resolution the shell will use at launch. A model of the
|
|
770
|
+
// shell can drift from the shell; the shell cannot drift from itself.
|
|
771
|
+
// On Windows, bare "bash" is a trap: PATH can resolve to WSL's
|
|
772
|
+
// System32\bash.exe — a different operating system with a different
|
|
773
|
+
// filesystem. The command will actually run in Git Bash, so the probe
|
|
774
|
+
// must ask Git Bash, located explicitly. If it cannot be located,
|
|
775
|
+
// abstain — never let the wrong interpreter answer for the right one.
|
|
776
|
+
function bashPath() {
|
|
777
|
+
if (platform() !== "win32") return "bash";
|
|
778
|
+
const candidates = [
|
|
779
|
+
process.env.CLAUDE_CODE_GIT_BASH_PATH,
|
|
780
|
+
"C:/Program Files/Git/bin/bash.exe",
|
|
781
|
+
"C:/Program Files/Git/usr/bin/bash.exe",
|
|
782
|
+
"C:/Program Files (x86)/Git/bin/bash.exe",
|
|
783
|
+
].filter(Boolean);
|
|
784
|
+
for (const c of candidates) { try { if (existsSync(c)) return c; } catch {} }
|
|
785
|
+
return null;
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
function shellKnows(name, cwd, shell) {
|
|
789
|
+
try {
|
|
790
|
+
if (shell === "powershell") {
|
|
791
|
+
const r = spawnSync("powershell", ["-NoProfile", "-NonInteractive", "-Command",
|
|
792
|
+
"if (Get-Command $env:CHECK_PROBE -ErrorAction SilentlyContinue) { exit 0 }; exit 1"],
|
|
793
|
+
{ cwd, timeout: 4000, stdio: "ignore", env: { ...process.env, CHECK_PROBE: name } });
|
|
794
|
+
return r.error ? null : r.status === 0;
|
|
795
|
+
}
|
|
796
|
+
const bash = bashPath();
|
|
797
|
+
if (!bash) return null;
|
|
798
|
+
const r = spawnSync(bash, ["-c", 'type -- "$1" >/dev/null 2>&1', "check", name],
|
|
799
|
+
{ cwd, timeout: 4000, stdio: "ignore" });
|
|
800
|
+
return r.error ? null : r.status === 0;
|
|
801
|
+
} catch { return null; }
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
// Tri-state ground truth from the interpreter itself: true = every command
|
|
805
|
+
// word proven resolvable, false = at least one proven unresolvable, null =
|
|
806
|
+
// cannot be determined without executing (subshells, expansions) — abstain,
|
|
807
|
+
// never guess.
|
|
808
|
+
function allBinariesExist(command, cwd, shell) {
|
|
809
|
+
try {
|
|
810
|
+
let bases = segmentBases(command);
|
|
811
|
+
if (shell !== "powershell") bases = bases.filter(b => !SHELL_BUILTINS.has(b));
|
|
812
|
+
if (bases.length === 0) return true;
|
|
813
|
+
if (bases.some(b => /[(){}$`]/.test(b))) return null;
|
|
814
|
+
let verdict = true;
|
|
815
|
+
for (const b of bases) {
|
|
816
|
+
const known = shellKnows(b, cwd, shell);
|
|
817
|
+
if (known === null) return null;
|
|
818
|
+
if (known === false) verdict = false;
|
|
819
|
+
}
|
|
820
|
+
return verdict;
|
|
821
|
+
} catch { return null; }
|
|
822
|
+
}
|
|
823
|
+
|
|
726
824
|
async function main() {
|
|
727
825
|
let f = "claude";
|
|
728
826
|
try {
|
|
@@ -739,10 +837,16 @@ async function main() {
|
|
|
739
837
|
if (!hookKey) { out(f, false, "check: GOL_CLIENT_ID not set. Get your key at https://golproductions.com/check"); return; }
|
|
740
838
|
const controller = new AbortController();
|
|
741
839
|
const timer = setTimeout(() => controller.abort(), 5000);
|
|
840
|
+
// Ask the same interpreter that will run the command.
|
|
841
|
+
const exists = allBinariesExist(c, p.cwd, p.tool_name === "PowerShell" ? "powershell" : "bash");
|
|
842
|
+
// Send only what the verdict needs. No transcript paths, no usernames
|
|
843
|
+
// beyond what cwd itself carries — the check is about the command.
|
|
844
|
+
const body = { command: c, cwd: p.cwd || process.cwd(), platform: f, channel: "npm", tool_name: p.tool_name, v: VERSION };
|
|
845
|
+
if (exists !== null) body.binary_exists = exists;
|
|
742
846
|
const res = await fetch(API, {
|
|
743
847
|
method: "POST",
|
|
744
848
|
headers: { "Content-Type": "application/json", "X-GOL-CLIENT-ID": hookKey, "User-Agent": "c/" + VERSION },
|
|
745
|
-
body: JSON.stringify(
|
|
849
|
+
body: JSON.stringify(body),
|
|
746
850
|
signal: controller.signal
|
|
747
851
|
});
|
|
748
852
|
clearTimeout(timer);
|