@synkro-sh/cli 1.0.10 → 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 CHANGED
@@ -1935,7 +1935,7 @@ __export(install_exports, {
1935
1935
  installCommand: () => installCommand,
1936
1936
  parseArgs: () => parseArgs
1937
1937
  });
1938
- 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";
1939
1939
  import { homedir as homedir4 } from "os";
1940
1940
  import { join as join4 } from "path";
1941
1941
  function sanitizeGatewayCandidate(raw) {
@@ -1949,6 +1949,7 @@ function parseArgs(argv) {
1949
1949
  else if (a.startsWith("--gateway=")) opts.gatewayUrl = a.slice("--gateway=".length);
1950
1950
  else if (a === "--skip-auth") opts.skipAuth = true;
1951
1951
  else if (a === "--no-mcp") opts.noMcp = true;
1952
+ else if (a === "--force" || a === "-f") opts.force = true;
1952
1953
  }
1953
1954
  if (!opts.gatewayUrl) {
1954
1955
  const fromEnv = sanitizeGatewayCandidate(process.env.SYNKRO_GATEWAY_URL);
@@ -2020,7 +2021,7 @@ function writeConfigEnv(opts) {
2020
2021
  `SYNKRO_GATEWAY_URL=${shellQuoteSingle(safeGateway)}`,
2021
2022
  `SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle(credsPath)}`,
2022
2023
  `SYNKRO_TIER=${shellQuoteSingle(safeTier)}`,
2023
- `SYNKRO_VERSION=${shellQuoteSingle("1.0.10")}`
2024
+ `SYNKRO_VERSION=${shellQuoteSingle("1.0.11")}`
2024
2025
  ];
2025
2026
  if (safeUserId) lines.push(`SYNKRO_USER_ID=${shellQuoteSingle(safeUserId)}`);
2026
2027
  if (safeOrgId) lines.push(`SYNKRO_ORG_ID=${shellQuoteSingle(safeOrgId)}`);
@@ -2051,6 +2052,33 @@ function assertGatewayAllowed(gatewayUrl) {
2051
2052
  throw new Error(`Gateway host not in allowlist (synkro.sh or *.synkro.sh): ${host}`);
2052
2053
  }
2053
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
+ }
2054
2082
  async function installCommand(opts = {}) {
2055
2083
  const gatewayUrl = opts.gatewayUrl || sanitizeGatewayCandidate(process.env.SYNKRO_GATEWAY_URL) || "https://api.synkro.sh";
2056
2084
  try {
@@ -2059,6 +2087,12 @@ async function installCommand(opts = {}) {
2059
2087
  console.error(err.message);
2060
2088
  process.exit(1);
2061
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
+ }
2062
2096
  console.log("Synkro install starting...\n");
2063
2097
  if (!isAuthenticated()) {
2064
2098
  console.log("Opening browser for Synkro auth...");
@@ -2112,7 +2146,7 @@ async function installCommand(opts = {}) {
2112
2146
  console.log(` ${scripts.sessionStartScript}
2113
2147
  `);
2114
2148
  writeGraderDaemon();
2115
- console.log("Wrote free-tier grader daemon:");
2149
+ console.log("Wrote local-tier grader daemon:");
2116
2150
  console.log(` ${GRADER_DAEMON_PATH}`);
2117
2151
  console.log(` ${GRADER_PRIMER_EDIT_PATH}`);
2118
2152
  console.log(` ${GRADER_PRIMER_BASH_PATH}
@@ -2130,8 +2164,6 @@ async function installCommand(opts = {}) {
2130
2164
  sessionStartScriptPath: scripts.sessionStartScript
2131
2165
  });
2132
2166
  console.log(`Configured ${agent.name} hooks at ${agent.settingsPath}`);
2133
- } else if (agent.kind === "codex") {
2134
- console.log(`Skipping ${agent.name} for now (v1.1 \u2014 Codex hook config coming soon)`);
2135
2167
  }
2136
2168
  }
2137
2169
  console.log();
@@ -2153,7 +2185,6 @@ async function installCommand(opts = {}) {
2153
2185
  const mcp = installMcpConfig({ gatewayUrl, bearerToken: minted.token });
2154
2186
  console.log(`Registered Synkro guardrails MCP server in ${mcp.path}`);
2155
2187
  console.log(` url: ${mcp.url}`);
2156
- console.log(` token: Synkro-signed JWT, scope=mcp:guardrails`);
2157
2188
  console.log(` expires: ${minted.expires_at} (~1 year)`);
2158
2189
  console.log(" (restart any running Claude Code session for it to load)");
2159
2190
  console.log();
@@ -2178,21 +2209,9 @@ async function installCommand(opts = {}) {
2178
2209
  `);
2179
2210
  console.log("\u2713 Synkro installed.");
2180
2211
  console.log();
2181
- console.log("Try it:");
2182
- console.log(" $ claude");
2183
- console.log(' > Run "echo hello" for me');
2184
- console.log(" (no warning \u2014 safe command)");
2185
- console.log();
2186
- console.log(" > Now propose: kubectl delete namespace production");
2187
- console.log(" (Synkro will block with a warning)");
2188
- console.log();
2189
2212
  console.log("Next steps:");
2190
2213
  console.log(" \u2022 synkro setup-github (enable PR scanning)");
2191
2214
  console.log(" \u2022 synkro status (check what is configured)");
2192
- if (hasClaudeCode && !opts.noMcp) {
2193
- console.log(' \u2022 Try in CC: "Add a Stripe webhook handler" \u2014 CC should call');
2194
- console.log(" synkro-guardrails.get_guardrails to fetch org-specific rules.");
2195
- }
2196
2215
  }
2197
2216
  var SYNKRO_DIR, HOOKS_DIR, BIN_DIR, CONFIG_PATH, GRADER_DAEMON_PATH, GRADER_PRIMER_EDIT_PATH, GRADER_PRIMER_BASH_PATH;
2198
2217
  var init_install = __esm({
@@ -2286,13 +2305,13 @@ var status_exports = {};
2286
2305
  __export(status_exports, {
2287
2306
  statusCommand: () => statusCommand
2288
2307
  });
2289
- import { existsSync as existsSync6, readFileSync as readFileSync4 } from "fs";
2308
+ import { existsSync as existsSync6, readFileSync as readFileSync5 } from "fs";
2290
2309
  import { homedir as homedir5 } from "os";
2291
2310
  import { join as join5 } from "path";
2292
2311
  function readConfigEnv() {
2293
2312
  if (!existsSync6(CONFIG_PATH2)) return {};
2294
2313
  const out = {};
2295
- const raw = readFileSync4(CONFIG_PATH2, "utf-8");
2314
+ const raw = readFileSync5(CONFIG_PATH2, "utf-8");
2296
2315
  for (const line of raw.split("\n")) {
2297
2316
  const trimmed = line.trim();
2298
2317
  if (!trimmed || trimmed.startsWith("#")) continue;
@@ -2559,13 +2578,13 @@ __export(setupGithub_exports, {
2559
2578
  });
2560
2579
  import { createInterface } from "readline/promises";
2561
2580
  import { stdin as input, stdout as output } from "process";
2562
- import { existsSync as existsSync8, readFileSync as readFileSync5 } from "fs";
2581
+ import { existsSync as existsSync8, readFileSync as readFileSync6 } from "fs";
2563
2582
  import { homedir as homedir6 } from "os";
2564
2583
  import { join as join7 } from "path";
2565
2584
  function readConfig() {
2566
2585
  if (!existsSync8(CONFIG_PATH3)) return {};
2567
2586
  const out = {};
2568
- for (const line of readFileSync5(CONFIG_PATH3, "utf-8").split("\n")) {
2587
+ for (const line of readFileSync6(CONFIG_PATH3, "utf-8").split("\n")) {
2569
2588
  const t = line.trim();
2570
2589
  if (!t || t.startsWith("#")) continue;
2571
2590
  const eq = t.indexOf("=");
@@ -3072,13 +3091,17 @@ function disconnectCommand(args2 = []) {
3072
3091
  const mcpRemoved = uninstallMcpConfig();
3073
3092
  console.log(`${mcpRemoved ? "\u2713" : "\xB7"} MCP guardrails server: ${mcpRemoved ? "removed entry from ~/.claude.json" : "no Synkro MCP entry found"}`);
3074
3093
  }
3075
- if (purge && existsSync9(SYNKRO_DIR4)) {
3076
- rmSync(SYNKRO_DIR4, { recursive: true, force: true });
3077
- console.log(`\u2713 Removed ${SYNKRO_DIR4} (--purge)`);
3078
- } else {
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)) {
3079
3102
  console.log(`Config preserved at ${SYNKRO_DIR4}. Run with --purge to remove.`);
3080
3103
  }
3081
- console.log("\nSynkro disconnected. Your AI agents will no longer be judged.");
3104
+ console.log("\nSynkro disconnected.");
3082
3105
  }
3083
3106
  var SYNKRO_DIR4;
3084
3107
  var init_disconnect = __esm({
@@ -3092,7 +3115,7 @@ var init_disconnect = __esm({
3092
3115
  });
3093
3116
 
3094
3117
  // cli/bootstrap.js
3095
- import { readFileSync as readFileSync6, existsSync as existsSync10 } from "fs";
3118
+ import { readFileSync as readFileSync7, existsSync as existsSync10 } from "fs";
3096
3119
  import { resolve } from "path";
3097
3120
  var envCandidates = [
3098
3121
  resolve(process.cwd(), ".env"),
@@ -3100,7 +3123,7 @@ var envCandidates = [
3100
3123
  ];
3101
3124
  for (const envPath of envCandidates) {
3102
3125
  if (!existsSync10(envPath)) continue;
3103
- const envContent = readFileSync6(envPath, "utf-8");
3126
+ const envContent = readFileSync7(envPath, "utf-8");
3104
3127
  for (const line of envContent.split("\n")) {
3105
3128
  const trimmed = line.trim();
3106
3129
  if (!trimmed || trimmed.startsWith("#")) continue;
@@ -3121,7 +3144,7 @@ Usage:
3121
3144
  synkro <command> [options]
3122
3145
 
3123
3146
  Commands:
3124
- install Install Synkro hooks for detected agents (Claude Code, etc.)
3147
+ install [--force] Install Synkro hooks for detected agents (Claude Code, etc.)
3125
3148
  login Authenticate with Synkro (browser OAuth via WorkOS)
3126
3149
  logout Clear local credentials
3127
3150
  status Show current setup state
@@ -3135,8 +3158,6 @@ Quick start:
3135
3158
  $ synkro install # one-time setup
3136
3159
  $ synkro setup-github # enable PR scanning (optional)
3137
3160
  $ claude # use Claude Code normally; Synkro judges in real time
3138
-
3139
- Docs: https://docs.synkro.sh
3140
3161
  `);
3141
3162
  }
3142
3163
  async function main() {