@synkro-sh/cli 1.4.62 → 1.4.64
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 +231 -52
- package/dist/bootstrap.js.map +1 -1
- package/package.json +1 -1
package/dist/bootstrap.js
CHANGED
|
@@ -837,7 +837,7 @@ var init_hookScriptsTs = __esm({
|
|
|
837
837
|
import { readFileSync, writeFileSync, appendFileSync, mkdirSync, existsSync, renameSync, openSync, closeSync, unlinkSync } from 'node:fs';
|
|
838
838
|
import { join, dirname, basename, extname, resolve as resolvePath } from 'node:path';
|
|
839
839
|
import { homedir } from 'node:os';
|
|
840
|
-
import { execSync
|
|
840
|
+
import { execSync } from 'node:child_process';
|
|
841
841
|
import { constants as FS_CONSTANTS } from 'node:fs';
|
|
842
842
|
|
|
843
843
|
// \u2500\u2500\u2500 Config \u2500\u2500\u2500
|
|
@@ -1116,56 +1116,79 @@ export function tag(rt: string, config: HookConfig): string {
|
|
|
1116
1116
|
return '[synkro:' + rt + ':' + rs + ']';
|
|
1117
1117
|
}
|
|
1118
1118
|
|
|
1119
|
-
// \u2500\u2500\u2500 Local Grading \u2500\u2500\u2500
|
|
1119
|
+
// \u2500\u2500\u2500 Local Grading (direct channel call) \u2500\u2500\u2500
|
|
1120
1120
|
|
|
1121
|
-
|
|
1122
|
-
return new Promise((resolve, reject) => {
|
|
1123
|
-
const cliBin = process.env.SYNKRO_CLI_BIN;
|
|
1124
|
-
let cmd: string;
|
|
1125
|
-
let args: string[];
|
|
1121
|
+
type GradeRole = 'grade-edit' | 'grade-bash' | 'grade-plan' | 'grade-cwe';
|
|
1126
1122
|
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
args = [cliBin, 'grade', surface];
|
|
1131
|
-
} else {
|
|
1132
|
-
cmd = 'synkro';
|
|
1133
|
-
args = ['grade', surface];
|
|
1134
|
-
}
|
|
1123
|
+
const ROLE_MAP: Record<string, GradeRole> = {
|
|
1124
|
+
edit: 'grade-edit', bash: 'grade-bash', plan: 'grade-plan', cwe: 'grade-cwe',
|
|
1125
|
+
};
|
|
1135
1126
|
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1127
|
+
const PRIMER_KEY: Record<GradeRole, string> = {
|
|
1128
|
+
'grade-edit': 'grader_primer_edit',
|
|
1129
|
+
'grade-bash': 'grader_primer_bash',
|
|
1130
|
+
'grade-plan': 'grader_primer_plan',
|
|
1131
|
+
'grade-cwe': 'grader_primer_cwe',
|
|
1132
|
+
};
|
|
1140
1133
|
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1134
|
+
let primerCache: { data: Record<string, string>; ts: number } | null = null;
|
|
1135
|
+
|
|
1136
|
+
async function fetchPrimer(role: GradeRole, jwt: string): Promise<string> {
|
|
1137
|
+
if (primerCache && Date.now() - primerCache.ts < 60_000) {
|
|
1138
|
+
const cached = primerCache.data[PRIMER_KEY[role]];
|
|
1139
|
+
if (cached) return cached;
|
|
1140
|
+
}
|
|
1141
|
+
const resp = await fetch(GATEWAY_URL + '/api/v1/cli/judge-prompts', {
|
|
1142
|
+
headers: { Authorization: 'Bearer ' + jwt },
|
|
1143
|
+
signal: AbortSignal.timeout(4000),
|
|
1144
|
+
});
|
|
1145
|
+
if (!resp.ok) throw new Error('primer fetch failed: ' + resp.status);
|
|
1146
|
+
const data = await resp.json() as Record<string, string>;
|
|
1147
|
+
primerCache = { data, ts: Date.now() };
|
|
1148
|
+
return data[PRIMER_KEY[role]] || '';
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
const CHANNEL_REPLY_INSTRUCTIONS = \`
|
|
1152
|
+
DELIVERY METHOD \u2014 MANDATORY, OVERRIDES ALL OTHER OUTPUT RULES:
|
|
1153
|
+
You are running inside a Synkro MCP channel. Do NOT output your verdict as text.
|
|
1154
|
+
Instead, after generating your verdict, call the reply tool EXACTLY ONCE with:
|
|
1155
|
+
- req_id: the req_id from this channel event's meta
|
|
1156
|
+
- result: your complete verdict block as a string (the <synkro-verdict>\u2026</synkro-verdict> XML)
|
|
1157
|
+
Any text output is silently discarded. Only the reply tool call is captured.\`;
|
|
1158
|
+
|
|
1159
|
+
async function channelGrade(role: GradeRole, prompt: string, jwt: string, port: number, timeoutMs = 20000): Promise<string> {
|
|
1160
|
+
const primer = await fetchPrimer(role, jwt);
|
|
1161
|
+
const content = primer + '\\n\\n' + CHANNEL_REPLY_INSTRUCTIONS + '\\n\\n---\\nPAYLOAD (the input to evaluate):\\n\\n' + prompt;
|
|
1162
|
+
const body = JSON.stringify({ role, content });
|
|
1163
|
+
|
|
1164
|
+
const resp = await fetch('http://127.0.0.1:' + port + '/submit', {
|
|
1165
|
+
method: 'POST',
|
|
1166
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1167
|
+
body,
|
|
1168
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
1159
1169
|
});
|
|
1170
|
+
|
|
1171
|
+
if (!resp.ok) {
|
|
1172
|
+
const text = await resp.text().catch(() => '');
|
|
1173
|
+
throw new Error('channel ' + resp.status + ': ' + text.slice(0, 200));
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
const data = await resp.json() as { result?: string; error?: string };
|
|
1177
|
+
if (data.error) throw new Error(data.error);
|
|
1178
|
+
return String(data.result || '');
|
|
1160
1179
|
}
|
|
1161
1180
|
|
|
1162
1181
|
export async function localGrade(surface: string, prompt: string): Promise<string> {
|
|
1163
1182
|
if (!(await channelUp())) throw new Error('SYNKRO_CHANNEL_DOWN');
|
|
1164
|
-
|
|
1183
|
+
const jwt = loadJwt();
|
|
1184
|
+
if (!jwt) throw new Error('NO_JWT');
|
|
1185
|
+
return channelGrade(ROLE_MAP[surface] || 'grade-edit', prompt, jwt, 8929);
|
|
1165
1186
|
}
|
|
1166
1187
|
|
|
1167
1188
|
export async function localGradeCwe(prompt: string): Promise<string> {
|
|
1168
|
-
|
|
1189
|
+
const jwt = loadJwt();
|
|
1190
|
+
if (!jwt) throw new Error('NO_JWT');
|
|
1191
|
+
return channelGrade('grade-cwe', prompt, jwt, 8930, 20000);
|
|
1169
1192
|
}
|
|
1170
1193
|
|
|
1171
1194
|
// \u2500\u2500\u2500 Verdict Parsing \u2500\u2500\u2500
|
|
@@ -1335,7 +1358,7 @@ export function ruleMode(ruleId: string, rules: Rule[]): 'blocking' | 'audit' {
|
|
|
1335
1358
|
// \u2500\u2500\u2500 Content Reconstruction \u2500\u2500\u2500
|
|
1336
1359
|
|
|
1337
1360
|
export function reconstructContent(toolName: string, toolInput: any, filePath: string, cwd?: string): string {
|
|
1338
|
-
const canRead = filePath &&
|
|
1361
|
+
const canRead = filePath && cwd && isPathUnder(filePath, cwd);
|
|
1339
1362
|
switch (toolName) {
|
|
1340
1363
|
case 'Write':
|
|
1341
1364
|
return toolInput.content || '';
|
|
@@ -1628,6 +1651,66 @@ export function aggregateUsage(transcriptPath: string): { model: string; totals:
|
|
|
1628
1651
|
return result;
|
|
1629
1652
|
}
|
|
1630
1653
|
|
|
1654
|
+
// \u2500\u2500\u2500 Scan Finding Dispatch \u2500\u2500\u2500
|
|
1655
|
+
|
|
1656
|
+
export function dispatchFinding(
|
|
1657
|
+
jwt: string,
|
|
1658
|
+
finding: {
|
|
1659
|
+
session_id: string;
|
|
1660
|
+
file_path: string;
|
|
1661
|
+
finding_type: 'cwe' | 'cve';
|
|
1662
|
+
finding_id: string;
|
|
1663
|
+
severity?: string;
|
|
1664
|
+
status: 'open' | 'resolved' | 'exempted';
|
|
1665
|
+
detail?: string;
|
|
1666
|
+
description?: string;
|
|
1667
|
+
package_name?: string;
|
|
1668
|
+
package_version?: string;
|
|
1669
|
+
fixed_version?: string;
|
|
1670
|
+
aliases?: string[];
|
|
1671
|
+
references?: Array<{ type: string; url: string }>;
|
|
1672
|
+
cwe_name?: string;
|
|
1673
|
+
},
|
|
1674
|
+
captureDepth: string,
|
|
1675
|
+
): void {
|
|
1676
|
+
const localEntry: Record<string, any> = {
|
|
1677
|
+
capture_type: 'scan_finding',
|
|
1678
|
+
...finding,
|
|
1679
|
+
};
|
|
1680
|
+
appendLocalTelemetry(localEntry);
|
|
1681
|
+
|
|
1682
|
+
if (captureDepth === 'local_only') return;
|
|
1683
|
+
|
|
1684
|
+
const cloudBody: Record<string, any> = {
|
|
1685
|
+
finding_type: finding.finding_type,
|
|
1686
|
+
finding_id: finding.finding_id,
|
|
1687
|
+
severity: finding.severity,
|
|
1688
|
+
status: finding.status,
|
|
1689
|
+
session_id: finding.session_id,
|
|
1690
|
+
};
|
|
1691
|
+
|
|
1692
|
+
if (captureDepth === 'evidence_on_violation' || captureDepth === 'full') {
|
|
1693
|
+
cloudBody.file_path = finding.file_path;
|
|
1694
|
+
cloudBody.package_name = finding.package_name;
|
|
1695
|
+
cloudBody.package_version = finding.package_version;
|
|
1696
|
+
cloudBody.fixed_version = finding.fixed_version;
|
|
1697
|
+
cloudBody.aliases = finding.aliases;
|
|
1698
|
+
cloudBody.references = finding.references;
|
|
1699
|
+
cloudBody.cwe_name = finding.cwe_name;
|
|
1700
|
+
}
|
|
1701
|
+
if (captureDepth === 'full') {
|
|
1702
|
+
cloudBody.detail = finding.detail;
|
|
1703
|
+
cloudBody.description = finding.description;
|
|
1704
|
+
}
|
|
1705
|
+
|
|
1706
|
+
fetch(GATEWAY_URL + '/api/v1/hook/finding', {
|
|
1707
|
+
method: 'POST',
|
|
1708
|
+
headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + jwt },
|
|
1709
|
+
body: JSON.stringify(cloudBody),
|
|
1710
|
+
signal: AbortSignal.timeout(3000),
|
|
1711
|
+
}).catch(() => {});
|
|
1712
|
+
}
|
|
1713
|
+
|
|
1631
1714
|
// \u2500\u2500\u2500 Output Helpers \u2500\u2500\u2500
|
|
1632
1715
|
|
|
1633
1716
|
export function outputJson(obj: any): void {
|
|
@@ -1853,7 +1936,7 @@ main();
|
|
|
1853
1936
|
import {
|
|
1854
1937
|
loadJwt, ensureFreshJwt, detectRepo, loadConfig, cweRoute, tag,
|
|
1855
1938
|
localGradeCwe, parseVerdict, reconstructContent, readStdin, log,
|
|
1856
|
-
outputJson, outputEmpty, GATEWAY_URL,
|
|
1939
|
+
outputJson, outputEmpty, dispatchFinding, GATEWAY_URL,
|
|
1857
1940
|
} from './_synkro-common.ts';
|
|
1858
1941
|
import { basename, extname } from 'node:path';
|
|
1859
1942
|
|
|
@@ -1979,6 +2062,11 @@ async function main() {
|
|
|
1979
2062
|
return;
|
|
1980
2063
|
}
|
|
1981
2064
|
|
|
2065
|
+
const cweNameMap = new Map<string, string>();
|
|
2066
|
+
for (const r of cweRules) {
|
|
2067
|
+
if (r.cwe && r.name) cweNameMap.set(r.cwe.toUpperCase(), r.name);
|
|
2068
|
+
}
|
|
2069
|
+
|
|
1982
2070
|
const displayIds = activeCweIds.slice(0, 3).join(', ');
|
|
1983
2071
|
const count = activeCweIds.length;
|
|
1984
2072
|
const label = count === 1 ? 'match' : 'matches';
|
|
@@ -1986,6 +2074,19 @@ async function main() {
|
|
|
1986
2074
|
const denyDetail = '[' + displayIds + '] ' + (verdict.reason || 'code weakness detected');
|
|
1987
2075
|
const ctx = 'CWE: ' + denyDetail + '\\nFix all issues before retrying. Do NOT ask the user to make the edit manually \u2014 resolve the weakness in code yourself.';
|
|
1988
2076
|
|
|
2077
|
+
for (const cweId of activeCweIds) {
|
|
2078
|
+
dispatchFinding(jwt, {
|
|
2079
|
+
session_id: sessionId,
|
|
2080
|
+
file_path: filePath,
|
|
2081
|
+
finding_type: 'cwe',
|
|
2082
|
+
finding_id: cweId,
|
|
2083
|
+
severity: verdict.severity || 'high',
|
|
2084
|
+
status: 'open',
|
|
2085
|
+
detail: verdict.reason || 'code weakness detected',
|
|
2086
|
+
cwe_name: cweNameMap.get(cweId.toUpperCase()) || undefined,
|
|
2087
|
+
}, config.captureDepth);
|
|
2088
|
+
}
|
|
2089
|
+
|
|
1989
2090
|
outputJson({
|
|
1990
2091
|
systemMessage: cweMsg,
|
|
1991
2092
|
hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'deny', permissionDecisionReason: ctx, additionalContext: ctx },
|
|
@@ -1993,6 +2094,14 @@ async function main() {
|
|
|
1993
2094
|
return;
|
|
1994
2095
|
}
|
|
1995
2096
|
|
|
2097
|
+
dispatchFinding(jwt, {
|
|
2098
|
+
session_id: sessionId,
|
|
2099
|
+
file_path: filePath,
|
|
2100
|
+
finding_type: 'cwe',
|
|
2101
|
+
finding_id: 'pass',
|
|
2102
|
+
status: 'resolved',
|
|
2103
|
+
}, config.captureDepth);
|
|
2104
|
+
|
|
1996
2105
|
outputJson({ systemMessage: cweTag + ' ' + fileShort + ' \\u2192 clean' });
|
|
1997
2106
|
return;
|
|
1998
2107
|
}
|
|
@@ -2012,7 +2121,7 @@ main();
|
|
|
2012
2121
|
import {
|
|
2013
2122
|
loadJwt, ensureFreshJwt, detectRepo, loadConfig, route, tag,
|
|
2014
2123
|
reconstructContent, readStdin, findNearestDeps, log,
|
|
2015
|
-
outputJson, outputEmpty, GATEWAY_URL,
|
|
2124
|
+
outputJson, outputEmpty, dispatchFinding, GATEWAY_URL,
|
|
2016
2125
|
} from './_synkro-common.ts';
|
|
2017
2126
|
import { basename } from 'node:path';
|
|
2018
2127
|
|
|
@@ -2042,6 +2151,7 @@ async function main() {
|
|
|
2042
2151
|
}
|
|
2043
2152
|
|
|
2044
2153
|
const toolInput = payload.tool_input || {};
|
|
2154
|
+
const sessionId = payload.session_id || '';
|
|
2045
2155
|
const cwd = payload.cwd || '';
|
|
2046
2156
|
|
|
2047
2157
|
const filePath = toolInput.file_path || toolInput.notebook_path || toolInput.path || '';
|
|
@@ -2103,15 +2213,35 @@ async function main() {
|
|
|
2103
2213
|
|
|
2104
2214
|
const findings = Array.isArray(cveResp?.findings) ? cveResp.findings : [];
|
|
2105
2215
|
if (findings.length > 0) {
|
|
2106
|
-
const
|
|
2107
|
-
const
|
|
2216
|
+
for (const f of findings.slice(0, 10)) {
|
|
2217
|
+
const cveId = (f.aliases || []).find((a: string) => a.startsWith('CVE-')) || f.id || 'unknown';
|
|
2218
|
+
dispatchFinding(jwt, {
|
|
2219
|
+
session_id: sessionId,
|
|
2220
|
+
file_path: filePath,
|
|
2221
|
+
finding_type: 'cve',
|
|
2222
|
+
finding_id: cveId,
|
|
2223
|
+
severity: f.severity || 'high',
|
|
2224
|
+
status: 'open',
|
|
2225
|
+
detail: f.summary || f.title || 'vulnerable dependency',
|
|
2226
|
+
description: f.details || undefined,
|
|
2227
|
+
package_name: f.package || undefined,
|
|
2228
|
+
package_version: f.version || undefined,
|
|
2229
|
+
fixed_version: f.fixed || undefined,
|
|
2230
|
+
aliases: f.aliases || undefined,
|
|
2231
|
+
references: f.references || undefined,
|
|
2232
|
+
}, config.captureDepth);
|
|
2233
|
+
}
|
|
2234
|
+
|
|
2235
|
+
const formatFinding = (f: any): string => {
|
|
2236
|
+
const id = (f.aliases || []).find((a: string) => a.startsWith('CVE-')) || f.id || '?';
|
|
2108
2237
|
const pkg = f.package || '?';
|
|
2109
2238
|
const ver = f.version || '?';
|
|
2110
2239
|
const title = f.title || f.summary || 'vulnerable';
|
|
2111
|
-
const fix = f.fixed ? ' (fix: >=' + f.fixed + ')' : ' (no safe version
|
|
2240
|
+
const fix = f.fixed ? ' (fix: >=' + f.fixed + ')' : ' (no safe version)';
|
|
2112
2241
|
return '[' + id + '] ' + pkg + '@' + ver + ': ' + title + fix;
|
|
2113
|
-
}
|
|
2242
|
+
};
|
|
2114
2243
|
|
|
2244
|
+
const top3 = findings.slice(0, 3).map(formatFinding).join('; ');
|
|
2115
2245
|
const count = findings.length;
|
|
2116
2246
|
const label = count === 1 ? 'advisory' : 'advisories';
|
|
2117
2247
|
const cveMsg = cveTag + ' ' + fileShort + ' \\u2192 ' + count + ' ' + label;
|
|
@@ -2181,14 +2311,32 @@ async function main() {
|
|
|
2181
2311
|
// \u2500\u2500\u2500 CVE scan for package install commands \u2500\u2500\u2500
|
|
2182
2312
|
if (toolName === 'Bash') {
|
|
2183
2313
|
const pkgInstallMatch = command.match(
|
|
2184
|
-
/(?:npm\\s+(?:install|i|add)|pnpm\\s+(?:add|install|i)|yarn\\s+add|bun\\s+(?:add|install|i)|
|
|
2314
|
+
/(?:npm\\s+(?:install|i|add)|pnpm\\s+(?:add|install|i)|yarn\\s+add|bun\\s+(?:add|install|i)|(?:uv\\s+)?pip3?\\s+install|go\\s+get|cargo\\s+add|gem\\s+install|composer\\s+require)\\s+(.+)/
|
|
2185
2315
|
);
|
|
2316
|
+
const isPip = /(?:uv\\s+)?pip3?\\s+install/.test(command);
|
|
2317
|
+
const isGo = command.match(/^go\\s+get/);
|
|
2318
|
+
const isCargo = command.match(/^cargo\\s+add/);
|
|
2319
|
+
const isGem = command.match(/^gem\\s+install/);
|
|
2320
|
+
const isComposer = command.match(/^composer\\s+require/);
|
|
2186
2321
|
if (pkgInstallMatch) {
|
|
2187
2322
|
const rawArgs = pkgInstallMatch[1];
|
|
2188
2323
|
const deps: Record<string, string> = {};
|
|
2189
2324
|
const tokens = rawArgs.split(/\\s+/);
|
|
2325
|
+
let skipNext = false;
|
|
2190
2326
|
for (const token of tokens) {
|
|
2191
|
-
if (
|
|
2327
|
+
if (skipNext) { skipNext = false; continue; }
|
|
2328
|
+
if (token.startsWith('-')) {
|
|
2329
|
+
if (/^--(python|target|prefix|root|constraint|requirement|index-url|extra-index-url|find-links|build|src|cache-dir)$/.test(token)) skipNext = true;
|
|
2330
|
+
continue;
|
|
2331
|
+
}
|
|
2332
|
+
if (isPip) {
|
|
2333
|
+
const pipMatch = token.match(/^([a-zA-Z0-9_.-]+)(?:[=~!<>]=?(.+))?$/);
|
|
2334
|
+
if (pipMatch) {
|
|
2335
|
+
deps[pipMatch[1]] = pipMatch[2]?.replace(/^=/, '') || '*';
|
|
2336
|
+
continue;
|
|
2337
|
+
}
|
|
2338
|
+
}
|
|
2339
|
+
// npm/yarn: pkg@1.0
|
|
2192
2340
|
const atIdx = token.lastIndexOf('@');
|
|
2193
2341
|
if (atIdx > 0) {
|
|
2194
2342
|
deps[token.slice(0, atIdx)] = token.slice(atIdx + 1);
|
|
@@ -2196,9 +2344,18 @@ async function main() {
|
|
|
2196
2344
|
deps[token] = '*';
|
|
2197
2345
|
}
|
|
2198
2346
|
}
|
|
2347
|
+
const manifestFile = isPip ? 'requirements.txt'
|
|
2348
|
+
: isGo ? 'go.mod'
|
|
2349
|
+
: isCargo ? 'Cargo.toml'
|
|
2350
|
+
: isGem ? 'Gemfile'
|
|
2351
|
+
: isComposer ? 'composer.json'
|
|
2352
|
+
: 'package.json';
|
|
2353
|
+
const manifestContent = isPip
|
|
2354
|
+
? Object.entries(deps).map(([k, v]) => v === '*' ? k : k + '==' + v).join('\\n')
|
|
2355
|
+
: JSON.stringify({ dependencies: deps });
|
|
2199
2356
|
if (Object.keys(deps).length > 0) {
|
|
2200
2357
|
try {
|
|
2201
|
-
const cveBody = { file_path:
|
|
2358
|
+
const cveBody = { file_path: manifestFile, content: manifestContent, dependencies: deps };
|
|
2202
2359
|
const cveResp = await fetch(GATEWAY_URL + '/api/v1/cve-scan', {
|
|
2203
2360
|
method: 'POST',
|
|
2204
2361
|
headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + jwt },
|
|
@@ -2524,7 +2681,7 @@ async function main() {
|
|
|
2524
2681
|
const usage = aggregateUsage(transcriptPath);
|
|
2525
2682
|
if (usage.totals.in + usage.totals.out > 0) {
|
|
2526
2683
|
const usageBody = {
|
|
2527
|
-
capture_type: '
|
|
2684
|
+
capture_type: 'usage_tick',
|
|
2528
2685
|
event_id: 'usage_' + Date.now() + '_' + process.pid,
|
|
2529
2686
|
hook_type: 'stop',
|
|
2530
2687
|
verdict: 'allow',
|
|
@@ -2856,7 +3013,7 @@ async function main() {
|
|
|
2856
3013
|
main();
|
|
2857
3014
|
`;
|
|
2858
3015
|
USER_PROMPT_SUBMIT_TS = `#!/usr/bin/env bun
|
|
2859
|
-
import { readStdin } from './_synkro-common.ts';
|
|
3016
|
+
import { readStdin, appendLocalTelemetry, aggregateUsage } from './_synkro-common.ts';
|
|
2860
3017
|
import { writeFileSync, mkdirSync } from 'node:fs';
|
|
2861
3018
|
import { join, dirname } from 'node:path';
|
|
2862
3019
|
import { homedir } from 'node:os';
|
|
@@ -2872,6 +3029,28 @@ async function main() {
|
|
|
2872
3029
|
mkdirSync(dirname(promptFile), { recursive: true });
|
|
2873
3030
|
writeFileSync(promptFile, msg, 'utf-8');
|
|
2874
3031
|
}
|
|
3032
|
+
|
|
3033
|
+
const sessionId = payload.session_id || '';
|
|
3034
|
+
const transcriptPath = payload.transcript_path || '';
|
|
3035
|
+
if (sessionId && transcriptPath) {
|
|
3036
|
+
const usage = aggregateUsage(transcriptPath);
|
|
3037
|
+
if (usage.totals.in + usage.totals.out > 0) {
|
|
3038
|
+
appendLocalTelemetry({
|
|
3039
|
+
capture_type: 'usage_tick',
|
|
3040
|
+
event_id: 'usage_' + Date.now() + '_' + process.pid,
|
|
3041
|
+
hook_type: 'prompt_submit',
|
|
3042
|
+
session_id: sessionId,
|
|
3043
|
+
model: usage.model || 'unknown',
|
|
3044
|
+
cc_model: usage.model || '',
|
|
3045
|
+
cc_usage: {
|
|
3046
|
+
input_tokens: usage.totals.in,
|
|
3047
|
+
output_tokens: usage.totals.out,
|
|
3048
|
+
cache_creation_input_tokens: usage.totals.cw,
|
|
3049
|
+
cache_read_input_tokens: usage.totals.cr,
|
|
3050
|
+
},
|
|
3051
|
+
});
|
|
3052
|
+
}
|
|
3053
|
+
}
|
|
2875
3054
|
} catch {}
|
|
2876
3055
|
}
|
|
2877
3056
|
|
|
@@ -5201,7 +5380,7 @@ function writeConfigEnv(opts) {
|
|
|
5201
5380
|
`SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle(credsPath)}`,
|
|
5202
5381
|
`SYNKRO_TIER=${shellQuoteSingle(safeTier)}`,
|
|
5203
5382
|
`SYNKRO_INFERENCE=${shellQuoteSingle(safeInference)}`,
|
|
5204
|
-
`SYNKRO_VERSION=${shellQuoteSingle("1.4.
|
|
5383
|
+
`SYNKRO_VERSION=${shellQuoteSingle("1.4.64")}`
|
|
5205
5384
|
];
|
|
5206
5385
|
if (safeSynkroBin) lines.push(`SYNKRO_CLI_BIN=${shellQuoteSingle(safeSynkroBin)}`);
|
|
5207
5386
|
if (safeUserId) lines.push(`SYNKRO_USER_ID=${shellQuoteSingle(safeUserId)}`);
|