@synkro-sh/cli 1.3.13 → 1.3.14
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 +308 -595
- package/dist/bootstrap.js.map +1 -1
- package/package.json +3 -3
package/dist/bootstrap.js
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
var __defProp = Object.defineProperty;
|
|
3
3
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
5
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
6
|
+
}) : x)(function(x) {
|
|
7
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
8
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
9
|
+
});
|
|
4
10
|
var __esm = (fn, res) => function __init() {
|
|
5
11
|
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
6
12
|
};
|
|
@@ -2432,13 +2438,10 @@ var init_auth = __esm({
|
|
|
2432
2438
|
|
|
2433
2439
|
// cli/api/projects.ts
|
|
2434
2440
|
import { config } from "dotenv";
|
|
2435
|
-
function resolveApiUrl() {
|
|
2436
|
-
for (const v of [process.env.SYNKRO_CRUD_URL, process.env.SYNKRO_API_URL]) {
|
|
2437
|
-
if (v && /^https?:\/\//.test(v)) return v;
|
|
2438
|
-
}
|
|
2439
|
-
return "https://api.synkro.sh/api";
|
|
2440
|
-
}
|
|
2441
2441
|
async function callApi(method, endpoint, body) {
|
|
2442
|
+
if (!API_URL) {
|
|
2443
|
+
throw new Error("SYNKRO_CRUD_URL (or SYNKRO_API_URL) is not set. Add it to your .env file.");
|
|
2444
|
+
}
|
|
2442
2445
|
const url = `${API_URL}${endpoint}`;
|
|
2443
2446
|
const accessToken = getAccessToken();
|
|
2444
2447
|
const headers = {
|
|
@@ -2475,7 +2478,7 @@ var init_projects = __esm({
|
|
|
2475
2478
|
"use strict";
|
|
2476
2479
|
init_auth();
|
|
2477
2480
|
config({ quiet: true });
|
|
2478
|
-
API_URL =
|
|
2481
|
+
API_URL = process.env.SYNKRO_CRUD_URL || process.env.SYNKRO_API_URL;
|
|
2479
2482
|
}
|
|
2480
2483
|
});
|
|
2481
2484
|
|
|
@@ -2512,7 +2515,7 @@ jobs:
|
|
|
2512
2515
|
run: |
|
|
2513
2516
|
npm config set prefix ~/.npm-global
|
|
2514
2517
|
npm install -g @synkro-sh/cli @anthropic-ai/claude-code
|
|
2515
|
-
echo "
|
|
2518
|
+
echo "$HOME/.npm-global/bin" >> $GITHUB_PATH
|
|
2516
2519
|
|
|
2517
2520
|
- name: Run Synkro PR scan
|
|
2518
2521
|
run: synkro-cli scan-pr
|
|
@@ -2580,7 +2583,7 @@ async function listAccessibleRepos(opts) {
|
|
|
2580
2583
|
const repos = [];
|
|
2581
2584
|
let page = 1;
|
|
2582
2585
|
while (page <= 5) {
|
|
2583
|
-
const url = `https://api.github.com/user/repos?per_page=100&page=${page}&affiliation=owner,collaborator
|
|
2586
|
+
const url = `https://api.github.com/user/repos?per_page=100&page=${page}&affiliation=owner,collaborator`;
|
|
2584
2587
|
const resp = await fetch(url, {
|
|
2585
2588
|
headers: {
|
|
2586
2589
|
Authorization: `Bearer ${opts.token}`,
|
|
@@ -2603,9 +2606,7 @@ async function listAccessibleRepos(opts) {
|
|
|
2603
2606
|
}
|
|
2604
2607
|
async function pushSecretsToRepo(opts, owner, repo, secrets) {
|
|
2605
2608
|
const pubkey = await getRepoPublicKey(opts, owner, repo);
|
|
2606
|
-
|
|
2607
|
-
await putRepoSecret(opts, owner, repo, "CLAUDE_CODE_OAUTH_TOKEN", secrets.claudeCodeOauthToken, pubkey);
|
|
2608
|
-
}
|
|
2609
|
+
await putRepoSecret(opts, owner, repo, "CLAUDE_CODE_OAUTH_TOKEN", secrets.claudeCodeOauthToken, pubkey);
|
|
2609
2610
|
await putRepoSecret(opts, owner, repo, "SYNKRO_API_KEY", secrets.synkroApiKey, pubkey);
|
|
2610
2611
|
}
|
|
2611
2612
|
function writeWorkflowFile(repoRootPath) {
|
|
@@ -2639,7 +2640,7 @@ var init_githubSetup = __esm({
|
|
|
2639
2640
|
});
|
|
2640
2641
|
|
|
2641
2642
|
// cli/commands/repoConnect.ts
|
|
2642
|
-
import { execSync as execSync2
|
|
2643
|
+
import { execSync as execSync2 } from "child_process";
|
|
2643
2644
|
import { createServer as createServer2 } from "http";
|
|
2644
2645
|
import { createInterface } from "readline";
|
|
2645
2646
|
function detectGitRepo() {
|
|
@@ -2712,10 +2713,11 @@ function waitForGithubToken() {
|
|
|
2712
2713
|
});
|
|
2713
2714
|
}
|
|
2714
2715
|
function openBrowser2(url) {
|
|
2716
|
+
const { execFile: execFile2 } = __require("child_process");
|
|
2715
2717
|
const plat = process.platform;
|
|
2716
|
-
if (plat === "darwin")
|
|
2717
|
-
else if (plat === "win32")
|
|
2718
|
-
else
|
|
2718
|
+
if (plat === "darwin") execFile2("open", [url]);
|
|
2719
|
+
else if (plat === "win32") execFile2("cmd", ["/c", "start", "", url]);
|
|
2720
|
+
else execFile2("xdg-open", [url]);
|
|
2719
2721
|
}
|
|
2720
2722
|
async function connectGithubAndSelectRepos() {
|
|
2721
2723
|
const url = `${SYNKRO_WEB_AUTH_URL2}/cli-github?port=${GITHUB_PORT}`;
|
|
@@ -2748,30 +2750,8 @@ async function connectGithubAndSelectRepos() {
|
|
|
2748
2750
|
rl.close();
|
|
2749
2751
|
}
|
|
2750
2752
|
}
|
|
2751
|
-
async function promptRepoConnection(
|
|
2753
|
+
async function promptRepoConnection() {
|
|
2752
2754
|
const localRepo = detectGitRepo();
|
|
2753
|
-
if (opts?.linkRepo !== void 0) {
|
|
2754
|
-
if (opts.linkRepo && localRepo) {
|
|
2755
|
-
try {
|
|
2756
|
-
const existing = await listProjects();
|
|
2757
|
-
const alreadyLinked = existing.some(
|
|
2758
|
-
(p) => p.repos?.some((r) => r.full_name === localRepo.fullName)
|
|
2759
|
-
);
|
|
2760
|
-
if (!alreadyLinked) {
|
|
2761
|
-
await createProject(localRepo.shortName, [{ full_name: localRepo.fullName }]);
|
|
2762
|
-
console.log(` \u2713 Created project "${localRepo.shortName}" linked to ${localRepo.fullName}`);
|
|
2763
|
-
} else {
|
|
2764
|
-
console.log(` \u2713 ${localRepo.fullName} is already linked to a Synkro project.`);
|
|
2765
|
-
}
|
|
2766
|
-
} catch (err) {
|
|
2767
|
-
console.warn(` \u26A0 Could not link repo: ${err.message}`);
|
|
2768
|
-
}
|
|
2769
|
-
} else if (opts.linkRepo) {
|
|
2770
|
-
console.warn(" \u26A0 --link-repo specified but not in a git repo. Skipping.");
|
|
2771
|
-
}
|
|
2772
|
-
console.log();
|
|
2773
|
-
return;
|
|
2774
|
-
}
|
|
2775
2755
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
2776
2756
|
try {
|
|
2777
2757
|
console.log("Connect repos to Synkro:\n");
|
|
@@ -2849,330 +2829,16 @@ var init_repoConnect = __esm({
|
|
|
2849
2829
|
}
|
|
2850
2830
|
});
|
|
2851
2831
|
|
|
2852
|
-
// cli/commands/setupGithub.ts
|
|
2853
|
-
var setupGithub_exports = {};
|
|
2854
|
-
__export(setupGithub_exports, {
|
|
2855
|
-
setupGithubCommand: () => setupGithubCommand
|
|
2856
|
-
});
|
|
2857
|
-
import { createInterface as createInterface2 } from "readline/promises";
|
|
2858
|
-
import { stdin as input, stdout as output } from "process";
|
|
2859
|
-
import { existsSync as existsSync6, readFileSync as readFileSync4 } from "fs";
|
|
2860
|
-
import { homedir as homedir4 } from "os";
|
|
2861
|
-
import { join as join5 } from "path";
|
|
2862
|
-
function readConfig() {
|
|
2863
|
-
if (!existsSync6(CONFIG_PATH)) return {};
|
|
2864
|
-
const out = {};
|
|
2865
|
-
for (const line of readFileSync4(CONFIG_PATH, "utf-8").split("\n")) {
|
|
2866
|
-
const t = line.trim();
|
|
2867
|
-
if (!t || t.startsWith("#")) continue;
|
|
2868
|
-
const eq = t.indexOf("=");
|
|
2869
|
-
if (eq > 0) {
|
|
2870
|
-
let val = t.slice(eq + 1).trim();
|
|
2871
|
-
if (val.startsWith("'") && val.endsWith("'") || val.startsWith('"') && val.endsWith('"')) {
|
|
2872
|
-
val = val.slice(1, -1);
|
|
2873
|
-
}
|
|
2874
|
-
out[t.slice(0, eq).trim()] = val;
|
|
2875
|
-
}
|
|
2876
|
-
}
|
|
2877
|
-
return out;
|
|
2878
|
-
}
|
|
2879
|
-
async function prompt(rl, q, opts = {}) {
|
|
2880
|
-
if (opts.silent) {
|
|
2881
|
-
process.stdout.write(q);
|
|
2882
|
-
const wasRaw = process.stdin.isRaw;
|
|
2883
|
-
if (process.stdin.setRawMode) process.stdin.setRawMode(true);
|
|
2884
|
-
return await new Promise((resolve2) => {
|
|
2885
|
-
let chunk = "";
|
|
2886
|
-
const onData = (data) => {
|
|
2887
|
-
const s = data.toString("utf-8");
|
|
2888
|
-
if (s === "\r" || s === "\n" || s === "\r\n") {
|
|
2889
|
-
process.stdin.removeListener("data", onData);
|
|
2890
|
-
if (process.stdin.setRawMode) process.stdin.setRawMode(wasRaw ?? false);
|
|
2891
|
-
process.stdout.write("\n");
|
|
2892
|
-
resolve2(chunk);
|
|
2893
|
-
return;
|
|
2894
|
-
}
|
|
2895
|
-
if (s === "") {
|
|
2896
|
-
process.exit(130);
|
|
2897
|
-
}
|
|
2898
|
-
if (s === "\x7F" || s === "\b") {
|
|
2899
|
-
chunk = chunk.slice(0, -1);
|
|
2900
|
-
return;
|
|
2901
|
-
}
|
|
2902
|
-
chunk += s;
|
|
2903
|
-
};
|
|
2904
|
-
process.stdin.on("data", onData);
|
|
2905
|
-
});
|
|
2906
|
-
}
|
|
2907
|
-
return await rl.question(q);
|
|
2908
|
-
}
|
|
2909
|
-
async function setupGithubCommand(opts = {}) {
|
|
2910
|
-
if (!isAuthenticated()) {
|
|
2911
|
-
console.error("Not authenticated. Run `synkro-cli login` first.");
|
|
2912
|
-
process.exit(1);
|
|
2913
|
-
}
|
|
2914
|
-
const config2 = readConfig();
|
|
2915
|
-
const gatewayUrl = (config2.SYNKRO_GATEWAY_URL || process.env.SYNKRO_GATEWAY_URL || "https://api.synkro.sh").replace(/\/$/, "");
|
|
2916
|
-
const jwt2 = getAccessToken();
|
|
2917
|
-
if (!jwt2) {
|
|
2918
|
-
console.error("Could not load access token from ~/.synkro/credentials.json. Run `synkro-cli login`.");
|
|
2919
|
-
process.exit(1);
|
|
2920
|
-
}
|
|
2921
|
-
console.log("Requesting CI API key from Synkro...");
|
|
2922
|
-
let synkroCiApiKey;
|
|
2923
|
-
try {
|
|
2924
|
-
const resp = await fetch(`${gatewayUrl}/api/v1/cli/ci-api-key`, {
|
|
2925
|
-
method: "POST",
|
|
2926
|
-
headers: {
|
|
2927
|
-
"Authorization": `Bearer ${jwt2}`,
|
|
2928
|
-
"Content-Type": "application/json"
|
|
2929
|
-
},
|
|
2930
|
-
body: "{}"
|
|
2931
|
-
});
|
|
2932
|
-
if (!resp.ok) {
|
|
2933
|
-
const errText = await resp.text().catch(() => "");
|
|
2934
|
-
console.error(`Failed to mint CI API key (${resp.status}): ${errText.slice(0, 200)}`);
|
|
2935
|
-
process.exit(1);
|
|
2936
|
-
}
|
|
2937
|
-
const minted = await resp.json();
|
|
2938
|
-
synkroCiApiKey = minted.api_key;
|
|
2939
|
-
console.log(` \u2713 Issued CI key (${synkroCiApiKey.slice(0, 18)}\u2026), expires ${minted.expires_at.slice(0, 10)}`);
|
|
2940
|
-
} catch (err) {
|
|
2941
|
-
console.error(`Failed to mint CI API key: ${err.message}`);
|
|
2942
|
-
process.exit(1);
|
|
2943
|
-
}
|
|
2944
|
-
let _rl = null;
|
|
2945
|
-
function getRL() {
|
|
2946
|
-
if (!_rl) _rl = createInterface2({ input, output });
|
|
2947
|
-
return _rl;
|
|
2948
|
-
}
|
|
2949
|
-
let ghToken = opts.githubToken || "";
|
|
2950
|
-
if (!ghToken) {
|
|
2951
|
-
try {
|
|
2952
|
-
const connResp = await fetch(`${gatewayUrl}/api/integrations/github/token`, {
|
|
2953
|
-
headers: { "Authorization": `Bearer ${jwt2}` }
|
|
2954
|
-
});
|
|
2955
|
-
if (connResp.ok) {
|
|
2956
|
-
const data = await connResp.json();
|
|
2957
|
-
ghToken = data.token;
|
|
2958
|
-
console.log(" \u2713 Using existing GitHub connection");
|
|
2959
|
-
}
|
|
2960
|
-
} catch {
|
|
2961
|
-
}
|
|
2962
|
-
}
|
|
2963
|
-
if (!ghToken) {
|
|
2964
|
-
console.log("Opening browser for GitHub authorization...");
|
|
2965
|
-
const RAW_WEB_AUTH_URL3 = process.env.SYNKRO_WEB_AUTH_URL;
|
|
2966
|
-
const webAuthUrl = RAW_WEB_AUTH_URL3 && /^https?:\/\//.test(RAW_WEB_AUTH_URL3) ? RAW_WEB_AUTH_URL3 : "https://app.synkro.sh";
|
|
2967
|
-
openBrowser2(`${webAuthUrl}/cli-github?port=8101`);
|
|
2968
|
-
console.log("Waiting for GitHub authorization...");
|
|
2969
|
-
try {
|
|
2970
|
-
ghToken = await waitForGithubToken();
|
|
2971
|
-
console.log(" \u2713 GitHub authorized");
|
|
2972
|
-
} catch (err) {
|
|
2973
|
-
console.error(`GitHub authorization failed: ${err.message}`);
|
|
2974
|
-
_rl?.close();
|
|
2975
|
-
process.exit(1);
|
|
2976
|
-
}
|
|
2977
|
-
}
|
|
2978
|
-
if (!ghToken) {
|
|
2979
|
-
console.error("No GitHub token available.");
|
|
2980
|
-
_rl?.close();
|
|
2981
|
-
process.exit(1);
|
|
2982
|
-
}
|
|
2983
|
-
let claudeToken = opts.claudeOauthToken || "";
|
|
2984
|
-
if (!claudeToken && !opts.skipClaudeToken) {
|
|
2985
|
-
try {
|
|
2986
|
-
const { execSync: execSyncImport } = await import("child_process");
|
|
2987
|
-
const { tmpdir } = await import("os");
|
|
2988
|
-
const tmpFile = join5(tmpdir(), `synkro-claude-token-${Date.now()}.txt`);
|
|
2989
|
-
console.log("\nGenerating Claude Code OAuth token \u2014 complete the browser auth...");
|
|
2990
|
-
try {
|
|
2991
|
-
if (process.platform === "darwin" || process.platform === "linux") {
|
|
2992
|
-
execSyncImport(`script -q ${tmpFile} claude setup-token`, {
|
|
2993
|
-
timeout: 12e4,
|
|
2994
|
-
stdio: "inherit"
|
|
2995
|
-
});
|
|
2996
|
-
} else {
|
|
2997
|
-
execSyncImport(`claude setup-token > "${tmpFile}" 2>&1`, {
|
|
2998
|
-
timeout: 12e4,
|
|
2999
|
-
stdio: "inherit",
|
|
3000
|
-
shell: true
|
|
3001
|
-
});
|
|
3002
|
-
}
|
|
3003
|
-
const { readFileSync: readSync, unlinkSync: unlinkSync3 } = await import("fs");
|
|
3004
|
-
const rawOutput = readSync(tmpFile, "utf-8");
|
|
3005
|
-
try {
|
|
3006
|
-
unlinkSync3(tmpFile);
|
|
3007
|
-
} catch {
|
|
3008
|
-
}
|
|
3009
|
-
const cleaned = rawOutput.replace(/\x1b\[[0-9;]*[A-Za-z]/g, "").replace(/\][^\x07\x1b]*(?:\x07|\x1b\\)/g, "").replace(/[\r\n]+/g, " ");
|
|
3010
|
-
const match = cleaned.match(/(sk-ant-oat01-[A-Za-z0-9_-]{50,})/);
|
|
3011
|
-
claudeToken = match?.[1] || "";
|
|
3012
|
-
} catch {
|
|
3013
|
-
try {
|
|
3014
|
-
const { unlinkSync: unlinkSync3 } = await import("fs");
|
|
3015
|
-
unlinkSync3(tmpFile);
|
|
3016
|
-
} catch {
|
|
3017
|
-
}
|
|
3018
|
-
}
|
|
3019
|
-
if (claudeToken) {
|
|
3020
|
-
console.log(" \u2713 Captured Claude Code OAuth token.");
|
|
3021
|
-
process.stdout.write(" Verifying token... ");
|
|
3022
|
-
try {
|
|
3023
|
-
const { execSync: verifyExec } = await import("child_process");
|
|
3024
|
-
const result = verifyExec('claude --print "respond with just ok"', {
|
|
3025
|
-
encoding: "utf-8",
|
|
3026
|
-
timeout: 3e4,
|
|
3027
|
-
env: { ...process.env, CLAUDE_CODE_OAUTH_TOKEN: claudeToken },
|
|
3028
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
3029
|
-
}).trim().toLowerCase();
|
|
3030
|
-
if (result.includes("ok")) {
|
|
3031
|
-
console.log("\u2713 inference works.\n");
|
|
3032
|
-
} else {
|
|
3033
|
-
console.log("\u26A0 unexpected response, token may not work.\n");
|
|
3034
|
-
}
|
|
3035
|
-
} catch {
|
|
3036
|
-
console.log("\u26A0 verification failed, token may not work.\n");
|
|
3037
|
-
}
|
|
3038
|
-
} else {
|
|
3039
|
-
console.warn(" \u26A0 Could not capture token. PR scans will use BYOK inference only.\n");
|
|
3040
|
-
}
|
|
3041
|
-
} catch {
|
|
3042
|
-
console.warn(" \u26A0 claude setup-token failed. PR scans will use BYOK inference only.\n");
|
|
3043
|
-
}
|
|
3044
|
-
}
|
|
3045
|
-
console.log("\nFetching accessible repos...");
|
|
3046
|
-
const repos = await listAccessibleRepos({ token: ghToken });
|
|
3047
|
-
if (repos.length === 0) {
|
|
3048
|
-
console.error("No accessible repos found. Verify the GitHub token has `repo` scope.");
|
|
3049
|
-
_rl?.close();
|
|
3050
|
-
process.exit(1);
|
|
3051
|
-
}
|
|
3052
|
-
let selected;
|
|
3053
|
-
if (opts.nonInteractive) {
|
|
3054
|
-
let currentFullName = null;
|
|
3055
|
-
try {
|
|
3056
|
-
const { execSync: gs } = await import("child_process");
|
|
3057
|
-
const remoteUrl = gs("git remote get-url origin", { encoding: "utf-8", timeout: 5e3 }).trim();
|
|
3058
|
-
const m = remoteUrl.match(/(?:github\.com)[:/](.+?)(?:\.git)?$/);
|
|
3059
|
-
if (m) currentFullName = m[1];
|
|
3060
|
-
} catch {
|
|
3061
|
-
}
|
|
3062
|
-
if (currentFullName) {
|
|
3063
|
-
const match = repos.find(
|
|
3064
|
-
(r) => r.full_name === currentFullName || r.full_name.toLowerCase() === currentFullName.toLowerCase()
|
|
3065
|
-
);
|
|
3066
|
-
if (match) {
|
|
3067
|
-
selected = [match];
|
|
3068
|
-
console.log(` Auto-selected current repo: ${match.full_name}`);
|
|
3069
|
-
} else {
|
|
3070
|
-
console.warn(` \u26A0 Current repo "${currentFullName}" not found in accessible repos. Skipping PR scan setup.`);
|
|
3071
|
-
console.warn(" Run `synkro-cli setup-github` manually to select a repo.");
|
|
3072
|
-
return;
|
|
3073
|
-
}
|
|
3074
|
-
} else {
|
|
3075
|
-
console.warn(" \u26A0 Not in a GitHub repo. Skipping PR scan setup.");
|
|
3076
|
-
console.warn(" Run `synkro-cli setup-github` from inside a repo.");
|
|
3077
|
-
return;
|
|
3078
|
-
}
|
|
3079
|
-
} else {
|
|
3080
|
-
console.log(`
|
|
3081
|
-
Found ${repos.length} accessible repo(s):
|
|
3082
|
-
`);
|
|
3083
|
-
repos.slice(0, 100).forEach((r, i) => {
|
|
3084
|
-
console.log(` ${String(i + 1).padStart(3)}. ${r.full_name}`);
|
|
3085
|
-
});
|
|
3086
|
-
console.log();
|
|
3087
|
-
const rl = getRL();
|
|
3088
|
-
const selectionRaw = await prompt(rl, "Select repos to enable (comma-separated numbers, e.g. 1,3,5): ");
|
|
3089
|
-
const selectedIdx = selectionRaw.split(",").map((s) => parseInt(s.trim(), 10) - 1).filter((n) => !isNaN(n) && n >= 0 && n < repos.length);
|
|
3090
|
-
if (selectedIdx.length === 0) {
|
|
3091
|
-
console.error("No valid selections.");
|
|
3092
|
-
rl.close();
|
|
3093
|
-
process.exit(1);
|
|
3094
|
-
}
|
|
3095
|
-
selected = selectedIdx.map((i) => repos[i]);
|
|
3096
|
-
console.log(`
|
|
3097
|
-
Will push secrets to ${selected.length} repo(s):`);
|
|
3098
|
-
for (const r of selected) console.log(` \u2022 ${r.full_name}`);
|
|
3099
|
-
console.log();
|
|
3100
|
-
const confirm = (await prompt(rl, "Continue? (yes/no): ")).trim().toLowerCase();
|
|
3101
|
-
if (confirm !== "yes" && confirm !== "y") {
|
|
3102
|
-
console.log("Cancelled.");
|
|
3103
|
-
rl.close();
|
|
3104
|
-
process.exit(0);
|
|
3105
|
-
}
|
|
3106
|
-
}
|
|
3107
|
-
_rl?.close();
|
|
3108
|
-
console.log();
|
|
3109
|
-
for (const r of selected) {
|
|
3110
|
-
process.stdout.write(`Pushing secrets to ${r.full_name}... `);
|
|
3111
|
-
try {
|
|
3112
|
-
await pushSecretsToRepo(
|
|
3113
|
-
{ token: ghToken },
|
|
3114
|
-
r.owner,
|
|
3115
|
-
r.repo,
|
|
3116
|
-
{
|
|
3117
|
-
claudeCodeOauthToken: claudeToken,
|
|
3118
|
-
synkroApiKey: synkroCiApiKey
|
|
3119
|
-
}
|
|
3120
|
-
);
|
|
3121
|
-
console.log("\u2713");
|
|
3122
|
-
} catch (err) {
|
|
3123
|
-
console.log(`\u2717 (${err instanceof Error ? err.message : String(err)})`);
|
|
3124
|
-
}
|
|
3125
|
-
}
|
|
3126
|
-
console.log();
|
|
3127
|
-
const gitRoot = findGitRoot(process.cwd());
|
|
3128
|
-
if (gitRoot) {
|
|
3129
|
-
const written = writeWorkflowFile(gitRoot);
|
|
3130
|
-
if (written) {
|
|
3131
|
-
console.log(`Wrote workflow: ${written}`);
|
|
3132
|
-
console.log("Commit and push it to enable PR scanning.");
|
|
3133
|
-
}
|
|
3134
|
-
} else {
|
|
3135
|
-
console.log("Not in a git repo. To enable scanning, add this file to your repo:");
|
|
3136
|
-
console.log(` Path: ${WORKFLOW_RELATIVE_PATH}`);
|
|
3137
|
-
console.log(` Content: run \`synkro-cli setup-github\` from inside a repo to write it automatically`);
|
|
3138
|
-
}
|
|
3139
|
-
console.log();
|
|
3140
|
-
console.log("\u2713 PR scan setup complete.");
|
|
3141
|
-
console.log(`Secrets pushed: ${SECRET_NAMES.CLAUDE_OAUTH}, ${SECRET_NAMES.SYNKRO_API_KEY}`);
|
|
3142
|
-
console.log("Open a PR on any selected repo to trigger your first Synkro scan.");
|
|
3143
|
-
}
|
|
3144
|
-
var SYNKRO_DIR, CONFIG_PATH;
|
|
3145
|
-
var init_setupGithub = __esm({
|
|
3146
|
-
"cli/commands/setupGithub.ts"() {
|
|
3147
|
-
"use strict";
|
|
3148
|
-
init_githubSetup();
|
|
3149
|
-
init_stub();
|
|
3150
|
-
init_repoConnect();
|
|
3151
|
-
SYNKRO_DIR = join5(homedir4(), ".synkro");
|
|
3152
|
-
CONFIG_PATH = join5(SYNKRO_DIR, "config.env");
|
|
3153
|
-
}
|
|
3154
|
-
});
|
|
3155
|
-
|
|
3156
2832
|
// cli/commands/install.ts
|
|
3157
2833
|
var install_exports = {};
|
|
3158
2834
|
__export(install_exports, {
|
|
3159
2835
|
installCommand: () => installCommand,
|
|
3160
2836
|
parseArgs: () => parseArgs
|
|
3161
2837
|
});
|
|
3162
|
-
import { existsSync as
|
|
3163
|
-
import {
|
|
3164
|
-
import {
|
|
3165
|
-
import { homedir as homedir5 } from "os";
|
|
3166
|
-
import { join as join6 } from "path";
|
|
2838
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync5, writeFileSync as writeFileSync5, chmodSync, readFileSync as readFileSync4, readdirSync } from "fs";
|
|
2839
|
+
import { homedir as homedir4 } from "os";
|
|
2840
|
+
import { join as join5 } from "path";
|
|
3167
2841
|
import { execSync as execSync3 } from "child_process";
|
|
3168
|
-
async function promptYesNo(question, defaultYes = true) {
|
|
3169
|
-
const rl = createInterface3({ input: stdinStream, output: stdoutStream });
|
|
3170
|
-
const suffix = defaultYes ? "[Y/n]" : "[y/N]";
|
|
3171
|
-
const answer = (await rl.question(`${question} ${suffix} `)).trim().toLowerCase();
|
|
3172
|
-
rl.close();
|
|
3173
|
-
if (answer === "") return defaultYes;
|
|
3174
|
-
return answer === "y" || answer === "yes";
|
|
3175
|
-
}
|
|
3176
2842
|
function sanitizeGatewayCandidate(raw) {
|
|
3177
2843
|
if (!raw) return void 0;
|
|
3178
2844
|
return /^https?:\/\//.test(raw) ? raw : void 0;
|
|
@@ -3185,17 +2851,6 @@ function parseArgs(argv) {
|
|
|
3185
2851
|
else if (a === "--skip-auth") opts.skipAuth = true;
|
|
3186
2852
|
else if (a === "--no-mcp") opts.noMcp = true;
|
|
3187
2853
|
else if (a === "--force" || a === "-f") opts.force = true;
|
|
3188
|
-
else if (a === "--non-interactive") opts.nonInteractive = true;
|
|
3189
|
-
else if (a === "--link-repo") opts.linkRepo = true;
|
|
3190
|
-
else if (a === "--sync-transcripts") opts.syncTranscripts = true;
|
|
3191
|
-
else if (a === "--no-sync-transcripts") opts.syncTranscripts = false;
|
|
3192
|
-
else if (a === "--pr-scan=claude-oauth") opts.prScan = "claude-oauth";
|
|
3193
|
-
else if (a === "--pr-scan=byok") opts.prScan = "byok";
|
|
3194
|
-
else if (a === "--pr-scan=skip") opts.prScan = "skip";
|
|
3195
|
-
else if (a.startsWith("--pr-scan=")) opts.prScan = a.slice("--pr-scan=".length);
|
|
3196
|
-
else if (a.startsWith("--inference-key=")) opts.inferenceKey = a.slice("--inference-key=".length);
|
|
3197
|
-
else if (a.startsWith("--inference-model=")) opts.inferenceModel = a.slice("--inference-model=".length);
|
|
3198
|
-
else if (a.startsWith("--github-token=")) opts.githubToken = a.slice("--github-token=".length);
|
|
3199
2854
|
}
|
|
3200
2855
|
if (!opts.gatewayUrl) {
|
|
3201
2856
|
const fromEnv = sanitizeGatewayCandidate(process.env.SYNKRO_GATEWAY_URL);
|
|
@@ -3204,7 +2859,7 @@ function parseArgs(argv) {
|
|
|
3204
2859
|
return opts;
|
|
3205
2860
|
}
|
|
3206
2861
|
function ensureSynkroDir() {
|
|
3207
|
-
mkdirSync5(
|
|
2862
|
+
mkdirSync5(SYNKRO_DIR, { recursive: true });
|
|
3208
2863
|
mkdirSync5(HOOKS_DIR, { recursive: true });
|
|
3209
2864
|
mkdirSync5(BIN_DIR, { recursive: true });
|
|
3210
2865
|
mkdirSync5(OFFSETS_DIR, { recursive: true });
|
|
@@ -3218,13 +2873,13 @@ function writeGraderDaemon() {
|
|
|
3218
2873
|
chmodSync(GRADER_PRIMER_BASH_PATH, 420);
|
|
3219
2874
|
}
|
|
3220
2875
|
function writeHookScripts() {
|
|
3221
|
-
const bashScriptPath =
|
|
3222
|
-
const bashFollowupScriptPath =
|
|
3223
|
-
const editCaptureScriptPath =
|
|
3224
|
-
const editPrecheckScriptPath =
|
|
3225
|
-
const stopSummaryScriptPath =
|
|
3226
|
-
const sessionStartScriptPath =
|
|
3227
|
-
const transcriptSyncScriptPath =
|
|
2876
|
+
const bashScriptPath = join5(HOOKS_DIR, "cc-bash-judge.sh");
|
|
2877
|
+
const bashFollowupScriptPath = join5(HOOKS_DIR, "cc-bash-followup.sh");
|
|
2878
|
+
const editCaptureScriptPath = join5(HOOKS_DIR, "cc-edit-capture.sh");
|
|
2879
|
+
const editPrecheckScriptPath = join5(HOOKS_DIR, "cc-edit-precheck.sh");
|
|
2880
|
+
const stopSummaryScriptPath = join5(HOOKS_DIR, "cc-stop-summary.sh");
|
|
2881
|
+
const sessionStartScriptPath = join5(HOOKS_DIR, "cc-session-start.sh");
|
|
2882
|
+
const transcriptSyncScriptPath = join5(HOOKS_DIR, "cc-transcript-sync.sh");
|
|
3228
2883
|
writeFileSync5(bashScriptPath, CC_BASH_JUDGE_SCRIPT, "utf-8");
|
|
3229
2884
|
writeFileSync5(bashFollowupScriptPath, CC_BASH_FOLLOWUP_SCRIPT, "utf-8");
|
|
3230
2885
|
writeFileSync5(editCaptureScriptPath, CC_EDIT_CAPTURE_SCRIPT, "utf-8");
|
|
@@ -3257,7 +2912,7 @@ function shellQuoteSingle(value) {
|
|
|
3257
2912
|
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
3258
2913
|
}
|
|
3259
2914
|
function writeConfigEnv(opts) {
|
|
3260
|
-
const credsPath =
|
|
2915
|
+
const credsPath = join5(SYNKRO_DIR, "credentials.json");
|
|
3261
2916
|
const safeGateway = sanitizeConfigValue(opts.gatewayUrl);
|
|
3262
2917
|
const safeUserId = sanitizeConfigValue(opts.userId);
|
|
3263
2918
|
const safeOrgId = sanitizeConfigValue(opts.orgId);
|
|
@@ -3270,14 +2925,14 @@ function writeConfigEnv(opts) {
|
|
|
3270
2925
|
`SYNKRO_GATEWAY_URL=${shellQuoteSingle(safeGateway)}`,
|
|
3271
2926
|
`SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle(credsPath)}`,
|
|
3272
2927
|
`SYNKRO_TIER=${shellQuoteSingle(safeTier)}`,
|
|
3273
|
-
`SYNKRO_VERSION=${shellQuoteSingle("1.3.
|
|
2928
|
+
`SYNKRO_VERSION=${shellQuoteSingle("1.3.14")}`
|
|
3274
2929
|
];
|
|
3275
2930
|
if (safeUserId) lines.push(`SYNKRO_USER_ID=${shellQuoteSingle(safeUserId)}`);
|
|
3276
2931
|
if (safeOrgId) lines.push(`SYNKRO_ORG_ID=${shellQuoteSingle(safeOrgId)}`);
|
|
3277
2932
|
if (safeEmail) lines.push(`SYNKRO_EMAIL=${shellQuoteSingle(safeEmail)}`);
|
|
3278
2933
|
lines.push("");
|
|
3279
|
-
writeFileSync5(
|
|
3280
|
-
chmodSync(
|
|
2934
|
+
writeFileSync5(CONFIG_PATH, lines.join("\n"), "utf-8");
|
|
2935
|
+
chmodSync(CONFIG_PATH, 384);
|
|
3281
2936
|
}
|
|
3282
2937
|
function assertGatewayAllowed(gatewayUrl) {
|
|
3283
2938
|
let parsed;
|
|
@@ -3302,19 +2957,19 @@ function assertGatewayAllowed(gatewayUrl) {
|
|
|
3302
2957
|
}
|
|
3303
2958
|
function isAlreadyInstalled() {
|
|
3304
2959
|
const requiredScripts = [
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
2960
|
+
join5(HOOKS_DIR, "cc-bash-judge.sh"),
|
|
2961
|
+
join5(HOOKS_DIR, "cc-bash-followup.sh"),
|
|
2962
|
+
join5(HOOKS_DIR, "cc-edit-precheck.sh"),
|
|
2963
|
+
join5(HOOKS_DIR, "cc-edit-capture.sh"),
|
|
2964
|
+
join5(HOOKS_DIR, "cc-stop-summary.sh"),
|
|
2965
|
+
join5(HOOKS_DIR, "cc-session-start.sh")
|
|
3311
2966
|
];
|
|
3312
|
-
if (!requiredScripts.every((p) =>
|
|
3313
|
-
if (!
|
|
3314
|
-
const settingsPath =
|
|
3315
|
-
if (!
|
|
2967
|
+
if (!requiredScripts.every((p) => existsSync6(p))) return false;
|
|
2968
|
+
if (!existsSync6(CONFIG_PATH)) return false;
|
|
2969
|
+
const settingsPath = join5(homedir4(), ".claude", "settings.json");
|
|
2970
|
+
if (!existsSync6(settingsPath)) return false;
|
|
3316
2971
|
try {
|
|
3317
|
-
const settings = JSON.parse(
|
|
2972
|
+
const settings = JSON.parse(readFileSync4(settingsPath, "utf-8"));
|
|
3318
2973
|
const hooks = settings?.hooks;
|
|
3319
2974
|
if (!hooks || typeof hooks !== "object") return false;
|
|
3320
2975
|
const hasManaged = (kind) => Array.isArray(hooks[kind]) && hooks[kind].some((entry) => entry?.__synkro_managed__ === true);
|
|
@@ -3373,11 +3028,7 @@ async function installCommand(opts = {}) {
|
|
|
3373
3028
|
console.error("No access token available after auth.");
|
|
3374
3029
|
process.exit(1);
|
|
3375
3030
|
}
|
|
3376
|
-
|
|
3377
|
-
await promptRepoConnection({ linkRepo: opts.linkRepo ?? true });
|
|
3378
|
-
} else {
|
|
3379
|
-
await promptRepoConnection();
|
|
3380
|
-
}
|
|
3031
|
+
await promptRepoConnection();
|
|
3381
3032
|
const agents = detectAgents();
|
|
3382
3033
|
if (agents.length === 0) {
|
|
3383
3034
|
console.error("No AI coding agents detected. Install Claude Code first: https://docs.claude.com/claude-code");
|
|
@@ -3401,9 +3052,9 @@ async function installCommand(opts = {}) {
|
|
|
3401
3052
|
`);
|
|
3402
3053
|
writeGraderDaemon();
|
|
3403
3054
|
for (const mode of ["edit", "bash"]) {
|
|
3404
|
-
const pidFile =
|
|
3055
|
+
const pidFile = join5(SYNKRO_DIR, "daemon", mode, "daemon.pid");
|
|
3405
3056
|
try {
|
|
3406
|
-
const pid = parseInt(
|
|
3057
|
+
const pid = parseInt(readFileSync4(pidFile, "utf-8").trim(), 10);
|
|
3407
3058
|
if (pid > 0) {
|
|
3408
3059
|
process.kill(pid, "SIGTERM");
|
|
3409
3060
|
console.log(`Stopped stale ${mode} daemon (pid ${pid})`);
|
|
@@ -3471,158 +3122,38 @@ async function installCommand(opts = {}) {
|
|
|
3471
3122
|
} catch {
|
|
3472
3123
|
}
|
|
3473
3124
|
writeConfigEnv({ gatewayUrl, userId, orgId, email });
|
|
3474
|
-
console.log(`Wrote config to ${
|
|
3475
|
-
`);
|
|
3476
|
-
const repo = detectGitRepo2();
|
|
3477
|
-
if (repo && getClaudeProjectsFolder()) {
|
|
3478
|
-
const syncHistory = opts.nonInteractive ? opts.syncTranscripts ?? true : await promptYesNo("Sync Claude Code session history for this repo?");
|
|
3479
|
-
if (syncHistory) {
|
|
3480
|
-
try {
|
|
3481
|
-
const ingested = await ingestSessionTranscripts(gatewayUrl, token, repo);
|
|
3482
|
-
if (ingested > 0) {
|
|
3483
|
-
console.log(`Indexed ${ingested} session insights from Claude Code history for ${repo}.`);
|
|
3484
|
-
console.log(" This helps the safety judge understand your workflow.\n");
|
|
3485
|
-
}
|
|
3486
|
-
} catch (err) {
|
|
3487
|
-
console.warn(` \u26A0 Session indexing skipped: ${err.message}
|
|
3488
|
-
`);
|
|
3489
|
-
}
|
|
3490
|
-
try {
|
|
3491
|
-
const result = await syncTranscriptsBulk(gatewayUrl, token, repo);
|
|
3492
|
-
if (result.messages > 0) {
|
|
3493
|
-
console.log(`Synced ${result.sessions} sessions (${result.messages} messages) from Claude Code history.`);
|
|
3494
|
-
console.log(" This data will be used to suggest guardrail rules.\n");
|
|
3495
|
-
}
|
|
3496
|
-
} catch (err) {
|
|
3497
|
-
console.warn(` \u26A0 Transcript sync skipped: ${err.message}
|
|
3125
|
+
console.log(`Wrote config to ${CONFIG_PATH}
|
|
3498
3126
|
`);
|
|
3127
|
+
try {
|
|
3128
|
+
const repo = detectGitRepo2();
|
|
3129
|
+
if (repo) {
|
|
3130
|
+
const ingested = await ingestSessionTranscripts(gatewayUrl, token, repo);
|
|
3131
|
+
if (ingested > 0) {
|
|
3132
|
+
console.log(`Indexed ${ingested} session insights from Claude Code history for ${repo}.`);
|
|
3133
|
+
console.log(" This helps the safety judge understand your workflow.\n");
|
|
3499
3134
|
}
|
|
3500
|
-
} else {
|
|
3501
|
-
console.log(" Skipped session history sync.\n");
|
|
3502
3135
|
}
|
|
3136
|
+
} catch (err) {
|
|
3137
|
+
console.warn(` \u26A0 Session indexing skipped: ${err.message}
|
|
3138
|
+
`);
|
|
3503
3139
|
}
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
if (
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
anthropic: ["claude-sonnet-4-6", "claude-opus-4-6", "claude-haiku-4-5"],
|
|
3512
|
-
openai: ["gpt-5.5", "gpt-5.4", "gpt-5.4-mini"],
|
|
3513
|
-
gemini: ["gemini-3.1-pro-preview", "gemini-3-flash-preview", "gemini-3.1-flash-lite-preview"]
|
|
3514
|
-
};
|
|
3515
|
-
if (repo) {
|
|
3516
|
-
let ghConnected = false;
|
|
3517
|
-
let ghLogin = "";
|
|
3518
|
-
try {
|
|
3519
|
-
const resp = await fetch(`${gatewayUrl}/api/integrations/github/connect`, {
|
|
3520
|
-
headers: { "Authorization": `Bearer ${token}` }
|
|
3521
|
-
});
|
|
3522
|
-
if (resp.ok) {
|
|
3523
|
-
const data = await resp.json();
|
|
3524
|
-
ghConnected = data.connected;
|
|
3525
|
-
ghLogin = data.github_login || "";
|
|
3140
|
+
try {
|
|
3141
|
+
const repo = detectGitRepo2();
|
|
3142
|
+
if (repo) {
|
|
3143
|
+
const result = await syncTranscriptsBulk(gatewayUrl, token, repo);
|
|
3144
|
+
if (result.messages > 0) {
|
|
3145
|
+
console.log(`Synced ${result.sessions} sessions (${result.messages} messages) from Claude Code history.`);
|
|
3146
|
+
console.log(" This data will be used to suggest guardrail rules.\n");
|
|
3526
3147
|
}
|
|
3527
|
-
} catch {
|
|
3528
|
-
}
|
|
3529
|
-
if (ghConnected) {
|
|
3530
|
-
console.log(`GitHub is connected (${ghLogin || "linked via dashboard"}).`);
|
|
3531
3148
|
}
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
if (setupGh) {
|
|
3535
|
-
let choice;
|
|
3536
|
-
if (prScanChoice) {
|
|
3537
|
-
choice = prScanChoice === "claude-oauth" ? "1" : prScanChoice === "byok" ? "2" : "3";
|
|
3538
|
-
} else {
|
|
3539
|
-
const rl = createInterface3({ input: stdinStream, output: stdoutStream });
|
|
3540
|
-
console.log("\nHow should PR scans authenticate for AI review?\n");
|
|
3541
|
-
console.log(" 1. Claude Code OAuth \u2014 opens browser, auto-captures token");
|
|
3542
|
-
console.log(" 2. Use your own API key \u2014 paste an Anthropic, OpenAI, or Gemini key");
|
|
3543
|
-
console.log(" 3. Skip for now\n");
|
|
3544
|
-
choice = (await rl.question("Choice [1/2/3]: ")).trim();
|
|
3545
|
-
rl.close();
|
|
3546
|
-
}
|
|
3547
|
-
if (choice === "1") {
|
|
3548
|
-
try {
|
|
3549
|
-
await setupGithubCommand({ githubToken: opts.githubToken, nonInteractive: opts.nonInteractive });
|
|
3550
|
-
} catch (err) {
|
|
3551
|
-
console.warn(` \u26A0 GitHub setup failed: ${err.message}`);
|
|
3552
|
-
console.warn(" Run `synkro-cli setup-github` to retry.\n");
|
|
3553
|
-
}
|
|
3554
|
-
} else if (choice === "2") {
|
|
3555
|
-
let apiKey = opts.inferenceKey || "";
|
|
3556
|
-
if (!apiKey) {
|
|
3557
|
-
const rl2 = createInterface3({ input: stdinStream, output: stdoutStream });
|
|
3558
|
-
apiKey = (await rl2.question("Paste your API key: ")).trim();
|
|
3559
|
-
rl2.close();
|
|
3560
|
-
}
|
|
3561
|
-
if (apiKey) {
|
|
3562
|
-
const provider = detectProviderFromKey(apiKey);
|
|
3563
|
-
if (provider) {
|
|
3564
|
-
let selectedModel = opts.inferenceModel || "";
|
|
3565
|
-
if (!selectedModel) {
|
|
3566
|
-
const models = PROVIDER_MODELS[provider];
|
|
3567
|
-
console.log(`
|
|
3568
|
-
Detected provider: ${provider}`);
|
|
3569
|
-
console.log(" Select a model for classification + grading:\n");
|
|
3570
|
-
models.forEach((m, i) => console.log(` ${i + 1}. ${m}`));
|
|
3571
|
-
console.log();
|
|
3572
|
-
const rl3 = createInterface3({ input: stdinStream, output: stdoutStream });
|
|
3573
|
-
const modelChoice = (await rl3.question(` Model [1-${models.length}]: `)).trim();
|
|
3574
|
-
rl3.close();
|
|
3575
|
-
const modelIdx = parseInt(modelChoice, 10) - 1;
|
|
3576
|
-
selectedModel = modelIdx >= 0 && modelIdx < models.length ? models[modelIdx] : models[0];
|
|
3577
|
-
}
|
|
3578
|
-
console.log(` Using: ${selectedModel}`);
|
|
3579
|
-
try {
|
|
3580
|
-
const resp = await fetch(`${gatewayUrl}/api/v1/settings/inference`, {
|
|
3581
|
-
method: "PUT",
|
|
3582
|
-
headers: {
|
|
3583
|
-
"Authorization": `Bearer ${token}`,
|
|
3584
|
-
"Content-Type": "application/json"
|
|
3585
|
-
},
|
|
3586
|
-
body: JSON.stringify({
|
|
3587
|
-
providers: { [provider]: { api_key: apiKey } },
|
|
3588
|
-
roles: {
|
|
3589
|
-
classification: { provider, model: selectedModel },
|
|
3590
|
-
grading: { provider, model: selectedModel }
|
|
3591
|
-
}
|
|
3592
|
-
})
|
|
3593
|
-
});
|
|
3594
|
-
if (resp.ok) {
|
|
3595
|
-
console.log(` \u2713 Saved ${provider} key and configured classification + grading with ${selectedModel}.
|
|
3596
|
-
`);
|
|
3597
|
-
} else {
|
|
3598
|
-
const err = await resp.text().catch(() => "");
|
|
3599
|
-
console.warn(` \u26A0 Failed to save key: ${err.slice(0, 200)}
|
|
3600
|
-
`);
|
|
3601
|
-
}
|
|
3602
|
-
} catch (err) {
|
|
3603
|
-
console.warn(` \u26A0 Failed to save key: ${err.message}
|
|
3149
|
+
} catch (err) {
|
|
3150
|
+
console.warn(` \u26A0 Transcript sync skipped: ${err.message}
|
|
3604
3151
|
`);
|
|
3605
|
-
}
|
|
3606
|
-
} else {
|
|
3607
|
-
console.warn(" \u26A0 Could not detect provider from key format. Configure manually in the dashboard.\n");
|
|
3608
|
-
}
|
|
3609
|
-
}
|
|
3610
|
-
try {
|
|
3611
|
-
await setupGithubCommand({ skipClaudeToken: true, githubToken: opts.githubToken, nonInteractive: opts.nonInteractive });
|
|
3612
|
-
} catch (err) {
|
|
3613
|
-
console.warn(` \u26A0 GitHub setup failed: ${err.message}`);
|
|
3614
|
-
console.warn(" Run `synkro-cli setup-github` to retry.\n");
|
|
3615
|
-
}
|
|
3616
|
-
} else {
|
|
3617
|
-
console.log(" Skipped. Run `synkro-cli setup-github` anytime to enable PR scanning.\n");
|
|
3618
|
-
}
|
|
3619
|
-
} else {
|
|
3620
|
-
console.log(" Skipped. Run `synkro-cli setup-github` anytime to enable PR scanning.\n");
|
|
3621
|
-
}
|
|
3622
3152
|
}
|
|
3623
3153
|
console.log("\u2713 Synkro installed.");
|
|
3624
3154
|
console.log();
|
|
3625
3155
|
console.log("Next steps:");
|
|
3156
|
+
console.log(" \u2022 synkro-cli setup-github (enable PR scanning)");
|
|
3626
3157
|
console.log(" \u2022 synkro-cli status (check what is configured)");
|
|
3627
3158
|
}
|
|
3628
3159
|
function detectGitRepo2() {
|
|
@@ -3636,18 +3167,18 @@ function detectGitRepo2() {
|
|
|
3636
3167
|
}
|
|
3637
3168
|
function getClaudeProjectsFolder() {
|
|
3638
3169
|
const cwd = process.cwd();
|
|
3639
|
-
const sanitized = cwd.replace(/\//g, "-");
|
|
3640
|
-
const projectsDir =
|
|
3641
|
-
return
|
|
3170
|
+
const sanitized = "-" + cwd.replace(/\//g, "-");
|
|
3171
|
+
const projectsDir = join5(homedir4(), ".claude", "projects", sanitized);
|
|
3172
|
+
return existsSync6(projectsDir) ? projectsDir : null;
|
|
3642
3173
|
}
|
|
3643
3174
|
function extractSessionInsights(projectsDir) {
|
|
3644
3175
|
const insights = [];
|
|
3645
3176
|
const files = readdirSync(projectsDir).filter((f) => f.endsWith(".jsonl"));
|
|
3646
3177
|
for (const file of files) {
|
|
3647
3178
|
const sessionId = file.replace(".jsonl", "");
|
|
3648
|
-
const filePath =
|
|
3179
|
+
const filePath = join5(projectsDir, file);
|
|
3649
3180
|
try {
|
|
3650
|
-
const content =
|
|
3181
|
+
const content = readFileSync4(filePath, "utf-8");
|
|
3651
3182
|
const lines = content.split("\n").filter(Boolean);
|
|
3652
3183
|
for (let i = 0; i < lines.length; i++) {
|
|
3653
3184
|
try {
|
|
@@ -3723,7 +3254,7 @@ function extractTextContent(content) {
|
|
|
3723
3254
|
return "";
|
|
3724
3255
|
}
|
|
3725
3256
|
function parseTranscriptFile(filePath) {
|
|
3726
|
-
const content =
|
|
3257
|
+
const content = readFileSync4(filePath, "utf-8");
|
|
3727
3258
|
const lines = content.split("\n").filter(Boolean);
|
|
3728
3259
|
const messages = [];
|
|
3729
3260
|
for (let i = 0; i < lines.length; i++) {
|
|
@@ -3774,7 +3305,7 @@ async function syncTranscriptsBulk(gatewayUrl, token, repo) {
|
|
|
3774
3305
|
const sessions = [];
|
|
3775
3306
|
for (const file of batch) {
|
|
3776
3307
|
const sessionId = file.replace(".jsonl", "");
|
|
3777
|
-
const filePath =
|
|
3308
|
+
const filePath = join5(projectsDir, file);
|
|
3778
3309
|
try {
|
|
3779
3310
|
const allMessages = parseTranscriptFile(filePath);
|
|
3780
3311
|
const messages = allMessages.length > maxMessagesPerSession ? allMessages.slice(-maxMessagesPerSession) : allMessages;
|
|
@@ -3803,18 +3334,18 @@ async function syncTranscriptsBulk(gatewayUrl, token, repo) {
|
|
|
3803
3334
|
}
|
|
3804
3335
|
for (const file of batch) {
|
|
3805
3336
|
const sessionId = file.replace(".jsonl", "");
|
|
3806
|
-
const filePath =
|
|
3337
|
+
const filePath = join5(projectsDir, file);
|
|
3807
3338
|
try {
|
|
3808
|
-
const content =
|
|
3339
|
+
const content = readFileSync4(filePath, "utf-8");
|
|
3809
3340
|
const lineCount = content.split("\n").filter(Boolean).length;
|
|
3810
|
-
writeFileSync5(
|
|
3341
|
+
writeFileSync5(join5(OFFSETS_DIR, sessionId), String(lineCount), "utf-8");
|
|
3811
3342
|
} catch {
|
|
3812
3343
|
}
|
|
3813
3344
|
}
|
|
3814
3345
|
}
|
|
3815
3346
|
return { sessions: totalSessions, messages: totalMessages };
|
|
3816
3347
|
}
|
|
3817
|
-
var
|
|
3348
|
+
var SYNKRO_DIR, HOOKS_DIR, BIN_DIR, CONFIG_PATH, GRADER_DAEMON_PATH, GRADER_PRIMER_EDIT_PATH, GRADER_PRIMER_BASH_PATH, OFFSETS_DIR;
|
|
3818
3349
|
var init_install = __esm({
|
|
3819
3350
|
"cli/commands/install.ts"() {
|
|
3820
3351
|
"use strict";
|
|
@@ -3825,15 +3356,14 @@ var init_install = __esm({
|
|
|
3825
3356
|
init_graderDaemon();
|
|
3826
3357
|
init_stub();
|
|
3827
3358
|
init_repoConnect();
|
|
3828
|
-
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
OFFSETS_DIR = join6(SYNKRO_DIR2, ".transcript-offsets");
|
|
3359
|
+
SYNKRO_DIR = join5(homedir4(), ".synkro");
|
|
3360
|
+
HOOKS_DIR = join5(SYNKRO_DIR, "hooks");
|
|
3361
|
+
BIN_DIR = join5(SYNKRO_DIR, "bin");
|
|
3362
|
+
CONFIG_PATH = join5(SYNKRO_DIR, "config.env");
|
|
3363
|
+
GRADER_DAEMON_PATH = join5(BIN_DIR, "grader_daemon.py");
|
|
3364
|
+
GRADER_PRIMER_EDIT_PATH = join5(SYNKRO_DIR, "grader-primer-edit.txt");
|
|
3365
|
+
GRADER_PRIMER_BASH_PATH = join5(SYNKRO_DIR, "grader-primer-bash.txt");
|
|
3366
|
+
OFFSETS_DIR = join5(SYNKRO_DIR, ".transcript-offsets");
|
|
3837
3367
|
}
|
|
3838
3368
|
});
|
|
3839
3369
|
|
|
@@ -3909,13 +3439,13 @@ var status_exports = {};
|
|
|
3909
3439
|
__export(status_exports, {
|
|
3910
3440
|
statusCommand: () => statusCommand
|
|
3911
3441
|
});
|
|
3912
|
-
import { existsSync as
|
|
3913
|
-
import { homedir as
|
|
3914
|
-
import { join as
|
|
3442
|
+
import { existsSync as existsSync7, readFileSync as readFileSync5 } from "fs";
|
|
3443
|
+
import { homedir as homedir5 } from "os";
|
|
3444
|
+
import { join as join6 } from "path";
|
|
3915
3445
|
function readConfigEnv() {
|
|
3916
|
-
if (!
|
|
3446
|
+
if (!existsSync7(CONFIG_PATH2)) return {};
|
|
3917
3447
|
const out = {};
|
|
3918
|
-
const raw =
|
|
3448
|
+
const raw = readFileSync5(CONFIG_PATH2, "utf-8");
|
|
3919
3449
|
for (const line of raw.split("\n")) {
|
|
3920
3450
|
const trimmed = line.trim();
|
|
3921
3451
|
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
@@ -3946,10 +3476,10 @@ function statusCommand() {
|
|
|
3946
3476
|
console.log(` tier: ${config2.SYNKRO_TIER ?? "(unset)"}`);
|
|
3947
3477
|
const info2 = getUserInfo();
|
|
3948
3478
|
const userId = info2?.id ?? config2.SYNKRO_USER_ID ?? "default";
|
|
3949
|
-
const tierCacheFile =
|
|
3479
|
+
const tierCacheFile = join6(SYNKRO_DIR2, `.tier-cache-${userId}`);
|
|
3950
3480
|
let inferenceTier = config2.SYNKRO_INFERENCE_TIER || null;
|
|
3951
|
-
if (!inferenceTier &&
|
|
3952
|
-
inferenceTier =
|
|
3481
|
+
if (!inferenceTier && existsSync7(tierCacheFile)) {
|
|
3482
|
+
inferenceTier = readFileSync5(tierCacheFile, "utf-8").trim() || null;
|
|
3953
3483
|
}
|
|
3954
3484
|
const tierLabel = inferenceTier === "fast" ? "'fast' (server-side grading)" : inferenceTier === "free" ? "'free' (local daemon grading)" : "(unknown \u2014 fires on next hook)";
|
|
3955
3485
|
console.log(` inference: ${tierLabel}`);
|
|
@@ -3976,19 +3506,19 @@ function statusCommand() {
|
|
|
3976
3506
|
}
|
|
3977
3507
|
}
|
|
3978
3508
|
console.log();
|
|
3979
|
-
const bashScript =
|
|
3980
|
-
const bashFollowupScript =
|
|
3981
|
-
const editPrecheckScript =
|
|
3982
|
-
const editCaptureScript =
|
|
3983
|
-
const stopSummaryScript =
|
|
3984
|
-
const sessionStartScript =
|
|
3509
|
+
const bashScript = join6(SYNKRO_DIR2, "hooks", "cc-bash-judge.sh");
|
|
3510
|
+
const bashFollowupScript = join6(SYNKRO_DIR2, "hooks", "cc-bash-followup.sh");
|
|
3511
|
+
const editPrecheckScript = join6(SYNKRO_DIR2, "hooks", "cc-edit-precheck.sh");
|
|
3512
|
+
const editCaptureScript = join6(SYNKRO_DIR2, "hooks", "cc-edit-capture.sh");
|
|
3513
|
+
const stopSummaryScript = join6(SYNKRO_DIR2, "hooks", "cc-stop-summary.sh");
|
|
3514
|
+
const sessionStartScript = join6(SYNKRO_DIR2, "hooks", "cc-session-start.sh");
|
|
3985
3515
|
console.log("Hook scripts:");
|
|
3986
|
-
console.log(` ${
|
|
3987
|
-
console.log(` ${
|
|
3988
|
-
console.log(` ${
|
|
3989
|
-
console.log(` ${
|
|
3990
|
-
console.log(` ${
|
|
3991
|
-
console.log(` ${
|
|
3516
|
+
console.log(` ${existsSync7(bashScript) ? "\u2713" : "\u2717"} ${bashScript}`);
|
|
3517
|
+
console.log(` ${existsSync7(bashFollowupScript) ? "\u2713" : "\u2717"} ${bashFollowupScript}`);
|
|
3518
|
+
console.log(` ${existsSync7(editPrecheckScript) ? "\u2713" : "\u2717"} ${editPrecheckScript}`);
|
|
3519
|
+
console.log(` ${existsSync7(editCaptureScript) ? "\u2713" : "\u2717"} ${editCaptureScript}`);
|
|
3520
|
+
console.log(` ${existsSync7(stopSummaryScript) ? "\u2713" : "\u2717"} ${stopSummaryScript}`);
|
|
3521
|
+
console.log(` ${existsSync7(sessionStartScript) ? "\u2713" : "\u2717"} ${sessionStartScript}`);
|
|
3992
3522
|
console.log();
|
|
3993
3523
|
const mcp = inspectMcpConfig();
|
|
3994
3524
|
console.log("Guardrails MCP server (Claude Code):");
|
|
@@ -4000,7 +3530,7 @@ function statusCommand() {
|
|
|
4000
3530
|
console.log(` expected at ${mcp.configPath} \u2192 mcpServers.synkro-guardrails`);
|
|
4001
3531
|
}
|
|
4002
3532
|
}
|
|
4003
|
-
var
|
|
3533
|
+
var SYNKRO_DIR2, CONFIG_PATH2;
|
|
4004
3534
|
var init_status = __esm({
|
|
4005
3535
|
"cli/commands/status.ts"() {
|
|
4006
3536
|
"use strict";
|
|
@@ -4008,8 +3538,8 @@ var init_status = __esm({
|
|
|
4008
3538
|
init_agentDetect();
|
|
4009
3539
|
init_ccHookConfig();
|
|
4010
3540
|
init_mcpConfig();
|
|
4011
|
-
|
|
4012
|
-
|
|
3541
|
+
SYNKRO_DIR2 = join6(homedir5(), ".synkro");
|
|
3542
|
+
CONFIG_PATH2 = join6(SYNKRO_DIR2, "config.env");
|
|
4013
3543
|
}
|
|
4014
3544
|
});
|
|
4015
3545
|
|
|
@@ -4039,7 +3569,7 @@ var unlink_exports = {};
|
|
|
4039
3569
|
__export(unlink_exports, {
|
|
4040
3570
|
unlinkCommand: () => unlinkCommand
|
|
4041
3571
|
});
|
|
4042
|
-
import { createInterface as
|
|
3572
|
+
import { createInterface as createInterface2 } from "readline";
|
|
4043
3573
|
function ask2(rl, question) {
|
|
4044
3574
|
return new Promise((resolve2) => rl.question(question, resolve2));
|
|
4045
3575
|
}
|
|
@@ -4067,7 +3597,7 @@ async function unlinkCommand() {
|
|
|
4067
3597
|
console.log(` ${i + 1}. ${r.fullName} (${r.projectName})`);
|
|
4068
3598
|
});
|
|
4069
3599
|
console.log();
|
|
4070
|
-
const rl =
|
|
3600
|
+
const rl = createInterface2({ input: process.stdin, output: process.stdout });
|
|
4071
3601
|
try {
|
|
4072
3602
|
const selection = await ask2(rl, " Select repos to unlink (comma-separated numbers): ");
|
|
4073
3603
|
const indices = selection.split(",").map((s) => parseInt(s.trim(), 10) - 1).filter((n) => !isNaN(n) && n >= 0 && n < linked.length);
|
|
@@ -4093,6 +3623,194 @@ var init_unlink = __esm({
|
|
|
4093
3623
|
}
|
|
4094
3624
|
});
|
|
4095
3625
|
|
|
3626
|
+
// cli/commands/setupGithub.ts
|
|
3627
|
+
var setupGithub_exports = {};
|
|
3628
|
+
__export(setupGithub_exports, {
|
|
3629
|
+
setupGithubCommand: () => setupGithubCommand
|
|
3630
|
+
});
|
|
3631
|
+
import { createInterface as createInterface3 } from "readline/promises";
|
|
3632
|
+
import { stdin as input, stdout as output } from "process";
|
|
3633
|
+
import { existsSync as existsSync8, readFileSync as readFileSync6 } from "fs";
|
|
3634
|
+
import { homedir as homedir6 } from "os";
|
|
3635
|
+
import { join as join7 } from "path";
|
|
3636
|
+
function readConfig() {
|
|
3637
|
+
if (!existsSync8(CONFIG_PATH3)) return {};
|
|
3638
|
+
const out = {};
|
|
3639
|
+
for (const line of readFileSync6(CONFIG_PATH3, "utf-8").split("\n")) {
|
|
3640
|
+
const t = line.trim();
|
|
3641
|
+
if (!t || t.startsWith("#")) continue;
|
|
3642
|
+
const eq = t.indexOf("=");
|
|
3643
|
+
if (eq > 0) out[t.slice(0, eq).trim()] = t.slice(eq + 1).trim();
|
|
3644
|
+
}
|
|
3645
|
+
return out;
|
|
3646
|
+
}
|
|
3647
|
+
async function prompt(rl, q, opts = {}) {
|
|
3648
|
+
if (opts.silent) {
|
|
3649
|
+
process.stdout.write(q);
|
|
3650
|
+
const wasRaw = process.stdin.isRaw;
|
|
3651
|
+
if (process.stdin.setRawMode) process.stdin.setRawMode(true);
|
|
3652
|
+
return await new Promise((resolve2) => {
|
|
3653
|
+
let chunk = "";
|
|
3654
|
+
const onData = (data) => {
|
|
3655
|
+
const s = data.toString("utf-8");
|
|
3656
|
+
if (s === "\r" || s === "\n" || s === "\r\n") {
|
|
3657
|
+
process.stdin.removeListener("data", onData);
|
|
3658
|
+
if (process.stdin.setRawMode) process.stdin.setRawMode(wasRaw ?? false);
|
|
3659
|
+
process.stdout.write("\n");
|
|
3660
|
+
resolve2(chunk);
|
|
3661
|
+
return;
|
|
3662
|
+
}
|
|
3663
|
+
if (s === "") {
|
|
3664
|
+
process.exit(130);
|
|
3665
|
+
}
|
|
3666
|
+
if (s === "\x7F" || s === "\b") {
|
|
3667
|
+
chunk = chunk.slice(0, -1);
|
|
3668
|
+
return;
|
|
3669
|
+
}
|
|
3670
|
+
chunk += s;
|
|
3671
|
+
};
|
|
3672
|
+
process.stdin.on("data", onData);
|
|
3673
|
+
});
|
|
3674
|
+
}
|
|
3675
|
+
return await rl.question(q);
|
|
3676
|
+
}
|
|
3677
|
+
async function setupGithubCommand() {
|
|
3678
|
+
if (!isAuthenticated()) {
|
|
3679
|
+
console.error("Not authenticated. Run `synkro-cli login` first.");
|
|
3680
|
+
process.exit(1);
|
|
3681
|
+
}
|
|
3682
|
+
const config2 = readConfig();
|
|
3683
|
+
const gatewayUrl = (config2.SYNKRO_GATEWAY_URL || process.env.SYNKRO_GATEWAY_URL || "https://api.synkro.sh").replace(/\/$/, "");
|
|
3684
|
+
const jwt2 = getAccessToken();
|
|
3685
|
+
if (!jwt2) {
|
|
3686
|
+
console.error("Could not load access token from ~/.synkro/credentials.json. Run `synkro-cli login`.");
|
|
3687
|
+
process.exit(1);
|
|
3688
|
+
}
|
|
3689
|
+
console.log("Requesting CI API key from Synkro...");
|
|
3690
|
+
let synkroCiApiKey;
|
|
3691
|
+
try {
|
|
3692
|
+
const resp = await fetch(`${gatewayUrl}/api/v1/cli/ci-api-key`, {
|
|
3693
|
+
method: "POST",
|
|
3694
|
+
headers: {
|
|
3695
|
+
"Authorization": `Bearer ${jwt2}`,
|
|
3696
|
+
"Content-Type": "application/json"
|
|
3697
|
+
},
|
|
3698
|
+
body: "{}"
|
|
3699
|
+
});
|
|
3700
|
+
if (!resp.ok) {
|
|
3701
|
+
const errText = await resp.text().catch(() => "");
|
|
3702
|
+
console.error(`Failed to mint CI API key (${resp.status}): ${errText.slice(0, 200)}`);
|
|
3703
|
+
process.exit(1);
|
|
3704
|
+
}
|
|
3705
|
+
const minted = await resp.json();
|
|
3706
|
+
synkroCiApiKey = minted.api_key;
|
|
3707
|
+
console.log(` \u2713 Issued CI key (${synkroCiApiKey.slice(0, 18)}\u2026), expires ${minted.expires_at.slice(0, 10)}`);
|
|
3708
|
+
} catch (err) {
|
|
3709
|
+
console.error(`Failed to mint CI API key: ${err.message}`);
|
|
3710
|
+
process.exit(1);
|
|
3711
|
+
}
|
|
3712
|
+
const rl = createInterface3({ input, output });
|
|
3713
|
+
console.log("Synkro PR scan setup\n");
|
|
3714
|
+
console.log("Requirements:");
|
|
3715
|
+
console.log(" \u2022 Claude Code Pro or Max subscription (for `claude setup-token`)");
|
|
3716
|
+
console.log(" \u2022 A GitHub personal access token with `repo` scope");
|
|
3717
|
+
console.log(" (create at https://github.com/settings/tokens?type=beta)\n");
|
|
3718
|
+
const ghToken = (await prompt(rl, "GitHub token (paste): ", { silent: true })).trim();
|
|
3719
|
+
if (!ghToken || !ghToken.startsWith("ghp_") && !ghToken.startsWith("github_pat_")) {
|
|
3720
|
+
console.error("Invalid GitHub token format. Expected ghp_... or github_pat_...");
|
|
3721
|
+
rl.close();
|
|
3722
|
+
process.exit(1);
|
|
3723
|
+
}
|
|
3724
|
+
console.log("\nNow get your Claude Code OAuth token:");
|
|
3725
|
+
console.log(" 1. In another terminal, run: claude setup-token");
|
|
3726
|
+
console.log(" 2. Complete the browser flow");
|
|
3727
|
+
console.log(" 3. Copy the resulting sk-ant-oat01-... token\n");
|
|
3728
|
+
const claudeToken = (await prompt(rl, "Claude Code OAuth token (paste): ", { silent: true })).trim();
|
|
3729
|
+
if (!claudeToken.startsWith("sk-ant-oat01-")) {
|
|
3730
|
+
console.error("Invalid token. Expected sk-ant-oat01-... \u2014 generate one with `claude setup-token`.");
|
|
3731
|
+
rl.close();
|
|
3732
|
+
process.exit(1);
|
|
3733
|
+
}
|
|
3734
|
+
console.log("\nFetching accessible repos...");
|
|
3735
|
+
const repos = await listAccessibleRepos({ token: ghToken });
|
|
3736
|
+
if (repos.length === 0) {
|
|
3737
|
+
console.error("No accessible repos found. Verify the GitHub token has `repo` scope.");
|
|
3738
|
+
rl.close();
|
|
3739
|
+
process.exit(1);
|
|
3740
|
+
}
|
|
3741
|
+
console.log(`
|
|
3742
|
+
Found ${repos.length} accessible repo(s):
|
|
3743
|
+
`);
|
|
3744
|
+
repos.slice(0, 100).forEach((r, i) => {
|
|
3745
|
+
console.log(` ${String(i + 1).padStart(3)}. ${r.full_name}`);
|
|
3746
|
+
});
|
|
3747
|
+
console.log();
|
|
3748
|
+
const selectionRaw = await prompt(rl, "Select repos to enable (comma-separated numbers, e.g. 1,3,5): ");
|
|
3749
|
+
const selectedIdx = selectionRaw.split(",").map((s) => parseInt(s.trim(), 10) - 1).filter((n) => !isNaN(n) && n >= 0 && n < repos.length);
|
|
3750
|
+
if (selectedIdx.length === 0) {
|
|
3751
|
+
console.error("No valid selections.");
|
|
3752
|
+
rl.close();
|
|
3753
|
+
process.exit(1);
|
|
3754
|
+
}
|
|
3755
|
+
const selected = selectedIdx.map((i) => repos[i]);
|
|
3756
|
+
console.log(`
|
|
3757
|
+
Will push secrets to ${selected.length} repo(s):`);
|
|
3758
|
+
for (const r of selected) console.log(` \u2022 ${r.full_name}`);
|
|
3759
|
+
console.log();
|
|
3760
|
+
const confirm = (await prompt(rl, "Continue? (yes/no): ")).trim().toLowerCase();
|
|
3761
|
+
if (confirm !== "yes" && confirm !== "y") {
|
|
3762
|
+
console.log("Cancelled.");
|
|
3763
|
+
rl.close();
|
|
3764
|
+
process.exit(0);
|
|
3765
|
+
}
|
|
3766
|
+
rl.close();
|
|
3767
|
+
console.log();
|
|
3768
|
+
for (const r of selected) {
|
|
3769
|
+
process.stdout.write(`Pushing secrets to ${r.full_name}... `);
|
|
3770
|
+
try {
|
|
3771
|
+
await pushSecretsToRepo(
|
|
3772
|
+
{ token: ghToken },
|
|
3773
|
+
r.owner,
|
|
3774
|
+
r.repo,
|
|
3775
|
+
{
|
|
3776
|
+
claudeCodeOauthToken: claudeToken,
|
|
3777
|
+
synkroApiKey: synkroCiApiKey
|
|
3778
|
+
}
|
|
3779
|
+
);
|
|
3780
|
+
console.log("\u2713");
|
|
3781
|
+
} catch (err) {
|
|
3782
|
+
console.log(`\u2717 (${err.message})`);
|
|
3783
|
+
}
|
|
3784
|
+
}
|
|
3785
|
+
console.log();
|
|
3786
|
+
const gitRoot = findGitRoot(process.cwd());
|
|
3787
|
+
if (gitRoot) {
|
|
3788
|
+
const written = writeWorkflowFile(gitRoot);
|
|
3789
|
+
if (written) {
|
|
3790
|
+
console.log(`Wrote workflow: ${written}`);
|
|
3791
|
+
console.log("Commit and push it to enable PR scanning.");
|
|
3792
|
+
}
|
|
3793
|
+
} else {
|
|
3794
|
+
console.log("Not in a git repo. To enable scanning, add this file to your repo:");
|
|
3795
|
+
console.log(` Path: ${WORKFLOW_RELATIVE_PATH}`);
|
|
3796
|
+
console.log(` Content: run \`synkro-cli setup-github\` from inside a repo to write it automatically`);
|
|
3797
|
+
}
|
|
3798
|
+
console.log();
|
|
3799
|
+
console.log("\u2713 PR scan setup complete.");
|
|
3800
|
+
console.log(`Secrets pushed: ${SECRET_NAMES.CLAUDE_OAUTH}, ${SECRET_NAMES.SYNKRO_API_KEY}`);
|
|
3801
|
+
console.log("Open a PR on any selected repo to trigger your first Synkro scan.");
|
|
3802
|
+
}
|
|
3803
|
+
var SYNKRO_DIR3, CONFIG_PATH3;
|
|
3804
|
+
var init_setupGithub = __esm({
|
|
3805
|
+
"cli/commands/setupGithub.ts"() {
|
|
3806
|
+
"use strict";
|
|
3807
|
+
init_githubSetup();
|
|
3808
|
+
init_stub();
|
|
3809
|
+
SYNKRO_DIR3 = join7(homedir6(), ".synkro");
|
|
3810
|
+
CONFIG_PATH3 = join7(SYNKRO_DIR3, "config.env");
|
|
3811
|
+
}
|
|
3812
|
+
});
|
|
3813
|
+
|
|
4096
3814
|
// cli/commands/scanPr.ts
|
|
4097
3815
|
var scanPr_exports = {};
|
|
4098
3816
|
__export(scanPr_exports, {
|
|
@@ -4270,14 +3988,17 @@ ${hunks}`;
|
|
|
4270
3988
|
}
|
|
4271
3989
|
);
|
|
4272
3990
|
let stdout = "";
|
|
3991
|
+
let stderr = "";
|
|
4273
3992
|
proc.stdout.on("data", (chunk) => {
|
|
4274
3993
|
stdout += chunk.toString();
|
|
4275
3994
|
});
|
|
4276
|
-
proc.stderr.on("data", () => {
|
|
3995
|
+
proc.stderr.on("data", (chunk) => {
|
|
3996
|
+
stderr += chunk.toString();
|
|
4277
3997
|
});
|
|
4278
3998
|
proc.on("close", (code) => {
|
|
4279
3999
|
const latencyMs = Date.now() - t0;
|
|
4280
4000
|
if (code !== 0) {
|
|
4001
|
+
console.warn(` claude exited ${code}: ${stderr.slice(0, 300)}`);
|
|
4281
4002
|
resolve2({ findings: [], latencyMs });
|
|
4282
4003
|
return;
|
|
4283
4004
|
}
|
|
@@ -4298,7 +4019,8 @@ ${hunks}`;
|
|
|
4298
4019
|
fix: f.fix
|
|
4299
4020
|
}));
|
|
4300
4021
|
resolve2({ findings, latencyMs });
|
|
4301
|
-
} catch {
|
|
4022
|
+
} catch (parseErr) {
|
|
4023
|
+
console.warn(` failed to parse claude response: ${stdout.slice(0, 300)}`);
|
|
4302
4024
|
resolve2({ findings: [], latencyMs });
|
|
4303
4025
|
}
|
|
4304
4026
|
});
|
|
@@ -4628,16 +4350,7 @@ Usage:
|
|
|
4628
4350
|
synkro <command> [options] (alias)
|
|
4629
4351
|
|
|
4630
4352
|
Commands:
|
|
4631
|
-
install [
|
|
4632
|
-
--force Reinstall from scratch
|
|
4633
|
-
--non-interactive Skip all interactive prompts (use flags)
|
|
4634
|
-
--link-repo Auto-link the local git repo
|
|
4635
|
-
--sync-transcripts Sync CC session history (default in non-interactive)
|
|
4636
|
-
--no-sync-transcripts Skip transcript sync
|
|
4637
|
-
--pr-scan=MODE PR scan auth: claude-oauth, byok, or skip
|
|
4638
|
-
--inference-key=KEY BYOK API key (Anthropic, OpenAI, or Gemini)
|
|
4639
|
-
--inference-model=M Model for classification + grading
|
|
4640
|
-
--github-token=TOK GitHub PAT for pushing secrets + workflow
|
|
4353
|
+
install [--force] Install Synkro hooks for detected agents (Claude Code, etc.)
|
|
4641
4354
|
login Authenticate with Synkro (browser OAuth via WorkOS)
|
|
4642
4355
|
logout Clear local credentials
|
|
4643
4356
|
status Show current setup state
|