@synkro-sh/cli 1.0.2 → 1.0.4

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
@@ -133,6 +133,16 @@ function installCCHooks(settingsPath, config) {
133
133
  ],
134
134
  [SYNKRO_MARKER]: true
135
135
  });
136
+ settings.hooks.PostToolUse.push({
137
+ matcher: "Bash",
138
+ hooks: [
139
+ {
140
+ type: "command",
141
+ command: config.bashFollowupScriptPath
142
+ }
143
+ ],
144
+ [SYNKRO_MARKER]: true
145
+ });
136
146
  settings.hooks.SessionEnd.push({
137
147
  hooks: [
138
148
  {
@@ -274,7 +284,7 @@ var init_mcpConfig = __esm({
274
284
  });
275
285
 
276
286
  // cli/installer/hookScripts.ts
277
- var CC_BASH_JUDGE_SCRIPT, CC_EDIT_PRECHECK_SCRIPT, CC_EDIT_CAPTURE_SCRIPT, CC_STOP_SUMMARY_SCRIPT, CC_SESSION_START_SCRIPT;
287
+ var CC_BASH_JUDGE_SCRIPT, CC_EDIT_PRECHECK_SCRIPT, CC_EDIT_CAPTURE_SCRIPT, CC_STOP_SUMMARY_SCRIPT, CC_SESSION_START_SCRIPT, CC_BASH_FOLLOWUP_SCRIPT;
278
288
  var init_hookScripts = __esm({
279
289
  "cli/installer/hookScripts.ts"() {
280
290
  "use strict";
@@ -1183,6 +1193,48 @@ fi
1183
1193
 
1184
1194
  jq -n --arg sys_msg "$SYS_MSG" '{ systemMessage: $sys_msg }'
1185
1195
  exit 0
1196
+ `;
1197
+ CC_BASH_FOLLOWUP_SCRIPT = `#!/bin/bash
1198
+ # Synkro PostToolUse Bash hook \u2014 minimal correction-followup fire.
1199
+ # No grading happens here; verdict already came from PreToolUse. This is just
1200
+ # the "user approved + agent ran it" capture.
1201
+ set -e
1202
+
1203
+ CONFIG_FILE="$HOME/.synkro/config.env"
1204
+ if [ -f "$CONFIG_FILE" ]; then
1205
+ set -a
1206
+ # shellcheck disable=SC1090
1207
+ . "$CONFIG_FILE"
1208
+ set +a
1209
+ fi
1210
+
1211
+ GATEWAY_URL="\${SYNKRO_GATEWAY_URL:-https://api.synkro.sh}"
1212
+ CREDS_PATH="\${SYNKRO_CREDENTIALS_PATH:-$HOME/.synkro/credentials.json}"
1213
+
1214
+ if [ ! -f "$CREDS_PATH" ]; then echo '{}'; exit 0; fi
1215
+ JWT=$(jq -r '.access_token // empty' "$CREDS_PATH" 2>/dev/null)
1216
+ if [ -z "$JWT" ]; then echo '{}'; exit 0; fi
1217
+
1218
+ PAYLOAD=$(cat)
1219
+ TOOL_NAME=$(echo "$PAYLOAD" | jq -r '.tool_name // empty' 2>/dev/null)
1220
+ if [ "$TOOL_NAME" != "Bash" ]; then echo '{}'; exit 0; fi
1221
+
1222
+ SESSION_ID=$(echo "$PAYLOAD" | jq -r '.session_id // empty' 2>/dev/null)
1223
+ TOOL_USE_ID=$(echo "$PAYLOAD" | jq -r '.tool_use_id // empty' 2>/dev/null)
1224
+ if [ -z "$SESSION_ID" ] || [ -z "$TOOL_USE_ID" ]; then echo '{}'; exit 0; fi
1225
+
1226
+ BODY=$(jq -n --arg sid "$SESSION_ID" --arg tid "$TOOL_USE_ID" \\
1227
+ '{session_id: $sid, tool_use_id: $tid, decision: "allow"}')
1228
+
1229
+ curl -sS -X POST "\${GATEWAY_URL}/api/v1/precheck-edit/correction-followup" \\
1230
+ -H "Content-Type: application/json" \\
1231
+ -H "Authorization: Bearer $JWT" \\
1232
+ -d "$BODY" \\
1233
+ --max-time 2 \\
1234
+ >/dev/null 2>&1 || true
1235
+
1236
+ echo '{}'
1237
+ exit 0
1186
1238
  `;
1187
1239
  }
1188
1240
  });
@@ -1543,22 +1595,26 @@ import { createServer } from "http";
1543
1595
  import { writeFileSync as writeFileSync3, readFileSync as readFileSync3, existsSync as existsSync4, mkdirSync as mkdirSync3, unlinkSync as unlinkSync2 } from "fs";
1544
1596
  import { homedir as homedir3, platform } from "os";
1545
1597
  import { join as join3, dirname as dirname3 } from "path";
1546
- import { exec } from "child_process";
1598
+ import { execFile } from "child_process";
1547
1599
  import jwt from "jsonwebtoken";
1548
1600
  function openBrowser(url) {
1549
1601
  const os = platform();
1550
- let command;
1602
+ let bin;
1603
+ let args2;
1551
1604
  switch (os) {
1552
1605
  case "darwin":
1553
- command = `open "${url}"`;
1606
+ bin = "open";
1607
+ args2 = [url];
1554
1608
  break;
1555
1609
  case "win32":
1556
- command = `start "" "${url}"`;
1610
+ bin = "cmd";
1611
+ args2 = ["/c", "start", "", url];
1557
1612
  break;
1558
1613
  default:
1559
- command = `xdg-open "${url}"`;
1614
+ bin = "xdg-open";
1615
+ args2 = [url];
1560
1616
  }
1561
- exec(command, (error) => {
1617
+ execFile(bin, args2, (error) => {
1562
1618
  if (error) {
1563
1619
  console.error("Failed to open browser automatically.");
1564
1620
  console.log(`Please open this URL manually: ${url}`);
@@ -1585,14 +1641,30 @@ function loadCredentials() {
1585
1641
  }
1586
1642
  function createCallbackServer() {
1587
1643
  const CORS_HEADERS = {
1588
- "Access-Control-Allow-Origin": "*",
1589
- "Access-Control-Allow-Methods": "GET, OPTIONS",
1590
- "Access-Control-Allow-Headers": "*"
1644
+ "Access-Control-Allow-Origin": SYNKRO_WEB_AUTH_URL,
1645
+ "Access-Control-Allow-Methods": "POST, OPTIONS",
1646
+ "Access-Control-Allow-Headers": "Content-Type",
1647
+ "Vary": "Origin"
1591
1648
  };
1592
1649
  return new Promise((resolve2, reject) => {
1593
1650
  const server = createServer((req, res) => {
1594
1651
  if (req.method === "OPTIONS") {
1595
- res.writeHead(204, CORS_HEADERS);
1652
+ const origin = req.headers.origin;
1653
+ if (origin === SYNKRO_WEB_AUTH_URL) {
1654
+ res.writeHead(204, CORS_HEADERS);
1655
+ } else {
1656
+ res.writeHead(204, {
1657
+ "Access-Control-Allow-Methods": "POST, OPTIONS",
1658
+ "Access-Control-Allow-Headers": "Content-Type",
1659
+ "Vary": "Origin"
1660
+ });
1661
+ }
1662
+ res.end();
1663
+ return;
1664
+ }
1665
+ const reqOrigin = req.headers.origin;
1666
+ if (reqOrigin && reqOrigin !== SYNKRO_WEB_AUTH_URL) {
1667
+ res.writeHead(403, { "Vary": "Origin" });
1596
1668
  res.end();
1597
1669
  return;
1598
1670
  }
@@ -1607,35 +1679,78 @@ function createCallbackServer() {
1607
1679
  res.end();
1608
1680
  return;
1609
1681
  }
1610
- const token = url.searchParams.get("token");
1611
- const refreshToken = url.searchParams.get("refresh_token") || "";
1612
- const userId = url.searchParams.get("user_id") || "";
1613
- const email = url.searchParams.get("email") || "";
1614
- const orgId = url.searchParams.get("org_id") || "";
1615
- const state = url.searchParams.get("state") || "";
1616
- if (!token) {
1617
- res.writeHead(400, { ...CORS_HEADERS, "Content-Type": "text/html" });
1682
+ if (req.method !== "POST") {
1683
+ res.writeHead(405, { ...CORS_HEADERS, "Allow": "POST, OPTIONS", "Content-Type": "text/html" });
1618
1684
  res.end(ERROR_HTML);
1619
- setTimeout(() => {
1620
- server.close();
1621
- reject(new Error("Authentication failed: missing token in callback"));
1622
- }, 500);
1623
1685
  return;
1624
1686
  }
1625
- const authData = {
1626
- access_token: token,
1627
- refresh_token: refreshToken,
1628
- user_id: userId || void 0,
1629
- email: email || void 0,
1630
- org_id: orgId || void 0,
1631
- state: state || void 0
1632
- };
1633
- res.writeHead(200, { ...CORS_HEADERS, "Content-Type": "text/html" });
1634
- res.end(SUCCESS_HTML);
1635
- setTimeout(() => {
1636
- server.close();
1637
- resolve2(authData);
1638
- }, 500);
1687
+ const MAX_BODY = 16 * 1024;
1688
+ const chunks = [];
1689
+ let total = 0;
1690
+ let aborted = false;
1691
+ req.on("data", (chunk) => {
1692
+ if (aborted) return;
1693
+ total += chunk.length;
1694
+ if (total > MAX_BODY) {
1695
+ aborted = true;
1696
+ res.writeHead(413, CORS_HEADERS);
1697
+ res.end();
1698
+ return;
1699
+ }
1700
+ chunks.push(chunk);
1701
+ });
1702
+ req.on("end", () => {
1703
+ if (aborted) return;
1704
+ let parsed;
1705
+ try {
1706
+ parsed = JSON.parse(Buffer.concat(chunks).toString("utf8"));
1707
+ } catch {
1708
+ res.writeHead(400, { ...CORS_HEADERS, "Content-Type": "application/json" });
1709
+ res.end(JSON.stringify({ error: "invalid_json" }));
1710
+ setTimeout(() => {
1711
+ server.close();
1712
+ reject(new Error("Authentication failed: invalid JSON body"));
1713
+ }, 200);
1714
+ return;
1715
+ }
1716
+ const token = parsed?.token;
1717
+ if (!token || typeof token !== "string") {
1718
+ res.writeHead(400, { ...CORS_HEADERS, "Content-Type": "application/json" });
1719
+ res.end(JSON.stringify({ error: "missing_token" }));
1720
+ setTimeout(() => {
1721
+ server.close();
1722
+ reject(new Error("Authentication failed: missing token"));
1723
+ }, 200);
1724
+ return;
1725
+ }
1726
+ const authData = {
1727
+ access_token: token,
1728
+ refresh_token: typeof parsed.refresh_token === "string" ? parsed.refresh_token : "",
1729
+ user_id: typeof parsed.user_id === "string" ? parsed.user_id : void 0,
1730
+ email: typeof parsed.email === "string" ? parsed.email : void 0,
1731
+ org_id: typeof parsed.org_id === "string" ? parsed.org_id : void 0,
1732
+ state: typeof parsed.state === "string" ? parsed.state : void 0
1733
+ };
1734
+ res.writeHead(200, { ...CORS_HEADERS, "Content-Type": "application/json" });
1735
+ res.end(JSON.stringify({ ok: true }));
1736
+ setTimeout(() => {
1737
+ server.close();
1738
+ resolve2(authData);
1739
+ }, 200);
1740
+ });
1741
+ req.on("error", (e) => {
1742
+ if (aborted) return;
1743
+ aborted = true;
1744
+ try {
1745
+ res.writeHead(500, CORS_HEADERS);
1746
+ res.end();
1747
+ } catch {
1748
+ }
1749
+ setTimeout(() => {
1750
+ server.close();
1751
+ reject(e);
1752
+ }, 200);
1753
+ });
1639
1754
  });
1640
1755
  server.listen(PORT);
1641
1756
  server.on("error", (error) => {
@@ -1713,80 +1828,13 @@ function clearCredentials() {
1713
1828
  unlinkSync2(AUTH_FILE);
1714
1829
  }
1715
1830
  }
1716
- var PORT, SYNKRO_WEB_AUTH_URL, AUTH_FILE, SUCCESS_HTML, ERROR_HTML;
1831
+ var PORT, SYNKRO_WEB_AUTH_URL, AUTH_FILE, ERROR_HTML;
1717
1832
  var init_stub = __esm({
1718
1833
  "cli/auth/stub.ts"() {
1719
1834
  "use strict";
1720
1835
  PORT = 8100;
1721
- SYNKRO_WEB_AUTH_URL = process.env.SYNKRO_WEB_AUTH_URL || "http://localhost:4322";
1836
+ SYNKRO_WEB_AUTH_URL = process.env.SYNKRO_WEB_AUTH_URL || "https://app.synkro.sh";
1722
1837
  AUTH_FILE = process.env.SYNKRO_AUTH_FILE || join3(homedir3(), ".synkro", "credentials.json");
1723
- SUCCESS_HTML = `
1724
- <!DOCTYPE html>
1725
- <html>
1726
- <head>
1727
- <title>Authentication Successful - Synkro CLI</title>
1728
- <style>
1729
- body {
1730
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
1731
- display: flex;
1732
- justify-content: center;
1733
- align-items: center;
1734
- height: 100vh;
1735
- margin: 0;
1736
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
1737
- }
1738
- .container {
1739
- background: white;
1740
- padding: 3rem;
1741
- border-radius: 1rem;
1742
- box-shadow: 0 20px 60px rgba(0,0,0,0.3);
1743
- text-align: center;
1744
- max-width: 400px;
1745
- }
1746
- .checkmark {
1747
- width: 80px;
1748
- height: 80px;
1749
- border-radius: 50%;
1750
- background: #10b981;
1751
- margin: 0 auto 1.5rem;
1752
- display: flex;
1753
- align-items: center;
1754
- justify-content: center;
1755
- }
1756
- .checkmark svg {
1757
- width: 50px;
1758
- height: 50px;
1759
- stroke: white;
1760
- }
1761
- h1 {
1762
- color: #1f2937;
1763
- margin: 0 0 0.5rem;
1764
- }
1765
- p {
1766
- color: #6b7280;
1767
- margin: 0;
1768
- }
1769
- .close-note {
1770
- margin-top: 1.5rem;
1771
- font-size: 0.875rem;
1772
- color: #9ca3af;
1773
- }
1774
- </style>
1775
- </head>
1776
- <body>
1777
- <div class="container">
1778
- <div class="checkmark">
1779
- <svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
1780
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="3" d="M5 13l4 4L19 7"></path>
1781
- </svg>
1782
- </div>
1783
- <h1>Authentication Successful!</h1>
1784
- <p>Your Synkro CLI has been authenticated.</p>
1785
- <p class="close-note">You can close this window and return to your terminal.</p>
1786
- </div>
1787
- </body>
1788
- </html>
1789
- `;
1790
1838
  ERROR_HTML = `
1791
1839
  <!DOCTYPE html>
1792
1840
  <html>
@@ -1865,22 +1913,26 @@ function writeGraderDaemon() {
1865
1913
  }
1866
1914
  function writeHookScripts() {
1867
1915
  const bashScriptPath = join4(HOOKS_DIR, "cc-bash-judge.sh");
1916
+ const bashFollowupScriptPath = join4(HOOKS_DIR, "cc-bash-followup.sh");
1868
1917
  const editCaptureScriptPath = join4(HOOKS_DIR, "cc-edit-capture.sh");
1869
1918
  const editPrecheckScriptPath = join4(HOOKS_DIR, "cc-edit-precheck.sh");
1870
1919
  const stopSummaryScriptPath = join4(HOOKS_DIR, "cc-stop-summary.sh");
1871
1920
  const sessionStartScriptPath = join4(HOOKS_DIR, "cc-session-start.sh");
1872
1921
  writeFileSync4(bashScriptPath, CC_BASH_JUDGE_SCRIPT, "utf-8");
1922
+ writeFileSync4(bashFollowupScriptPath, CC_BASH_FOLLOWUP_SCRIPT, "utf-8");
1873
1923
  writeFileSync4(editCaptureScriptPath, CC_EDIT_CAPTURE_SCRIPT, "utf-8");
1874
1924
  writeFileSync4(editPrecheckScriptPath, CC_EDIT_PRECHECK_SCRIPT, "utf-8");
1875
1925
  writeFileSync4(stopSummaryScriptPath, CC_STOP_SUMMARY_SCRIPT, "utf-8");
1876
1926
  writeFileSync4(sessionStartScriptPath, CC_SESSION_START_SCRIPT, "utf-8");
1877
1927
  chmodSync(bashScriptPath, 493);
1928
+ chmodSync(bashFollowupScriptPath, 493);
1878
1929
  chmodSync(editCaptureScriptPath, 493);
1879
1930
  chmodSync(editPrecheckScriptPath, 493);
1880
1931
  chmodSync(stopSummaryScriptPath, 493);
1881
1932
  chmodSync(sessionStartScriptPath, 493);
1882
1933
  return {
1883
1934
  bashScript: bashScriptPath,
1935
+ bashFollowupScript: bashFollowupScriptPath,
1884
1936
  editCaptureScript: editCaptureScriptPath,
1885
1937
  editPrecheckScript: editPrecheckScriptPath,
1886
1938
  stopSummaryScript: stopSummaryScriptPath,
@@ -1910,7 +1962,7 @@ function writeConfigEnv(opts) {
1910
1962
  `SYNKRO_GATEWAY_URL=${shellQuoteSingle(safeGateway)}`,
1911
1963
  `SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle(credsPath)}`,
1912
1964
  `SYNKRO_TIER=${shellQuoteSingle(safeTier)}`,
1913
- `SYNKRO_VERSION='1.0.0'`
1965
+ `SYNKRO_VERSION=${shellQuoteSingle("1.0.4")}`
1914
1966
  ];
1915
1967
  if (safeUserId) lines.push(`SYNKRO_USER_ID=${shellQuoteSingle(safeUserId)}`);
1916
1968
  if (safeOrgId) lines.push(`SYNKRO_ORG_ID=${shellQuoteSingle(safeOrgId)}`);
@@ -1942,7 +1994,7 @@ function assertGatewayAllowed(gatewayUrl) {
1942
1994
  }
1943
1995
  }
1944
1996
  async function installCommand(opts = {}) {
1945
- const gatewayUrl = opts.gatewayUrl || process.env.SYNKRO_GATEWAY_URL || "http://localhost:8788";
1997
+ const gatewayUrl = opts.gatewayUrl || process.env.SYNKRO_GATEWAY_URL || "https://api.synkro.sh";
1946
1998
  try {
1947
1999
  assertGatewayAllowed(gatewayUrl);
1948
2000
  } catch (err) {
@@ -1972,7 +2024,7 @@ async function installCommand(opts = {}) {
1972
2024
  }
1973
2025
  });
1974
2026
  if (!result) {
1975
- console.error("Authentication failed. Make sure the Synkro web app is running on http://localhost:4322 (or set SYNKRO_WEB_AUTH_URL).");
2027
+ console.error("Authentication failed. If you are running a self-hosted dashboard, set SYNKRO_WEB_AUTH_URL to its origin.");
1976
2028
  process.exit(1);
1977
2029
  }
1978
2030
  }
@@ -1995,6 +2047,7 @@ async function installCommand(opts = {}) {
1995
2047
  const scripts = writeHookScripts();
1996
2048
  console.log("Wrote hook scripts:");
1997
2049
  console.log(` ${scripts.bashScript}`);
2050
+ console.log(` ${scripts.bashFollowupScript}`);
1998
2051
  console.log(` ${scripts.editCaptureScript}`);
1999
2052
  console.log(` ${scripts.editPrecheckScript}`);
2000
2053
  console.log(` ${scripts.stopSummaryScript}`);
@@ -2012,6 +2065,7 @@ async function installCommand(opts = {}) {
2012
2065
  hasClaudeCode = true;
2013
2066
  installCCHooks(agent.settingsPath, {
2014
2067
  bashJudgeScriptPath: scripts.bashScript,
2068
+ bashFollowupScriptPath: scripts.bashFollowupScript,
2015
2069
  editCaptureScriptPath: scripts.editCaptureScript,
2016
2070
  editPrecheckScriptPath: scripts.editPrecheckScript,
2017
2071
  stopSummaryScriptPath: scripts.stopSummaryScript,
@@ -2233,11 +2287,15 @@ function statusCommand() {
2233
2287
  }
2234
2288
  console.log();
2235
2289
  const bashScript = join5(SYNKRO_DIR2, "hooks", "cc-bash-judge.sh");
2290
+ const bashFollowupScript = join5(SYNKRO_DIR2, "hooks", "cc-bash-followup.sh");
2291
+ const editPrecheckScript = join5(SYNKRO_DIR2, "hooks", "cc-edit-precheck.sh");
2236
2292
  const editCaptureScript = join5(SYNKRO_DIR2, "hooks", "cc-edit-capture.sh");
2237
2293
  const stopSummaryScript = join5(SYNKRO_DIR2, "hooks", "cc-stop-summary.sh");
2238
2294
  const sessionStartScript = join5(SYNKRO_DIR2, "hooks", "cc-session-start.sh");
2239
2295
  console.log("Hook scripts:");
2240
2296
  console.log(` ${existsSync6(bashScript) ? "\u2713" : "\u2717"} ${bashScript}`);
2297
+ console.log(` ${existsSync6(bashFollowupScript) ? "\u2713" : "\u2717"} ${bashFollowupScript}`);
2298
+ console.log(` ${existsSync6(editPrecheckScript) ? "\u2713" : "\u2717"} ${editPrecheckScript}`);
2241
2299
  console.log(` ${existsSync6(editCaptureScript) ? "\u2713" : "\u2717"} ${editCaptureScript}`);
2242
2300
  console.log(` ${existsSync6(stopSummaryScript) ? "\u2713" : "\u2717"} ${stopSummaryScript}`);
2243
2301
  console.log(` ${existsSync6(sessionStartScript) ? "\u2713" : "\u2717"} ${sessionStartScript}`);