@golproductions/check 3.1.4 → 3.1.5
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 +74 -14
- package/src/mcp.js +11 -4
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import { homedir, platform, arch } from "node:os";
|
|
|
5
5
|
import { pipeline } from "node:stream/promises";
|
|
6
6
|
import { Readable } from "node:stream";
|
|
7
7
|
|
|
8
|
-
const VERSION = "3.1.
|
|
8
|
+
const VERSION = "3.1.5";
|
|
9
9
|
const BINARY_VERSION = "3.0.0";
|
|
10
10
|
const API = "https://triage.golproductions.com/preflight";
|
|
11
11
|
const CDN = "https://pub-e55366a7f5994be9be04f0e205179f4a.r2.dev/releases";
|
|
@@ -13,6 +13,12 @@ const CLIENT_ID = process.env.GOL_CLIENT_ID || "";
|
|
|
13
13
|
|
|
14
14
|
function getClientId() {
|
|
15
15
|
if (CLIENT_ID) return CLIENT_ID;
|
|
16
|
+
// Universal fallback written at install time. Cursor and Gemini spawn hooks
|
|
17
|
+
// with no GOL_CLIENT_ID in the environment, so the env var alone is not enough.
|
|
18
|
+
try {
|
|
19
|
+
const k = readFileSync(join(homedir(), ".check", "key"), "utf8").trim();
|
|
20
|
+
if (k) return k;
|
|
21
|
+
} catch {}
|
|
16
22
|
try {
|
|
17
23
|
const cfg = JSON.parse(readFileSync(join(homedir(), ".claude", "settings.json"), "utf8"));
|
|
18
24
|
return cfg.env?.GOL_CLIENT_ID || "";
|
|
@@ -115,6 +121,8 @@ async function install() {
|
|
|
115
121
|
mkdirSync(hooksDir, { recursive: true });
|
|
116
122
|
const scriptPath = join(hooksDir, "check.mjs").replace(/\\/g, "/");
|
|
117
123
|
writeFileSync(scriptPath, scriptSrc, "utf8");
|
|
124
|
+
// Key file is the fallback for tools that don't pass env to hooks (Cursor, Gemini).
|
|
125
|
+
writeFileSync(join(hooksDir, "key"), key, "utf8");
|
|
118
126
|
|
|
119
127
|
const ext = platform() === "win32" ? ".exe" : "";
|
|
120
128
|
const binaryPath = join(hooksDir, "truth-gate" + ext).replace(/\\/g, "/");
|
|
@@ -128,15 +136,19 @@ async function install() {
|
|
|
128
136
|
dir: join(home, ".claude"),
|
|
129
137
|
file: "settings.json",
|
|
130
138
|
config: (existing) => {
|
|
139
|
+
// Merge, never clobber: drop stale Check entries, keep the user's own
|
|
140
|
+
// hooks, then append ours.
|
|
131
141
|
existing.hooks = existing.hooks || {};
|
|
132
|
-
existing.hooks.PreToolUse = [
|
|
142
|
+
existing.hooks.PreToolUse = (existing.hooks.PreToolUse || []).filter(h => !h.hooks?.some(isCheckHook));
|
|
143
|
+
existing.hooks.PreToolUse.push({
|
|
133
144
|
matcher: "Bash|PowerShell",
|
|
134
145
|
hooks: [{ type: "command", command: "node", args: [scriptPath] }]
|
|
135
|
-
}
|
|
136
|
-
existing.hooks.PostToolUse = [
|
|
146
|
+
});
|
|
147
|
+
existing.hooks.PostToolUse = (existing.hooks.PostToolUse || []).filter(h => !h.hooks?.some(isCheckHook));
|
|
148
|
+
existing.hooks.PostToolUse.push(
|
|
137
149
|
{ matcher: "Bash|PowerShell", hooks: [{ type: "command", command: binaryPath }] },
|
|
138
150
|
{ matcher: "Edit|Write|NotebookEdit", hooks: [{ type: "command", command: binaryPath }] }
|
|
139
|
-
|
|
151
|
+
);
|
|
140
152
|
existing.env = existing.env || {};
|
|
141
153
|
existing.env.GOL_CLIENT_ID = key;
|
|
142
154
|
return existing;
|
|
@@ -166,9 +178,10 @@ async function install() {
|
|
|
166
178
|
config: (existing) => {
|
|
167
179
|
existing.version = existing.version || 1;
|
|
168
180
|
existing.hooks = existing.hooks || {};
|
|
169
|
-
existing.hooks.beforeShellExecution = [
|
|
181
|
+
existing.hooks.beforeShellExecution = (existing.hooks.beforeShellExecution || []).filter(h => !isCheckRef(h.command));
|
|
182
|
+
existing.hooks.beforeShellExecution.push({
|
|
170
183
|
command: "node " + scriptPath
|
|
171
|
-
}
|
|
184
|
+
});
|
|
172
185
|
return existing;
|
|
173
186
|
}
|
|
174
187
|
},
|
|
@@ -178,13 +191,15 @@ async function install() {
|
|
|
178
191
|
file: "hooks.json",
|
|
179
192
|
config: (existing) => {
|
|
180
193
|
existing.hooks = existing.hooks || {};
|
|
181
|
-
existing.hooks.PreToolUse = [
|
|
194
|
+
existing.hooks.PreToolUse = (existing.hooks.PreToolUse || []).filter(h => !h.hooks?.some(isCheckHook));
|
|
195
|
+
existing.hooks.PreToolUse.push({
|
|
182
196
|
matcher: "Bash|PowerShell",
|
|
183
197
|
hooks: [{ type: "command", command: "node", args: [scriptPath] }]
|
|
184
|
-
}
|
|
185
|
-
existing.hooks.PostToolUse = [
|
|
198
|
+
});
|
|
199
|
+
existing.hooks.PostToolUse = (existing.hooks.PostToolUse || []).filter(h => !h.hooks?.some(isCheckHook));
|
|
200
|
+
existing.hooks.PostToolUse.push(
|
|
186
201
|
{ matcher: "Bash|PowerShell", hooks: [{ type: "command", command: binaryPath }] }
|
|
187
|
-
|
|
202
|
+
);
|
|
188
203
|
existing.env = existing.env || {};
|
|
189
204
|
existing.env.GOL_CLIENT_ID = key;
|
|
190
205
|
return existing;
|
|
@@ -297,6 +312,22 @@ async function uninstall() {
|
|
|
297
312
|
|
|
298
313
|
console.log("\n Removing Check...\n");
|
|
299
314
|
|
|
315
|
+
// Silent lifecycle ping. No output, failures swallowed — the uninstall
|
|
316
|
+
// itself must never be affected. Awaited just before exit so the process
|
|
317
|
+
// doesn't kill it mid-flight; the 3s abort caps worst-case delay.
|
|
318
|
+
let uninstallPing = Promise.resolve();
|
|
319
|
+
try {
|
|
320
|
+
const key = getClientId();
|
|
321
|
+
if (key) {
|
|
322
|
+
uninstallPing = fetch(API.replace("/preflight", "/log"), {
|
|
323
|
+
method: "POST",
|
|
324
|
+
headers: { "Content-Type": "application/json", "X-GOL-CLIENT-ID": key, "User-Agent": "c/" + VERSION },
|
|
325
|
+
body: JSON.stringify({ verdict: "uninstall", channel: "npm", os: platform(), arch: arch(), version: VERSION }),
|
|
326
|
+
signal: AbortSignal.timeout(3000),
|
|
327
|
+
}).catch(() => {});
|
|
328
|
+
}
|
|
329
|
+
} catch {}
|
|
330
|
+
|
|
300
331
|
const hookTargets = [
|
|
301
332
|
{
|
|
302
333
|
name: "Claude Code",
|
|
@@ -421,6 +452,7 @@ async function uninstall() {
|
|
|
421
452
|
console.log(" CLAUDE.md / AGENTS.md rules were left in place.\n");
|
|
422
453
|
}
|
|
423
454
|
|
|
455
|
+
try { await uninstallPing; } catch {}
|
|
424
456
|
process.exit(0);
|
|
425
457
|
}
|
|
426
458
|
|
|
@@ -658,6 +690,33 @@ function out(f, ok, reason) {
|
|
|
658
690
|
process.exit(0);
|
|
659
691
|
}
|
|
660
692
|
|
|
693
|
+
// Out of checks is a billing state, not a bad command. Warn, then let the
|
|
694
|
+
// command run without the gate. The warning fires once per day per machine.
|
|
695
|
+
function outCreditsExhausted(f, reason) {
|
|
696
|
+
let warned = true;
|
|
697
|
+
try {
|
|
698
|
+
const marker = join(homedir(), ".check", "credits-warned-" + new Date().toISOString().slice(0, 10));
|
|
699
|
+
warned = existsSync(marker);
|
|
700
|
+
if (!warned) { mkdirSync(join(homedir(), ".check"), { recursive: true }); writeFileSync(marker, "1"); }
|
|
701
|
+
} catch { warned = false; }
|
|
702
|
+
const msg = warned ? undefined : reason;
|
|
703
|
+
if (f === "gemini") {
|
|
704
|
+
process.stdout.write(JSON.stringify({ decision: "allow" }));
|
|
705
|
+
if (msg) process.stderr.write(msg);
|
|
706
|
+
process.exit(0);
|
|
707
|
+
}
|
|
708
|
+
if (f === "cursor") {
|
|
709
|
+
const r = { permission: "allow" };
|
|
710
|
+
if (msg) r.user_message = msg;
|
|
711
|
+
process.stdout.write(JSON.stringify(r));
|
|
712
|
+
process.exit(0);
|
|
713
|
+
}
|
|
714
|
+
const r = { hookSpecificOutput: { hookEventName: "PreToolUse", permissionDecision: "allow" } };
|
|
715
|
+
if (msg) r.systemMessage = msg;
|
|
716
|
+
process.stdout.write(JSON.stringify(r));
|
|
717
|
+
process.exit(0);
|
|
718
|
+
}
|
|
719
|
+
|
|
661
720
|
async function main() {
|
|
662
721
|
let f = "claude";
|
|
663
722
|
try {
|
|
@@ -670,12 +729,13 @@ async function main() {
|
|
|
670
729
|
f = detect(p);
|
|
671
730
|
const c = cmd(p, f);
|
|
672
731
|
if (!c) { out(f, true); return; }
|
|
673
|
-
|
|
732
|
+
const hookKey = getClientId();
|
|
733
|
+
if (!hookKey) { out(f, false, "check: GOL_CLIENT_ID not set. Get your key at https://golproductions.com/check"); return; }
|
|
674
734
|
const controller = new AbortController();
|
|
675
735
|
const timer = setTimeout(() => controller.abort(), 5000);
|
|
676
736
|
const res = await fetch(API, {
|
|
677
737
|
method: "POST",
|
|
678
|
-
headers: { "Content-Type": "application/json", "X-GOL-CLIENT-ID":
|
|
738
|
+
headers: { "Content-Type": "application/json", "X-GOL-CLIENT-ID": hookKey, "User-Agent": "c/" + VERSION },
|
|
679
739
|
body: JSON.stringify({ command: c, cwd: p.cwd || process.cwd(), platform: f, channel: "npm", tool_name: p.tool_name, transcript_path: p.transcript_path, v: VERSION }),
|
|
680
740
|
signal: controller.signal
|
|
681
741
|
});
|
|
@@ -691,7 +751,7 @@ async function main() {
|
|
|
691
751
|
if (res.status === 402) {
|
|
692
752
|
const free = d.daily_free || 120;
|
|
693
753
|
const upsell = d.upgrade || "Top up at https://www.golproductions.com/console.html";
|
|
694
|
-
|
|
754
|
+
outCreditsExhausted(f, `check: you've used all ${free} free checks for today and your balance is empty. Check is off until tomorrow — commands run unverified. Credits you buy never expire. ${upsell}`);
|
|
695
755
|
return;
|
|
696
756
|
}
|
|
697
757
|
out(f, false, d.reason || d.error || "check: request not authorized, command blocked");
|
package/src/mcp.js
CHANGED
|
@@ -6,7 +6,7 @@ import { execFile } from "node:child_process";
|
|
|
6
6
|
import { promisify } from "node:util";
|
|
7
7
|
|
|
8
8
|
const execFileAsync = promisify(execFile);
|
|
9
|
-
const VERSION = "3.
|
|
9
|
+
const VERSION = "3.1.5";
|
|
10
10
|
const API = "https://triage.golproductions.com/preflight";
|
|
11
11
|
const CLIENT_ID = process.env.GOL_CLIENT_ID;
|
|
12
12
|
const IS_WIN = process.platform === "win32";
|
|
@@ -20,11 +20,17 @@ async function validate(command) {
|
|
|
20
20
|
const res = await fetch(API, {
|
|
21
21
|
method: "POST",
|
|
22
22
|
headers: { "Content-Type": "application/json", "X-GOL-CLIENT-ID": CLIENT_ID, "User-Agent": "c/" + VERSION },
|
|
23
|
-
body: JSON.stringify({ command, v: VERSION })
|
|
23
|
+
body: JSON.stringify({ command, v: VERSION, channel: "mcp", platform: "mcp" })
|
|
24
24
|
});
|
|
25
|
-
|
|
25
|
+
const d = await res.json();
|
|
26
|
+
d._status = res.status;
|
|
27
|
+
return d;
|
|
26
28
|
}
|
|
27
29
|
|
|
30
|
+
// 402 = out of free checks with an empty balance. That is a billing state,
|
|
31
|
+
// not a bad command: warn and let the command through unverified.
|
|
32
|
+
const CREDITS_MSG = "Check: daily free checks used and balance is empty. Command NOT verified — Check resumes tomorrow or after a top-up at https://www.golproductions.com/console.html";
|
|
33
|
+
|
|
28
34
|
const server = new McpServer({ name: "check", version: VERSION });
|
|
29
35
|
|
|
30
36
|
server.tool(
|
|
@@ -34,6 +40,7 @@ server.tool(
|
|
|
34
40
|
async ({ command }) => {
|
|
35
41
|
try {
|
|
36
42
|
const d = await validate(command);
|
|
43
|
+
if (d._status === 402) return { content: [{ type: "text", text: `Verdict: RUNNABLE (unverified). ${CREDITS_MSG}` }] };
|
|
37
44
|
if (!d.verdict) return { content: [{ type: "text", text: `Error: ${d.error || "unknown"}` }], isError: true };
|
|
38
45
|
return { content: [{ type: "text", text: `Verdict: ${d.verdict === "runnable" ? "RUNNABLE" : "INVALID"}` }] };
|
|
39
46
|
} catch (err) {
|
|
@@ -49,7 +56,7 @@ server.tool(
|
|
|
49
56
|
async ({ command }) => {
|
|
50
57
|
try {
|
|
51
58
|
const d = await validate(command);
|
|
52
|
-
if (d.verdict !== "runnable") return { content: [{ type: "text", text: "BLOCKED: " + (d.reason || "command is invalid") }] };
|
|
59
|
+
if (d._status !== 402 && d.verdict !== "runnable") return { content: [{ type: "text", text: "BLOCKED: " + (d.reason || "command is invalid") }] };
|
|
53
60
|
const { stdout, stderr } = await execFileAsync(IS_WIN ? "cmd" : "bash", IS_WIN ? ["/c", command] : ["-c", command], { timeout: 30000 });
|
|
54
61
|
return { content: [{ type: "text", text: (stdout || "") + (stderr ? "\nSTDERR: " + stderr : "") || "(no output)" }] };
|
|
55
62
|
} catch (err) {
|