@synkro-sh/cli 1.6.31 → 1.6.32
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 +295 -120
- package/dist/bootstrap.js.map +1 -1
- package/package.json +1 -1
package/dist/bootstrap.js
CHANGED
|
@@ -1131,6 +1131,66 @@ export function detectRepo(
|
|
|
1131
1131
|
return '';
|
|
1132
1132
|
}
|
|
1133
1133
|
|
|
1134
|
+
// \u2500\u2500\u2500 Git Root \u2500\u2500\u2500
|
|
1135
|
+
|
|
1136
|
+
export function findGitRoot(cwd: string): string {
|
|
1137
|
+
if (!cwd) return '';
|
|
1138
|
+
try {
|
|
1139
|
+
return execSync('git rev-parse --show-toplevel 2>/dev/null', { cwd, timeout: 2000, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
|
|
1140
|
+
} catch { return ''; }
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
// \u2500\u2500\u2500 .synkro file \u2500\u2500\u2500
|
|
1144
|
+
|
|
1145
|
+
export interface SynkroFileConfig {
|
|
1146
|
+
version: number;
|
|
1147
|
+
grader: { pool: 'auto' | 'claude' | 'cursor'; mode?: string };
|
|
1148
|
+
ruleset: string;
|
|
1149
|
+
scanning: { cwe: boolean; cve: boolean };
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
const SYNKRO_FILE_DEFAULTS: SynkroFileConfig = {
|
|
1153
|
+
version: 1,
|
|
1154
|
+
grader: { pool: 'auto' },
|
|
1155
|
+
ruleset: 'default',
|
|
1156
|
+
scanning: { cwe: true, cve: true },
|
|
1157
|
+
};
|
|
1158
|
+
|
|
1159
|
+
let _synkroFileCache: SynkroFileConfig | undefined;
|
|
1160
|
+
|
|
1161
|
+
export function loadSynkroFile(cwd?: string): SynkroFileConfig {
|
|
1162
|
+
if (_synkroFileCache) return _synkroFileCache;
|
|
1163
|
+
const root = cwd ? findGitRoot(cwd) : '';
|
|
1164
|
+
if (!root) { _synkroFileCache = SYNKRO_FILE_DEFAULTS; return _synkroFileCache; }
|
|
1165
|
+
const fp = root + '/.synkro';
|
|
1166
|
+
try {
|
|
1167
|
+
if (!existsSync(fp)) { _synkroFileCache = SYNKRO_FILE_DEFAULTS; return _synkroFileCache; }
|
|
1168
|
+
const parsed = JSON.parse(readFileSync(fp, 'utf-8'));
|
|
1169
|
+
_synkroFileCache = {
|
|
1170
|
+
version: parsed.version || 1,
|
|
1171
|
+
grader: {
|
|
1172
|
+
pool: ['auto', 'claude', 'cursor'].includes(parsed.grader?.pool) ? parsed.grader.pool : 'auto',
|
|
1173
|
+
mode: ['local', 'byok'].includes(parsed.grader?.mode) ? parsed.grader.mode : undefined,
|
|
1174
|
+
},
|
|
1175
|
+
ruleset: typeof parsed.ruleset === 'string' ? parsed.ruleset : 'default',
|
|
1176
|
+
scanning: {
|
|
1177
|
+
cwe: parsed.scanning?.cwe !== false,
|
|
1178
|
+
cve: parsed.scanning?.cve !== false,
|
|
1179
|
+
},
|
|
1180
|
+
};
|
|
1181
|
+
return _synkroFileCache;
|
|
1182
|
+
} catch {
|
|
1183
|
+
_synkroFileCache = SYNKRO_FILE_DEFAULTS;
|
|
1184
|
+
return _synkroFileCache;
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
export function effectiveGraderPool(synkroFile: SynkroFileConfig, hookAgentKind: AgentKind): AgentKind {
|
|
1189
|
+
if (synkroFile.grader.pool === 'auto') return hookAgentKind;
|
|
1190
|
+
if (synkroFile.grader.pool === 'claude') return 'claude_code';
|
|
1191
|
+
return 'cursor';
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1134
1194
|
// \u2500\u2500\u2500 Channel Health \u2500\u2500\u2500
|
|
1135
1195
|
|
|
1136
1196
|
export async function channelUp(port = 18929): Promise<boolean> {
|
|
@@ -1273,14 +1333,16 @@ export async function loadConfig(jwt: string, query?: string): Promise<HookConfi
|
|
|
1273
1333
|
|
|
1274
1334
|
// \u2500\u2500\u2500 Routing \u2500\u2500\u2500
|
|
1275
1335
|
|
|
1276
|
-
export async function route(config: HookConfig): Promise<'local' | 'cloud'> {
|
|
1336
|
+
export async function route(config: HookConfig, synkroFile?: SynkroFileConfig): Promise<'local' | 'cloud'> {
|
|
1337
|
+
const gradingMode = synkroFile?.grader?.mode || process.env.SYNKRO_GRADING_MODE || config.gradingMode || 'local';
|
|
1338
|
+
if (gradingMode === 'byok') return 'cloud';
|
|
1277
1339
|
if (config.captureDepth === 'local_only') return 'local';
|
|
1278
1340
|
if (await channelUp()) return 'local';
|
|
1279
1341
|
return 'cloud';
|
|
1280
1342
|
}
|
|
1281
1343
|
|
|
1282
|
-
export async function cweRoute(config: HookConfig): Promise<'local' | 'byok' | 'skip'> {
|
|
1283
|
-
const gradingMode = process.env.SYNKRO_GRADING_MODE || config.gradingMode || 'local';
|
|
1344
|
+
export async function cweRoute(config: HookConfig, synkroFile?: SynkroFileConfig): Promise<'local' | 'byok' | 'skip'> {
|
|
1345
|
+
const gradingMode = synkroFile?.grader?.mode || process.env.SYNKRO_GRADING_MODE || config.gradingMode || 'local';
|
|
1284
1346
|
if (gradingMode === 'byok') return 'byok';
|
|
1285
1347
|
if (await cweChannelUp()) return 'local';
|
|
1286
1348
|
return 'skip';
|
|
@@ -1288,10 +1350,11 @@ export async function cweRoute(config: HookConfig): Promise<'local' | 'byok' | '
|
|
|
1288
1350
|
|
|
1289
1351
|
// \u2500\u2500\u2500 Tag Building \u2500\u2500\u2500
|
|
1290
1352
|
|
|
1291
|
-
export function tag(rt: string, config: HookConfig): string {
|
|
1353
|
+
export function tag(rt: string, config: HookConfig, grader?: string): string {
|
|
1292
1354
|
if (config.silent) return '[synkro:silent]';
|
|
1293
1355
|
const rs = config.policyName || 'all';
|
|
1294
|
-
|
|
1356
|
+
const g = grader ? ':' + (grader === 'claude_code' ? 'claude' : grader) : '';
|
|
1357
|
+
return '[synkro:' + rt + ':' + rs + g + ']';
|
|
1295
1358
|
}
|
|
1296
1359
|
|
|
1297
1360
|
// \u2500\u2500\u2500 Local Grading (direct channel call) \u2500\u2500\u2500
|
|
@@ -3077,6 +3140,22 @@ function cursorHookExit(): never {
|
|
|
3077
3140
|
|
|
3078
3141
|
const UNAVAIL_LOG = join(HOME, '.synkro', 'grader-unavailable.log');
|
|
3079
3142
|
|
|
3143
|
+
export function isGraderNotConfigured(errorMessage: string): boolean {
|
|
3144
|
+
const lower = errorMessage.toLowerCase();
|
|
3145
|
+
return lower.includes('no worker') || lower.includes('not configured') || lower.includes('agent_kind') || lower.includes('no pool');
|
|
3146
|
+
}
|
|
3147
|
+
|
|
3148
|
+
export function graderUnavailableMessage(hook: string, target: string, errorMessage: string, agentKind: AgentKind = 'claude_code'): string {
|
|
3149
|
+
if (errorMessage === 'SYNKRO_CHANNEL_DOWN') {
|
|
3150
|
+
return hook + ' ' + target + ' \u2192 local grader unavailable (container not running), skipped';
|
|
3151
|
+
}
|
|
3152
|
+
if (isGraderNotConfigured(errorMessage)) {
|
|
3153
|
+
const agent = agentKind === 'cursor' ? 'Cursor' : 'Claude Code';
|
|
3154
|
+
return hook + ' ' + target + ' \u2192 local grader not configured for ' + agent + '. Add this agent to your .synkro file and run \`synkro install\`.';
|
|
3155
|
+
}
|
|
3156
|
+
return hook + ' ' + target + ' \u2192 local grader unavailable, skipped';
|
|
3157
|
+
}
|
|
3158
|
+
|
|
3080
3159
|
export function logGraderUnavailable(hook: string, target: string, errorMessage: string): void {
|
|
3081
3160
|
try {
|
|
3082
3161
|
const entry = {
|
|
@@ -3153,8 +3232,9 @@ import {
|
|
|
3153
3232
|
readStdin, extractTranscript, readLastPrompt, findNearestDeps, filePathFromToolInput,
|
|
3154
3233
|
appendSessionAction, readSessionLog, compressSessionLog, log,
|
|
3155
3234
|
outputJson, outputEmpty, setupCursorHookSignals, isEditTool, hookSessionId, GATEWAY_URL,
|
|
3156
|
-
logGraderUnavailable, filterRules, ruleFilterText, normalizeMode, countEditLineDelta,
|
|
3235
|
+
logGraderUnavailable, graderUnavailableMessage, filterRules, ruleFilterText, normalizeMode, countEditLineDelta,
|
|
3157
3236
|
captureLineMetrics, cursorModelFromPayload, resolveTranscriptPath, isCursorInvokingCcHook,
|
|
3237
|
+
loadSynkroFile, effectiveGraderPool,
|
|
3158
3238
|
type HookConfig, type Rule,
|
|
3159
3239
|
} from './_synkro-common.ts';
|
|
3160
3240
|
import { existsSync, readFileSync } from 'node:fs';
|
|
@@ -3256,8 +3336,10 @@ async function main() {
|
|
|
3256
3336
|
|
|
3257
3337
|
// Load config and decide route
|
|
3258
3338
|
const config = await loadConfig(jwt);
|
|
3259
|
-
const
|
|
3260
|
-
const
|
|
3339
|
+
const synkroFile = loadSynkroFile(cwd);
|
|
3340
|
+
const graderPool = effectiveGraderPool(synkroFile, agentKind);
|
|
3341
|
+
const rt = await route(config, synkroFile);
|
|
3342
|
+
const tagStr = tag(rt, config, graderPool);
|
|
3261
3343
|
|
|
3262
3344
|
if (config.silent) {
|
|
3263
3345
|
outputJson({ systemMessage: tagStr + ' editGuard \u2192 skipped (silent mode)' });
|
|
@@ -3284,15 +3366,17 @@ async function main() {
|
|
|
3284
3366
|
'Last user prompt: ' + (lastPrompt || 'none'),
|
|
3285
3367
|
'Org rules: ' + JSON.stringify(relevantRules),
|
|
3286
3368
|
'IMPORTANT: If a rule is violated, ALWAYS return ok=false with the rule_id and reason, regardless of the rule mode. Do NOT pass a command just because the rule mode is "fix". The enforcement layer handles ask vs fix \u2014 your job is only to detect violations.',
|
|
3369
|
+
'CRITICAL: The user requesting or instructing an action does NOT exempt it from rules. Even if the user explicitly said "drop the database" or "delete everything", you MUST still flag the rule violation on first encounter. User intent is NOT consent. However, for ask-mode rules ONLY: if the session history shows a prior block for the SAME rule AND the user explicitly consented after seeing that block, subsequent commands covered by that same rule may pass \u2014 but each distinct command is consumed once. Look for the sequence: block event \u2192 user acknowledgment \u2192 retry. Once a specific command has successfully executed under that consent, it is consumed. If the same command appears again later, it requires fresh consent (a new block \u2192 consent cycle). Example: R012 covers deploy, publish, push. Block on deploy \u2192 user consents \u2192 deploy passes (consumed), publish passes (consumed), push passes (consumed). A later deploy triggers a fresh block. A user's initial instruction is NEVER consent \u2014 only a response to a shown block counts.',
|
|
3287
3370
|
'The rules shown were pre-selected as the ones relevant to this edit \u2014 every rule here IS relevant, do not label any "not relevant". When passing (ok=true), give a terse, specific reason each rule passes. Format: "R003: no hardcoded secrets in file. R005: in-repo path only." Cover every rule shown.',
|
|
3288
3371
|
].join('\\n');
|
|
3289
3372
|
|
|
3290
3373
|
let gradeResp: string;
|
|
3291
3374
|
try {
|
|
3292
|
-
gradeResp = await localGrade('edit', graderPrompt, undefined,
|
|
3375
|
+
gradeResp = await localGrade('edit', graderPrompt, undefined, graderPool);
|
|
3293
3376
|
} catch (err) {
|
|
3294
|
-
|
|
3295
|
-
|
|
3377
|
+
const errMsg = (err as Error).message || String(err);
|
|
3378
|
+
logGraderUnavailable('editGuard', fileShort, errMsg);
|
|
3379
|
+
outputJson({ systemMessage: tagStr + ' ' + graderUnavailableMessage('editGuard', fileShort, errMsg, graderPool) });
|
|
3296
3380
|
return;
|
|
3297
3381
|
}
|
|
3298
3382
|
|
|
@@ -3406,7 +3490,8 @@ import {
|
|
|
3406
3490
|
localGradeCwe, parseVerdict, reconstructContent, readStdin, log,
|
|
3407
3491
|
outputJson, outputEmpty, setupCursorHookSignals, isEditTool, isShellTool, isCursorHookFormat,
|
|
3408
3492
|
extractShellCodeWrites, hookSessionId, filePathFromToolInput, emitBlockScanFindings, dispatchFinding, dispatchCapture, GATEWAY_URL,
|
|
3409
|
-
logGraderUnavailable, resolveTranscriptPath, isCursorInvokingCcHook,
|
|
3493
|
+
logGraderUnavailable, graderUnavailableMessage, resolveTranscriptPath, isCursorInvokingCcHook,
|
|
3494
|
+
loadSynkroFile, effectiveGraderPool,
|
|
3410
3495
|
} from './_synkro-common.ts';
|
|
3411
3496
|
import { basename, extname, resolve, join, dirname } from 'node:path';
|
|
3412
3497
|
import { readFileSync, readdirSync, existsSync } from 'node:fs';
|
|
@@ -3619,10 +3704,12 @@ async function main() {
|
|
|
3619
3704
|
jwt = await ensureFreshJwt(jwt);
|
|
3620
3705
|
|
|
3621
3706
|
const config = await loadConfig(jwt);
|
|
3622
|
-
const
|
|
3707
|
+
const synkroFile = loadSynkroFile(cwd);
|
|
3708
|
+
const graderPool = effectiveGraderPool(synkroFile, agentKind);
|
|
3709
|
+
const rt = await cweRoute(config, synkroFile);
|
|
3623
3710
|
|
|
3624
3711
|
if (config.silent) {
|
|
3625
|
-
outputJson({ systemMessage: '[synkro:' + rt + ':cweScan] skipped (silent mode)' });
|
|
3712
|
+
outputJson({ systemMessage: '[synkro:' + rt + ':cweScan:' + (graderPool === 'claude_code' ? 'claude' : graderPool) + '] skipped (silent mode)' });
|
|
3626
3713
|
return;
|
|
3627
3714
|
}
|
|
3628
3715
|
|
|
@@ -3643,10 +3730,11 @@ async function main() {
|
|
|
3643
3730
|
}
|
|
3644
3731
|
}
|
|
3645
3732
|
|
|
3646
|
-
const
|
|
3733
|
+
const graderLabel = graderPool === 'claude_code' ? 'claude' : graderPool;
|
|
3734
|
+
const cweTag = '[synkro:' + rt + ':cweScan:' + graderLabel + ']';
|
|
3647
3735
|
|
|
3648
3736
|
if (rt === 'skip') {
|
|
3649
|
-
outputJson({ systemMessage: cweTag + ' ' + fileShort
|
|
3737
|
+
outputJson({ systemMessage: cweTag + ' ' + graderUnavailableMessage('cweGuard', fileShort, 'channel down', graderPool) });
|
|
3650
3738
|
return;
|
|
3651
3739
|
}
|
|
3652
3740
|
|
|
@@ -3828,23 +3916,23 @@ async function main() {
|
|
|
3828
3916
|
const chunk2 = cweContent.slice(mid - OVERLAP);
|
|
3829
3917
|
try {
|
|
3830
3918
|
const [resp1, resp2] = await Promise.all([
|
|
3831
|
-
localGradeCwe(buildCwePrompt(chunk1),
|
|
3832
|
-
localGradeCwe(buildCwePrompt(chunk2),
|
|
3919
|
+
localGradeCwe(buildCwePrompt(chunk1), graderPool),
|
|
3920
|
+
localGradeCwe(buildCwePrompt(chunk2), graderPool),
|
|
3833
3921
|
]);
|
|
3834
3922
|
gradeResponses = [resp1, resp2];
|
|
3835
3923
|
} catch (gradeErr: any) {
|
|
3836
3924
|
const reason = gradeErr?.message || String(gradeErr);
|
|
3837
3925
|
logGraderUnavailable('cweGuard', fileShort, reason);
|
|
3838
|
-
outputJson({ systemMessage: cweTag + ' ' +
|
|
3926
|
+
outputJson({ systemMessage: cweTag + ' ' + graderUnavailableMessage('cweGuard', fileShort, reason, graderPool) });
|
|
3839
3927
|
return;
|
|
3840
3928
|
}
|
|
3841
3929
|
} else {
|
|
3842
3930
|
try {
|
|
3843
|
-
gradeResponses = [await localGradeCwe(buildCwePrompt(cweContent),
|
|
3931
|
+
gradeResponses = [await localGradeCwe(buildCwePrompt(cweContent), graderPool)];
|
|
3844
3932
|
} catch (gradeErr: any) {
|
|
3845
3933
|
const reason = gradeErr?.message || String(gradeErr);
|
|
3846
3934
|
logGraderUnavailable('cweGuard', fileShort, reason);
|
|
3847
|
-
outputJson({ systemMessage: cweTag + ' ' +
|
|
3935
|
+
outputJson({ systemMessage: cweTag + ' ' + graderUnavailableMessage('cweGuard', fileShort, reason, graderPool) });
|
|
3848
3936
|
return;
|
|
3849
3937
|
}
|
|
3850
3938
|
}
|
|
@@ -4244,7 +4332,7 @@ import {
|
|
|
4244
4332
|
loadJwt, ensureFreshJwt, detectRepo, loadConfig, route, tag,
|
|
4245
4333
|
readStdin, runInstallScan, emitBlockScanFindings, dispatchCapture, dispatchScanResult, hashCommand,
|
|
4246
4334
|
outputJson, outputEmpty, setupCursorHookSignals, isShellTool, hookSessionId, log, GATEWAY_URL,
|
|
4247
|
-
resolveTranscriptPath, isCursorHookFormat,
|
|
4335
|
+
resolveTranscriptPath, isCursorHookFormat, loadSynkroFile, effectiveGraderPool,
|
|
4248
4336
|
} from './_synkro-common.ts';
|
|
4249
4337
|
import { writeFileSync, mkdirSync } from 'node:fs';
|
|
4250
4338
|
import { join } from 'node:path';
|
|
@@ -4290,11 +4378,13 @@ async function main() {
|
|
|
4290
4378
|
const transcriptPath = resolveTranscriptPath(payload);
|
|
4291
4379
|
const repo = detectRepo(cwd, transcriptPath, command, workspaceRoots);
|
|
4292
4380
|
const config = await loadConfig(jwt);
|
|
4293
|
-
const
|
|
4294
|
-
const
|
|
4381
|
+
const isCursor = isCursorHookFormat();
|
|
4382
|
+
const synkroFile = loadSynkroFile(cwd);
|
|
4383
|
+
const graderPool = effectiveGraderPool(synkroFile, isCursor ? 'cursor' : 'claude_code');
|
|
4384
|
+
const rt = await route(config, synkroFile);
|
|
4385
|
+
const tagStr = tag(rt, config, graderPool);
|
|
4295
4386
|
|
|
4296
4387
|
const rawModel = String(payload.model ?? payload.model_id ?? '');
|
|
4297
|
-
const isCursor = isCursorHookFormat();
|
|
4298
4388
|
const model = isCursor ? (rawModel ? (rawModel.startsWith('cursor/') ? rawModel : 'cursor/' + rawModel) : 'cursor') : (rawModel || '');
|
|
4299
4389
|
|
|
4300
4390
|
if (scan.action === 'block') {
|
|
@@ -4362,7 +4452,8 @@ import {
|
|
|
4362
4452
|
parseVerdict, dispatchCapture, dispatchFinding, ruleMode, postWithRetry, readStdin,
|
|
4363
4453
|
extractTranscript, readLastPrompt, appendSessionAction, readSessionLog, compressSessionLog, log,
|
|
4364
4454
|
outputJson, outputEmpty, setupCursorHookSignals, isShellTool, hookSessionId, GATEWAY_URL,
|
|
4365
|
-
logGraderUnavailable, filterRules, ruleFilterText, normalizeMode, appendLocalTelemetry, isSafeInRepoRead,
|
|
4455
|
+
logGraderUnavailable, graderUnavailableMessage, filterRules, ruleFilterText, normalizeMode, appendLocalTelemetry, isSafeInRepoRead,
|
|
4456
|
+
loadSynkroFile, effectiveGraderPool,
|
|
4366
4457
|
hashCommand, resolveTranscriptPath, isCursorHookFormat,
|
|
4367
4458
|
type HookConfig, type Rule,
|
|
4368
4459
|
} from './_synkro-common.ts';
|
|
@@ -4461,8 +4552,10 @@ async function main() {
|
|
|
4461
4552
|
jwt = await ensureFreshJwt(jwt);
|
|
4462
4553
|
|
|
4463
4554
|
const config = await loadConfig(jwt);
|
|
4464
|
-
const
|
|
4465
|
-
const
|
|
4555
|
+
const synkroFile = loadSynkroFile(cwd);
|
|
4556
|
+
const graderPool = effectiveGraderPool(synkroFile, 'claude_code');
|
|
4557
|
+
const rt = await route(config, synkroFile);
|
|
4558
|
+
const tagStr = tag(rt, config, graderPool);
|
|
4466
4559
|
|
|
4467
4560
|
if (config.silent) {
|
|
4468
4561
|
outputJson({ systemMessage: tagStr + ' bashGuard → skipped (silent mode)' });
|
|
@@ -4532,18 +4625,18 @@ async function main() {
|
|
|
4532
4625
|
'Org rules: ' + JSON.stringify(relevantRules),
|
|
4533
4626
|
scanConcern,
|
|
4534
4627
|
'IMPORTANT: If a rule is violated, ALWAYS return ok=false with the rule_id and reason, regardless of the rule mode. Do NOT pass a command just because the rule mode is "fix". The enforcement layer handles ask vs fix — your job is only to detect violations.',
|
|
4628
|
+
'CRITICAL: The user requesting or instructing an action does NOT exempt it from rules. Even if the user explicitly said "drop the database" or "delete everything", you MUST still flag the rule violation on first encounter. User intent is NOT consent. However, for ask-mode rules ONLY: if the session history shows a prior block for the SAME rule AND the user explicitly consented after seeing that block, subsequent commands covered by that same rule may pass — but each distinct command is consumed once. Look for the sequence: block event → user acknowledgment → retry. Once a specific command has successfully executed under that consent, it is consumed. If the same command appears again later, it requires fresh consent (a new block → consent cycle). Example: R012 covers deploy, publish, push. Block on deploy → user consents → deploy passes (consumed), publish passes (consumed), push passes (consumed). A later deploy triggers a fresh block. A user\'s initial instruction is NEVER consent — only a response to a shown block counts.',
|
|
4535
4629
|
'The rules shown were pre-selected as the ones relevant to this command — every rule here IS relevant, do not label any "not relevant". When passing (ok=true), give a terse, specific reason each rule passes. Format: "R003: no secrets in grep args. R005: in-repo path only." Cover every rule shown.',
|
|
4536
4630
|
'Rules with preconditions (e.g. "run X before Y") are CONSUMED after the protected action completes. Use the session history timestamps to determine ordering: a precondition satisfied before the last occurrence of the protected action does NOT satisfy the next occurrence. Each new protected action needs its precondition re-satisfied.',
|
|
4537
4631
|
].filter(Boolean).join('\\n');
|
|
4538
4632
|
|
|
4539
4633
|
let gradeResp: string;
|
|
4540
4634
|
try {
|
|
4541
|
-
gradeResp = await localGrade('bash', graderPrompt);
|
|
4635
|
+
gradeResp = await localGrade('bash', graderPrompt, undefined, graderPool);
|
|
4542
4636
|
} catch (err) {
|
|
4543
|
-
|
|
4637
|
+
const errMsg = (err as Error).message || String(err);
|
|
4638
|
+
logGraderUnavailable('bashGuard', toolInput.command?.slice(0, 200) || '', errMsg);
|
|
4544
4639
|
if (scanConcern) {
|
|
4545
|
-
// Grader unavailable to run the consent check — fail closed on a
|
|
4546
|
-
// scanner-flagged install (ask-mode so the user can still consent).
|
|
4547
4640
|
const ctx = scanBlockContext + ' Synkro flagged this install but the grader is unavailable to check consent — ask the user for explicit consent, then retry.';
|
|
4548
4641
|
outputJson({
|
|
4549
4642
|
systemMessage: tagStr + ' bashGuard → install blocked (scan flagged; grader unavailable)',
|
|
@@ -4551,7 +4644,7 @@ async function main() {
|
|
|
4551
4644
|
});
|
|
4552
4645
|
return;
|
|
4553
4646
|
}
|
|
4554
|
-
outputJson({ systemMessage: tagStr + '
|
|
4647
|
+
outputJson({ systemMessage: tagStr + ' ' + graderUnavailableMessage('bashGuard', cmdShort, errMsg) });
|
|
4555
4648
|
return;
|
|
4556
4649
|
}
|
|
4557
4650
|
|
|
@@ -4656,7 +4749,8 @@ import {
|
|
|
4656
4749
|
parseVerdict, dispatchCapture, ruleMode, postWithRetry, readStdin,
|
|
4657
4750
|
extractTranscript, readLastPrompt, appendSessionAction, readSessionLog, compressSessionLog, log,
|
|
4658
4751
|
outputJson, outputEmpty, setupCursorHookSignals, isAgentTool, hookSessionId, GATEWAY_URL,
|
|
4659
|
-
logGraderUnavailable, filterRules, normalizeMode, resolveTranscriptPath, isCursorInvokingCcHook,
|
|
4752
|
+
logGraderUnavailable, graderUnavailableMessage, filterRules, normalizeMode, resolveTranscriptPath, isCursorInvokingCcHook,
|
|
4753
|
+
loadSynkroFile, effectiveGraderPool,
|
|
4660
4754
|
type HookConfig, type Rule,
|
|
4661
4755
|
} from './_synkro-common.ts';
|
|
4662
4756
|
|
|
@@ -4715,8 +4809,10 @@ async function main() {
|
|
|
4715
4809
|
}
|
|
4716
4810
|
|
|
4717
4811
|
const config = await loadConfig(jwt);
|
|
4718
|
-
const
|
|
4719
|
-
const
|
|
4812
|
+
const synkroFile = loadSynkroFile(cwd);
|
|
4813
|
+
const graderPool = effectiveGraderPool(synkroFile, agentKind);
|
|
4814
|
+
const rt = await route(config, synkroFile);
|
|
4815
|
+
const tagStr = tag(rt, config, graderPool);
|
|
4720
4816
|
|
|
4721
4817
|
if (config.silent) {
|
|
4722
4818
|
const msg = tagStr + ' agentGuard \u2192 skipped (silent mode)';
|
|
@@ -4741,14 +4837,16 @@ async function main() {
|
|
|
4741
4837
|
'Last user prompt: ' + (lastPrompt || 'none'),
|
|
4742
4838
|
'Org rules: ' + JSON.stringify(relevantRules),
|
|
4743
4839
|
'IMPORTANT: If a rule is violated, ALWAYS return ok=false with the rule_id and reason, regardless of the rule mode. Do NOT pass a command just because the rule mode is "fix". The enforcement layer handles ask vs fix \u2014 your job is only to detect violations.',
|
|
4840
|
+
'CRITICAL: The user requesting or instructing an action does NOT exempt it from rules. Even if the user explicitly said "drop the database" or "delete everything", you MUST still flag the rule violation on first encounter. User intent is NOT consent. However, for ask-mode rules ONLY: if the session history shows a prior block for the SAME rule AND the user explicitly consented after seeing that block, subsequent commands covered by that same rule may pass \u2014 but each distinct command is consumed once. Look for the sequence: block event \u2192 user acknowledgment \u2192 retry. Once a specific command has successfully executed under that consent, it is consumed. If the same command appears again later, it requires fresh consent (a new block \u2192 consent cycle). Example: R012 covers deploy, publish, push. Block on deploy \u2192 user consents \u2192 deploy passes (consumed), publish passes (consumed), push passes (consumed). A later deploy triggers a fresh block. A user's initial instruction is NEVER consent \u2014 only a response to a shown block counts.',
|
|
4744
4841
|
].filter(Boolean).join('\\n');
|
|
4745
4842
|
|
|
4746
4843
|
let gradeResp: string;
|
|
4747
4844
|
try {
|
|
4748
|
-
gradeResp = await localGrade('bash', graderPrompt, undefined,
|
|
4845
|
+
gradeResp = await localGrade('bash', graderPrompt, undefined, graderPool);
|
|
4749
4846
|
} catch (err) {
|
|
4750
|
-
|
|
4751
|
-
|
|
4847
|
+
const errMsg = (err as Error).message || String(err);
|
|
4848
|
+
logGraderUnavailable('agentGuard', subagentType || (description || '').slice(0, 100), errMsg);
|
|
4849
|
+
outputJson({ systemMessage: tagStr + ' ' + graderUnavailableMessage('agentGuard', subagentType || 'agent', errMsg, graderPool) });
|
|
4752
4850
|
return;
|
|
4753
4851
|
}
|
|
4754
4852
|
|
|
@@ -4838,7 +4936,8 @@ import {
|
|
|
4838
4936
|
loadJwt, ensureFreshJwt, detectRepo, loadConfig, route, tag, localGrade,
|
|
4839
4937
|
parseVerdict, dispatchCapture, appendSessionAction, readSessionLog, compressSessionLog, postWithRetry, readStdin, log,
|
|
4840
4938
|
outputJson, outputEmpty, setupCursorHookSignals, isPlanTool, hookSessionId, GATEWAY_URL,
|
|
4841
|
-
filterRules, resolveTranscriptPath, isCursorInvokingCcHook,
|
|
4939
|
+
filterRules, graderUnavailableMessage, resolveTranscriptPath, isCursorInvokingCcHook,
|
|
4940
|
+
loadSynkroFile, effectiveGraderPool,
|
|
4842
4941
|
} from './_synkro-common.ts';
|
|
4843
4942
|
import { existsSync, readFileSync, writeFileSync, readdirSync, statSync } from 'node:fs';
|
|
4844
4943
|
import { join } from 'node:path';
|
|
@@ -4923,8 +5022,10 @@ async function main() {
|
|
|
4923
5022
|
const ccModel = agentKind === 'cursor' ? (rawModel ? (rawModel.startsWith('cursor/') ? rawModel : 'cursor/' + rawModel) : 'cursor') : (rawModel || '');
|
|
4924
5023
|
|
|
4925
5024
|
const config = await loadConfig(jwt);
|
|
4926
|
-
const
|
|
4927
|
-
const
|
|
5025
|
+
const synkroFile = loadSynkroFile(cwd);
|
|
5026
|
+
const graderPool = effectiveGraderPool(synkroFile, agentKind);
|
|
5027
|
+
const rt = await route(config, synkroFile);
|
|
5028
|
+
const tagStr = tag(rt, config, graderPool);
|
|
4928
5029
|
|
|
4929
5030
|
if (config.silent) {
|
|
4930
5031
|
outputJson({ systemMessage: tagStr + ' planReview \u2192 skipped (silent mode)' });
|
|
@@ -4945,9 +5046,9 @@ async function main() {
|
|
|
4945
5046
|
|
|
4946
5047
|
let gradeResp: string;
|
|
4947
5048
|
try {
|
|
4948
|
-
gradeResp = await localGrade('plan', graderPrompt, undefined,
|
|
4949
|
-
} catch {
|
|
4950
|
-
outputJson({ systemMessage: tagStr + ' planReview
|
|
5049
|
+
gradeResp = await localGrade('plan', graderPrompt, undefined, graderPool);
|
|
5050
|
+
} catch (err) {
|
|
5051
|
+
outputJson({ systemMessage: tagStr + ' ' + graderUnavailableMessage('planReview', 'plan', (err as Error).message || String(err), graderPool) });
|
|
4951
5052
|
return;
|
|
4952
5053
|
}
|
|
4953
5054
|
|
|
@@ -5090,7 +5191,7 @@ main();
|
|
|
5090
5191
|
import {
|
|
5091
5192
|
loadJwt, detectRepo, channelUp, tag, readStdin, writeCachedRepo,
|
|
5092
5193
|
outputJson, outputEmpty, setupCursorHookSignals, hookSessionId, resolveTranscriptPath, GATEWAY_URL,
|
|
5093
|
-
isLocalStorageMode, log, type HookConfig,
|
|
5194
|
+
isLocalStorageMode, loadSynkroFile, log, type HookConfig,
|
|
5094
5195
|
} from './_synkro-common.ts';
|
|
5095
5196
|
|
|
5096
5197
|
async function main() {
|
|
@@ -5110,7 +5211,9 @@ async function main() {
|
|
|
5110
5211
|
let jwt = loadJwt();
|
|
5111
5212
|
|
|
5112
5213
|
const isChannelUp = await channelUp();
|
|
5113
|
-
const
|
|
5214
|
+
const synkroFile = loadSynkroFile(cwd);
|
|
5215
|
+
const gradingMode = synkroFile.grader.mode || process.env.SYNKRO_GRADING_MODE || 'local';
|
|
5216
|
+
const rt = gradingMode === 'byok' ? 'cloud' : (isChannelUp ? 'local' : 'cloud');
|
|
5114
5217
|
|
|
5115
5218
|
let policyName = '';
|
|
5116
5219
|
let silent = false;
|
|
@@ -5142,15 +5245,24 @@ async function main() {
|
|
|
5142
5245
|
} catch {}
|
|
5143
5246
|
}
|
|
5144
5247
|
|
|
5145
|
-
const fakeConfig: HookConfig = { captureDepth: 'local_only', tier: 'standard', silent, policyName, rules: [] };
|
|
5248
|
+
const fakeConfig: HookConfig = { captureDepth: 'local_only', tier: 'standard', silent, policyName, rules: [], scanExemptions: [], gradingMode, storageMode: process.env.SYNKRO_STORAGE_MODE || 'local' };
|
|
5146
5249
|
const tagStr = tag(rt, fakeConfig);
|
|
5147
|
-
const routeLine = tagStr + ' inference: ' + (isChannelUp ? 'local-cc (channel reachable on 127.0.0.1:18929)' : 'cloud (local-cc channel not reachable)');
|
|
5250
|
+
const routeLine = tagStr + ' inference: ' + (gradingMode === 'byok' ? 'cloud (BYOK)' : isChannelUp ? 'local-cc (channel reachable on 127.0.0.1:18929)' : 'cloud (local-cc channel not reachable)');
|
|
5148
5251
|
|
|
5149
5252
|
if (!jwt) {
|
|
5150
5253
|
outputJson({ systemMessage: routeLine });
|
|
5151
5254
|
return;
|
|
5152
5255
|
}
|
|
5153
5256
|
|
|
5257
|
+
// Sync grading mode to API profile so the dashboard reflects the actual config
|
|
5258
|
+
const fastInference = gradingMode === 'byok';
|
|
5259
|
+
fetch(GATEWAY_URL + '/api/v1/cli/me', {
|
|
5260
|
+
method: 'PATCH',
|
|
5261
|
+
headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + jwt },
|
|
5262
|
+
body: JSON.stringify({ fast_inference: fastInference }),
|
|
5263
|
+
signal: AbortSignal.timeout(3000),
|
|
5264
|
+
}).catch(() => {});
|
|
5265
|
+
|
|
5154
5266
|
if (!openFindings) {
|
|
5155
5267
|
outputJson({ systemMessage: routeLine });
|
|
5156
5268
|
} else if (openFindings === 1) {
|
|
@@ -5342,7 +5454,8 @@ import {
|
|
|
5342
5454
|
parseVerdict, dispatchCapture, dispatchFinding, ruleMode, normalizeMode, filterRules, ruleFilterText,
|
|
5343
5455
|
isSafeInRepoRead, resolveTranscriptPath, postWithRetry, readStdin, hashCommand,
|
|
5344
5456
|
extractTranscript, readLastPrompt, readSessionLog, compressSessionLog,
|
|
5345
|
-
appendLocalTelemetry, logGraderUnavailable, log, GATEWAY_URL,
|
|
5457
|
+
appendLocalTelemetry, logGraderUnavailable, graderUnavailableMessage, log, GATEWAY_URL,
|
|
5458
|
+
loadSynkroFile, effectiveGraderPool,
|
|
5346
5459
|
type Rule,
|
|
5347
5460
|
} from './_synkro-common.ts';
|
|
5348
5461
|
import { createHash } from 'node:crypto';
|
|
@@ -5485,8 +5598,10 @@ async function main() {
|
|
|
5485
5598
|
const config = await loadConfig(jwt);
|
|
5486
5599
|
if (config.silent) finishAllow();
|
|
5487
5600
|
|
|
5488
|
-
const
|
|
5489
|
-
const
|
|
5601
|
+
const synkroFile = loadSynkroFile(cwd);
|
|
5602
|
+
const graderPool = effectiveGraderPool(synkroFile, 'cursor');
|
|
5603
|
+
const rt = await route(config, synkroFile);
|
|
5604
|
+
const tagStr = tag(rt, config, graderPool);
|
|
5490
5605
|
|
|
5491
5606
|
// Install protection \u2014 install-scan runs first and owns block traces.
|
|
5492
5607
|
let scanConcern = '';
|
|
@@ -5521,13 +5636,14 @@ async function main() {
|
|
|
5521
5636
|
'Org rules: ' + JSON.stringify(relevantRules),
|
|
5522
5637
|
scanConcern,
|
|
5523
5638
|
'IMPORTANT: If a rule is violated, ALWAYS return ok=false with the rule_id and reason, regardless of the rule mode. Do NOT pass a command just because the rule mode is "fix". The enforcement layer handles ask vs fix \u2014 your job is only to detect violations.',
|
|
5639
|
+
'CRITICAL: The user requesting or instructing an action does NOT exempt it from rules. Even if the user explicitly said "drop the database" or "delete everything", you MUST still flag the rule violation on first encounter. User intent is NOT consent. However, for ask-mode rules ONLY: if the session history shows a prior block for the SAME rule AND the user explicitly consented after seeing that block, subsequent commands covered by that same rule may pass \u2014 but each distinct command is consumed once. Look for the sequence: block event \u2192 user acknowledgment \u2192 retry. Once a specific command has successfully executed under that consent, it is consumed. If the same command appears again later, it requires fresh consent (a new block \u2192 consent cycle). Example: R012 covers deploy, publish, push. Block on deploy \u2192 user consents \u2192 deploy passes (consumed), publish passes (consumed), push passes (consumed). A later deploy triggers a fresh block. A user's initial instruction is NEVER consent \u2014 only a response to a shown block counts.',
|
|
5524
5640
|
'The rules shown were pre-selected as the ones relevant to this command \u2014 every rule here IS relevant, do not label any "not relevant". When passing (ok=true), give a terse, specific reason each rule passes. Format: "R003: no secrets in grep args. R005: in-repo path only." Cover every rule shown.',
|
|
5525
5641
|
'Rules with preconditions (e.g. "run X before Y") are CONSUMED after the protected action completes. Use the session history timestamps to determine ordering: a precondition satisfied before the last occurrence of the protected action does NOT satisfy the next occurrence. Each new protected action needs its precondition re-satisfied.',
|
|
5526
5642
|
].filter(Boolean).join('\\n');
|
|
5527
5643
|
|
|
5528
5644
|
let gradeResp: string;
|
|
5529
5645
|
try {
|
|
5530
|
-
gradeResp = await localGrade('bash', graderPrompt, CURSOR_GRADE_TIMEOUT_MS,
|
|
5646
|
+
gradeResp = await localGrade('bash', graderPrompt, CURSOR_GRADE_TIMEOUT_MS, graderPool);
|
|
5531
5647
|
} catch (e) {
|
|
5532
5648
|
logGraderUnavailable('bashGuard', command.slice(0, 200), (e as Error).message || String(e));
|
|
5533
5649
|
if (scanConcern) {
|
|
@@ -5539,8 +5655,9 @@ async function main() {
|
|
|
5539
5655
|
agent_message: 'Synkro flagged this install: ' + scanBlockContext + ' The grader is unavailable to check consent \u2014 ask the user for explicit consent, then retry.',
|
|
5540
5656
|
});
|
|
5541
5657
|
}
|
|
5542
|
-
|
|
5543
|
-
|
|
5658
|
+
const errMsg = (e as Error).message || String(e);
|
|
5659
|
+
log('bashGuard ' + cmdShort + ' \u2192 pass (grade unavailable): ' + errMsg);
|
|
5660
|
+
finishWith({ permission: 'allow', user_message: tagStr + ' ' + graderUnavailableMessage('bashGuard', cmdShort, errMsg, graderPool) });
|
|
5544
5661
|
}
|
|
5545
5662
|
|
|
5546
5663
|
const verdict = parseVerdict(gradeResp);
|
|
@@ -6908,10 +7025,10 @@ __export(dockerInstall_exports, {
|
|
|
6908
7025
|
splitWorkers: () => splitWorkers,
|
|
6909
7026
|
waitForContainerReady: () => waitForContainerReady
|
|
6910
7027
|
});
|
|
6911
|
-
import { copyFileSync, existsSync as existsSync7, mkdirSync as mkdirSync7, readdirSync as readdirSync2 } from "fs";
|
|
7028
|
+
import { copyFileSync, existsSync as existsSync7, mkdirSync as mkdirSync7, readFileSync as readFileSync6, readdirSync as readdirSync2 } from "fs";
|
|
6912
7029
|
import { homedir as homedir6 } from "os";
|
|
6913
7030
|
import { join as join6 } from "path";
|
|
6914
|
-
import { spawnSync as spawnSync2 } from "child_process";
|
|
7031
|
+
import { execSync as execSync4, spawnSync as spawnSync2 } from "child_process";
|
|
6915
7032
|
function splitWorkers(total, providers) {
|
|
6916
7033
|
const t = Math.max(0, Math.floor(total));
|
|
6917
7034
|
const hasClaude = providers.includes("claude_code");
|
|
@@ -6929,6 +7046,19 @@ function normalizeProvider(p) {
|
|
|
6929
7046
|
if (v === "cursor") return "cursor";
|
|
6930
7047
|
return null;
|
|
6931
7048
|
}
|
|
7049
|
+
function readSynkroFilePool() {
|
|
7050
|
+
try {
|
|
7051
|
+
const root = execSync4("git rev-parse --show-toplevel 2>/dev/null", { encoding: "utf-8", timeout: 3e3, stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
7052
|
+
if (!root) return "auto";
|
|
7053
|
+
const fp = join6(root, ".synkro");
|
|
7054
|
+
if (!existsSync7(fp)) return "auto";
|
|
7055
|
+
const parsed = JSON.parse(readFileSync6(fp, "utf-8"));
|
|
7056
|
+
const pool = parsed?.grader?.pool;
|
|
7057
|
+
if (pool === "cursor" || pool === "claude") return pool;
|
|
7058
|
+
} catch {
|
|
7059
|
+
}
|
|
7060
|
+
return "auto";
|
|
7061
|
+
}
|
|
6932
7062
|
function resolveWorkerConfig(rest) {
|
|
6933
7063
|
let workers = 8;
|
|
6934
7064
|
let explicit = false;
|
|
@@ -6962,8 +7092,15 @@ function resolveWorkerConfig(rest) {
|
|
|
6962
7092
|
workers = Math.min(workers, 64);
|
|
6963
7093
|
let provs = providers;
|
|
6964
7094
|
if (provs.length === 0) {
|
|
6965
|
-
|
|
6966
|
-
if (
|
|
7095
|
+
const synkroPool = readSynkroFilePool();
|
|
7096
|
+
if (synkroPool === "cursor") {
|
|
7097
|
+
provs = ["cursor"];
|
|
7098
|
+
} else if (synkroPool === "claude") {
|
|
7099
|
+
provs = ["claude_code"];
|
|
7100
|
+
} else {
|
|
7101
|
+
provs = detectAgents().map((a) => a.kind);
|
|
7102
|
+
if (provs.length === 0) provs = ["claude_code"];
|
|
7103
|
+
}
|
|
6967
7104
|
}
|
|
6968
7105
|
return { ...splitWorkers(workers, provs), explicit };
|
|
6969
7106
|
}
|
|
@@ -7368,15 +7505,15 @@ __export(setupGithub_exports, {
|
|
|
7368
7505
|
});
|
|
7369
7506
|
import { createInterface as createInterface2 } from "readline/promises";
|
|
7370
7507
|
import { stdin as input, stdout as output } from "process";
|
|
7371
|
-
import { execSync as
|
|
7372
|
-
import { existsSync as existsSync8, readFileSync as
|
|
7508
|
+
import { execSync as execSync5, spawn as nodeSpawn } from "child_process";
|
|
7509
|
+
import { existsSync as existsSync8, readFileSync as readFileSync7, unlinkSync as unlinkSync3 } from "fs";
|
|
7373
7510
|
import { homedir as homedir7, platform as platform3 } from "os";
|
|
7374
7511
|
import { join as join7 } from "path";
|
|
7375
7512
|
import { execFile as execFile2 } from "child_process";
|
|
7376
7513
|
function readConfig() {
|
|
7377
7514
|
if (!existsSync8(CONFIG_PATH)) return {};
|
|
7378
7515
|
const out = {};
|
|
7379
|
-
for (const line of
|
|
7516
|
+
for (const line of readFileSync7(CONFIG_PATH, "utf-8").split("\n")) {
|
|
7380
7517
|
const t = line.trim();
|
|
7381
7518
|
if (!t || t.startsWith("#")) continue;
|
|
7382
7519
|
const eq = t.indexOf("=");
|
|
@@ -7446,7 +7583,7 @@ function captureClaudeSetupToken() {
|
|
|
7446
7583
|
proc.on("close", (code) => {
|
|
7447
7584
|
let raw = "";
|
|
7448
7585
|
try {
|
|
7449
|
-
raw =
|
|
7586
|
+
raw = readFileSync7(tmpFile, "utf-8");
|
|
7450
7587
|
} catch (e) {
|
|
7451
7588
|
reject(new Error(`Could not read script output file: ${e.message}`));
|
|
7452
7589
|
return;
|
|
@@ -7578,7 +7715,7 @@ async function setupGithubCommand(opts = {}) {
|
|
|
7578
7715
|
}
|
|
7579
7716
|
} catch {
|
|
7580
7717
|
try {
|
|
7581
|
-
ghToken =
|
|
7718
|
+
ghToken = execSync5("gh auth token", { encoding: "utf-8", timeout: 5e3 }).trim();
|
|
7582
7719
|
} catch {
|
|
7583
7720
|
console.error("GitHub not connected. Run `synkro-cli setup-github` interactively to connect.");
|
|
7584
7721
|
return;
|
|
@@ -7612,7 +7749,7 @@ async function setupGithubCommand(opts = {}) {
|
|
|
7612
7749
|
}
|
|
7613
7750
|
console.log(" Validating token...");
|
|
7614
7751
|
try {
|
|
7615
|
-
const validateResult =
|
|
7752
|
+
const validateResult = execSync5(
|
|
7616
7753
|
'claude --print --output-format json "say ok"',
|
|
7617
7754
|
{ env: { ...process.env, CLAUDE_CODE_OAUTH_TOKEN: claudeToken }, encoding: "utf-8", timeout: 3e4, stdio: ["ignore", "pipe", "pipe"] }
|
|
7618
7755
|
);
|
|
@@ -7629,7 +7766,7 @@ async function setupGithubCommand(opts = {}) {
|
|
|
7629
7766
|
if (opts.nonInteractive) {
|
|
7630
7767
|
let currentFullName = null;
|
|
7631
7768
|
try {
|
|
7632
|
-
const remoteUrl =
|
|
7769
|
+
const remoteUrl = execSync5("git remote get-url origin", { encoding: "utf-8", timeout: 5e3 }).trim();
|
|
7633
7770
|
const m = remoteUrl.match(/(?:github\.com)[:/](.+?)(?:\.git)?$/);
|
|
7634
7771
|
if (m) currentFullName = m[1];
|
|
7635
7772
|
} catch {
|
|
@@ -7730,10 +7867,10 @@ __export(install_exports, {
|
|
|
7730
7867
|
installCommand: () => installCommand,
|
|
7731
7868
|
parseArgs: () => parseArgs
|
|
7732
7869
|
});
|
|
7733
|
-
import { existsSync as existsSync9, mkdirSync as mkdirSync8, writeFileSync as writeFileSync7, chmodSync as chmodSync2, readFileSync as
|
|
7870
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync8, writeFileSync as writeFileSync7, chmodSync as chmodSync2, readFileSync as readFileSync8, readdirSync as readdirSync3 } from "fs";
|
|
7734
7871
|
import { homedir as homedir8 } from "os";
|
|
7735
7872
|
import { join as join8 } from "path";
|
|
7736
|
-
import { execSync as
|
|
7873
|
+
import { execSync as execSync6, spawnSync as spawnSync3 } from "child_process";
|
|
7737
7874
|
import { createInterface as createInterface3 } from "readline";
|
|
7738
7875
|
function sanitizeGatewayCandidate(raw) {
|
|
7739
7876
|
if (!raw) return void 0;
|
|
@@ -7976,7 +8113,7 @@ function writeConfigEnv(opts) {
|
|
|
7976
8113
|
`SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle(credsPath)}`,
|
|
7977
8114
|
`SYNKRO_TIER=${shellQuoteSingle(safeTier)}`,
|
|
7978
8115
|
`SYNKRO_INFERENCE=${shellQuoteSingle(safeInference)}`,
|
|
7979
|
-
`SYNKRO_VERSION=${shellQuoteSingle("1.6.
|
|
8116
|
+
`SYNKRO_VERSION=${shellQuoteSingle("1.6.32")}`
|
|
7980
8117
|
];
|
|
7981
8118
|
if (safeSynkroBin) lines.push(`SYNKRO_CLI_BIN=${shellQuoteSingle(safeSynkroBin)}`);
|
|
7982
8119
|
if (safeUserId) lines.push(`SYNKRO_USER_ID=${shellQuoteSingle(safeUserId)}`);
|
|
@@ -7999,7 +8136,7 @@ function resolveDeploymentMode() {
|
|
|
7999
8136
|
if (envOverride === "bare-host" || envOverride === "docker") return envOverride;
|
|
8000
8137
|
try {
|
|
8001
8138
|
if (existsSync9(CONFIG_PATH2)) {
|
|
8002
|
-
const m =
|
|
8139
|
+
const m = readFileSync8(CONFIG_PATH2, "utf-8").match(/^SYNKRO_DEPLOYMENT_MODE='([^']*)'/m);
|
|
8003
8140
|
const val = m?.[1]?.toLowerCase();
|
|
8004
8141
|
if (val === "bare-host" || val === "docker") return val;
|
|
8005
8142
|
}
|
|
@@ -8007,40 +8144,41 @@ function resolveDeploymentMode() {
|
|
|
8007
8144
|
}
|
|
8008
8145
|
return "docker";
|
|
8009
8146
|
}
|
|
8010
|
-
function collectLocalMetadata() {
|
|
8147
|
+
function collectLocalMetadata(includeClaudeCode = true) {
|
|
8011
8148
|
const meta = { platform: process.platform };
|
|
8012
8149
|
try {
|
|
8013
|
-
meta.display_name =
|
|
8150
|
+
meta.display_name = execSync6("git config user.name", { encoding: "utf-8", timeout: 3e3 }).trim();
|
|
8014
8151
|
} catch {
|
|
8015
8152
|
}
|
|
8016
8153
|
try {
|
|
8017
|
-
const remote =
|
|
8154
|
+
const remote = execSync6("git remote get-url origin", { encoding: "utf-8", timeout: 3e3, stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
8018
8155
|
const sshMatch = remote.match(/^git@[^:]+:(.+?)(?:\.git)?$/);
|
|
8019
8156
|
const httpMatch = remote.match(/^https?:\/\/[^/]+\/(.+?)(?:\.git)?$/);
|
|
8020
8157
|
const m = sshMatch || httpMatch;
|
|
8021
8158
|
if (m) meta.active_repo = m[1];
|
|
8022
8159
|
} catch {
|
|
8023
8160
|
}
|
|
8161
|
+
if (!includeClaudeCode) return meta;
|
|
8024
8162
|
try {
|
|
8025
|
-
meta.cc_version =
|
|
8163
|
+
meta.cc_version = execSync6("claude --version", { encoding: "utf-8", timeout: 5e3 }).trim().split("\n")[0];
|
|
8026
8164
|
} catch {
|
|
8027
8165
|
}
|
|
8028
8166
|
const claudeDir = join8(homedir8(), ".claude");
|
|
8029
8167
|
try {
|
|
8030
|
-
const settings = JSON.parse(
|
|
8168
|
+
const settings = JSON.parse(readFileSync8(join8(claudeDir, "settings.json"), "utf-8"));
|
|
8031
8169
|
const plugins = Object.keys(settings.enabledPlugins ?? {}).filter((k) => settings.enabledPlugins[k]);
|
|
8032
8170
|
if (plugins.length) meta.enabled_plugins = plugins;
|
|
8033
8171
|
if (settings.permissions?.defaultMode) meta.permissions_mode = settings.permissions.defaultMode;
|
|
8034
8172
|
} catch {
|
|
8035
8173
|
}
|
|
8036
8174
|
try {
|
|
8037
|
-
const mcpCache = JSON.parse(
|
|
8175
|
+
const mcpCache = JSON.parse(readFileSync8(join8(claudeDir, "mcp-needs-auth-cache.json"), "utf-8"));
|
|
8038
8176
|
const mcpNames = Object.keys(mcpCache);
|
|
8039
8177
|
if (mcpNames.length) meta.mcp_servers = mcpNames;
|
|
8040
8178
|
} catch {
|
|
8041
8179
|
}
|
|
8042
8180
|
try {
|
|
8043
|
-
const mcpList =
|
|
8181
|
+
const mcpList = execSync6("claude mcp list 2>/dev/null", { encoding: "utf-8", timeout: 1e4 });
|
|
8044
8182
|
const connected = mcpList.split("\n").filter((l) => l.includes("Connected")).map((l) => l.split(":")[0].trim()).filter(Boolean);
|
|
8045
8183
|
if (connected.length) meta.mcp_servers_connected = connected;
|
|
8046
8184
|
} catch {
|
|
@@ -8049,7 +8187,7 @@ function collectLocalMetadata() {
|
|
|
8049
8187
|
const sessionsDir = join8(claudeDir, "sessions");
|
|
8050
8188
|
const files = readdirSync3(sessionsDir).filter((f) => f.endsWith(".json")).slice(-5);
|
|
8051
8189
|
for (const f of files) {
|
|
8052
|
-
const s = JSON.parse(
|
|
8190
|
+
const s = JSON.parse(readFileSync8(join8(sessionsDir, f), "utf-8"));
|
|
8053
8191
|
if (s.version) {
|
|
8054
8192
|
meta.cc_version = meta.cc_version || s.version;
|
|
8055
8193
|
break;
|
|
@@ -8059,14 +8197,14 @@ function collectLocalMetadata() {
|
|
|
8059
8197
|
}
|
|
8060
8198
|
return meta;
|
|
8061
8199
|
}
|
|
8062
|
-
async function fetchUserProfile(gatewayUrl, token) {
|
|
8200
|
+
async function fetchUserProfile(gatewayUrl, token, hasClaudeCode = true) {
|
|
8063
8201
|
try {
|
|
8064
8202
|
const resp = await fetch(`${gatewayUrl}/api/v1/cli/me`, {
|
|
8065
8203
|
headers: { "Authorization": `Bearer ${token}` }
|
|
8066
8204
|
});
|
|
8067
8205
|
if (!resp.ok) return { tier: "pro", inference: "fast", localInference: false, captureDepth: "full" };
|
|
8068
8206
|
const data = await resp.json();
|
|
8069
|
-
const meta = collectLocalMetadata();
|
|
8207
|
+
const meta = collectLocalMetadata(hasClaudeCode);
|
|
8070
8208
|
fetch(`${gatewayUrl}/api/v1/cli/me`, {
|
|
8071
8209
|
method: "PATCH",
|
|
8072
8210
|
headers: { "Authorization": `Bearer ${token}`, "Content-Type": "application/json" },
|
|
@@ -8177,22 +8315,11 @@ async function installCommand(opts = {}) {
|
|
|
8177
8315
|
}
|
|
8178
8316
|
ensureSynkroDir();
|
|
8179
8317
|
const scripts = writeHookScripts();
|
|
8180
|
-
console.log("Wrote hook scripts
|
|
8181
|
-
console.log(` ${scripts.bashScript}`);
|
|
8182
|
-
console.log(` ${scripts.bashFollowupScript}`);
|
|
8183
|
-
console.log(` ${scripts.editPrecheckScript}`);
|
|
8184
|
-
console.log(` ${scripts.cwePrecheckScript}`);
|
|
8185
|
-
console.log(` ${scripts.cvePrecheckScript}`);
|
|
8186
|
-
console.log(` ${scripts.planJudgeScript}`);
|
|
8187
|
-
console.log(` ${scripts.agentJudgeScript}`);
|
|
8188
|
-
console.log(` ${scripts.stopSummaryScript}`);
|
|
8189
|
-
console.log(` ${scripts.sessionStartScript}`);
|
|
8190
|
-
console.log(` ${scripts.transcriptSyncScript}
|
|
8191
|
-
`);
|
|
8318
|
+
console.log("Wrote hook scripts to ~/.synkro/hooks/\n");
|
|
8192
8319
|
for (const mode of ["edit", "bash"]) {
|
|
8193
8320
|
const pidFile = join8(SYNKRO_DIR4, "daemon", mode, "daemon.pid");
|
|
8194
8321
|
try {
|
|
8195
|
-
const pid = parseInt(
|
|
8322
|
+
const pid = parseInt(readFileSync8(pidFile, "utf-8").trim(), 10);
|
|
8196
8323
|
if (pid > 0) {
|
|
8197
8324
|
process.kill(pid, "SIGTERM");
|
|
8198
8325
|
console.log(`Stopped stale ${mode} grader daemon (pid ${pid})`);
|
|
@@ -8262,7 +8389,7 @@ async function installCommand(opts = {}) {
|
|
|
8262
8389
|
email = info.email;
|
|
8263
8390
|
} catch {
|
|
8264
8391
|
}
|
|
8265
|
-
const profile = await fetchUserProfile(gatewayUrl, token);
|
|
8392
|
+
const profile = await fetchUserProfile(gatewayUrl, token, hasClaudeCode);
|
|
8266
8393
|
const cloudOnly = gradingMode === "byok" && storageMode === "cloud";
|
|
8267
8394
|
const useLocalMcp = !cloudOnly;
|
|
8268
8395
|
if (cloudOnly) {
|
|
@@ -8379,6 +8506,7 @@ async function installCommand(opts = {}) {
|
|
|
8379
8506
|
} catch (err) {
|
|
8380
8507
|
console.warn(` \u26A0 Could not cache judge prompts: ${err.message}`);
|
|
8381
8508
|
}
|
|
8509
|
+
writeSynkroFileIfMissing({ hasClaudeCode, hasCursor, gradingMode });
|
|
8382
8510
|
console.log();
|
|
8383
8511
|
if (useLocalMcp) {
|
|
8384
8512
|
const { assertDockerAvailable: assertDockerAvailable2 } = await Promise.resolve().then(() => (init_dockerInstall(), dockerInstall_exports));
|
|
@@ -8394,10 +8522,18 @@ async function installCommand(opts = {}) {
|
|
|
8394
8522
|
}
|
|
8395
8523
|
console.log("Installing Synkro server container...");
|
|
8396
8524
|
const totalWorkers = parseInt(process.env.SYNKRO_WORKERS_PER_POOL || "8", 10);
|
|
8397
|
-
const
|
|
8398
|
-
|
|
8399
|
-
if (
|
|
8525
|
+
const synkroFilePool = readSynkroFilePool2();
|
|
8526
|
+
let providers = [];
|
|
8527
|
+
if (synkroFilePool === "cursor") {
|
|
8528
|
+
providers = ["cursor"];
|
|
8529
|
+
} else if (synkroFilePool === "claude") {
|
|
8530
|
+
providers = ["claude_code"];
|
|
8531
|
+
} else {
|
|
8532
|
+
if (hasClaudeCode) providers.push("claude_code");
|
|
8533
|
+
if (hasCursor) providers.push("cursor");
|
|
8534
|
+
}
|
|
8400
8535
|
const { claudeWorkers, cursorWorkers } = splitWorkers(totalWorkers, providers);
|
|
8536
|
+
if (synkroFilePool !== "auto") console.log(` .synkro: grader pool set to ${synkroFilePool}`);
|
|
8401
8537
|
console.log(` worker pool: ${claudeWorkers} claude + ${cursorWorkers} cursor`);
|
|
8402
8538
|
const connectedRepo = detectGitRepo2() || void 0;
|
|
8403
8539
|
const { image, hostMcpPort, hostGraderPort, hostCwePort, hostPglitePort } = await dockerInstall({ claudeWorkers, cursorWorkers, connectedRepo });
|
|
@@ -8407,7 +8543,7 @@ async function installCommand(opts = {}) {
|
|
|
8407
8543
|
const ready = await waitForContainerReady(6e4);
|
|
8408
8544
|
if (ready) {
|
|
8409
8545
|
console.log(" \u2713 container ready");
|
|
8410
|
-
const mcpJwt =
|
|
8546
|
+
const mcpJwt = readFileSync8(join8(SYNKRO_DIR4, ".mcp-jwt"), "utf-8").trim();
|
|
8411
8547
|
try {
|
|
8412
8548
|
const ingestResp = await fetch(`http://127.0.0.1:${hostMcpPort}/api/ingest`, {
|
|
8413
8549
|
method: "POST",
|
|
@@ -8431,14 +8567,14 @@ async function installCommand(opts = {}) {
|
|
|
8431
8567
|
}
|
|
8432
8568
|
console.log();
|
|
8433
8569
|
}
|
|
8434
|
-
if (transcriptConsent) {
|
|
8570
|
+
if (transcriptConsent && hasClaudeCode) {
|
|
8435
8571
|
const repo = detectGitRepo2();
|
|
8436
8572
|
if (repo) {
|
|
8437
8573
|
if (storageMode === "local") {
|
|
8438
8574
|
try {
|
|
8439
8575
|
let mcpToken = "";
|
|
8440
8576
|
try {
|
|
8441
|
-
mcpToken =
|
|
8577
|
+
mcpToken = readFileSync8(join8(SYNKRO_DIR4, ".mcp-jwt"), "utf-8").trim();
|
|
8442
8578
|
} catch {
|
|
8443
8579
|
}
|
|
8444
8580
|
if (mcpToken) {
|
|
@@ -8502,10 +8638,49 @@ async function installCommand(opts = {}) {
|
|
|
8502
8638
|
}
|
|
8503
8639
|
console.log("\u2713 Synkro installed.");
|
|
8504
8640
|
}
|
|
8641
|
+
function writeSynkroFileIfMissing(opts) {
|
|
8642
|
+
try {
|
|
8643
|
+
const root = execSync6("git rev-parse --show-toplevel", { encoding: "utf-8", timeout: 3e3, stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
8644
|
+
if (!root) return;
|
|
8645
|
+
const fp = join8(root, ".synkro");
|
|
8646
|
+
if (existsSync9(fp)) {
|
|
8647
|
+
console.log(` .synkro: ${fp} (existing, respected)`);
|
|
8648
|
+
return;
|
|
8649
|
+
}
|
|
8650
|
+
let pool = "auto";
|
|
8651
|
+
if (opts.hasClaudeCode && !opts.hasCursor) pool = "claude";
|
|
8652
|
+
else if (opts.hasCursor && !opts.hasClaudeCode) pool = "cursor";
|
|
8653
|
+
const config = {
|
|
8654
|
+
version: 1,
|
|
8655
|
+
grader: {
|
|
8656
|
+
pool,
|
|
8657
|
+
mode: opts.gradingMode === "byok" ? "byok" : "local"
|
|
8658
|
+
},
|
|
8659
|
+
ruleset: "default",
|
|
8660
|
+
scanning: { cwe: true, cve: true }
|
|
8661
|
+
};
|
|
8662
|
+
writeFileSync7(fp, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
8663
|
+
console.log(` .synkro: wrote ${fp} (pool=${pool}, mode=${config.grader.mode})`);
|
|
8664
|
+
} catch {
|
|
8665
|
+
}
|
|
8666
|
+
}
|
|
8667
|
+
function readSynkroFilePool2() {
|
|
8668
|
+
try {
|
|
8669
|
+
const root = execSync6("git rev-parse --show-toplevel", { encoding: "utf-8", timeout: 3e3, stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
8670
|
+
if (!root) return "auto";
|
|
8671
|
+
const fp = join8(root, ".synkro");
|
|
8672
|
+
if (!existsSync9(fp)) return "auto";
|
|
8673
|
+
const parsed = JSON.parse(readFileSync8(fp, "utf-8"));
|
|
8674
|
+
const pool = parsed?.grader?.pool;
|
|
8675
|
+
if (pool === "cursor" || pool === "claude") return pool;
|
|
8676
|
+
} catch {
|
|
8677
|
+
}
|
|
8678
|
+
return "auto";
|
|
8679
|
+
}
|
|
8505
8680
|
function detectGitRepo2() {
|
|
8506
8681
|
const run = (cmd2) => {
|
|
8507
8682
|
try {
|
|
8508
|
-
return
|
|
8683
|
+
return execSync6(cmd2, { encoding: "utf-8", timeout: 5e3, stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
8509
8684
|
} catch {
|
|
8510
8685
|
return "";
|
|
8511
8686
|
}
|
|
@@ -8530,7 +8705,7 @@ function extractSessionInsights(projectsDir) {
|
|
|
8530
8705
|
const sessionId = file.replace(".jsonl", "");
|
|
8531
8706
|
const filePath = join8(projectsDir, file);
|
|
8532
8707
|
try {
|
|
8533
|
-
const content =
|
|
8708
|
+
const content = readFileSync8(filePath, "utf-8");
|
|
8534
8709
|
const lines = content.split("\n").filter(Boolean);
|
|
8535
8710
|
for (let i = 0; i < lines.length; i++) {
|
|
8536
8711
|
try {
|
|
@@ -8606,7 +8781,7 @@ function extractTextContent(content) {
|
|
|
8606
8781
|
return "";
|
|
8607
8782
|
}
|
|
8608
8783
|
function parseTranscriptFile(filePath) {
|
|
8609
|
-
const content =
|
|
8784
|
+
const content = readFileSync8(filePath, "utf-8");
|
|
8610
8785
|
const lines = content.split("\n").filter(Boolean);
|
|
8611
8786
|
const messages = [];
|
|
8612
8787
|
for (let i = 0; i < lines.length; i++) {
|
|
@@ -8676,7 +8851,7 @@ async function syncTranscriptsLocal(mcpPort, mcpToken, repo) {
|
|
|
8676
8851
|
process.stdout.write(`\r Progress: ${i + 1}/${files.length} sessions (${totalMessages} messages embedded) `);
|
|
8677
8852
|
}
|
|
8678
8853
|
try {
|
|
8679
|
-
const content =
|
|
8854
|
+
const content = readFileSync8(join8(projectsDir, file), "utf-8");
|
|
8680
8855
|
const lineCount = content.split("\n").filter(Boolean).length;
|
|
8681
8856
|
writeFileSync7(join8(OFFSETS_DIR, sessionId), String(lineCount), "utf-8");
|
|
8682
8857
|
} catch {
|
|
@@ -8730,7 +8905,7 @@ async function syncTranscriptsBulk(gatewayUrl, token, repo) {
|
|
|
8730
8905
|
const sessionId = file.replace(".jsonl", "");
|
|
8731
8906
|
const filePath = join8(projectsDir, file);
|
|
8732
8907
|
try {
|
|
8733
|
-
const content =
|
|
8908
|
+
const content = readFileSync8(filePath, "utf-8");
|
|
8734
8909
|
const lineCount = content.split("\n").filter(Boolean).length;
|
|
8735
8910
|
writeFileSync7(join8(OFFSETS_DIR, sessionId), String(lineCount), "utf-8");
|
|
8736
8911
|
} catch {
|
|
@@ -8809,7 +8984,7 @@ rl.on('line', async (line) => {
|
|
|
8809
8984
|
});
|
|
8810
8985
|
|
|
8811
8986
|
// cli/local-cc/install.ts
|
|
8812
|
-
import { existsSync as existsSync10, mkdirSync as mkdirSync9, writeFileSync as writeFileSync8, readFileSync as
|
|
8987
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync9, writeFileSync as writeFileSync8, readFileSync as readFileSync9, chmodSync as chmodSync3, copyFileSync as copyFileSync2, renameSync as renameSync4, unlinkSync as unlinkSync4, openSync, fsyncSync, closeSync } from "fs";
|
|
8813
8988
|
import { join as join9 } from "path";
|
|
8814
8989
|
import { homedir as homedir9 } from "os";
|
|
8815
8990
|
import { spawnSync as spawnSync4 } from "child_process";
|
|
@@ -8848,7 +9023,7 @@ function safelyMutateClaudeJson(mutator) {
|
|
|
8848
9023
|
if (!existsSync10(CLAUDE_JSON_PATH)) {
|
|
8849
9024
|
return;
|
|
8850
9025
|
}
|
|
8851
|
-
const originalText =
|
|
9026
|
+
const originalText = readFileSync9(CLAUDE_JSON_PATH, "utf-8");
|
|
8852
9027
|
let parsed;
|
|
8853
9028
|
try {
|
|
8854
9029
|
parsed = JSON.parse(originalText);
|
|
@@ -9418,7 +9593,7 @@ var init_disconnect = __esm({
|
|
|
9418
9593
|
});
|
|
9419
9594
|
|
|
9420
9595
|
// cli/local-cc/turnLog.ts
|
|
9421
|
-
import { appendFileSync, existsSync as existsSync12, mkdirSync as mkdirSync10, openSync as openSync2, readFileSync as
|
|
9596
|
+
import { appendFileSync, existsSync as existsSync12, mkdirSync as mkdirSync10, openSync as openSync2, readFileSync as readFileSync10, readSync, closeSync as closeSync2, statSync as statSync2, watchFile, unwatchFile } from "fs";
|
|
9422
9597
|
import { dirname as dirname6, join as join11 } from "path";
|
|
9423
9598
|
import { homedir as homedir11 } from "os";
|
|
9424
9599
|
function truncate(s, max = PREVIEW_MAX) {
|
|
@@ -9460,7 +9635,7 @@ function readRecentTurns(n = 20) {
|
|
|
9460
9635
|
try {
|
|
9461
9636
|
const size = statSync2(TURN_LOG_PATH).size;
|
|
9462
9637
|
if (size === 0) return [];
|
|
9463
|
-
const text =
|
|
9638
|
+
const text = readFileSync10(TURN_LOG_PATH, "utf-8");
|
|
9464
9639
|
const lines = text.split("\n").filter(Boolean);
|
|
9465
9640
|
const lastN = lines.slice(-n).reverse();
|
|
9466
9641
|
return lastN.map((line) => {
|
|
@@ -9906,13 +10081,13 @@ var init_pueue = __esm({
|
|
|
9906
10081
|
});
|
|
9907
10082
|
|
|
9908
10083
|
// cli/local-cc/settings.ts
|
|
9909
|
-
import { existsSync as existsSync13, readFileSync as
|
|
10084
|
+
import { existsSync as existsSync13, readFileSync as readFileSync11 } from "fs";
|
|
9910
10085
|
import { homedir as homedir13 } from "os";
|
|
9911
10086
|
import { join as join13 } from "path";
|
|
9912
10087
|
function isLocalCCEnabled() {
|
|
9913
10088
|
if (!existsSync13(CONFIG_PATH3)) return false;
|
|
9914
10089
|
try {
|
|
9915
|
-
const content =
|
|
10090
|
+
const content = readFileSync11(CONFIG_PATH3, "utf-8");
|
|
9916
10091
|
const match = content.match(/^SYNKRO_LOCAL_INFERENCE='([^']*)'/m);
|
|
9917
10092
|
return match?.[1] === "yes";
|
|
9918
10093
|
} catch {
|
|
@@ -9936,7 +10111,7 @@ import { spawnSync as spawnSync7 } from "child_process";
|
|
|
9936
10111
|
import { homedir as homedir14 } from "os";
|
|
9937
10112
|
import { join as join14 } from "path";
|
|
9938
10113
|
import { readFileSync as fsReadFileSync, existsSync as fsExistsSync } from "fs";
|
|
9939
|
-
import { existsSync as existsSync14, readFileSync as
|
|
10114
|
+
import { existsSync as existsSync14, readFileSync as readFileSync12, writeFileSync as writeFileSync9 } from "fs";
|
|
9940
10115
|
function deploymentMode() {
|
|
9941
10116
|
const env = (process.env.SYNKRO_DEPLOYMENT_MODE || "").toLowerCase();
|
|
9942
10117
|
if (env === "docker") return "docker";
|
|
@@ -10043,14 +10218,14 @@ TROUBLESHOOTING
|
|
|
10043
10218
|
}
|
|
10044
10219
|
function readGatewayUrl() {
|
|
10045
10220
|
if (existsSync14(CONFIG_PATH4)) {
|
|
10046
|
-
const m =
|
|
10221
|
+
const m = readFileSync12(CONFIG_PATH4, "utf-8").match(/^SYNKRO_GATEWAY_URL='([^']*)'/m);
|
|
10047
10222
|
if (m) return m[1];
|
|
10048
10223
|
}
|
|
10049
10224
|
return "https://api.synkro.sh";
|
|
10050
10225
|
}
|
|
10051
10226
|
function updateLocalInferenceFlag(enabled) {
|
|
10052
10227
|
if (!existsSync14(CONFIG_PATH4)) return;
|
|
10053
|
-
let content =
|
|
10228
|
+
let content = readFileSync12(CONFIG_PATH4, "utf-8");
|
|
10054
10229
|
const flag = enabled ? "yes" : "no";
|
|
10055
10230
|
if (content.includes("SYNKRO_LOCAL_INFERENCE=")) {
|
|
10056
10231
|
content = content.replace(/^SYNKRO_LOCAL_INFERENCE='[^']*'/m, `SYNKRO_LOCAL_INFERENCE='${flag}'`);
|
|
@@ -10579,13 +10754,13 @@ var config_exports = {};
|
|
|
10579
10754
|
__export(config_exports, {
|
|
10580
10755
|
configCommand: () => configCommand
|
|
10581
10756
|
});
|
|
10582
|
-
import { readFileSync as
|
|
10757
|
+
import { readFileSync as readFileSync13, writeFileSync as writeFileSync10, existsSync as existsSync15 } from "fs";
|
|
10583
10758
|
import { join as join15 } from "path";
|
|
10584
10759
|
import { homedir as homedir15 } from "os";
|
|
10585
10760
|
function readConfigEnv() {
|
|
10586
10761
|
if (!existsSync15(CONFIG_PATH5)) return {};
|
|
10587
10762
|
const out = {};
|
|
10588
|
-
for (const line of
|
|
10763
|
+
for (const line of readFileSync13(CONFIG_PATH5, "utf-8").split("\n")) {
|
|
10589
10764
|
const t = line.trim();
|
|
10590
10765
|
if (!t || t.startsWith("#")) continue;
|
|
10591
10766
|
const eq = t.indexOf("=");
|
|
@@ -10598,7 +10773,7 @@ function updateConfigValue(key, value) {
|
|
|
10598
10773
|
console.error("No config found. Run `synkro install` first.");
|
|
10599
10774
|
process.exit(1);
|
|
10600
10775
|
}
|
|
10601
|
-
const lines =
|
|
10776
|
+
const lines = readFileSync13(CONFIG_PATH5, "utf-8").split("\n");
|
|
10602
10777
|
const pattern = new RegExp(`^${key}=`);
|
|
10603
10778
|
let found = false;
|
|
10604
10779
|
const updated = lines.map((line) => {
|
|
@@ -10725,14 +10900,14 @@ var init_config = __esm({
|
|
|
10725
10900
|
});
|
|
10726
10901
|
|
|
10727
10902
|
// cli/bootstrap.js
|
|
10728
|
-
import { readFileSync as
|
|
10903
|
+
import { readFileSync as readFileSync14, existsSync as existsSync16 } from "fs";
|
|
10729
10904
|
import { resolve as resolve2 } from "path";
|
|
10730
10905
|
var envCandidates = [
|
|
10731
10906
|
resolve2(process.env.HOME ?? "", ".synkro", "config.env")
|
|
10732
10907
|
];
|
|
10733
10908
|
for (const envPath of envCandidates) {
|
|
10734
10909
|
if (!existsSync16(envPath)) continue;
|
|
10735
|
-
const envContent =
|
|
10910
|
+
const envContent = readFileSync14(envPath, "utf-8");
|
|
10736
10911
|
for (const line of envContent.split("\n")) {
|
|
10737
10912
|
const trimmed = line.trim();
|
|
10738
10913
|
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
@@ -10747,7 +10922,7 @@ var args = process.argv.slice(2);
|
|
|
10747
10922
|
var cmd = args[0] || "";
|
|
10748
10923
|
var subArgs = args.slice(1);
|
|
10749
10924
|
function printVersion() {
|
|
10750
|
-
console.log("1.6.
|
|
10925
|
+
console.log("1.6.32");
|
|
10751
10926
|
}
|
|
10752
10927
|
function printHelp2() {
|
|
10753
10928
|
console.log(`Synkro CLI \u2014 runtime safety for AI coding agents
|