@synkro-sh/cli 1.0.9 → 1.0.11
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 +86 -61
- package/dist/bootstrap.js.map +1 -1
- package/package.json +1 -1
package/dist/bootstrap.js
CHANGED
|
@@ -667,39 +667,43 @@ case "$TOOL_NAME" in
|
|
|
667
667
|
Write)
|
|
668
668
|
# Write replaces the entire file \u2014 content IS the full post-edit file.
|
|
669
669
|
PROPOSED=$(echo "$TOOL_INPUT" | jq -r '.content // ""' 2>/dev/null) ;;
|
|
670
|
-
Edit)
|
|
670
|
+
Edit|MultiEdit)
|
|
671
671
|
# Reconstruct the full post-edit file by applying the diff to file_before.
|
|
672
672
|
# Sending only new_string (the diff hunk) blinds the local grader to
|
|
673
673
|
# violations elsewhere in the file \u2014 the grader needs whole-file context
|
|
674
|
-
# to identify multi-violation edits and cross-line patterns.
|
|
675
|
-
#
|
|
676
|
-
#
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
PROPOSED="$
|
|
674
|
+
# to identify multi-violation edits and cross-line patterns.
|
|
675
|
+
#
|
|
676
|
+
# We use python (already a daemon dependency) instead of bash parameter
|
|
677
|
+
# expansion because macOS ships bash 3.2, where the quoted-pattern form
|
|
678
|
+
# of \${var//PAT/REPL} leaks the quote characters into the result.
|
|
679
|
+
# Python's str.replace() handles arbitrary strings cleanly. Args go via
|
|
680
|
+
# env vars (not argv) so 64 KB file content doesn't trip ARG_MAX limits.
|
|
681
|
+
if [ -n "$FILE_BEFORE" ] && command -v python3 >/dev/null 2>&1; then
|
|
682
|
+
PROPOSED=$(FILE_BEFORE_LITERAL="$FILE_BEFORE" TOOL_INPUT_LITERAL="$TOOL_INPUT" python3 -c '
|
|
683
|
+
import os, json, sys
|
|
684
|
+
fb = os.environ.get("FILE_BEFORE_LITERAL", "")
|
|
685
|
+
ti = json.loads(os.environ.get("TOOL_INPUT_LITERAL", "{}"))
|
|
686
|
+
result = fb
|
|
687
|
+
if "old_string" in ti and "new_string" in ti:
|
|
688
|
+
if ti["old_string"]:
|
|
689
|
+
result = result.replace(ti["old_string"], ti["new_string"], 1)
|
|
690
|
+
elif "edits" in ti and isinstance(ti["edits"], list):
|
|
691
|
+
for e in ti["edits"]:
|
|
692
|
+
old = e.get("old_string", "") if isinstance(e, dict) else ""
|
|
693
|
+
new = e.get("new_string", "") if isinstance(e, dict) else ""
|
|
694
|
+
if old:
|
|
695
|
+
result = result.replace(old, new, 1)
|
|
696
|
+
sys.stdout.write(result)
|
|
697
|
+
' 2>/dev/null)
|
|
683
698
|
fi
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
i=0
|
|
693
|
-
while [ "$i" -lt "$EDIT_COUNT" ]; do
|
|
694
|
-
OLD=$(echo "$EDITS_JSON" | jq -r ".[$i].old_string // \\"\\"")
|
|
695
|
-
NEW=$(echo "$EDITS_JSON" | jq -r ".[$i].new_string // \\"\\"")
|
|
696
|
-
if [ -n "$OLD" ]; then
|
|
697
|
-
PROPOSED="\${PROPOSED//"$OLD"/"$NEW"}"
|
|
698
|
-
fi
|
|
699
|
-
i=$((i + 1))
|
|
700
|
-
done
|
|
701
|
-
else
|
|
702
|
-
PROPOSED=$(echo "$TOOL_INPUT" | jq -r '[.edits[]?.new_string // ""] | join("\\n\\n--- chunk ---\\n\\n")' 2>/dev/null)
|
|
699
|
+
# Fall back to the diff-hunk-only shape if reconstruction failed or
|
|
700
|
+
# file_before was empty (new file via Edit, etc.).
|
|
701
|
+
if [ -z "$PROPOSED" ]; then
|
|
702
|
+
if [ "$TOOL_NAME" = "MultiEdit" ]; then
|
|
703
|
+
PROPOSED=$(echo "$TOOL_INPUT" | jq -r '[.edits[]?.new_string // ""] | join("\\n\\n--- chunk ---\\n\\n")' 2>/dev/null)
|
|
704
|
+
else
|
|
705
|
+
PROPOSED=$(echo "$TOOL_INPUT" | jq -r '.new_string // ""' 2>/dev/null)
|
|
706
|
+
fi
|
|
703
707
|
fi
|
|
704
708
|
;;
|
|
705
709
|
NotebookEdit)
|
|
@@ -1931,7 +1935,7 @@ __export(install_exports, {
|
|
|
1931
1935
|
installCommand: () => installCommand,
|
|
1932
1936
|
parseArgs: () => parseArgs
|
|
1933
1937
|
});
|
|
1934
|
-
import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync4, chmodSync } from "fs";
|
|
1938
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync4, writeFileSync as writeFileSync4, chmodSync, readFileSync as readFileSync4 } from "fs";
|
|
1935
1939
|
import { homedir as homedir4 } from "os";
|
|
1936
1940
|
import { join as join4 } from "path";
|
|
1937
1941
|
function sanitizeGatewayCandidate(raw) {
|
|
@@ -1945,6 +1949,7 @@ function parseArgs(argv) {
|
|
|
1945
1949
|
else if (a.startsWith("--gateway=")) opts.gatewayUrl = a.slice("--gateway=".length);
|
|
1946
1950
|
else if (a === "--skip-auth") opts.skipAuth = true;
|
|
1947
1951
|
else if (a === "--no-mcp") opts.noMcp = true;
|
|
1952
|
+
else if (a === "--force" || a === "-f") opts.force = true;
|
|
1948
1953
|
}
|
|
1949
1954
|
if (!opts.gatewayUrl) {
|
|
1950
1955
|
const fromEnv = sanitizeGatewayCandidate(process.env.SYNKRO_GATEWAY_URL);
|
|
@@ -2016,7 +2021,7 @@ function writeConfigEnv(opts) {
|
|
|
2016
2021
|
`SYNKRO_GATEWAY_URL=${shellQuoteSingle(safeGateway)}`,
|
|
2017
2022
|
`SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle(credsPath)}`,
|
|
2018
2023
|
`SYNKRO_TIER=${shellQuoteSingle(safeTier)}`,
|
|
2019
|
-
`SYNKRO_VERSION=${shellQuoteSingle("1.0.
|
|
2024
|
+
`SYNKRO_VERSION=${shellQuoteSingle("1.0.11")}`
|
|
2020
2025
|
];
|
|
2021
2026
|
if (safeUserId) lines.push(`SYNKRO_USER_ID=${shellQuoteSingle(safeUserId)}`);
|
|
2022
2027
|
if (safeOrgId) lines.push(`SYNKRO_ORG_ID=${shellQuoteSingle(safeOrgId)}`);
|
|
@@ -2047,6 +2052,33 @@ function assertGatewayAllowed(gatewayUrl) {
|
|
|
2047
2052
|
throw new Error(`Gateway host not in allowlist (synkro.sh or *.synkro.sh): ${host}`);
|
|
2048
2053
|
}
|
|
2049
2054
|
}
|
|
2055
|
+
function isAlreadyInstalled() {
|
|
2056
|
+
const requiredScripts = [
|
|
2057
|
+
join4(HOOKS_DIR, "cc-bash-judge.sh"),
|
|
2058
|
+
join4(HOOKS_DIR, "cc-bash-followup.sh"),
|
|
2059
|
+
join4(HOOKS_DIR, "cc-edit-precheck.sh"),
|
|
2060
|
+
join4(HOOKS_DIR, "cc-edit-capture.sh"),
|
|
2061
|
+
join4(HOOKS_DIR, "cc-stop-summary.sh"),
|
|
2062
|
+
join4(HOOKS_DIR, "cc-session-start.sh")
|
|
2063
|
+
];
|
|
2064
|
+
if (!requiredScripts.every((p) => existsSync5(p))) return false;
|
|
2065
|
+
if (!existsSync5(CONFIG_PATH)) return false;
|
|
2066
|
+
const settingsPath = join4(homedir4(), ".claude", "settings.json");
|
|
2067
|
+
if (!existsSync5(settingsPath)) return false;
|
|
2068
|
+
try {
|
|
2069
|
+
const settings = JSON.parse(readFileSync4(settingsPath, "utf-8"));
|
|
2070
|
+
const hooks = settings?.hooks;
|
|
2071
|
+
if (!hooks || typeof hooks !== "object") return false;
|
|
2072
|
+
const hasManaged = (kind) => Array.isArray(hooks[kind]) && hooks[kind].some((entry) => entry?.__synkro_managed__ === true);
|
|
2073
|
+
if (!hasManaged("PreToolUse")) return false;
|
|
2074
|
+
if (!hasManaged("PostToolUse")) return false;
|
|
2075
|
+
if (!hasManaged("SessionEnd")) return false;
|
|
2076
|
+
if (!hasManaged("SessionStart")) return false;
|
|
2077
|
+
} catch {
|
|
2078
|
+
return false;
|
|
2079
|
+
}
|
|
2080
|
+
return true;
|
|
2081
|
+
}
|
|
2050
2082
|
async function installCommand(opts = {}) {
|
|
2051
2083
|
const gatewayUrl = opts.gatewayUrl || sanitizeGatewayCandidate(process.env.SYNKRO_GATEWAY_URL) || "https://api.synkro.sh";
|
|
2052
2084
|
try {
|
|
@@ -2055,6 +2087,12 @@ async function installCommand(opts = {}) {
|
|
|
2055
2087
|
console.error(err.message);
|
|
2056
2088
|
process.exit(1);
|
|
2057
2089
|
}
|
|
2090
|
+
if (!opts.force && isAuthenticated() && isAlreadyInstalled()) {
|
|
2091
|
+
console.log("\u2713 Synkro is already installed and configured.");
|
|
2092
|
+
console.log(" Run `synkro update` to refresh hook scripts and judge prompts.");
|
|
2093
|
+
console.log(" Run `synkro install --force` to reinstall from scratch.");
|
|
2094
|
+
return;
|
|
2095
|
+
}
|
|
2058
2096
|
console.log("Synkro install starting...\n");
|
|
2059
2097
|
if (!isAuthenticated()) {
|
|
2060
2098
|
console.log("Opening browser for Synkro auth...");
|
|
@@ -2108,7 +2146,7 @@ async function installCommand(opts = {}) {
|
|
|
2108
2146
|
console.log(` ${scripts.sessionStartScript}
|
|
2109
2147
|
`);
|
|
2110
2148
|
writeGraderDaemon();
|
|
2111
|
-
console.log("Wrote
|
|
2149
|
+
console.log("Wrote local-tier grader daemon:");
|
|
2112
2150
|
console.log(` ${GRADER_DAEMON_PATH}`);
|
|
2113
2151
|
console.log(` ${GRADER_PRIMER_EDIT_PATH}`);
|
|
2114
2152
|
console.log(` ${GRADER_PRIMER_BASH_PATH}
|
|
@@ -2126,8 +2164,6 @@ async function installCommand(opts = {}) {
|
|
|
2126
2164
|
sessionStartScriptPath: scripts.sessionStartScript
|
|
2127
2165
|
});
|
|
2128
2166
|
console.log(`Configured ${agent.name} hooks at ${agent.settingsPath}`);
|
|
2129
|
-
} else if (agent.kind === "codex") {
|
|
2130
|
-
console.log(`Skipping ${agent.name} for now (v1.1 \u2014 Codex hook config coming soon)`);
|
|
2131
2167
|
}
|
|
2132
2168
|
}
|
|
2133
2169
|
console.log();
|
|
@@ -2149,7 +2185,6 @@ async function installCommand(opts = {}) {
|
|
|
2149
2185
|
const mcp = installMcpConfig({ gatewayUrl, bearerToken: minted.token });
|
|
2150
2186
|
console.log(`Registered Synkro guardrails MCP server in ${mcp.path}`);
|
|
2151
2187
|
console.log(` url: ${mcp.url}`);
|
|
2152
|
-
console.log(` token: Synkro-signed JWT, scope=mcp:guardrails`);
|
|
2153
2188
|
console.log(` expires: ${minted.expires_at} (~1 year)`);
|
|
2154
2189
|
console.log(" (restart any running Claude Code session for it to load)");
|
|
2155
2190
|
console.log();
|
|
@@ -2174,21 +2209,9 @@ async function installCommand(opts = {}) {
|
|
|
2174
2209
|
`);
|
|
2175
2210
|
console.log("\u2713 Synkro installed.");
|
|
2176
2211
|
console.log();
|
|
2177
|
-
console.log("Try it:");
|
|
2178
|
-
console.log(" $ claude");
|
|
2179
|
-
console.log(' > Run "echo hello" for me');
|
|
2180
|
-
console.log(" (no warning \u2014 safe command)");
|
|
2181
|
-
console.log();
|
|
2182
|
-
console.log(" > Now propose: kubectl delete namespace production");
|
|
2183
|
-
console.log(" (Synkro will block with a warning)");
|
|
2184
|
-
console.log();
|
|
2185
2212
|
console.log("Next steps:");
|
|
2186
2213
|
console.log(" \u2022 synkro setup-github (enable PR scanning)");
|
|
2187
2214
|
console.log(" \u2022 synkro status (check what is configured)");
|
|
2188
|
-
if (hasClaudeCode && !opts.noMcp) {
|
|
2189
|
-
console.log(' \u2022 Try in CC: "Add a Stripe webhook handler" \u2014 CC should call');
|
|
2190
|
-
console.log(" synkro-guardrails.get_guardrails to fetch org-specific rules.");
|
|
2191
|
-
}
|
|
2192
2215
|
}
|
|
2193
2216
|
var SYNKRO_DIR, HOOKS_DIR, BIN_DIR, CONFIG_PATH, GRADER_DAEMON_PATH, GRADER_PRIMER_EDIT_PATH, GRADER_PRIMER_BASH_PATH;
|
|
2194
2217
|
var init_install = __esm({
|
|
@@ -2282,13 +2305,13 @@ var status_exports = {};
|
|
|
2282
2305
|
__export(status_exports, {
|
|
2283
2306
|
statusCommand: () => statusCommand
|
|
2284
2307
|
});
|
|
2285
|
-
import { existsSync as existsSync6, readFileSync as
|
|
2308
|
+
import { existsSync as existsSync6, readFileSync as readFileSync5 } from "fs";
|
|
2286
2309
|
import { homedir as homedir5 } from "os";
|
|
2287
2310
|
import { join as join5 } from "path";
|
|
2288
2311
|
function readConfigEnv() {
|
|
2289
2312
|
if (!existsSync6(CONFIG_PATH2)) return {};
|
|
2290
2313
|
const out = {};
|
|
2291
|
-
const raw =
|
|
2314
|
+
const raw = readFileSync5(CONFIG_PATH2, "utf-8");
|
|
2292
2315
|
for (const line of raw.split("\n")) {
|
|
2293
2316
|
const trimmed = line.trim();
|
|
2294
2317
|
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
@@ -2555,13 +2578,13 @@ __export(setupGithub_exports, {
|
|
|
2555
2578
|
});
|
|
2556
2579
|
import { createInterface } from "readline/promises";
|
|
2557
2580
|
import { stdin as input, stdout as output } from "process";
|
|
2558
|
-
import { existsSync as existsSync8, readFileSync as
|
|
2581
|
+
import { existsSync as existsSync8, readFileSync as readFileSync6 } from "fs";
|
|
2559
2582
|
import { homedir as homedir6 } from "os";
|
|
2560
2583
|
import { join as join7 } from "path";
|
|
2561
2584
|
function readConfig() {
|
|
2562
2585
|
if (!existsSync8(CONFIG_PATH3)) return {};
|
|
2563
2586
|
const out = {};
|
|
2564
|
-
for (const line of
|
|
2587
|
+
for (const line of readFileSync6(CONFIG_PATH3, "utf-8").split("\n")) {
|
|
2565
2588
|
const t = line.trim();
|
|
2566
2589
|
if (!t || t.startsWith("#")) continue;
|
|
2567
2590
|
const eq = t.indexOf("=");
|
|
@@ -3068,13 +3091,17 @@ function disconnectCommand(args2 = []) {
|
|
|
3068
3091
|
const mcpRemoved = uninstallMcpConfig();
|
|
3069
3092
|
console.log(`${mcpRemoved ? "\u2713" : "\xB7"} MCP guardrails server: ${mcpRemoved ? "removed entry from ~/.claude.json" : "no Synkro MCP entry found"}`);
|
|
3070
3093
|
}
|
|
3071
|
-
if (purge
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3094
|
+
if (purge) {
|
|
3095
|
+
if (existsSync9(SYNKRO_DIR4)) {
|
|
3096
|
+
rmSync(SYNKRO_DIR4, { recursive: true, force: true });
|
|
3097
|
+
console.log(`\u2713 Removed ${SYNKRO_DIR4}`);
|
|
3098
|
+
} else {
|
|
3099
|
+
console.log(`\xB7 ${SYNKRO_DIR4} already gone, nothing to remove`);
|
|
3100
|
+
}
|
|
3101
|
+
} else if (existsSync9(SYNKRO_DIR4)) {
|
|
3075
3102
|
console.log(`Config preserved at ${SYNKRO_DIR4}. Run with --purge to remove.`);
|
|
3076
3103
|
}
|
|
3077
|
-
console.log("\nSynkro disconnected.
|
|
3104
|
+
console.log("\nSynkro disconnected.");
|
|
3078
3105
|
}
|
|
3079
3106
|
var SYNKRO_DIR4;
|
|
3080
3107
|
var init_disconnect = __esm({
|
|
@@ -3088,7 +3115,7 @@ var init_disconnect = __esm({
|
|
|
3088
3115
|
});
|
|
3089
3116
|
|
|
3090
3117
|
// cli/bootstrap.js
|
|
3091
|
-
import { readFileSync as
|
|
3118
|
+
import { readFileSync as readFileSync7, existsSync as existsSync10 } from "fs";
|
|
3092
3119
|
import { resolve } from "path";
|
|
3093
3120
|
var envCandidates = [
|
|
3094
3121
|
resolve(process.cwd(), ".env"),
|
|
@@ -3096,7 +3123,7 @@ var envCandidates = [
|
|
|
3096
3123
|
];
|
|
3097
3124
|
for (const envPath of envCandidates) {
|
|
3098
3125
|
if (!existsSync10(envPath)) continue;
|
|
3099
|
-
const envContent =
|
|
3126
|
+
const envContent = readFileSync7(envPath, "utf-8");
|
|
3100
3127
|
for (const line of envContent.split("\n")) {
|
|
3101
3128
|
const trimmed = line.trim();
|
|
3102
3129
|
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
@@ -3117,7 +3144,7 @@ Usage:
|
|
|
3117
3144
|
synkro <command> [options]
|
|
3118
3145
|
|
|
3119
3146
|
Commands:
|
|
3120
|
-
install
|
|
3147
|
+
install [--force] Install Synkro hooks for detected agents (Claude Code, etc.)
|
|
3121
3148
|
login Authenticate with Synkro (browser OAuth via WorkOS)
|
|
3122
3149
|
logout Clear local credentials
|
|
3123
3150
|
status Show current setup state
|
|
@@ -3131,8 +3158,6 @@ Quick start:
|
|
|
3131
3158
|
$ synkro install # one-time setup
|
|
3132
3159
|
$ synkro setup-github # enable PR scanning (optional)
|
|
3133
3160
|
$ claude # use Claude Code normally; Synkro judges in real time
|
|
3134
|
-
|
|
3135
|
-
Docs: https://docs.synkro.sh
|
|
3136
3161
|
`);
|
|
3137
3162
|
}
|
|
3138
3163
|
async function main() {
|