@synkro-sh/cli 1.6.9 → 1.6.10

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
@@ -3984,23 +3984,13 @@ async function main() {
3984
3984
  }).catch(() => {});
3985
3985
  }
3986
3986
 
3987
- if (process.env.SYNKRO_TRANSCRIPT_CONSENT === 'no') { outputEmpty(); return; }
3988
-
3987
+ // Transcript consent gates only CLOUD transmission. Local persistence \u2014
3988
+ // this machine's own PGLite \u2014 is the same category as the local telemetry
3989
+ // already captured for every command, so it always runs.
3990
+ const cloudConsent = process.env.SYNKRO_TRANSCRIPT_CONSENT !== 'no';
3989
3991
  const gitRepo = detectRepo(cwd);
3990
- if (!gitRepo) { outputEmpty(); return; }
3991
-
3992
- let captureDepth = 'local_only';
3993
- try {
3994
- const r = await fetch(GATEWAY_URL + '/api/v1/hook/config', {
3995
- headers: { Authorization: 'Bearer ' + jwt },
3996
- signal: AbortSignal.timeout(3000),
3997
- });
3998
- const data = await r.json() as any;
3999
- captureDepth = data.capture_depth || 'local_only';
4000
- } catch {}
4001
-
4002
- if (captureDepth === 'local_only') { outputEmpty(); return; }
4003
3992
 
3993
+ // Offset-tracked extraction of new user/assistant turns from the transcript.
4004
3994
  const offsetDir = join(homedir(), '.synkro', '.transcript-offsets');
4005
3995
  mkdirSync(offsetDir, { recursive: true });
4006
3996
  const offsetFile = join(offsetDir, sessionId);
@@ -4035,7 +4025,7 @@ async function main() {
4035
4025
  }).join(' ').slice(0, 8000);
4036
4026
  }
4037
4027
 
4038
- const msg: any = { message_index: i, type: entry.type, content: text };
4028
+ const msg: any = { message_index: i, type: entry.type, content: text, ts: entry.timestamp || null };
4039
4029
  if (entry.type === 'assistant') {
4040
4030
  const toolCalls = (Array.isArray(content) ? content : [])
4041
4031
  .filter((c: any) => c?.type === 'tool_use')
@@ -4053,16 +4043,39 @@ async function main() {
4053
4043
 
4054
4044
  if (messages.length === 0) { outputEmpty(); return; }
4055
4045
 
4056
- const syncBody = {
4057
- repo: gitRepo,
4058
- sessions: [{ cc_session_id: sessionId, messages }],
4059
- };
4060
- fetch(GATEWAY_URL + '/api/v1/cli/sync-transcripts', {
4061
- method: 'POST',
4062
- headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + jwt },
4063
- body: JSON.stringify(syncBody),
4064
- signal: AbortSignal.timeout(10000),
4065
- }).catch(() => {});
4046
+ // Local persist \u2014 always. The conversation timeline lives in PGLite.
4047
+ const mcpPort = process.env.SYNKRO_MCP_PORT || '18931';
4048
+ let mcpToken = '';
4049
+ try { mcpToken = readFileSync(join(homedir(), '.synkro', '.mcp-jwt'), 'utf-8').trim(); } catch {}
4050
+ if (mcpToken) {
4051
+ fetch('http://127.0.0.1:' + mcpPort + '/api/conversation-sync', {
4052
+ method: 'POST',
4053
+ headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + mcpToken },
4054
+ body: JSON.stringify({ session_id: sessionId, repo: gitRepo || '', messages }),
4055
+ signal: AbortSignal.timeout(5000),
4056
+ }).catch(() => {});
4057
+ }
4058
+
4059
+ // Cloud sync \u2014 only when consented and the org isn't local-only.
4060
+ if (cloudConsent && gitRepo) {
4061
+ let captureDepth = 'local_only';
4062
+ try {
4063
+ const r = await fetch(GATEWAY_URL + '/api/v1/hook/config', {
4064
+ headers: { Authorization: 'Bearer ' + jwt },
4065
+ signal: AbortSignal.timeout(3000),
4066
+ });
4067
+ const data = await r.json() as any;
4068
+ captureDepth = data.capture_depth || 'local_only';
4069
+ } catch {}
4070
+ if (captureDepth !== 'local_only') {
4071
+ fetch(GATEWAY_URL + '/api/v1/cli/sync-transcripts', {
4072
+ method: 'POST',
4073
+ headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + jwt },
4074
+ body: JSON.stringify({ repo: gitRepo, sessions: [{ cc_session_id: sessionId, messages }] }),
4075
+ signal: AbortSignal.timeout(10000),
4076
+ }).catch(() => {});
4077
+ }
4078
+ }
4066
4079
 
