@synkro-sh/cli 1.3.12 → 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 +307 -587
- package/dist/bootstrap.js.map +1 -1
- package/package.json +1 -1
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
|
|
@@ -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,323 +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
|
-
const rl = createInterface2({ input, output });
|
|
2945
|
-
let ghToken = opts.githubToken || "";
|
|
2946
|
-
if (!ghToken) {
|
|
2947
|
-
try {
|
|
2948
|
-
const connResp = await fetch(`${gatewayUrl}/api/integrations/github/token`, {
|
|
2949
|
-
headers: { "Authorization": `Bearer ${jwt2}` }
|
|
2950
|
-
});
|
|
2951
|
-
if (connResp.ok) {
|
|
2952
|
-
const data = await connResp.json();
|
|
2953
|
-
ghToken = data.token;
|
|
2954
|
-
console.log(" \u2713 Using existing GitHub connection");
|
|
2955
|
-
}
|
|
2956
|
-
} catch {
|
|
2957
|
-
}
|
|
2958
|
-
}
|
|
2959
|
-
if (!ghToken) {
|
|
2960
|
-
console.log("Opening browser for GitHub authorization...");
|
|
2961
|
-
const RAW_WEB_AUTH_URL3 = process.env.SYNKRO_WEB_AUTH_URL;
|
|
2962
|
-
const webAuthUrl = RAW_WEB_AUTH_URL3 && /^https?:\/\//.test(RAW_WEB_AUTH_URL3) ? RAW_WEB_AUTH_URL3 : "https://app.synkro.sh";
|
|
2963
|
-
openBrowser2(`${webAuthUrl}/cli-github?port=8101`);
|
|
2964
|
-
console.log("Waiting for GitHub authorization...");
|
|
2965
|
-
try {
|
|
2966
|
-
ghToken = await waitForGithubToken();
|
|
2967
|
-
console.log(" \u2713 GitHub authorized");
|
|
2968
|
-
} catch (err) {
|
|
2969
|
-
console.error(`GitHub authorization failed: ${err.message}`);
|
|
2970
|
-
rl.close();
|
|
2971
|
-
process.exit(1);
|
|
2972
|
-
}
|
|
2973
|
-
}
|
|
2974
|
-
if (!ghToken) {
|
|
2975
|
-
console.error("No GitHub token available.");
|
|
2976
|
-
rl.close();
|
|
2977
|
-
process.exit(1);
|
|
2978
|
-
}
|
|
2979
|
-
let claudeToken = opts.claudeOauthToken || "";
|
|
2980
|
-
if (!claudeToken && !opts.skipClaudeToken) {
|
|
2981
|
-
try {
|
|
2982
|
-
const { execSync: execSyncImport } = await import("child_process");
|
|
2983
|
-
const { tmpdir } = await import("os");
|
|
2984
|
-
const tmpFile = join5(tmpdir(), `synkro-claude-token-${Date.now()}.txt`);
|
|
2985
|
-
console.log("\nGenerating Claude Code OAuth token \u2014 complete the browser auth...");
|
|
2986
|
-
try {
|
|
2987
|
-
if (process.platform === "darwin" || process.platform === "linux") {
|
|
2988
|
-
execSyncImport(`script -q ${tmpFile} claude setup-token`, {
|
|
2989
|
-
timeout: 12e4,
|
|
2990
|
-
stdio: "inherit"
|
|
2991
|
-
});
|
|
2992
|
-
} else {
|
|
2993
|
-
execSyncImport(`claude setup-token > "${tmpFile}" 2>&1`, {
|
|
2994
|
-
timeout: 12e4,
|
|
2995
|
-
stdio: "inherit",
|
|
2996
|
-
shell: true
|
|
2997
|
-
});
|
|
2998
|
-
}
|
|
2999
|
-
const { readFileSync: readSync, unlinkSync: unlinkSync3 } = await import("fs");
|
|
3000
|
-
const rawOutput = readSync(tmpFile, "utf-8");
|
|
3001
|
-
try {
|
|
3002
|
-
unlinkSync3(tmpFile);
|
|
3003
|
-
} catch {
|
|
3004
|
-
}
|
|
3005
|
-
const cleaned = rawOutput.replace(/\x1b\[[0-9;]*[A-Za-z]/g, "").replace(/\][^\x07\x1b]*(?:\x07|\x1b\\)/g, "").replace(/[\r\n]+/g, " ");
|
|
3006
|
-
const match = cleaned.match(/(sk-ant-oat01-[A-Za-z0-9_-]{50,})/);
|
|
3007
|
-
claudeToken = match?.[1] || "";
|
|
3008
|
-
} catch {
|
|
3009
|
-
try {
|
|
3010
|
-
const { unlinkSync: unlinkSync3 } = await import("fs");
|
|
3011
|
-
unlinkSync3(tmpFile);
|
|
3012
|
-
} catch {
|
|
3013
|
-
}
|
|
3014
|
-
}
|
|
3015
|
-
if (claudeToken) {
|
|
3016
|
-
console.log(" \u2713 Captured Claude Code OAuth token.");
|
|
3017
|
-
process.stdout.write(" Verifying token... ");
|
|
3018
|
-
try {
|
|
3019
|
-
const { execSync: verifyExec } = await import("child_process");
|
|
3020
|
-
const result = verifyExec('claude --print "respond with just ok"', {
|
|
3021
|
-
encoding: "utf-8",
|
|
3022
|
-
timeout: 3e4,
|
|
3023
|
-
env: { ...process.env, CLAUDE_CODE_OAUTH_TOKEN: claudeToken },
|
|
3024
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
3025
|
-
}).trim().toLowerCase();
|
|
3026
|
-
if (result.includes("ok")) {
|
|
3027
|
-
console.log("\u2713 inference works.\n");
|
|
3028
|
-
} else {
|
|
3029
|
-
console.log("\u26A0 unexpected response, token may not work.\n");
|
|
3030
|
-
}
|
|
3031
|
-
} catch {
|
|
3032
|
-
console.log("\u26A0 verification failed, token may not work.\n");
|
|
3033
|
-
}
|
|
3034
|
-
} else {
|
|
3035
|
-
console.warn(" \u26A0 Could not capture token. PR scans will use BYOK inference only.\n");
|
|
3036
|
-
}
|
|
3037
|
-
} catch {
|
|
3038
|
-
console.warn(" \u26A0 claude setup-token failed. PR scans will use BYOK inference only.\n");
|
|
3039
|
-
}
|
|
3040
|
-
}
|
|
3041
|
-
console.log("\nFetching accessible repos...");
|
|
3042
|
-
const repos = await listAccessibleRepos({ token: ghToken });
|
|
3043
|
-
if (repos.length === 0) {
|
|
3044
|
-
console.error("No accessible repos found. Verify the GitHub token has `repo` scope.");
|
|
3045
|
-
rl.close();
|
|
3046
|
-
process.exit(1);
|
|
3047
|
-
}
|
|
3048
|
-
let selected;
|
|
3049
|
-
if (opts.nonInteractive) {
|
|
3050
|
-
let currentFullName = null;
|
|
3051
|
-
try {
|
|
3052
|
-
const { execSync: gs } = await import("child_process");
|
|
3053
|
-
const remoteUrl = gs("git remote get-url origin", { encoding: "utf-8", timeout: 5e3 }).trim();
|
|
3054
|
-
const m = remoteUrl.match(/(?:github\.com)[:/](.+?)(?:\.git)?$/);
|
|
3055
|
-
if (m) currentFullName = m[1];
|
|
3056
|
-
} catch {
|
|
3057
|
-
}
|
|
3058
|
-
if (currentFullName) {
|
|
3059
|
-
const match = repos.find((r) => r.full_name === currentFullName);
|
|
3060
|
-
if (match) {
|
|
3061
|
-
selected = [match];
|
|
3062
|
-
console.log(` Auto-selected current repo: ${match.full_name}`);
|
|
3063
|
-
} else {
|
|
3064
|
-
console.error(`Current repo "${currentFullName}" not found in accessible repos.`);
|
|
3065
|
-
rl.close();
|
|
3066
|
-
process.exit(1);
|
|
3067
|
-
}
|
|
3068
|
-
} else {
|
|
3069
|
-
console.error("Not in a GitHub repo. Cannot auto-select in non-interactive mode.");
|
|
3070
|
-
rl.close();
|
|
3071
|
-
process.exit(1);
|
|
3072
|
-
}
|
|
3073
|
-
} else {
|
|
3074
|
-
console.log(`
|
|
3075
|
-
Found ${repos.length} accessible repo(s):
|
|
3076
|
-
`);
|
|
3077
|
-
repos.slice(0, 100).forEach((r, i) => {
|
|
3078
|
-
console.log(` ${String(i + 1).padStart(3)}. ${r.full_name}`);
|
|
3079
|
-
});
|
|
3080
|
-
console.log();
|
|
3081
|
-
const selectionRaw = await prompt(rl, "Select repos to enable (comma-separated numbers, e.g. 1,3,5): ");
|
|
3082
|
-
const selectedIdx = selectionRaw.split(",").map((s) => parseInt(s.trim(), 10) - 1).filter((n) => !isNaN(n) && n >= 0 && n < repos.length);
|
|
3083
|
-
if (selectedIdx.length === 0) {
|
|
3084
|
-
console.error("No valid selections.");
|
|
3085
|
-
rl.close();
|
|
3086
|
-
process.exit(1);
|
|
3087
|
-
}
|
|
3088
|
-
selected = selectedIdx.map((i) => repos[i]);
|
|
3089
|
-
console.log(`
|
|
3090
|
-
Will push secrets to ${selected.length} repo(s):`);
|
|
3091
|
-
for (const r of selected) console.log(` \u2022 ${r.full_name}`);
|
|
3092
|
-
console.log();
|
|
3093
|
-
const confirm = (await prompt(rl, "Continue? (yes/no): ")).trim().toLowerCase();
|
|
3094
|
-
if (confirm !== "yes" && confirm !== "y") {
|
|
3095
|
-
console.log("Cancelled.");
|
|
3096
|
-
rl.close();
|
|
3097
|
-
process.exit(0);
|
|
3098
|
-
}
|
|
3099
|
-
}
|
|
3100
|
-
rl.close();
|
|
3101
|
-
console.log();
|
|
3102
|
-
for (const r of selected) {
|
|
3103
|
-
process.stdout.write(`Pushing secrets to ${r.full_name}... `);
|
|
3104
|
-
try {
|
|
3105
|
-
await pushSecretsToRepo(
|
|
3106
|
-
{ token: ghToken },
|
|
3107
|
-
r.owner,
|
|
3108
|
-
r.repo,
|
|
3109
|
-
{
|
|
3110
|
-
claudeCodeOauthToken: claudeToken,
|
|
3111
|
-
synkroApiKey: synkroCiApiKey
|
|
3112
|
-
}
|
|
3113
|
-
);
|
|
3114
|
-
console.log("\u2713");
|
|
3115
|
-
} catch (err) {
|
|
3116
|
-
console.log(`\u2717 (${err.message})`);
|
|
3117
|
-
}
|
|
3118
|
-
}
|
|
3119
|
-
console.log();
|
|
3120
|
-
const gitRoot = findGitRoot(process.cwd());
|
|
3121
|
-
if (gitRoot) {
|
|
3122
|
-
const written = writeWorkflowFile(gitRoot);
|
|
3123
|
-
if (written) {
|
|
3124
|
-
console.log(`Wrote workflow: ${written}`);
|
|
3125
|
-
console.log("Commit and push it to enable PR scanning.");
|
|
3126
|
-
}
|
|
3127
|
-
} else {
|
|
3128
|
-
console.log("Not in a git repo. To enable scanning, add this file to your repo:");
|
|
3129
|
-
console.log(` Path: ${WORKFLOW_RELATIVE_PATH}`);
|
|
3130
|
-
console.log(` Content: run \`synkro-cli setup-github\` from inside a repo to write it automatically`);
|
|
3131
|
-
}
|
|
3132
|
-
console.log();
|
|
3133
|
-
console.log("\u2713 PR scan setup complete.");
|
|
3134
|
-
console.log(`Secrets pushed: ${SECRET_NAMES.CLAUDE_OAUTH}, ${SECRET_NAMES.SYNKRO_API_KEY}`);
|
|
3135
|
-
console.log("Open a PR on any selected repo to trigger your first Synkro scan.");
|
|
3136
|
-
}
|
|
3137
|
-
var SYNKRO_DIR, CONFIG_PATH;
|
|
3138
|
-
var init_setupGithub = __esm({
|
|
3139
|
-
"cli/commands/setupGithub.ts"() {
|
|
3140
|
-
"use strict";
|
|
3141
|
-
init_githubSetup();
|
|
3142
|
-
init_stub();
|
|
3143
|
-
init_repoConnect();
|
|
3144
|
-
SYNKRO_DIR = join5(homedir4(), ".synkro");
|
|
3145
|
-
CONFIG_PATH = join5(SYNKRO_DIR, "config.env");
|
|
3146
|
-
}
|
|
3147
|
-
});
|
|
3148
|
-
|
|
3149
2832
|
// cli/commands/install.ts
|
|
3150
2833
|
var install_exports = {};
|
|
3151
2834
|
__export(install_exports, {
|
|
3152
2835
|
installCommand: () => installCommand,
|
|
3153
2836
|
parseArgs: () => parseArgs
|
|
3154
2837
|
});
|
|
3155
|
-
import { existsSync as
|
|
3156
|
-
import {
|
|
3157
|
-
import {
|
|
3158
|
-
import { homedir as homedir5 } from "os";
|
|
3159
|
-
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";
|
|
3160
2841
|
import { execSync as execSync3 } from "child_process";
|
|
3161
|
-
async function promptYesNo(question, defaultYes = true) {
|
|
3162
|
-
const rl = createInterface3({ input: stdinStream, output: stdoutStream });
|
|
3163
|
-
const suffix = defaultYes ? "[Y/n]" : "[y/N]";
|
|
3164
|
-
const answer = (await rl.question(`${question} ${suffix} `)).trim().toLowerCase();
|
|
3165
|
-
rl.close();
|
|
3166
|
-
if (answer === "") return defaultYes;
|
|
3167
|
-
return answer === "y" || answer === "yes";
|
|
3168
|
-
}
|
|
3169
2842
|
function sanitizeGatewayCandidate(raw) {
|
|
3170
2843
|
if (!raw) return void 0;
|
|
3171
2844
|
return /^https?:\/\//.test(raw) ? raw : void 0;
|
|
@@ -3178,17 +2851,6 @@ function parseArgs(argv) {
|
|
|
3178
2851
|
else if (a === "--skip-auth") opts.skipAuth = true;
|
|
3179
2852
|
else if (a === "--no-mcp") opts.noMcp = true;
|
|
3180
2853
|
else if (a === "--force" || a === "-f") opts.force = true;
|
|
3181
|
-
else if (a === "--non-interactive") opts.nonInteractive = true;
|
|
3182
|
-
else if (a === "--link-repo") opts.linkRepo = true;
|
|
3183
|
-
else if (a === "--sync-transcripts") opts.syncTranscripts = true;
|
|
3184
|
-
else if (a === "--no-sync-transcripts") opts.syncTranscripts = false;
|
|
3185
|
-
else if (a === "--pr-scan=claude-oauth") opts.prScan = "claude-oauth";
|
|
3186
|
-
else if (a === "--pr-scan=byok") opts.prScan = "byok";
|
|
3187
|
-
else if (a === "--pr-scan=skip") opts.prScan = "skip";
|
|
3188
|
-
else if (a.startsWith("--pr-scan=")) opts.prScan = a.slice("--pr-scan=".length);
|
|
3189
|
-
else if (a.startsWith("--inference-key=")) opts.inferenceKey = a.slice("--inference-key=".length);
|
|
3190
|
-
else if (a.startsWith("--inference-model=")) opts.inferenceModel = a.slice("--inference-model=".length);
|
|
3191
|
-
else if (a.startsWith("--github-token=")) opts.githubToken = a.slice("--github-token=".length);
|
|
3192
2854
|
}
|
|
3193
2855
|
if (!opts.gatewayUrl) {
|
|
3194
2856
|
const fromEnv = sanitizeGatewayCandidate(process.env.SYNKRO_GATEWAY_URL);
|
|
@@ -3197,7 +2859,7 @@ function parseArgs(argv) {
|
|
|
3197
2859
|
return opts;
|
|
3198
2860
|
}
|
|
3199
2861
|
function ensureSynkroDir() {
|
|
3200
|
-
mkdirSync5(
|
|
2862
|
+
mkdirSync5(SYNKRO_DIR, { recursive: true });
|
|
3201
2863
|
mkdirSync5(HOOKS_DIR, { recursive: true });
|
|
3202
2864
|
mkdirSync5(BIN_DIR, { recursive: true });
|
|
3203
2865
|
mkdirSync5(OFFSETS_DIR, { recursive: true });
|
|
@@ -3211,13 +2873,13 @@ function writeGraderDaemon() {
|
|
|
3211
2873
|
chmodSync(GRADER_PRIMER_BASH_PATH, 420);
|
|
3212
2874
|
}
|
|
3213
2875
|
function writeHookScripts() {
|
|
3214
|
-
const bashScriptPath =
|
|
3215
|
-
const bashFollowupScriptPath =
|
|
3216
|
-
const editCaptureScriptPath =
|
|
3217
|
-
const editPrecheckScriptPath =
|
|
3218
|
-
const stopSummaryScriptPath =
|
|
3219
|
-
const sessionStartScriptPath =
|
|
3220
|
-
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");
|
|
3221
2883
|
writeFileSync5(bashScriptPath, CC_BASH_JUDGE_SCRIPT, "utf-8");
|
|
3222
2884
|
writeFileSync5(bashFollowupScriptPath, CC_BASH_FOLLOWUP_SCRIPT, "utf-8");
|
|
3223
2885
|
writeFileSync5(editCaptureScriptPath, CC_EDIT_CAPTURE_SCRIPT, "utf-8");
|
|
@@ -3250,7 +2912,7 @@ function shellQuoteSingle(value) {
|
|
|
3250
2912
|
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
3251
2913
|
}
|
|
3252
2914
|
function writeConfigEnv(opts) {
|
|
3253
|
-
const credsPath =
|
|
2915
|
+
const credsPath = join5(SYNKRO_DIR, "credentials.json");
|
|
3254
2916
|
const safeGateway = sanitizeConfigValue(opts.gatewayUrl);
|
|
3255
2917
|
const safeUserId = sanitizeConfigValue(opts.userId);
|
|
3256
2918
|
const safeOrgId = sanitizeConfigValue(opts.orgId);
|
|
@@ -3263,14 +2925,14 @@ function writeConfigEnv(opts) {
|
|
|
3263
2925
|
`SYNKRO_GATEWAY_URL=${shellQuoteSingle(safeGateway)}`,
|
|
3264
2926
|
`SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle(credsPath)}`,
|
|
3265
2927
|
`SYNKRO_TIER=${shellQuoteSingle(safeTier)}`,
|
|
3266
|
-
`SYNKRO_VERSION=${shellQuoteSingle("1.3.
|
|
2928
|
+
`SYNKRO_VERSION=${shellQuoteSingle("1.3.14")}`
|
|
3267
2929
|
];
|
|
3268
2930
|
if (safeUserId) lines.push(`SYNKRO_USER_ID=${shellQuoteSingle(safeUserId)}`);
|
|
3269
2931
|
if (safeOrgId) lines.push(`SYNKRO_ORG_ID=${shellQuoteSingle(safeOrgId)}`);
|
|
3270
2932
|
if (safeEmail) lines.push(`SYNKRO_EMAIL=${shellQuoteSingle(safeEmail)}`);
|
|
3271
2933
|
lines.push("");
|
|
3272
|
-
writeFileSync5(
|
|
3273
|
-
chmodSync(
|
|
2934
|
+
writeFileSync5(CONFIG_PATH, lines.join("\n"), "utf-8");
|
|
2935
|
+
chmodSync(CONFIG_PATH, 384);
|
|
3274
2936
|
}
|
|
3275
2937
|
function assertGatewayAllowed(gatewayUrl) {
|
|
3276
2938
|
let parsed;
|
|
@@ -3295,19 +2957,19 @@ function assertGatewayAllowed(gatewayUrl) {
|
|
|
3295
2957
|
}
|
|
3296
2958
|
function isAlreadyInstalled() {
|
|
3297
2959
|
const requiredScripts = [
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
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")
|
|
3304
2966
|
];
|
|
3305
|
-
if (!requiredScripts.every((p) =>
|
|
3306
|
-
if (!
|
|
3307
|
-
const settingsPath =
|
|
3308
|
-
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;
|
|
3309
2971
|
try {
|
|
3310
|
-
const settings = JSON.parse(
|
|
2972
|
+
const settings = JSON.parse(readFileSync4(settingsPath, "utf-8"));
|
|
3311
2973
|
const hooks = settings?.hooks;
|
|
3312
2974
|
if (!hooks || typeof hooks !== "object") return false;
|
|
3313
2975
|
const hasManaged = (kind) => Array.isArray(hooks[kind]) && hooks[kind].some((entry) => entry?.__synkro_managed__ === true);
|
|
@@ -3366,11 +3028,7 @@ async function installCommand(opts = {}) {
|
|
|
3366
3028
|
console.error("No access token available after auth.");
|
|
3367
3029
|
process.exit(1);
|
|
3368
3030
|
}
|
|
3369
|
-
|
|
3370
|
-
await promptRepoConnection({ linkRepo: opts.linkRepo ?? true });
|
|
3371
|
-
} else {
|
|
3372
|
-
await promptRepoConnection();
|
|
3373
|
-
}
|
|
3031
|
+
await promptRepoConnection();
|
|
3374
3032
|
const agents = detectAgents();
|
|
3375
3033
|
if (agents.length === 0) {
|
|
3376
3034
|
console.error("No AI coding agents detected. Install Claude Code first: https://docs.claude.com/claude-code");
|
|
@@ -3394,9 +3052,9 @@ async function installCommand(opts = {}) {
|
|
|
3394
3052
|
`);
|
|
3395
3053
|
writeGraderDaemon();
|
|
3396
3054
|
for (const mode of ["edit", "bash"]) {
|
|
3397
|
-
const pidFile =
|
|
3055
|
+
const pidFile = join5(SYNKRO_DIR, "daemon", mode, "daemon.pid");
|
|
3398
3056
|
try {
|
|
3399
|
-
const pid = parseInt(
|
|
3057
|
+
const pid = parseInt(readFileSync4(pidFile, "utf-8").trim(), 10);
|
|
3400
3058
|
if (pid > 0) {
|
|
3401
3059
|
process.kill(pid, "SIGTERM");
|
|
3402
3060
|
console.log(`Stopped stale ${mode} daemon (pid ${pid})`);
|
|
@@ -3464,158 +3122,38 @@ async function installCommand(opts = {}) {
|
|
|
3464
3122
|
} catch {
|
|
3465
3123
|
}
|
|
3466
3124
|
writeConfigEnv({ gatewayUrl, userId, orgId, email });
|
|
3467
|
-
console.log(`Wrote config to ${
|
|
3468
|
-
`);
|
|
3469
|
-
const repo = detectGitRepo2();
|
|
3470
|
-
if (repo && getClaudeProjectsFolder()) {
|
|
3471
|
-
const syncHistory = opts.nonInteractive ? opts.syncTranscripts ?? true : await promptYesNo("Sync Claude Code session history for this repo?");
|
|
3472
|
-
if (syncHistory) {
|
|
3473
|
-
try {
|
|
3474
|
-
const ingested = await ingestSessionTranscripts(gatewayUrl, token, repo);
|
|
3475
|
-
if (ingested > 0) {
|
|
3476
|
-
console.log(`Indexed ${ingested} session insights from Claude Code history for ${repo}.`);
|
|
3477
|
-
console.log(" This helps the safety judge understand your workflow.\n");
|
|
3478
|
-
}
|
|
3479
|
-
} catch (err) {
|
|
3480
|
-
console.warn(` \u26A0 Session indexing skipped: ${err.message}
|
|
3481
|
-
`);
|
|
3482
|
-
}
|
|
3483
|
-
try {
|
|
3484
|
-
const result = await syncTranscriptsBulk(gatewayUrl, token, repo);
|
|
3485
|
-
if (result.messages > 0) {
|
|
3486
|
-
console.log(`Synced ${result.sessions} sessions (${result.messages} messages) from Claude Code history.`);
|
|
3487
|
-
console.log(" This data will be used to suggest guardrail rules.\n");
|
|
3488
|
-
}
|
|
3489
|
-
} catch (err) {
|
|
3490
|
-
console.warn(` \u26A0 Transcript sync skipped: ${err.message}
|
|
3125
|
+
console.log(`Wrote config to ${CONFIG_PATH}
|
|
3491
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");
|
|
3492
3134
|
}
|
|
3493
|
-
} else {
|
|
3494
|
-
console.log(" Skipped session history sync.\n");
|
|
3495
3135
|
}
|
|
3136
|
+
} catch (err) {
|
|
3137
|
+
console.warn(` \u26A0 Session indexing skipped: ${err.message}
|
|
3138
|
+
`);
|
|
3496
3139
|
}
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
if (
|
|
3500
|
-
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
anthropic: ["claude-sonnet-4-6", "claude-opus-4-6", "claude-haiku-4-5"],
|
|
3505
|
-
openai: ["gpt-5.5", "gpt-5.4", "gpt-5.4-mini"],
|
|
3506
|
-
gemini: ["gemini-3.1-pro-preview", "gemini-3-flash-preview", "gemini-3.1-flash-lite-preview"]
|
|
3507
|
-
};
|
|
3508
|
-
if (repo) {
|
|
3509
|
-
let ghConnected = false;
|
|
3510
|
-
let ghLogin = "";
|
|
3511
|
-
try {
|
|
3512
|
-
const resp = await fetch(`${gatewayUrl}/api/integrations/github/connect`, {
|
|
3513
|
-
headers: { "Authorization": `Bearer ${token}` }
|
|
3514
|
-
});
|
|
3515
|
-
if (resp.ok) {
|
|
3516
|
-
const data = await resp.json();
|
|
3517
|
-
ghConnected = data.connected;
|
|
3518
|
-
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");
|
|
3519
3147
|
}
|
|
3520
|
-
} catch {
|
|
3521
|
-
}
|
|
3522
|
-
if (ghConnected) {
|
|
3523
|
-
console.log(`GitHub is connected (${ghLogin || "linked via dashboard"}).`);
|
|
3524
3148
|
}
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
if (setupGh) {
|
|
3528
|
-
let choice;
|
|
3529
|
-
if (prScanChoice) {
|
|
3530
|
-
choice = prScanChoice === "claude-oauth" ? "1" : prScanChoice === "byok" ? "2" : "3";
|
|
3531
|
-
} else {
|
|
3532
|
-
const rl = createInterface3({ input: stdinStream, output: stdoutStream });
|
|
3533
|
-
console.log("\nHow should PR scans authenticate for AI review?\n");
|
|
3534
|
-
console.log(" 1. Claude Code OAuth \u2014 opens browser, auto-captures token");
|
|
3535
|
-
console.log(" 2. Use your own API key \u2014 paste an Anthropic, OpenAI, or Gemini key");
|
|
3536
|
-
console.log(" 3. Skip for now\n");
|
|
3537
|
-
choice = (await rl.question("Choice [1/2/3]: ")).trim();
|
|
3538
|
-
rl.close();
|
|
3539
|
-
}
|
|
3540
|
-
if (choice === "1") {
|
|
3541
|
-
try {
|
|
3542
|
-
await setupGithubCommand({ githubToken: opts.githubToken, nonInteractive: opts.nonInteractive });
|
|
3543
|
-
} catch (err) {
|
|
3544
|
-
console.warn(` \u26A0 GitHub setup failed: ${err.message}`);
|
|
3545
|
-
console.warn(" Run `synkro-cli setup-github` to retry.\n");
|
|
3546
|
-
}
|
|
3547
|
-
} else if (choice === "2") {
|
|
3548
|
-
let apiKey = opts.inferenceKey || "";
|
|
3549
|
-
if (!apiKey) {
|
|
3550
|
-
const rl2 = createInterface3({ input: stdinStream, output: stdoutStream });
|
|
3551
|
-
apiKey = (await rl2.question("Paste your API key: ")).trim();
|
|
3552
|
-
rl2.close();
|
|
3553
|
-
}
|
|
3554
|
-
if (apiKey) {
|
|
3555
|
-
const provider = detectProviderFromKey(apiKey);
|
|
3556
|
-
if (provider) {
|
|
3557
|
-
let selectedModel = opts.inferenceModel || "";
|
|
3558
|
-
if (!selectedModel) {
|
|
3559
|
-
const models = PROVIDER_MODELS[provider];
|
|
3560
|
-
console.log(`
|
|
3561
|
-
Detected provider: ${provider}`);
|
|
3562
|
-
console.log(" Select a model for classification + grading:\n");
|
|
3563
|
-
models.forEach((m, i) => console.log(` ${i + 1}. ${m}`));
|
|
3564
|
-
console.log();
|
|
3565
|
-
const rl3 = createInterface3({ input: stdinStream, output: stdoutStream });
|
|
3566
|
-
const modelChoice = (await rl3.question(` Model [1-${models.length}]: `)).trim();
|
|
3567
|
-
rl3.close();
|
|
3568
|
-
const modelIdx = parseInt(modelChoice, 10) - 1;
|
|
3569
|
-
selectedModel = modelIdx >= 0 && modelIdx < models.length ? models[modelIdx] : models[0];
|
|
3570
|
-
}
|
|
3571
|
-
console.log(` Using: ${selectedModel}`);
|
|
3572
|
-
try {
|
|
3573
|
-
const resp = await fetch(`${gatewayUrl}/api/v1/settings/inference`, {
|
|
3574
|
-
method: "PUT",
|
|
3575
|
-
headers: {
|
|
3576
|
-
"Authorization": `Bearer ${token}`,
|
|
3577
|
-
"Content-Type": "application/json"
|
|
3578
|
-
},
|
|
3579
|
-
body: JSON.stringify({
|
|
3580
|
-
providers: { [provider]: { api_key: apiKey } },
|
|
3581
|
-
roles: {
|
|
3582
|
-
classification: { provider, model: selectedModel },
|
|
3583
|
-
grading: { provider, model: selectedModel }
|
|
3584
|
-
}
|
|
3585
|
-
})
|
|
3586
|
-
});
|
|
3587
|
-
if (resp.ok) {
|
|
3588
|
-
console.log(` \u2713 Saved ${provider} key and configured classification + grading with ${selectedModel}.
|
|
3589
|
-
`);
|
|
3590
|
-
} else {
|
|
3591
|
-
const err = await resp.text().catch(() => "");
|
|
3592
|
-
console.warn(` \u26A0 Failed to save key: ${err.slice(0, 200)}
|
|
3593
|
-
`);
|
|
3594
|
-
}
|
|
3595
|
-
} catch (err) {
|
|
3596
|
-
console.warn(` \u26A0 Failed to save key: ${err.message}
|
|
3149
|
+
} catch (err) {
|
|
3150
|
+
console.warn(` \u26A0 Transcript sync skipped: ${err.message}
|
|
3597
3151
|
`);
|
|
3598
|
-
}
|
|
3599
|
-
} else {
|
|
3600
|
-
console.warn(" \u26A0 Could not detect provider from key format. Configure manually in the dashboard.\n");
|
|
3601
|
-
}
|
|
3602
|
-
}
|
|
3603
|
-
try {
|
|
3604
|
-
await setupGithubCommand({ skipClaudeToken: true, githubToken: opts.githubToken, nonInteractive: opts.nonInteractive });
|
|
3605
|
-
} catch (err) {
|
|
3606
|
-
console.warn(` \u26A0 GitHub setup failed: ${err.message}`);
|
|
3607
|
-
console.warn(" Run `synkro-cli setup-github` to retry.\n");
|
|
3608
|
-
}
|
|
3609
|
-
} else {
|
|
3610
|
-
console.log(" Skipped. Run `synkro-cli setup-github` anytime to enable PR scanning.\n");
|
|
3611
|
-
}
|
|
3612
|
-
} else {
|
|
3613
|
-
console.log(" Skipped. Run `synkro-cli setup-github` anytime to enable PR scanning.\n");
|
|
3614
|
-
}
|
|
3615
3152
|
}
|
|
3616
3153
|
console.log("\u2713 Synkro installed.");
|
|
3617
3154
|
console.log();
|
|
3618
3155
|
console.log("Next steps:");
|
|
3156
|
+
console.log(" \u2022 synkro-cli setup-github (enable PR scanning)");
|
|
3619
3157
|
console.log(" \u2022 synkro-cli status (check what is configured)");
|
|
3620
3158
|
}
|
|
3621
3159
|
function detectGitRepo2() {
|
|
@@ -3629,18 +3167,18 @@ function detectGitRepo2() {
|
|
|
3629
3167
|
}
|
|
3630
3168
|
function getClaudeProjectsFolder() {
|
|
3631
3169
|
const cwd = process.cwd();
|
|
3632
|
-
const sanitized = cwd.replace(/\//g, "-");
|
|
3633
|
-
const projectsDir =
|
|
3634
|
-
return
|
|
3170
|
+
const sanitized = "-" + cwd.replace(/\//g, "-");
|
|
3171
|
+
const projectsDir = join5(homedir4(), ".claude", "projects", sanitized);
|
|
3172
|
+
return existsSync6(projectsDir) ? projectsDir : null;
|
|
3635
3173
|
}
|
|
3636
3174
|
function extractSessionInsights(projectsDir) {
|
|
3637
3175
|
const insights = [];
|
|
3638
3176
|
const files = readdirSync(projectsDir).filter((f) => f.endsWith(".jsonl"));
|
|
3639
3177
|
for (const file of files) {
|
|
3640
3178
|
const sessionId = file.replace(".jsonl", "");
|
|
3641
|
-
const filePath =
|
|
3179
|
+
const filePath = join5(projectsDir, file);
|
|
3642
3180
|
try {
|
|
3643
|
-
const content =
|
|
3181
|
+
const content = readFileSync4(filePath, "utf-8");
|
|
3644
3182
|
const lines = content.split("\n").filter(Boolean);
|
|
3645
3183
|
for (let i = 0; i < lines.length; i++) {
|
|
3646
3184
|
try {
|
|
@@ -3716,7 +3254,7 @@ function extractTextContent(content) {
|
|
|
3716
3254
|
return "";
|
|
3717
3255
|
}
|
|
3718
3256
|
function parseTranscriptFile(filePath) {
|
|
3719
|
-
const content =
|
|
3257
|
+
const content = readFileSync4(filePath, "utf-8");
|
|
3720
3258
|
const lines = content.split("\n").filter(Boolean);
|
|
3721
3259
|
const messages = [];
|
|
3722
3260
|
for (let i = 0; i < lines.length; i++) {
|
|
@@ -3767,7 +3305,7 @@ async function syncTranscriptsBulk(gatewayUrl, token, repo) {
|
|
|
3767
3305
|
const sessions = [];
|
|
3768
3306
|
for (const file of batch) {
|
|
3769
3307
|
const sessionId = file.replace(".jsonl", "");
|
|
3770
|
-
const filePath =
|
|
3308
|
+
const filePath = join5(projectsDir, file);
|
|
3771
3309
|
try {
|
|
3772
3310
|
const allMessages = parseTranscriptFile(filePath);
|
|
3773
3311
|
const messages = allMessages.length > maxMessagesPerSession ? allMessages.slice(-maxMessagesPerSession) : allMessages;
|
|
@@ -3796,18 +3334,18 @@ async function syncTranscriptsBulk(gatewayUrl, token, repo) {
|
|
|
3796
3334
|
}
|
|
3797
3335
|
for (const file of batch) {
|
|
3798
3336
|
const sessionId = file.replace(".jsonl", "");
|
|
3799
|
-
const filePath =
|
|
3337
|
+
const filePath = join5(projectsDir, file);
|
|
3800
3338
|
try {
|
|
3801
|
-
const content =
|
|
3339
|
+
const content = readFileSync4(filePath, "utf-8");
|
|
3802
3340
|
const lineCount = content.split("\n").filter(Boolean).length;
|
|
3803
|
-
writeFileSync5(
|
|
3341
|
+
writeFileSync5(join5(OFFSETS_DIR, sessionId), String(lineCount), "utf-8");
|
|
3804
3342
|
} catch {
|
|
3805
3343
|
}
|
|
3806
3344
|
}
|
|
3807
3345
|
}
|
|
3808
3346
|
return { sessions: totalSessions, messages: totalMessages };
|
|
3809
3347
|
}
|
|
3810
|
-
var
|
|
3348
|
+
var SYNKRO_DIR, HOOKS_DIR, BIN_DIR, CONFIG_PATH, GRADER_DAEMON_PATH, GRADER_PRIMER_EDIT_PATH, GRADER_PRIMER_BASH_PATH, OFFSETS_DIR;
|
|
3811
3349
|
var init_install = __esm({
|
|
3812
3350
|
"cli/commands/install.ts"() {
|
|
3813
3351
|
"use strict";
|
|
@@ -3818,15 +3356,14 @@ var init_install = __esm({
|
|
|
3818
3356
|
init_graderDaemon();
|
|
3819
3357
|
init_stub();
|
|
3820
3358
|
init_repoConnect();
|
|
3821
|
-
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
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");
|
|
3830
3367
|
}
|
|
3831
3368
|
});
|
|
3832
3369
|
|
|
@@ -3902,13 +3439,13 @@ var status_exports = {};
|
|
|
3902
3439
|
__export(status_exports, {
|
|
3903
3440
|
statusCommand: () => statusCommand
|
|
3904
3441
|
});
|
|
3905
|
-
import { existsSync as
|
|
3906
|
-
import { homedir as
|
|
3907
|
-
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";
|
|
3908
3445
|
function readConfigEnv() {
|
|
3909
|
-
if (!
|
|
3446
|
+
if (!existsSync7(CONFIG_PATH2)) return {};
|
|
3910
3447
|
const out = {};
|
|
3911
|
-
const raw =
|
|
3448
|
+
const raw = readFileSync5(CONFIG_PATH2, "utf-8");
|
|
3912
3449
|
for (const line of raw.split("\n")) {
|
|
3913
3450
|
const trimmed = line.trim();
|
|
3914
3451
|
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
@@ -3939,10 +3476,10 @@ function statusCommand() {
|
|
|
3939
3476
|
console.log(` tier: ${config2.SYNKRO_TIER ?? "(unset)"}`);
|
|
3940
3477
|
const info2 = getUserInfo();
|
|
3941
3478
|
const userId = info2?.id ?? config2.SYNKRO_USER_ID ?? "default";
|
|
3942
|
-
const tierCacheFile =
|
|
3479
|
+
const tierCacheFile = join6(SYNKRO_DIR2, `.tier-cache-${userId}`);
|
|
3943
3480
|
let inferenceTier = config2.SYNKRO_INFERENCE_TIER || null;
|
|
3944
|
-
if (!inferenceTier &&
|
|
3945
|
-
inferenceTier =
|
|
3481
|
+
if (!inferenceTier && existsSync7(tierCacheFile)) {
|
|
3482
|
+
inferenceTier = readFileSync5(tierCacheFile, "utf-8").trim() || null;
|
|
3946
3483
|
}
|
|
3947
3484
|
const tierLabel = inferenceTier === "fast" ? "'fast' (server-side grading)" : inferenceTier === "free" ? "'free' (local daemon grading)" : "(unknown \u2014 fires on next hook)";
|
|
3948
3485
|
console.log(` inference: ${tierLabel}`);
|
|
@@ -3969,19 +3506,19 @@ function statusCommand() {
|
|
|
3969
3506
|
}
|
|
3970
3507
|
}
|
|
3971
3508
|
console.log();
|
|
3972
|
-
const bashScript =
|
|
3973
|
-
const bashFollowupScript =
|
|
3974
|
-
const editPrecheckScript =
|
|
3975
|
-
const editCaptureScript =
|
|
3976
|
-
const stopSummaryScript =
|
|
3977
|
-
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");
|
|
3978
3515
|
console.log("Hook scripts:");
|
|
3979
|
-
console.log(` ${
|
|
3980
|
-
console.log(` ${
|
|
3981
|
-
console.log(` ${
|
|
3982
|
-
console.log(` ${
|
|
3983
|
-
console.log(` ${
|
|
3984
|
-
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}`);
|
|
3985
3522
|
console.log();
|
|
3986
3523
|
const mcp = inspectMcpConfig();
|
|
3987
3524
|
console.log("Guardrails MCP server (Claude Code):");
|
|
@@ -3993,7 +3530,7 @@ function statusCommand() {
|
|
|
3993
3530
|
console.log(` expected at ${mcp.configPath} \u2192 mcpServers.synkro-guardrails`);
|
|
3994
3531
|
}
|
|
3995
3532
|
}
|
|
3996
|
-
var
|
|
3533
|
+
var SYNKRO_DIR2, CONFIG_PATH2;
|
|
3997
3534
|
var init_status = __esm({
|
|
3998
3535
|
"cli/commands/status.ts"() {
|
|
3999
3536
|
"use strict";
|
|
@@ -4001,8 +3538,8 @@ var init_status = __esm({
|
|
|
4001
3538
|
init_agentDetect();
|
|
4002
3539
|
init_ccHookConfig();
|
|
4003
3540
|
init_mcpConfig();
|
|
4004
|
-
|
|
4005
|
-
|
|
3541
|
+
SYNKRO_DIR2 = join6(homedir5(), ".synkro");
|
|
3542
|
+
CONFIG_PATH2 = join6(SYNKRO_DIR2, "config.env");
|
|
4006
3543
|
}
|
|
4007
3544
|
});
|
|
4008
3545
|
|
|
@@ -4032,7 +3569,7 @@ var unlink_exports = {};
|
|
|
4032
3569
|
__export(unlink_exports, {
|
|
4033
3570
|
unlinkCommand: () => unlinkCommand
|
|
4034
3571
|
});
|
|
4035
|
-
import { createInterface as
|
|
3572
|
+
import { createInterface as createInterface2 } from "readline";
|
|
4036
3573
|
function ask2(rl, question) {
|
|
4037
3574
|
return new Promise((resolve2) => rl.question(question, resolve2));
|
|
4038
3575
|
}
|
|
@@ -4060,7 +3597,7 @@ async function unlinkCommand() {
|
|
|
4060
3597
|
console.log(` ${i + 1}. ${r.fullName} (${r.projectName})`);
|
|
4061
3598
|
});
|
|
4062
3599
|
console.log();
|
|
4063
|
-
const rl =
|
|
3600
|
+
const rl = createInterface2({ input: process.stdin, output: process.stdout });
|
|
4064
3601
|
try {
|
|
4065
3602
|
const selection = await ask2(rl, " Select repos to unlink (comma-separated numbers): ");
|
|
4066
3603
|
const indices = selection.split(",").map((s) => parseInt(s.trim(), 10) - 1).filter((n) => !isNaN(n) && n >= 0 && n < linked.length);
|
|
@@ -4086,6 +3623,194 @@ var init_unlink = __esm({
|
|
|
4086
3623
|
}
|
|
4087
3624
|
});
|
|
4088
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
|
+
|
|
4089
3814
|
// cli/commands/scanPr.ts
|
|
4090
3815
|
var scanPr_exports = {};
|
|
4091
3816
|
__export(scanPr_exports, {
|
|
@@ -4263,14 +3988,17 @@ ${hunks}`;
|
|
|
4263
3988
|
}
|
|
4264
3989
|
);
|
|
4265
3990
|
let stdout = "";
|
|
3991
|
+
let stderr = "";
|
|
4266
3992
|
proc.stdout.on("data", (chunk) => {
|
|
4267
3993
|
stdout += chunk.toString();
|
|
4268
3994
|
});
|
|
4269
|
-
proc.stderr.on("data", () => {
|
|
3995
|
+
proc.stderr.on("data", (chunk) => {
|
|
3996
|
+
stderr += chunk.toString();
|
|
4270
3997
|
});
|
|
4271
3998
|
proc.on("close", (code) => {
|
|
4272
3999
|
const latencyMs = Date.now() - t0;
|
|
4273
4000
|
if (code !== 0) {
|
|
4001
|
+
console.warn(` claude exited ${code}: ${stderr.slice(0, 300)}`);
|
|
4274
4002
|
resolve2({ findings: [], latencyMs });
|
|
4275
4003
|
return;
|
|
4276
4004
|
}
|
|
@@ -4291,7 +4019,8 @@ ${hunks}`;
|
|
|
4291
4019
|
fix: f.fix
|
|
4292
4020
|
}));
|
|
4293
4021
|
resolve2({ findings, latencyMs });
|
|
4294
|
-
} catch {
|
|
4022
|
+
} catch (parseErr) {
|
|
4023
|
+
console.warn(` failed to parse claude response: ${stdout.slice(0, 300)}`);
|
|
4295
4024
|
resolve2({ findings: [], latencyMs });
|
|
4296
4025
|
}
|
|
4297
4026
|
});
|
|
@@ -4621,16 +4350,7 @@ Usage:
|
|
|
4621
4350
|
synkro <command> [options] (alias)
|
|
4622
4351
|
|
|
4623
4352
|
Commands:
|
|
4624
|
-
install [
|
|
4625
|
-
--force Reinstall from scratch
|
|
4626
|
-
--non-interactive Skip all interactive prompts (use flags)
|
|
4627
|
-
--link-repo Auto-link the local git repo
|
|
4628
|
-
--sync-transcripts Sync CC session history (default in non-interactive)
|
|
4629
|
-
--no-sync-transcripts Skip transcript sync
|
|
4630
|
-
--pr-scan=MODE PR scan auth: claude-oauth, byok, or skip
|
|
4631
|
-
--inference-key=KEY BYOK API key (Anthropic, OpenAI, or Gemini)
|
|
4632
|
-
--inference-model=M Model for classification + grading
|
|
4633
|
-
--github-token=TOK GitHub PAT for pushing secrets + workflow
|
|
4353
|
+
install [--force] Install Synkro hooks for detected agents (Claude Code, etc.)
|
|
4634
4354
|
login Authenticate with Synkro (browser OAuth via WorkOS)
|
|
4635
4355
|
logout Clear local credentials
|
|
4636
4356
|
status Show current setup state
|