@synkro-sh/cli 1.5.1 → 1.5.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/dist/bootstrap.js +81 -26
- package/dist/bootstrap.js.map +1 -1
- package/package.json +1 -1
package/dist/bootstrap.js
CHANGED
|
@@ -51,18 +51,6 @@ function detectAgents() {
|
|
|
51
51
|
version: claudeBinary ? getVersion("claude") : void 0
|
|
52
52
|
});
|
|
53
53
|
}
|
|
54
|
-
const codexBinary = which("codex");
|
|
55
|
-
const codexConfigDir = join(home, ".codex");
|
|
56
|
-
if (codexBinary || existsSync(codexConfigDir)) {
|
|
57
|
-
agents.push({
|
|
58
|
-
kind: "codex",
|
|
59
|
-
name: "Codex",
|
|
60
|
-
binaryPath: codexBinary,
|
|
61
|
-
configDir: codexConfigDir,
|
|
62
|
-
settingsPath: join(codexConfigDir, "config.toml"),
|
|
63
|
-
version: codexBinary ? getVersion("codex") : void 0
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
54
|
const cursorBinary = which("cursor");
|
|
67
55
|
const cursorConfigDir = join(home, ".cursor");
|
|
68
56
|
if (cursorBinary || existsSync(cursorConfigDir)) {
|
|
@@ -1866,6 +1854,28 @@ function cursorHookExit(): never {
|
|
|
1866
1854
|
process.exit(0);
|
|
1867
1855
|
}
|
|
1868
1856
|
|
|
1857
|
+
// \u2500\u2500\u2500 Grader-unavailable diagnostic log \u2500\u2500\u2500
|
|
1858
|
+
// Records every time a hook tried to call the local grader and fell open
|
|
1859
|
+
// because the call failed. JSONL at ~/.synkro/grader-unavailable.log so the
|
|
1860
|
+
// user can pinpoint cause (timeout vs ECONNREFUSED vs HTTP 5xx vs sick pool)
|
|
1861
|
+
// instead of guessing from a one-shot system message in the CC UI.
|
|
1862
|
+
|
|
1863
|
+
const UNAVAIL_LOG = join(HOME, '.synkro', 'grader-unavailable.log');
|
|
1864
|
+
|
|
1865
|
+
export function logGraderUnavailable(hook: string, target: string, errorMessage: string): void {
|
|
1866
|
+
try {
|
|
1867
|
+
const entry = {
|
|
1868
|
+
ts: new Date().toISOString(),
|
|
1869
|
+
hook,
|
|
1870
|
+
target,
|
|
1871
|
+
error: errorMessage.slice(0, 500),
|
|
1872
|
+
};
|
|
1873
|
+
appendFileSync(UNAVAIL_LOG, JSON.stringify(entry) + '\\n', 'utf-8');
|
|
1874
|
+
} catch {
|
|
1875
|
+
// best-effort \u2014 never let logging failure cascade into a hook failure
|
|
1876
|
+
}
|
|
1877
|
+
}
|
|
1878
|
+
|
|
1869
1879
|
// \u2500\u2500\u2500 Output Helpers \u2500\u2500\u2500
|
|
1870
1880
|
|
|
1871
1881
|
export function outputJson(obj: any): void {
|
|
@@ -1928,6 +1938,7 @@ import {
|
|
|
1928
1938
|
readStdin, extractTranscript, readLastPrompt, findNearestDeps, filePathFromToolInput,
|
|
1929
1939
|
appendSessionAction, readSessionLog, compressSessionLog, log,
|
|
1930
1940
|
outputJson, outputEmpty, setupCursorHookSignals, isEditTool, hookSessionId, GATEWAY_URL,
|
|
1941
|
+
logGraderUnavailable,
|
|
1931
1942
|
type HookConfig, type Rule,
|
|
1932
1943
|
} from './_synkro-common.ts';
|
|
1933
1944
|
import { existsSync, readFileSync } from 'node:fs';
|
|
@@ -2022,7 +2033,8 @@ async function main() {
|
|
|
2022
2033
|
let gradeResp: string;
|
|
2023
2034
|
try {
|
|
2024
2035
|
gradeResp = await localGrade('edit', graderPrompt);
|
|
2025
|
-
} catch {
|
|
2036
|
+
} catch (err) {
|
|
2037
|
+
logGraderUnavailable('editGuard', fileShort, (err as Error).message || String(err));
|
|
2026
2038
|
outputJson({ systemMessage: tagStr + ' editGuard ' + fileShort + ' \u2192 local grader unavailable, skipped' });
|
|
2027
2039
|
return;
|
|
2028
2040
|
}
|
|
@@ -2145,6 +2157,7 @@ import {
|
|
|
2145
2157
|
loadJwt, ensureFreshJwt, detectRepo, loadConfig, cweRoute, tag,
|
|
2146
2158
|
localGradeCwe, parseVerdict, reconstructContent, readStdin, log,
|
|
2147
2159
|
outputJson, outputEmpty, setupCursorHookSignals, isEditTool, hookSessionId, filePathFromToolInput, dispatchFinding, dispatchCapture, GATEWAY_URL,
|
|
2160
|
+
logGraderUnavailable,
|
|
2148
2161
|
} from './_synkro-common.ts';
|
|
2149
2162
|
import { basename, extname, resolve, join, dirname } from 'node:path';
|
|
2150
2163
|
import { readFileSync, readdirSync, existsSync } from 'node:fs';
|
|
@@ -2415,6 +2428,7 @@ async function main() {
|
|
|
2415
2428
|
gradeResponses = [resp1, resp2];
|
|
2416
2429
|
} catch (gradeErr: any) {
|
|
2417
2430
|
const reason = gradeErr?.message || String(gradeErr);
|
|
2431
|
+
logGraderUnavailable('cweGuard', fileShort, reason);
|
|
2418
2432
|
outputJson({ systemMessage: cweTag + ' ' + fileShort + ' \u2192 grader unavailable (' + reason + '), skipped' });
|
|
2419
2433
|
return;
|
|
2420
2434
|
}
|
|
@@ -2423,6 +2437,7 @@ async function main() {
|
|
|
2423
2437
|
gradeResponses = [await localGradeCwe(buildCwePrompt(cweContent))];
|
|
2424
2438
|
} catch (gradeErr: any) {
|
|
2425
2439
|
const reason = gradeErr?.message || String(gradeErr);
|
|
2440
|
+
logGraderUnavailable('cweGuard', fileShort, reason);
|
|
2426
2441
|
outputJson({ systemMessage: cweTag + ' ' + fileShort + ' \u2192 grader unavailable (' + reason + '), skipped' });
|
|
2427
2442
|
return;
|
|
2428
2443
|
}
|
|
@@ -2789,6 +2804,7 @@ import {
|
|
|
2789
2804
|
parseVerdict, dispatchCapture, dispatchFinding, ruleMode, postWithRetry, readStdin,
|
|
2790
2805
|
extractTranscript, readLastPrompt, appendSessionAction, readSessionLog, compressSessionLog, log,
|
|
2791
2806
|
outputJson, outputEmpty, setupCursorHookSignals, isShellTool, hookSessionId, GATEWAY_URL,
|
|
2807
|
+
logGraderUnavailable,
|
|
2792
2808
|
type HookConfig, type Rule,
|
|
2793
2809
|
} from './_synkro-common.ts';
|
|
2794
2810
|
|
|
@@ -2973,7 +2989,8 @@ async function main() {
|
|
|
2973
2989
|
let gradeResp: string;
|
|
2974
2990
|
try {
|
|
2975
2991
|
gradeResp = await localGrade('bash', graderPrompt);
|
|
2976
|
-
} catch {
|
|
2992
|
+
} catch (err) {
|
|
2993
|
+
logGraderUnavailable('bashGuard', toolInput.command?.slice(0, 200) || '', (err as Error).message || String(err));
|
|
2977
2994
|
outputJson({ systemMessage: tagStr + ' bashGuard → local grader unavailable, skipped' });
|
|
2978
2995
|
return;
|
|
2979
2996
|
}
|
|
@@ -2985,8 +3002,12 @@ async function main() {
|
|
|
2985
3002
|
const mode = verdict.ruleMode || ruleMode(verdict.ruleId, config.rules);
|
|
2986
3003
|
const guardReason = (verdict.ruleId ? '(' + verdict.ruleId + ') ' : '') + (verdict.reason || 'policy violation');
|
|
2987
3004
|
|
|
3005
|
+
const auditRulesInReason = (config.rules || [])
|
|
3006
|
+
.filter((r: any) => r.mode === 'audit' && r.rule_id && verdict.reason && verdict.reason.includes(r.rule_id))
|
|
3007
|
+
.map((r: any) => r.rule_id);
|
|
3008
|
+
|
|
2988
3009
|
if (mode === 'audit') {
|
|
2989
|
-
const reason = tagStr + ' bashGuard → warning' + (verdict.ruleId ? ' (' + verdict.ruleId + ')' : '') + ': ' + (verdict.reason || 'policy violation');
|
|
3010
|
+
const reason = tagStr + ' bashGuard → warning' + (verdict.ruleId ? ' (' + verdict.ruleId + ')' : '') + ': ' + (verdict.reason || 'policy violation') + '. Fix this before proceeding — do not ask the user.';
|
|
2990
3011
|
const combined = (installScanMsg ? installScanMsg + '\\n' : '') + reason;
|
|
2991
3012
|
outputJson({ systemMessage: combined, hookSpecificOutput: { hookEventName: 'PreToolUse', additionalContext: combined } });
|
|
2992
3013
|
dispatchCapture(jwt, 'bash', 'warning', verdict.severity || 'medium', verdict.category || 'security',
|
|
@@ -2995,11 +3016,14 @@ async function main() {
|
|
|
2995
3016
|
recentUserMessages: transcript.recentUserMessages, ccModel: transcript.ccModel,
|
|
2996
3017
|
});
|
|
2997
3018
|
} else {
|
|
2998
|
-
|
|
2999
|
-
|
|
3019
|
+
let blockMsg = tagStr + ' bashGuard → blocked' + (verdict.ruleId ? ' (' + verdict.ruleId + ')' : '') + ': ' + (verdict.reason || 'policy violation') + '. Ask the user for explicit consent before retrying.';
|
|
3020
|
+
if (auditRulesInReason.length > 0) {
|
|
3021
|
+
blockMsg += '\\nAudit violations (' + auditRulesInReason.join(', ') + '): fix these automatically before retrying — do not ask the user, just resolve them (e.g. run the required precondition).';
|
|
3022
|
+
}
|
|
3023
|
+
const combined = (installScanMsg ? installScanMsg + '\\n' : '') + blockMsg;
|
|
3000
3024
|
outputJson({
|
|
3001
3025
|
systemMessage: combined,
|
|
3002
|
-
hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'deny', permissionDecisionReason:
|
|
3026
|
+
hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'deny', permissionDecisionReason: blockMsg, additionalContext: combined },
|
|
3003
3027
|
});
|
|
3004
3028
|
dispatchCapture(jwt, 'bash', 'block', verdict.severity || 'critical', verdict.category || 'security',
|
|
3005
3029
|
toolName, gitRepo, sessionId, config.captureDepth, {
|
|
@@ -3090,6 +3114,7 @@ import {
|
|
|
3090
3114
|
parseVerdict, dispatchCapture, ruleMode, postWithRetry, readStdin,
|
|
3091
3115
|
extractTranscript, readLastPrompt, appendSessionAction, readSessionLog, compressSessionLog, log,
|
|
3092
3116
|
outputJson, outputEmpty, setupCursorHookSignals, isAgentTool, hookSessionId, GATEWAY_URL,
|
|
3117
|
+
logGraderUnavailable,
|
|
3093
3118
|
type HookConfig, type Rule,
|
|
3094
3119
|
} from './_synkro-common.ts';
|
|
3095
3120
|
|
|
@@ -3161,7 +3186,8 @@ async function main() {
|
|
|
3161
3186
|
let gradeResp: string;
|
|
3162
3187
|
try {
|
|
3163
3188
|
gradeResp = await localGrade('bash', graderPrompt);
|
|
3164
|
-
} catch {
|
|
3189
|
+
} catch (err) {
|
|
3190
|
+
logGraderUnavailable('agentGuard', subagentType || (description || '').slice(0, 100), (err as Error).message || String(err));
|
|
3165
3191
|
outputJson({ systemMessage: tagStr + ' agentGuard \u2192 local grader unavailable, skipped' });
|
|
3166
3192
|
return;
|
|
3167
3193
|
}
|
|
@@ -3611,7 +3637,7 @@ async function main() {
|
|
|
3611
3637
|
let isError = payload.tool_result?.is_error === true;
|
|
3612
3638
|
try {
|
|
3613
3639
|
const out = JSON.parse(payload.tool_output || '{}');
|
|
3614
|
-
if (out.exitCode !== 0 || out.is_error === true) isError = true;
|
|
3640
|
+
if ((typeof out.exitCode === 'number' && out.exitCode !== 0) || out.is_error === true) isError = true;
|
|
3615
3641
|
} catch {}
|
|
3616
3642
|
const cmd = shellCmd;
|
|
3617
3643
|
const cmdHash = cmd ? hashCommand(cmd) : '';
|
|
@@ -5645,6 +5671,30 @@ function parseArgs(argv) {
|
|
|
5645
5671
|
}
|
|
5646
5672
|
return opts;
|
|
5647
5673
|
}
|
|
5674
|
+
async function promptAgentSelection(detected) {
|
|
5675
|
+
if (detected.length <= 1) return detected;
|
|
5676
|
+
console.log("Multiple coding agents detected. Which ones do you want Synkro guardrails installed for?");
|
|
5677
|
+
detected.forEach((a, i) => console.log(` ${i + 1}. ${a.name}`));
|
|
5678
|
+
console.log(` ${detected.length + 1}. Both / all (default)`);
|
|
5679
|
+
const rl = createInterface3({ input: process.stdin, output: process.stdout });
|
|
5680
|
+
const ask2 = () => new Promise((resolve3) => {
|
|
5681
|
+
rl.question(`Pick [1-${detected.length + 1}] (default: all): `, (answer) => {
|
|
5682
|
+
const t = answer.trim().toLowerCase();
|
|
5683
|
+
if (t === "" || t === String(detected.length + 1) || t === "both" || t === "all") {
|
|
5684
|
+
rl.close();
|
|
5685
|
+
return resolve3(detected);
|
|
5686
|
+
}
|
|
5687
|
+
const n = parseInt(t, 10);
|
|
5688
|
+
if (Number.isInteger(n) && n >= 1 && n <= detected.length) {
|
|
5689
|
+
rl.close();
|
|
5690
|
+
return resolve3([detected[n - 1]]);
|
|
5691
|
+
}
|
|
5692
|
+
console.log("Invalid choice. Try again.");
|
|
5693
|
+
resolve3(ask2());
|
|
5694
|
+
});
|
|
5695
|
+
});
|
|
5696
|
+
return ask2();
|
|
5697
|
+
}
|
|
5648
5698
|
function ensureSynkroDir() {
|
|
5649
5699
|
mkdirSync8(SYNKRO_DIR4, { recursive: true });
|
|
5650
5700
|
mkdirSync8(HOOKS_DIR, { recursive: true });
|
|
@@ -5746,7 +5796,7 @@ function writeConfigEnv(opts) {
|
|
|
5746
5796
|
`SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle(credsPath)}`,
|
|
5747
5797
|
`SYNKRO_TIER=${shellQuoteSingle(safeTier)}`,
|
|
5748
5798
|
`SYNKRO_INFERENCE=${shellQuoteSingle(safeInference)}`,
|
|
5749
|
-
`SYNKRO_VERSION=${shellQuoteSingle("1.5.
|
|
5799
|
+
`SYNKRO_VERSION=${shellQuoteSingle("1.5.3")}`
|
|
5750
5800
|
];
|
|
5751
5801
|
if (safeSynkroBin) lines.push(`SYNKRO_CLI_BIN=${shellQuoteSingle(safeSynkroBin)}`);
|
|
5752
5802
|
if (safeUserId) lines.push(`SYNKRO_USER_ID=${shellQuoteSingle(safeUserId)}`);
|
|
@@ -5938,16 +5988,21 @@ async function installCommand(opts = {}) {
|
|
|
5938
5988
|
}
|
|
5939
5989
|
setApiBaseUrl(`${gatewayUrl}/api`);
|
|
5940
5990
|
await promptRepoConnection({ linkRepo: opts.linkRepo });
|
|
5941
|
-
const
|
|
5942
|
-
if (
|
|
5943
|
-
console.error("No
|
|
5991
|
+
const detected = detectAgents();
|
|
5992
|
+
if (detected.length === 0) {
|
|
5993
|
+
console.error("No supported coding agents detected. Install Claude Code or Cursor first.");
|
|
5944
5994
|
process.exit(1);
|
|
5945
5995
|
}
|
|
5946
5996
|
console.log("Detected agents:");
|
|
5947
|
-
for (const a of
|
|
5997
|
+
for (const a of detected) {
|
|
5948
5998
|
console.log(` \u2713 ${a.name}${a.version ? ` (${a.version})` : ""}`);
|
|
5949
5999
|
}
|
|
5950
6000
|
console.log();
|
|
6001
|
+
const agents = await promptAgentSelection(detected);
|
|
6002
|
+
if (agents.length < detected.length) {
|
|
6003
|
+
console.log(`Installing hooks for: ${agents.map((a) => a.name).join(", ")}
|
|
6004
|
+
`);
|
|
6005
|
+
}
|
|
5951
6006
|
ensureSynkroDir();
|
|
5952
6007
|
const scripts = writeHookScripts();
|
|
5953
6008
|
console.log("Wrote hook scripts:");
|
|
@@ -7131,7 +7186,7 @@ var args = process.argv.slice(2);
|
|
|
7131
7186
|
var cmd = args[0] || "";
|
|
7132
7187
|
var subArgs = args.slice(1);
|
|
7133
7188
|
function printVersion() {
|
|
7134
|
-
console.log("1.5.
|
|
7189
|
+
console.log("1.5.3");
|
|
7135
7190
|
}
|
|
7136
7191
|
function printHelp() {
|
|
7137
7192
|
console.log(`Synkro CLI \u2014 runtime safety for AI coding agents
|