4067
4080
  outputEmpty();
4068
4081
  } catch {
@@ -6049,6 +6062,12 @@ async function dockerInstall(opts = {}) {
6049
6062
  ...process.env.SYNKRO_CURSOR_MODEL ? ["-e", `SYNKRO_CURSOR_MODEL=${process.env.SYNKRO_CURSOR_MODEL}`] : [],
6050
6063
  // Connected repo — the server seeds the local app + ruleset named after it.
6051
6064
  ...opts.connectedRepo ? ["-e", `SYNKRO_CONNECTED_REPO=${opts.connectedRepo}`] : [],
6065
+ // Real account identity (from config.env via process.env) — telemetry is
6066
+ // stamped with these instead of a `local-user` placeholder, so the data is
6067
+ // correctly attributed from the first graded command.
6068
+ ...process.env.SYNKRO_ORG_ID ? ["-e", `SYNKRO_ORG_ID=${process.env.SYNKRO_ORG_ID}`] : [],
6069
+ ...process.env.SYNKRO_USER_ID ? ["-e", `SYNKRO_USER_ID=${process.env.SYNKRO_USER_ID}`] : [],
6070
+ ...process.env.SYNKRO_EMAIL ? ["-e", `SYNKRO_EMAIL=${process.env.SYNKRO_EMAIL}`] : [],
6052
6071
  image
6053
6072
  ];
6054
6073
  const run = spawnSync2("docker", args2, { encoding: "utf-8", stdio: "inherit", timeout: 6e4 });
@@ -6271,6 +6290,7 @@ var init_dockerInstall = __esm({
6271
6290
  // cli/commands/install.ts
6272
6291
  var install_exports = {};
6273
6292
  __export(install_exports, {
6293
+ detectGitRepo: () => detectGitRepo2,
6274
6294
  installCommand: () => installCommand,
6275
6295
  parseArgs: () => parseArgs
6276
6296
  });
@@ -6459,7 +6479,7 @@ function writeConfigEnv(opts) {
6459
6479
  `SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle(credsPath)}`,
6460
6480
  `SYNKRO_TIER=${shellQuoteSingle(safeTier)}`,
6461
6481
  `SYNKRO_INFERENCE=${shellQuoteSingle(safeInference)}`,
6462
- `SYNKRO_VERSION=${shellQuoteSingle("1.6.9")}`
6482
+ `SYNKRO_VERSION=${shellQuoteSingle("1.6.10")}`
6463
6483
  ];
6464
6484
  if (safeSynkroBin) lines.push(`SYNKRO_CLI_BIN=${shellQuoteSingle(safeSynkroBin)}`);
6465
6485
  if (safeUserId) lines.push(`SYNKRO_USER_ID=${shellQuoteSingle(safeUserId)}`);
@@ -6586,6 +6606,11 @@ function assertGatewayAllowed(gatewayUrl) {
6586
6606
  }
6587
6607
  }
6588
6608
  async function installCommand(opts = {}) {
6609
+ if (!detectGitRepo2()) {
6610
+ console.error("Synkro must be installed inside a git repository.");
6611
+ console.error(" `cd` into your project \u2014 a git repo with an `origin` remote \u2014 and re-run `synkro install`.");
6612
+ process.exit(1);
6613
+ }
6589
6614
  const gatewayUrl = opts.gatewayUrl || sanitizeGatewayCandidate(process.env.SYNKRO_GATEWAY_URL) || "https://api.synkro.sh";
6590
6615
  try {
6591
6616
  assertGatewayAllowed(gatewayUrl);
@@ -6925,15 +6950,19 @@ async function installCommand(opts = {}) {
6925
6950
  console.log("\u2713 Synkro installed.");
6926
6951
  }
6927
6952
  function detectGitRepo2() {
6928
- try {
6929
- const remoteUrl = execSync5("git remote get-url origin", { encoding: "utf-8", timeout: 5e3 }).trim();
6930
- const sshMatch = remoteUrl.match(/^git@[^:]+:(.+?)(?:\.git)?$/);
6931
- const httpMatch = remoteUrl.match(/^https?:\/\/[^/]+\/(.+?)(?:\.git)?$/);
6932
- const match = sshMatch || httpMatch;
6933
- return match ? match[1] : null;
6934
- } catch {
6935
- return null;
6953
+ const run = (cmd2) => {
6954
+ try {
6955
+ return execSync5(cmd2, { encoding: "utf-8", timeout: 5e3, stdio: ["pipe", "pipe", "pipe"] }).trim();
6956
+ } catch {
6957
+ return "";
6958
+ }
6959
+ };
6960
+ const remoteUrl = run("git remote get-url origin");
6961
+ if (remoteUrl) {
6962
+ return remoteUrl.replace(/^git@[^:]+:/, "").replace(/^https?:\/\/[^/]+\//, "").replace(/\.git$/, "");
6936
6963
  }
6964
+ const root = run("git rev-parse --show-toplevel");
6965
+ return root ? root.split("/").pop() || null : null;
6937
6966
  }
6938
6967
  function getClaudeProjectsFolder() {
6939
6968
  const cwd = process.cwd();
@@ -8861,6 +8890,9 @@ __export(lifecycle_exports, {
8861
8890
  stopCommand: () => stopCommand,
8862
8891
  updateCommand: () => updateCommand
8863
8892
  });
8893
+ function resolveConnectedRepo() {
8894
+ return detectGitRepo2() ?? readContainerConfig()?.connectedRepo ?? void 0;
8895
+ }
8864
8896
  async function stopCommand() {
8865
8897
  assertDockerAvailable();
8866
8898
  console.log("Synkro: stopping server\n");
@@ -8877,7 +8909,7 @@ async function startCommand(rest = []) {
8877
8909
  if (cfg.explicit) {
8878
8910
  console.log(`Synkro: starting server (${cfg.claudeWorkers} claude + ${cfg.cursorWorkers} cursor)
8879
8911
  `);
8880
- await dockerUpdate({ claudeWorkers: cfg.claudeWorkers, cursorWorkers: cfg.cursorWorkers });
8912
+ await dockerUpdate({ claudeWorkers: cfg.claudeWorkers, cursorWorkers: cfg.cursorWorkers, connectedRepo: resolveConnectedRepo() });
8881
8913
  const ready = await waitForContainerReady(6e4);
8882
8914
  if (!ready) {
8883
8915
  console.error("\n\u26A0 container did not pass /healthz within 60s");
@@ -8907,7 +8939,7 @@ async function updateCommand() {
8907
8939
  console.log("Synkro: updating to the latest container image");
8908
8940
  console.log(` preserving pool: ${claudeWorkers} claude + ${cursorWorkers} cursor worker(s)
8909
8941
  `);
8910
- await dockerUpdate({ claudeWorkers, cursorWorkers, connectedRepo: cfg.connectedRepo });
8942
+ await dockerUpdate({ claudeWorkers, cursorWorkers, connectedRepo: resolveConnectedRepo() });
8911
8943
  const ready = await waitForContainerReady(9e4);
8912
8944
  if (!ready) {
8913
8945
  console.error("\n\u26A0 container did not pass its health check within 90s \u2014 check: docker logs synkro-server");
@@ -8921,7 +8953,7 @@ async function restartCommand(rest = []) {
8921
8953
  if (cfg.explicit) {
8922
8954
  console.log(`Synkro: restarting server (${cfg.claudeWorkers} claude + ${cfg.cursorWorkers} cursor)
8923
8955
  `);
8924
- await dockerUpdate({ claudeWorkers: cfg.claudeWorkers, cursorWorkers: cfg.cursorWorkers });
8956
+ await dockerUpdate({ claudeWorkers: cfg.claudeWorkers, cursorWorkers: cfg.cursorWorkers, connectedRepo: resolveConnectedRepo() });
8925
8957
  const ready = await waitForContainerReady(6e4);
8926
8958
  if (!ready) {
8927
8959
  console.error("\n\u26A0 container did not pass /healthz within 60s");
@@ -8944,6 +8976,7 @@ var init_lifecycle = __esm({
8944
8976
  "cli/commands/lifecycle.ts"() {
8945
8977
  "use strict";
8946
8978
  init_dockerInstall();
8979
+ init_install();
8947
8980
  }
8948
8981
  });
8949
8982
 
@@ -8971,7 +9004,7 @@ var args = process.argv.slice(2);
8971
9004
  var cmd = args[0] || "";
8972
9005
  var subArgs = args.slice(1);
8973
9006
  function printVersion() {
8974
- console.log("1.6.9");
9007
+ console.log("1.6.10");
8975
9008
  }
8976
9009
  function printHelp2() {
8977
9010
  console.log(`Synkro CLI \u2014 runtime safety for AI coding agents