@synkro-sh/cli 1.6.75 → 1.6.77
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 +92 -9
- package/dist/bootstrap.js.map +1 -1
- package/package.json +1 -1
package/dist/bootstrap.js
CHANGED
|
@@ -957,6 +957,10 @@ function validateGatewayUrl(raw: string): string {
|
|
|
957
957
|
}
|
|
958
958
|
export const GATEWAY_URL = validateGatewayUrl(process.env.SYNKRO_GATEWAY_URL || 'https://api.synkro.sh');
|
|
959
959
|
export const CREDS_PATH = process.env.SYNKRO_CREDENTIALS_PATH || join(HOME, '.synkro', 'credentials.json');
|
|
960
|
+
// The long-lived (1yr) Synkro-signed MCP token, minted + stored at install time.
|
|
961
|
+
// In cloud mode the hook authenticates grade/scan/telemetry calls with THIS,
|
|
962
|
+
// not the ~5-min WorkOS access token \u2014 so a long session never 401s the grader.
|
|
963
|
+
export const MCP_JWT_PATH = process.env.SYNKRO_MCP_JWT_PATH || join(HOME, '.synkro', '.mcp-jwt');
|
|
960
964
|
const LAST_PROMPT_FILE = join(HOME, '.synkro', '.last-prompt');
|
|
961
965
|
|
|
962
966
|
// \u2500\u2500\u2500 Path Validation \u2500\u2500\u2500
|
|
@@ -1046,6 +1050,16 @@ export function log(msg: string): void {
|
|
|
1046
1050
|
// \u2500\u2500\u2500 JWT Management \u2500\u2500\u2500
|
|
1047
1051
|
|
|
1048
1052
|
export function loadJwt(): string | null {
|
|
1053
|
+
// Cloud mode: prefer the long-lived (1yr) MCP token. It's org+user scoped and
|
|
1054
|
+
// accepted on the whole cloud-hook surface (grade/submit, cwe-rules, hook/*,
|
|
1055
|
+
// cli/*), so grading survives sessions far longer than the ~5-min WorkOS token.
|
|
1056
|
+
// jwtIsExpired sees a year of runway, so ensureFreshJwt never needs to refresh.
|
|
1057
|
+
if (process.env.SYNKRO_STORAGE_MODE === 'cloud') {
|
|
1058
|
+
try {
|
|
1059
|
+
const mcp = readFileSync(MCP_JWT_PATH, 'utf-8').trim();
|
|
1060
|
+
if (mcp) return mcp;
|
|
1061
|
+
} catch { /* no mcp token on disk \u2014 fall back to the login token below */ }
|
|
1062
|
+
}
|
|
1049
1063
|
try {
|
|
1050
1064
|
if (!existsSync(CREDS_PATH)) return null;
|
|
1051
1065
|
const creds = JSON.parse(readFileSync(CREDS_PATH, 'utf-8'));
|
|
@@ -1603,7 +1617,13 @@ export async function route(config: HookConfig, synkroFile?: SynkroFileConfig):
|
|
|
1603
1617
|
if (process.env.SYNKRO_DEPLOY_LOCATION === 'cloud') return 'local';
|
|
1604
1618
|
if (config.captureDepth === 'local_only') return 'local';
|
|
1605
1619
|
if (await channelUp()) return 'local';
|
|
1606
|
-
|
|
1620
|
+
// Local channel down. Do NOT silently fall back to cloud grading when in local
|
|
1621
|
+
// mode \u2014 the cloud judge endpoint (/api/v1/hook/judge) writes guard_checks /
|
|
1622
|
+
// guard_violations to Timescale, leaking local activity into the cloud
|
|
1623
|
+
// dashboard. Only an explicit cloud storage posture (which install couples to
|
|
1624
|
+
// grader location = cloud) may route to the cloud judge; otherwise stay local
|
|
1625
|
+
// (localGrade emits a grader-unavailable message if the container is truly down).
|
|
1626
|
+
return process.env.SYNKRO_STORAGE_MODE === 'cloud' ? 'cloud' : 'local';
|
|
1607
1627
|
}
|
|
1608
1628
|
|
|
1609
1629
|
export async function cweRoute(config: HookConfig, synkroFile?: SynkroFileConfig): Promise<'local' | 'byok' | 'skip'> {
|
|
@@ -1724,6 +1744,34 @@ async function hostedGrade(role: GradeRole, prompt: string, jwt: string, timeout
|
|
|
1724
1744
|
return String(data.result || '');
|
|
1725
1745
|
}
|
|
1726
1746
|
|
|
1747
|
+
// Cloud transcript capture \u2014 the cloud mirror of the local /api/conversation-sync
|
|
1748
|
+
// + /api/session-action telemetry. Posts to the API /api/v1/cli/sync-transcripts
|
|
1749
|
+
// (the org is derived from the login JWT, same one cloud grading uses). Best-effort:
|
|
1750
|
+
// never throws into the hook path. messages/actions must already be in the route
|
|
1751
|
+
// shape: message {message_index,type,content,model?,timestamp?,tool_calls?};
|
|
1752
|
+
// action {step,tool,summary,file,outcome}.
|
|
1753
|
+
async function syncTranscriptCloud(
|
|
1754
|
+
sessionId: string,
|
|
1755
|
+
repo: string,
|
|
1756
|
+
messages: Array<Record<string, unknown>>,
|
|
1757
|
+
actions: Array<Record<string, unknown>>,
|
|
1758
|
+
): Promise<boolean> {
|
|
1759
|
+
if (!sessionId || (messages.length === 0 && actions.length === 0)) return false;
|
|
1760
|
+
const jwt = loadJwt();
|
|
1761
|
+
if (!jwt) return false;
|
|
1762
|
+
try {
|
|
1763
|
+
const resp = await fetch(GATEWAY_URL + '/api/v1/cli/sync-transcripts', {
|
|
1764
|
+
method: 'POST',
|
|
1765
|
+
headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + jwt },
|
|
1766
|
+
body: JSON.stringify({ repo: repo || '', sessions: [{ cc_session_id: sessionId, messages, actions }] }),
|
|
1767
|
+
signal: AbortSignal.timeout(5000),
|
|
1768
|
+
});
|
|
1769
|
+
return resp.ok;
|
|
1770
|
+
} catch {
|
|
1771
|
+
return false;
|
|
1772
|
+
}
|
|
1773
|
+
}
|
|
1774
|
+
|
|
1727
1775
|
// Local-channel default 24s \u2014 MUST stay below the CC hook timeout (50s for
|
|
1728
1776
|
// edit/bash/cwe in ccHookConfig.ts) so the AbortSignal fires and the caller's
|
|
1729
1777
|
// catch fails open cleanly. A hung on-device worker should fail open fast; only
|
|
@@ -2040,6 +2088,12 @@ export function appendSessionAction(sessionId: string, entry: SessionAction): vo
|
|
|
2040
2088
|
appendFileSync(logPath, JSON.stringify(entry) + '\\n', 'utf-8');
|
|
2041
2089
|
} catch {}
|
|
2042
2090
|
|
|
2091
|
+
// Cloud: ship the action to the API transcript-sync (org from login JWT) with an
|
|
2092
|
+
// explicit step so streamed single-action sends stay idempotent + ordered.
|
|
2093
|
+
if (deployIsCloud()) {
|
|
2094
|
+
void syncTranscriptCloud(sessionId, '', [], [{ step, tool: entry.tool, summary: entry.summary, file: entry.file, outcome: entry.outcome }]);
|
|
2095
|
+
return;
|
|
2096
|
+
}
|
|
2043
2097
|
const mcpPort = process.env.SYNKRO_MCP_PORT || '18931';
|
|
2044
2098
|
let mcpToken = '';
|
|
2045
2099
|
try { mcpToken = readFileSync(join(HOME, '.synkro', '.mcp-jwt'), 'utf-8').trim(); } catch {}
|
|
@@ -2811,15 +2865,23 @@ export async function pushConversationMessage(
|
|
|
2811
2865
|
const text = content.trim().slice(0, 8000);
|
|
2812
2866
|
if (!sessionId || !text || isProviderRedactedPlaceholder(text)) return false;
|
|
2813
2867
|
|
|
2868
|
+
const seq = typeof opts.seq === 'number'
|
|
2869
|
+
? opts.seq
|
|
2870
|
+
: Date.now() % 1_000_000_000;
|
|
2871
|
+
|
|
2872
|
+
// Cloud: send to the API transcript-sync (org from login JWT) instead of the
|
|
2873
|
+
// local MCP server.
|
|
2874
|
+
if (deployIsCloud()) {
|
|
2875
|
+
return syncTranscriptCloud(sessionId, opts.gitRepo || '', [{
|
|
2876
|
+
message_index: seq, type: role, content: text, timestamp: new Date().toISOString(),
|
|
2877
|
+
}], []);
|
|
2878
|
+
}
|
|
2879
|
+
|
|
2814
2880
|
const mcpPort = process.env.SYNKRO_MCP_PORT || '18931';
|
|
2815
2881
|
let mcpToken = '';
|
|
2816
2882
|
try { mcpToken = readFileSync(join(HOME, '.synkro', '.mcp-jwt'), 'utf-8').trim(); } catch {}
|
|
2817
2883
|
if (!mcpToken) return false;
|
|
2818
2884
|
|
|
2819
|
-
const seq = typeof opts.seq === 'number'
|
|
2820
|
-
? opts.seq
|
|
2821
|
-
: Date.now() % 1_000_000_000;
|
|
2822
|
-
|
|
2823
2885
|
try {
|
|
2824
2886
|
const body: Record<string, unknown> = {
|
|
2825
2887
|
session_id: sessionId,
|
|
@@ -2973,6 +3035,22 @@ export async function syncConversationTranscript(
|
|
|
2973
3035
|
return { ingested: 0, messages: [] };
|
|
2974
3036
|
}
|
|
2975
3037
|
|
|
3038
|
+
// Cloud: ship the tail to the API transcript-sync (org from login JWT). Map the
|
|
3039
|
+
// local message shape (ts) to the cloud route's (timestamp).
|
|
3040
|
+
if (deployIsCloud()) {
|
|
3041
|
+
const cloudMsgs = messages.map((m) => ({
|
|
3042
|
+
message_index: m.message_index,
|
|
3043
|
+
type: m.type,
|
|
3044
|
+
content: m.content,
|
|
3045
|
+
model: (m.model as string | null) ?? undefined,
|
|
3046
|
+
timestamp: (m.ts as string | null) ?? undefined,
|
|
3047
|
+
tool_calls: m.tool_calls,
|
|
3048
|
+
}));
|
|
3049
|
+
const ok = await syncTranscriptCloud(sessionId, gitRepo, cloudMsgs, []);
|
|
3050
|
+
if (ok) writeFileSync(offsetFile, String(totalLines), 'utf-8');
|
|
3051
|
+
return { ingested: ok ? messages.length : 0, messages };
|
|
3052
|
+
}
|
|
3053
|
+
|
|
2976
3054
|
const rawPort = parseInt(process.env.SYNKRO_MCP_PORT || '18931', 10);
|
|
2977
3055
|
const mcpPort = (rawPort > 0 && rawPort < 65536) ? rawPort : 18931;
|
|
2978
3056
|
let mcpToken = '';
|
|
@@ -10592,7 +10670,7 @@ function writeConfigEnv(opts) {
|
|
|
10592
10670
|
`SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle(credsPath)}`,
|
|
10593
10671
|
`SYNKRO_TIER=${shellQuoteSingle(safeTier)}`,
|
|
10594
10672
|
`SYNKRO_INFERENCE=${shellQuoteSingle(safeInference)}`,
|
|
10595
|
-
`SYNKRO_VERSION=${shellQuoteSingle("1.6.
|
|
10673
|
+
`SYNKRO_VERSION=${shellQuoteSingle("1.6.77")}`
|
|
10596
10674
|
];
|
|
10597
10675
|
if (safeSynkroBin) lines.push(`SYNKRO_CLI_BIN=${shellQuoteSingle(safeSynkroBin)}`);
|
|
10598
10676
|
if (safeUserId) lines.push(`SYNKRO_USER_ID=${shellQuoteSingle(safeUserId)}`);
|
|
@@ -10738,6 +10816,9 @@ async function printWorkerDebug(base, jwt2) {
|
|
|
10738
10816
|
`);
|
|
10739
10817
|
return;
|
|
10740
10818
|
}
|
|
10819
|
+
if (d.grader_error_code) {
|
|
10820
|
+
console.warn(` \u25B8 DIAGNOSIS [${d.grader_error_code}]: ${d.grader_error_detail || ""}`);
|
|
10821
|
+
}
|
|
10741
10822
|
for (const w of d.workers.slice(0, 2)) {
|
|
10742
10823
|
console.warn(` [${w.worker}] launcher log:`);
|
|
10743
10824
|
for (const l of String(w.logTail || "").split("\n").filter((l2) => l2.trim()).slice(-6)) console.warn(" " + l.slice(0, 240));
|
|
@@ -10815,14 +10896,16 @@ async function verifyCloudGrader(jwt2) {
|
|
|
10815
10896
|
}
|
|
10816
10897
|
const detail = (body.error || raw || "").trim().slice(0, 300) || "(empty response)";
|
|
10817
10898
|
console.warn(` \u2717 smoke grade FAILED (HTTP ${r.status}): ${detail}`);
|
|
10818
|
-
if (
|
|
10899
|
+
if (body.error_code) {
|
|
10900
|
+
console.warn(` \u25B8 ${body.error_code}: ${body.hint || ""}`);
|
|
10901
|
+
} else if (/no grader worker ready/i.test(detail)) {
|
|
10819
10902
|
console.warn(" \u2192 the worker pool never registered (cold-start, or the pool failed to boot).");
|
|
10820
10903
|
} else if (/connection lost|network|502/i.test(detail)) {
|
|
10821
10904
|
console.warn(" \u2192 the container dropped the connection mid-grade \u2014 the claude worker crashed or hung (most often a bad/expired setup-token).");
|
|
10822
10905
|
} else {
|
|
10823
10906
|
console.warn(" \u2192 the worker returned an error; most often a bad/expired Claude setup-token.");
|
|
10824
10907
|
}
|
|
10825
|
-
console.warn(" Re-run `synkro install` \u2192 cloud to re-authorize the hosted worker.\n");
|
|
10908
|
+
if (!body.error_code) console.warn(" Re-run `synkro install` \u2192 cloud to re-authorize the hosted worker.\n");
|
|
10826
10909
|
await printWorkerDebug(base, jwt2);
|
|
10827
10910
|
return false;
|
|
10828
10911
|
} catch (e) {
|
|
@@ -14262,7 +14345,7 @@ var args = process.argv.slice(2);
|
|
|
14262
14345
|
var cmd = args[0] || "";
|
|
14263
14346
|
var subArgs = args.slice(1);
|
|
14264
14347
|
function printVersion() {
|
|
14265
|
-
console.log("1.6.
|
|
14348
|
+
console.log("1.6.77");
|
|
14266
14349
|
}
|
|
14267
14350
|
function printHelp2() {
|
|
14268
14351
|
console.log(`Synkro CLI \u2014 runtime safety for AI coding agents
|