@synkro-sh/cli 1.3.18 → 1.3.20

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
@@ -2652,7 +2652,9 @@ import { createInterface } from "readline";
2652
2652
  function detectGitRepo() {
2653
2653
  try {
2654
2654
  const remoteUrl = execSync2("git remote get-url origin", { encoding: "utf-8", timeout: 5e3 }).trim();
2655
- const match = remoteUrl.match(/(?:github\.com|gitlab\.com|bitbucket\.org)[:/](.+?)(?:\.git)?$/);
2655
+ const sshMatch = remoteUrl.match(/^git@[^:]+:(.+?)(?:\.git)?$/);
2656
+ const httpMatch = remoteUrl.match(/^https?:\/\/[^/]+\/(.+?)(?:\.git)?$/);
2657
+ const match = sshMatch || httpMatch;
2656
2658
  if (!match) return null;
2657
2659
  const fullName = match[1];
2658
2660
  return { fullName, shortName: fullName.split("/").pop() || fullName };
@@ -2721,9 +2723,12 @@ function waitForGithubToken() {
2721
2723
  function openBrowser2(url) {
2722
2724
  const { execFile: execFile2 } = __require("child_process");
2723
2725
  const plat = process.platform;
2724
- if (plat === "darwin") execFile2("open", [url]);
2725
- else if (plat === "win32") execFile2("cmd", ["/c", "start", "", url]);
2726
- else execFile2("xdg-open", [url]);
2726
+ const cb = (err) => {
2727
+ if (err) console.log(` Open this URL manually: ${url}`);
2728
+ };
2729
+ if (plat === "darwin") execFile2("open", [url], cb);
2730
+ else if (plat === "win32") execFile2("cmd", ["/c", "start", "", url], cb);
2731
+ else execFile2("xdg-open", [url], cb);
2727
2732
  }
2728
2733
  async function connectGithubAndSelectRepos() {
2729
2734
  const url = `${SYNKRO_WEB_AUTH_URL2}/cli-github?port=${GITHUB_PORT}`;
@@ -3224,6 +3229,7 @@ function writeConfigEnv(opts) {
3224
3229
  const safeOrgId = sanitizeConfigValue(opts.orgId);
3225
3230
  const safeEmail = sanitizeConfigValue(opts.email);
3226
3231
  const safeTier = sanitizeConfigValue(opts.tier ?? "pro", 32);
3232
+ const safeInference = sanitizeConfigValue(opts.inference ?? "fast", 16);
3227
3233
  const lines = [
3228
3234
  "# Synkro CLI config (managed by synkro install)",
3229
3235
  "# JWT auth \u2014 the hook scripts read SYNKRO_CREDENTIALS_PATH at runtime",
@@ -3231,7 +3237,8 @@ function writeConfigEnv(opts) {
3231
3237
  `SYNKRO_GATEWAY_URL=${shellQuoteSingle(safeGateway)}`,
3232
3238
  `SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle(credsPath)}`,
3233
3239
  `SYNKRO_TIER=${shellQuoteSingle(safeTier)}`,
3234
- `SYNKRO_VERSION=${shellQuoteSingle("1.3.18")}`
3240
+ `SYNKRO_INFERENCE=${shellQuoteSingle(safeInference)}`,
3241
+ `SYNKRO_VERSION=${shellQuoteSingle("1.3.20")}`
3235
3242
  ];
3236
3243
  if (safeUserId) lines.push(`SYNKRO_USER_ID=${shellQuoteSingle(safeUserId)}`);
3237
3244
  if (safeOrgId) lines.push(`SYNKRO_ORG_ID=${shellQuoteSingle(safeOrgId)}`);
@@ -3243,6 +3250,42 @@ function writeConfigEnv(opts) {
3243
3250
  writeFileSync5(CONFIG_PATH2, lines.join("\n"), "utf-8");
3244
3251
  chmodSync(CONFIG_PATH2, 384);
3245
3252
  }
3253
+ function collectLocalMetadata() {
3254
+ const meta = { platform: process.platform };
3255
+ try {
3256
+ meta.display_name = execSync4("git config user.name", { encoding: "utf-8", timeout: 3e3 }).trim();
3257
+ } catch {
3258
+ }
3259
+ try {
3260
+ const remote = execSync4("git remote get-url origin", { encoding: "utf-8", timeout: 3e3 }).trim();
3261
+ const m = remote.match(/(?:github\.com)[:/](.+?)(?:\.git)?$/);
3262
+ if (m) meta.active_repo = m[1];
3263
+ } catch {
3264
+ }
3265
+ return meta;
3266
+ }
3267
+ async function fetchUserProfile(gatewayUrl, token) {
3268
+ try {
3269
+ const resp = await fetch(`${gatewayUrl}/api/v1/cli/me`, {
3270
+ headers: { "Authorization": `Bearer ${token}` }
3271
+ });
3272
+ if (!resp.ok) return { tier: "pro", inference: "fast" };
3273
+ const data = await resp.json();
3274
+ const meta = collectLocalMetadata();
3275
+ fetch(`${gatewayUrl}/api/v1/cli/me`, {
3276
+ method: "PATCH",
3277
+ headers: { "Authorization": `Bearer ${token}`, "Content-Type": "application/json" },
3278
+ body: JSON.stringify(meta)
3279
+ }).catch(() => {
3280
+ });
3281
+ return {
3282
+ tier: data.plan_tier ?? "pro",
3283
+ inference: data.fast_inference ? "fast" : "standard"
3284
+ };
3285
+ } catch {
3286
+ return { tier: "pro", inference: "fast" };
3287
+ }
3288
+ }
3246
3289
  function assertGatewayAllowed(gatewayUrl) {
3247
3290
  let parsed;
3248
3291
  try {
@@ -3459,8 +3502,10 @@ async function installCommand(opts = {}) {
3459
3502
  email = info.email;
3460
3503
  } catch {
3461
3504
  }
3462
- writeConfigEnv({ gatewayUrl, userId, orgId, email, transcriptConsent });
3463
- console.log(`Wrote config to ${CONFIG_PATH2}
3505
+ const profile = await fetchUserProfile(gatewayUrl, token);
3506
+ writeConfigEnv({ gatewayUrl, userId, orgId, email, tier: profile.tier, inference: profile.inference, transcriptConsent });
3507
+ console.log(`Wrote config to ${CONFIG_PATH2}`);
3508
+ console.log(` inference: ${profile.inference} (server-side grading)
3464
3509
  `);
3465
3510
  if (transcriptConsent) {
3466
3511
  try {
@@ -3506,7 +3551,9 @@ async function installCommand(opts = {}) {
3506
3551
  function detectGitRepo2() {
3507
3552
  try {
3508
3553
  const remoteUrl = execSync4("git remote get-url origin", { encoding: "utf-8", timeout: 5e3 }).trim();
3509
- const match = remoteUrl.match(/(?:github\.com|gitlab\.com|bitbucket\.org)[:/](.+?)(?:\.git)?$/);
3554
+ const sshMatch = remoteUrl.match(/^git@[^:]+:(.+?)(?:\.git)?$/);
3555
+ const httpMatch = remoteUrl.match(/^https?:\/\/[^/]+\/(.+?)(?:\.git)?$/);
3556
+ const match = sshMatch || httpMatch;
3510
3557
  return match ? match[1] : null;
3511
3558
  } catch {
3512
3559
  return null;
@@ -3806,7 +3853,7 @@ function readConfigEnv() {
3806
3853
  }
3807
3854
  return out;
3808
3855
  }
3809
- function statusCommand() {
3856
+ async function statusCommand() {
3810
3857
  console.log("Synkro CLI status\n");
3811
3858
  if (isAuthenticated()) {
3812
3859
  const info = getUserInfo();
@@ -3818,19 +3865,30 @@ function statusCommand() {
3818
3865
  }
3819
3866
  console.log();
3820
3867
  const config = readConfigEnv();
3868
+ const gatewayUrl = (config.SYNKRO_GATEWAY_URL || "https://api.synkro.sh").replace(/^['"]|['"]$/g, "");
3869
+ let serverTier = config.SYNKRO_TIER || "(unset)";
3870
+ let serverInference = config.SYNKRO_INFERENCE || "fast";
3871
+ const token = getAccessToken();
3872
+ if (token) {
3873
+ try {
3874
+ const resp = await fetch(`${gatewayUrl}/api/v1/cli/me`, {
3875
+ headers: { "Authorization": `Bearer ${token}` },
3876
+ signal: AbortSignal.timeout(5e3)
3877
+ });
3878
+ if (resp.ok) {
3879
+ const data = await resp.json();
3880
+ serverInference = data.fast_inference ? "fast" : "standard";
3881
+ serverTier = data.plan_tier ?? data.tier ?? serverTier;
3882
+ }
3883
+ } catch {
3884
+ }
3885
+ }
3821
3886
  console.log("Config:");
3822
- console.log(` gateway: ${config.SYNKRO_GATEWAY_URL ?? "(unset)"}`);
3887
+ console.log(` gateway: ${gatewayUrl}`);
3823
3888
  console.log(` credentials: ${config.SYNKRO_CREDENTIALS_PATH ?? "(unset)"}`);
3824
- console.log(` tier: ${config.SYNKRO_TIER ?? "(unset)"}`);
3825
- const info2 = getUserInfo();
3826
- const userId = info2?.id ?? config.SYNKRO_USER_ID ?? "default";
3827
- const tierCacheFile = join7(SYNKRO_DIR3, `.tier-cache-${userId}`);
3828
- let inferenceTier = config.SYNKRO_INFERENCE_TIER || null;
3829
- if (!inferenceTier && existsSync8(tierCacheFile)) {
3830
- inferenceTier = readFileSync6(tierCacheFile, "utf-8").trim() || null;
3831
- }
3832
- const tierLabel = inferenceTier === "fast" ? "'fast' (server-side grading)" : inferenceTier === "free" ? "'free' (local daemon grading)" : "(unknown \u2014 fires on next hook)";
3833
- console.log(` inference: ${tierLabel}`);
3889
+ console.log(` tier: ${serverTier}`);
3890
+ const inferenceLabel = serverInference === "fast" ? "'fast' (server-side grading)" : "'standard' (local grading)";
3891
+ console.log(` inference: ${inferenceLabel}`);
3834
3892
  console.log(` version: ${config.SYNKRO_VERSION ?? "(unset)"}`);
3835
3893
  console.log();
3836
3894
  const agents = detectAgents();
@@ -3971,6 +4029,102 @@ var init_unlink = __esm({
3971
4029
  }
3972
4030
  });
3973
4031
 
4032
+ // cli/commands/config.ts
4033
+ var config_exports = {};
4034
+ __export(config_exports, {
4035
+ configCommand: () => configCommand
4036
+ });
4037
+ import { readFileSync as readFileSync7, writeFileSync as writeFileSync6, existsSync as existsSync9 } from "fs";
4038
+ import { join as join8 } from "path";
4039
+ import { homedir as homedir7 } from "os";
4040
+ function readConfigEnv2() {
4041
+ if (!existsSync9(CONFIG_PATH4)) return {};
4042
+ const out = {};
4043
+ for (const line of readFileSync7(CONFIG_PATH4, "utf-8").split("\n")) {
4044
+ const t = line.trim();
4045
+ if (!t || t.startsWith("#")) continue;
4046
+ const eq = t.indexOf("=");
4047
+ if (eq > 0) out[t.slice(0, eq).trim()] = t.slice(eq + 1).trim().replace(/^['"]|['"]$/g, "");
4048
+ }
4049
+ return out;
4050
+ }
4051
+ function updateConfigValue(key, value) {
4052
+ if (!existsSync9(CONFIG_PATH4)) {
4053
+ console.error("No config found. Run `synkro install` first.");
4054
+ process.exit(1);
4055
+ }
4056
+ const lines = readFileSync7(CONFIG_PATH4, "utf-8").split("\n");
4057
+ const pattern = new RegExp(`^${key}=`);
4058
+ let found = false;
4059
+ const updated = lines.map((line) => {
4060
+ if (pattern.test(line.trim())) {
4061
+ found = true;
4062
+ return `${key}='${value}'`;
4063
+ }
4064
+ return line;
4065
+ });
4066
+ if (!found) updated.splice(updated.length - 1, 0, `${key}='${value}'`);
4067
+ writeFileSync6(CONFIG_PATH4, updated.join("\n"), "utf-8");
4068
+ }
4069
+ async function configCommand(args2) {
4070
+ if (args2.length === 0) {
4071
+ const config2 = readConfigEnv2();
4072
+ console.log("Synkro config:\n");
4073
+ console.log(` inference: ${config2.SYNKRO_INFERENCE || "fast"}`);
4074
+ console.log(` tier: ${config2.SYNKRO_TIER || "pro"}`);
4075
+ console.log(` gateway: ${config2.SYNKRO_GATEWAY_URL || "https://api.synkro.sh"}`);
4076
+ console.log(` version: ${config2.SYNKRO_VERSION || "?"}`);
4077
+ console.log(`
4078
+ To change: synkro config --inference fast|standard`);
4079
+ return;
4080
+ }
4081
+ let inferenceValue;
4082
+ for (const a of args2) {
4083
+ if (a.startsWith("--inference=")) inferenceValue = a.slice("--inference=".length);
4084
+ else if (a === "--inference" && args2.indexOf(a) + 1 < args2.length) inferenceValue = args2[args2.indexOf(a) + 1];
4085
+ }
4086
+ if (!inferenceValue || !["fast", "standard"].includes(inferenceValue)) {
4087
+ console.error("Usage: synkro config --inference fast|standard");
4088
+ process.exit(1);
4089
+ }
4090
+ if (!isAuthenticated()) {
4091
+ console.error("Not authenticated. Run `synkro login` first.");
4092
+ process.exit(1);
4093
+ }
4094
+ const token = getAccessToken();
4095
+ const config = readConfigEnv2();
4096
+ const gatewayUrl = (config.SYNKRO_GATEWAY_URL || "https://api.synkro.sh").replace(/\/$/, "");
4097
+ try {
4098
+ const resp = await fetch(`${gatewayUrl}/api/v1/cli/me`, {
4099
+ method: "PATCH",
4100
+ headers: {
4101
+ "Authorization": `Bearer ${token}`,
4102
+ "Content-Type": "application/json"
4103
+ },
4104
+ body: JSON.stringify({ fast_inference: inferenceValue === "fast" })
4105
+ });
4106
+ if (!resp.ok) {
4107
+ const errText = await resp.text().catch(() => "");
4108
+ console.error(`Failed to update: ${resp.status} ${errText.slice(0, 200)}`);
4109
+ process.exit(1);
4110
+ }
4111
+ } catch (err) {
4112
+ console.error(`Failed to reach server: ${err.message}`);
4113
+ process.exit(1);
4114
+ }
4115
+ updateConfigValue("SYNKRO_INFERENCE", inferenceValue);
4116
+ console.log(`\u2713 Inference set to '${inferenceValue}'.`);
4117
+ }
4118
+ var SYNKRO_DIR4, CONFIG_PATH4;
4119
+ var init_config = __esm({
4120
+ "cli/commands/config.ts"() {
4121
+ "use strict";
4122
+ init_stub();
4123
+ SYNKRO_DIR4 = join8(homedir7(), ".synkro");
4124
+ CONFIG_PATH4 = join8(SYNKRO_DIR4, "config.env");
4125
+ }
4126
+ });
4127
+
3974
4128
  // cli/commands/scanPr.ts
3975
4129
  var scanPr_exports = {};
3976
4130
  __export(scanPr_exports, {
@@ -4563,9 +4717,9 @@ var disconnect_exports = {};
4563
4717
  __export(disconnect_exports, {
4564
4718
  disconnectCommand: () => disconnectCommand
4565
4719
  });
4566
- import { existsSync as existsSync9, rmSync } from "fs";
4567
- import { homedir as homedir7 } from "os";
4568
- import { join as join8 } from "path";
4720
+ import { existsSync as existsSync10, rmSync } from "fs";
4721
+ import { homedir as homedir8 } from "os";
4722
+ import { join as join9 } from "path";
4569
4723
  function disconnectCommand(args2 = []) {
4570
4724
  const purge = args2.includes("--purge");
4571
4725
  console.log("Synkro disconnect starting...\n");
@@ -4583,25 +4737,25 @@ function disconnectCommand(args2 = []) {
4583
4737
  console.log(`${mcpRemoved ? "\u2713" : "\xB7"} MCP guardrails server: ${mcpRemoved ? "removed entry from ~/.claude.json" : "no Synkro MCP entry found"}`);
4584
4738
  }
4585
4739
  if (purge) {
4586
- if (existsSync9(SYNKRO_DIR4)) {
4587
- rmSync(SYNKRO_DIR4, { recursive: true, force: true });
4588
- console.log(`\u2713 Removed ${SYNKRO_DIR4}`);
4740
+ if (existsSync10(SYNKRO_DIR5)) {
4741
+ rmSync(SYNKRO_DIR5, { recursive: true, force: true });
4742
+ console.log(`\u2713 Removed ${SYNKRO_DIR5}`);
4589
4743
  } else {
4590
- console.log(`\xB7 ${SYNKRO_DIR4} already gone, nothing to remove`);
4744
+ console.log(`\xB7 ${SYNKRO_DIR5} already gone, nothing to remove`);
4591
4745
  }
4592
- } else if (existsSync9(SYNKRO_DIR4)) {
4593
- console.log(`Config preserved at ${SYNKRO_DIR4}. Run with --purge to remove.`);
4746
+ } else if (existsSync10(SYNKRO_DIR5)) {
4747
+ console.log(`Config preserved at ${SYNKRO_DIR5}. Run with --purge to remove.`);
4594
4748
  }
4595
4749
  console.log("\nSynkro disconnected.");
4596
4750
  }
4597
- var SYNKRO_DIR4;
4751
+ var SYNKRO_DIR5;
4598
4752
  var init_disconnect = __esm({
4599
4753
  "cli/commands/disconnect.ts"() {
4600
4754
  "use strict";
4601
4755
  init_agentDetect();
4602
4756
  init_ccHookConfig();
4603
4757
  init_mcpConfig();
4604
- SYNKRO_DIR4 = join8(homedir7(), ".synkro");
4758
+ SYNKRO_DIR5 = join9(homedir8(), ".synkro");
4605
4759
  }
4606
4760
  });
4607
4761
 
@@ -4643,15 +4797,15 @@ var init_reinstall = __esm({
4643
4797
  });
4644
4798
 
4645
4799
  // cli/bootstrap.js
4646
- import { readFileSync as readFileSync7, existsSync as existsSync10 } from "fs";
4800
+ import { readFileSync as readFileSync8, existsSync as existsSync11 } from "fs";
4647
4801
  import { resolve } from "path";
4648
4802
  var envCandidates = [
4649
4803
  resolve(process.cwd(), ".env"),
4650
4804
  resolve(process.env.HOME ?? "", ".synkro", "config.env")
4651
4805
  ];
4652
4806
  for (const envPath of envCandidates) {
4653
- if (!existsSync10(envPath)) continue;
4654
- const envContent = readFileSync7(envPath, "utf-8");
4807
+ if (!existsSync11(envPath)) continue;
4808
+ const envContent = readFileSync8(envPath, "utf-8");
4655
4809
  for (const line of envContent.split("\n")) {
4656
4810
  const trimmed = line.trim();
4657
4811
  if (!trimmed || trimmed.startsWith("#")) continue;
@@ -4679,6 +4833,7 @@ Commands:
4679
4833
  status Show current setup state
4680
4834
  link Link repos to a Synkro project (local git or GitHub OAuth)
4681
4835
  unlink Remove repo links from Synkro projects
4836
+ config View or change settings (e.g. --inference fast|standard)
4682
4837
  setup-github Configure GitHub PR scanning (push secrets + workflow file)
4683
4838
  update Refresh hook configs and judge prompts
4684
4839
  disconnect [--purge] Remove Synkro hooks from agents (--purge also removes ~/.synkro)
@@ -4713,7 +4868,7 @@ async function main() {
4713
4868
  }
4714
4869
  case "status": {
4715
4870
  const { statusCommand: statusCommand2 } = await Promise.resolve().then(() => (init_status(), status_exports));
4716
- statusCommand2();
4871
+ await statusCommand2();
4717
4872
  break;
4718
4873
  }
4719
4874
  case "link": {
@@ -4726,6 +4881,11 @@ async function main() {
4726
4881
  await unlinkCommand2();
4727
4882
  break;
4728
4883
  }
4884
+ case "config": {
4885
+ const { configCommand: configCommand2 } = await Promise.resolve().then(() => (init_config(), config_exports));
4886
+ await configCommand2(subArgs);
4887
+ break;
4888
+ }
4729
4889
  case "setup-github": {
4730
4890
  const { setupGithubCommand: setupGithubCommand2 } = await Promise.resolve().then(() => (init_setupGithub(), setupGithub_exports));
4731
4891
  const ghOpts = {};